Skip to content

Commit 00792aa

Browse files
committed
[libunwind] Fix execution flow imbalance when using C++ Exceptions
This patch should fix tracing support when using C++ Exceptions. When unwinding C++ Exceptions, libunwind would have a first searching phase to detect its caller landing pad using the personality function, and a second phase that returns to the landing pad and restore the register context by also updating the link register to point to the landing pad address found inthe first phase. Since it changes the link register value and returns all the frames that have been called in libunwind, it causes an imbalance in the execution flow which breaks Apple Processor Trace analysis and affects its clients, like Instruments.app. This patch addresses the issue by generating the right amount of `ret` instructions for every function called in libcxx & libunwind to rebalance the execution flow, right before returning to the catch block. rdar://131181678 rdar://131622012 Signed-off-by: Med Ismail Bennani <[email protected]>
1 parent ea10026 commit 00792aa

File tree

12 files changed

+396
-19
lines changed

12 files changed

+396
-19
lines changed

libunwind/src/Registers.hpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,8 +1832,9 @@ inline const char *Registers_ppc64::getRegisterName(int regNum) {
18321832
/// Registers_arm64 holds the register state of a thread in a 64-bit arm
18331833
/// process.
18341834
class _LIBUNWIND_HIDDEN Registers_arm64;
1835-
extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *);
18361835
extern "C" int64_t __libunwind_Registers_arm64_za_disable();
1836+
extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *,
1837+
unsigned walkedFrames);
18371838

18381839
#if defined(_LIBUNWIND_USE_GCS)
18391840
extern "C" void *__libunwind_shstk_get_jump_target() {
@@ -1861,10 +1862,17 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18611862
v128 getVectorRegister(int num) const;
18621863
void setVectorRegister(int num, v128 value);
18631864
static const char *getRegisterName(int num);
1864-
void jumpto() {
1865-
zaDisable();
1866-
__libunwind_Registers_arm64_jumpto(this);
1865+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1866+
_LIBUNWIND_TRACE_NO_INLINE
1867+
void returnto(unsigned walkedFrames) {
1868+
__libunwind_Registers_arm64_jumpto(this, walkedFrames);
1869+
}
1870+
#else
1871+
void jumpto() {
1872+
zaDisable();
1873+
__libunwind_Registers_arm64_jumpto(this, 0);
18671874
}
1875+
#endif
18681876
static constexpr int lastDwarfRegNum() {
18691877
return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64;
18701878
}

libunwind/src/UnwindCursor.hpp

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,9 @@ class _LIBUNWIND_HIDDEN AbstractUnwindCursor {
472472
virtual void getInfo(unw_proc_info_t *) {
473473
_LIBUNWIND_ABORT("getInfo not implemented");
474474
}
475-
virtual void jumpto() { _LIBUNWIND_ABORT("jumpto not implemented"); }
475+
_LIBUNWIND_TRACE_NO_INLINE virtual void jumpto() {
476+
_LIBUNWIND_ABORT("jumpto not implemented");
477+
}
476478
virtual bool isSignalFrame() {
477479
_LIBUNWIND_ABORT("isSignalFrame not implemented");
478480
}
@@ -489,6 +491,12 @@ class _LIBUNWIND_HIDDEN AbstractUnwindCursor {
489491
virtual void saveVFPAsX() { _LIBUNWIND_ABORT("saveVFPAsX not implemented"); }
490492
#endif
491493

494+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
495+
virtual void setWalkedFrames(unsigned) {
496+
_LIBUNWIND_ABORT("setWalkedFrames not implemented");
497+
}
498+
#endif
499+
492500
#ifdef _AIX
493501
virtual uintptr_t getDataRelBase() {
494502
_LIBUNWIND_ABORT("getDataRelBase not implemented");
@@ -965,7 +973,8 @@ class UnwindCursor : public AbstractUnwindCursor{
965973
virtual void setFloatReg(int, unw_fpreg_t);
966974
virtual int step(bool stage2 = false);
967975
virtual void getInfo(unw_proc_info_t *);
968-
virtual void jumpto();
976+
_LIBUNWIND_TRACE_NO_INLINE
977+
virtual void jumpto();
969978
virtual bool isSignalFrame();
970979
virtual bool getFunctionName(char *buf, size_t len, unw_word_t *off);
971980
virtual void setInfoBasedOnIPRegister(bool isReturnAddress = false);
@@ -974,6 +983,10 @@ class UnwindCursor : public AbstractUnwindCursor{
974983
virtual void saveVFPAsX();
975984
#endif
976985

986+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
987+
virtual void setWalkedFrames(unsigned);
988+
#endif
989+
977990
#ifdef _AIX
978991
virtual uintptr_t getDataRelBase();
979992
#endif
@@ -1356,6 +1369,9 @@ class UnwindCursor : public AbstractUnwindCursor{
13561369
defined(_LIBUNWIND_TARGET_HAIKU)
13571370
bool _isSigReturn = false;
13581371
#endif
1372+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1373+
uint32_t _walkedFrames;
1374+
#endif
13591375
};
13601376

13611377

@@ -1410,7 +1426,46 @@ void UnwindCursor<A, R>::setFloatReg(int regNum, unw_fpreg_t value) {
14101426
}
14111427

14121428
template <typename A, typename R> void UnwindCursor<A, R>::jumpto() {
1429+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1430+
/*
1431+
1432+
The value of `_walkedFrames` is computed in `unwind_phase2` and represents the
1433+
number of frames walked starting `unwind_phase2` to get to the landing pad.
1434+
1435+
```
1436+
// uc is initialized by __unw_getcontext in the parent frame.
1437+
// The first stack frame walked is unwind_phase2.
1438+
unsigned framesWalked = 1;
1439+
```
1440+
1441+
To that, we need to add the number of function calls in libunwind between
1442+
`unwind_phase2` & `__libunwind_Registers_arm64_jumpto` which performs the long
1443+
jump, to rebalance the execution flow.
1444+
1445+
```
1446+
frame #0: libunwind.1.dylib`__libunwind_Registers_arm64_jumpto at UnwindRegistersRestore.S:646
1447+
frame #1: libunwind.1.dylib`libunwind::Registers_arm64::returnto at Registers.hpp:2291:3
1448+
frame #2: libunwind.1.dylib`libunwind::UnwindCursor<libunwind::LocalAddressSpace, libunwind::Registers_arm64>::jumpto at UnwindCursor.hpp:1474:14
1449+
frame #3: libunwind.1.dylib`__unw_resume at libunwind.cpp:375:7
1450+
frame #4: libunwind.1.dylib`__unw_resume_with_frames_walked at libunwind.cpp:363:10
1451+
frame #5: libunwind.1.dylib`unwind_phase2 at UnwindLevel1.c:328:9
1452+
frame #6: libunwind.1.dylib`_Unwind_RaiseException at UnwindLevel1.c:480:10
1453+
frame #7: libc++abi.dylib`__cxa_throw at cxa_exception.cpp:295:5
1454+
...
1455+
```
1456+
1457+
If we look at the backtrace from `__libunwind_Registers_arm64_jumpto`, we see
1458+
there are 5 frames on the stack to reach `unwind_phase2`. However, only 4 of
1459+
them will never return, since `__libunwind_Registers_arm64_jumpto` returns
1460+
back to the landing pad, so we need to subtract 1 to the number of
1461+
`_EXTRA_LIBUNWIND_FRAMES_WALKED`.
1462+
*/
1463+
1464+
static constexpr size_t _EXTRA_LIBUNWIND_FRAMES_WALKED = 5 - 1;
1465+
_registers.returnto(_walkedFrames + _EXTRA_LIBUNWIND_FRAMES_WALKED);
1466+
#else
14131467
_registers.jumpto();
1468+
#endif
14141469
}
14151470

14161471
#ifdef __arm__
@@ -1419,6 +1474,13 @@ template <typename A, typename R> void UnwindCursor<A, R>::saveVFPAsX() {
14191474
}
14201475
#endif
14211476

1477+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1478+
template <typename A, typename R>
1479+
void UnwindCursor<A, R>::setWalkedFrames(unsigned walkedFrames) {
1480+
_walkedFrames = walkedFrames;
1481+
}
1482+
#endif
1483+
14221484
#ifdef _AIX
14231485
template <typename A, typename R>
14241486
uintptr_t UnwindCursor<A, R>::getDataRelBase() {

libunwind/src/UnwindLevel1.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,15 @@
4848
// avoided when invoking the `jumpto()` function. To do this, we use inline
4949
// assemblies to "goto" the `jumpto()` for these architectures.
5050
#if !defined(_LIBUNWIND_USE_CET) && !defined(_LIBUNWIND_USE_GCS)
51-
#define __unw_phase2_resume(cursor, fn) \
51+
#define __unw_phase2_resume(cursor, payload) \
5252
do { \
53-
(void)fn; \
54-
__unw_resume((cursor)); \
53+
__unw_resume_with_frames_walked((cursor), (payload)); \
5554
} while (0)
5655
#elif defined(_LIBUNWIND_TARGET_I386)
5756
#define __shstk_step_size (4)
58-
#define __unw_phase2_resume(cursor, fn) \
57+
#define __unw_phase2_resume(cursor, payload) \
5958
do { \
60-
_LIBUNWIND_POP_SHSTK_SSP((fn)); \
59+
_LIBUNWIND_POP_SHSTK_SSP((payload)); \
6160
void *shstkRegContext = __libunwind_shstk_get_registers((cursor)); \
6261
void *shstkJumpAddress = __libunwind_shstk_get_jump_target(); \
6362
__asm__ volatile("push %%edi\n\t" \
@@ -67,26 +66,27 @@
6766
} while (0)
6867
#elif defined(_LIBUNWIND_TARGET_X86_64)
6968
#define __shstk_step_size (8)
70-
#define __unw_phase2_resume(cursor, fn) \
69+
#define __unw_phase2_resume(cursor, payload) \
7170
do { \
72-
_LIBUNWIND_POP_SHSTK_SSP((fn)); \
71+
_LIBUNWIND_POP_SHSTK_SSP((payload)); \
7372
void *shstkRegContext = __libunwind_shstk_get_registers((cursor)); \
7473
void *shstkJumpAddress = __libunwind_shstk_get_jump_target(); \
7574
__asm__ volatile("jmpq *%%rdx\n\t" ::"D"(shstkRegContext), \
7675
"d"(shstkJumpAddress)); \
7776
} while (0)
7877
#elif defined(_LIBUNWIND_TARGET_AARCH64)
7978
#define __shstk_step_size (8)
80-
#define __unw_phase2_resume(cursor, fn) \
79+
#define __unw_phase2_resume(cursor, payload) \
8180
do { \
82-
_LIBUNWIND_POP_SHSTK_SSP((fn)); \
81+
_LIBUNWIND_POP_SHSTK_SSP((payload)); \
8382
void *shstkRegContext = __libunwind_shstk_get_registers((cursor)); \
8483
void *shstkJumpAddress = __libunwind_shstk_get_jump_target(); \
8584
__asm__ volatile("mov x0, %0\n\t" \
85+
"mov x1, wzr\n\t" \
8686
"br %1\n\t" \
8787
: \
8888
: "r"(shstkRegContext), "r"(shstkJumpAddress) \
89-
: "x0"); \
89+
: "x0", "x1"); \
9090
} while (0)
9191
#endif
9292

@@ -205,6 +205,8 @@ extern int __unw_step_stage2(unw_cursor_t *);
205205
#if defined(_LIBUNWIND_USE_GCS)
206206
// Enable the GCS target feature to permit gcspop instructions to be used.
207207
__attribute__((target("+gcs")))
208+
#else
209+
_LIBUNWIND_TRACE_NO_INLINE
208210
#endif
209211
static _Unwind_Reason_Code
210212
unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor,
@@ -349,6 +351,8 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor,
349351
#if defined(_LIBUNWIND_USE_GCS)
350352
// Enable the GCS target feature to permit gcspop instructions to be used.
351353
__attribute__((target("+gcs")))
354+
#else
355+
_LIBUNWIND_TRACE_NO_INLINE
352356
#endif
353357
static _Unwind_Reason_Code
354358
unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor,

libunwind/src/UnwindRegistersRestore.S

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,13 +645,26 @@ Lnovec:
645645
#endif
646646

647647
//
648-
// extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *);
648+
// extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *, unsigned);
649649
//
650650
// On entry:
651651
// thread_state pointer is in x0
652+
// walked_frames counter is in x1
652653
//
653654
.p2align 2
654655
DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto)
656+
657+
#if defined(_LIBUNWIND_TRACE_RET_INJECT)
658+
cbz w1, 1f
659+
0:
660+
subs w1, w1, #1
661+
adr x16, #8
662+
ret x16
663+
664+
b.ne 0b
665+
1:
666+
#endif
667+
655668
// skip restore of x0,x1 for now
656669
ldp x2, x3, [x0, #0x010]
657670
ldp x4, x5, [x0, #0x020]

libunwind/src/assembly.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@
132132

133133
#if defined(__APPLE__)
134134

135+
#if defined(__aarch64__) || defined(__arm64__) || defined(__arm64e__)
136+
#define _LIBUNWIND_TRACE_RET_INJECT 1
137+
#endif
138+
135139
#define SYMBOL_IS_FUNC(name)
136140
#define HIDDEN_SYMBOL(name) .private_extern name
137141
#if defined(_LIBUNWIND_HIDE_SYMBOLS)

libunwind/src/config.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#define _LIBUNWIND_SUPPORT_COMPACT_UNWIND 1
2929
#define _LIBUNWIND_SUPPORT_DWARF_UNWIND 1
3030
#endif
31+
#if defined(__aarch64__) || defined(__arm64__) || defined(__arm64e__)
32+
#define _LIBUNWIND_TRACE_RET_INJECT 1
33+
#endif
3134
#elif defined(_WIN32)
3235
#ifdef __SEH__
3336
#define _LIBUNWIND_SUPPORT_SEH_UNWIND 1
@@ -61,6 +64,12 @@
6164
#endif
6265
#endif
6366

67+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
68+
#define _LIBUNWIND_TRACE_NO_INLINE __attribute__((noinline, disable_tail_calls))
69+
#else
70+
#define _LIBUNWIND_TRACE_NO_INLINE
71+
#endif
72+
6473
#if defined(_LIBUNWIND_HIDE_SYMBOLS)
6574
// The CMake file passes -fvisibility=hidden to control ELF/Mach-O visibility.
6675
#define _LIBUNWIND_EXPORT

libunwind/src/libunwind.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,27 @@ _LIBUNWIND_HIDDEN int __unw_get_proc_info(unw_cursor_t *cursor,
247247
}
248248
_LIBUNWIND_WEAK_ALIAS(__unw_get_proc_info, unw_get_proc_info)
249249

250-
/// Resume execution at cursor position (aka longjump).
250+
/// Rebalance the execution flow by injecting the right amount of `ret`
251+
/// instruction relatively to the amount of `walkedFrames` then resume execution
252+
/// at cursor position (aka longjump).
253+
_LIBUNWIND_HIDDEN int __unw_resume_with_frames_walked(unw_cursor_t *cursor,
254+
unsigned walkedFrames) {
255+
_LIBUNWIND_TRACE_API("__unw_resume(cursor=%p, walkedFrames=%u)",
256+
static_cast<void *>(cursor), walkedFrames);
257+
#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
258+
// Inform the ASan runtime that now might be a good time to clean stuff up.
259+
__asan_handle_no_return();
260+
#endif
261+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
262+
AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
263+
co->setWalkedFrames(walkedFrames);
264+
#endif
265+
return __unw_resume(cursor);
266+
}
267+
_LIBUNWIND_WEAK_ALIAS(__unw_resume_with_frames_walked,
268+
unw_resume_with_frames_walked)
269+
270+
/// Legacy function. Resume execution at cursor position (aka longjump).
251271
_LIBUNWIND_HIDDEN int __unw_resume(unw_cursor_t *cursor) {
252272
_LIBUNWIND_TRACE_API("__unw_resume(cursor=%p)", static_cast<void *>(cursor));
253273
#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)

libunwind/src/libunwind_ext.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ extern int __unw_get_reg(unw_cursor_t *, unw_regnum_t, unw_word_t *);
3030
extern int __unw_get_fpreg(unw_cursor_t *, unw_regnum_t, unw_fpreg_t *);
3131
extern int __unw_set_reg(unw_cursor_t *, unw_regnum_t, unw_word_t);
3232
extern int __unw_set_fpreg(unw_cursor_t *, unw_regnum_t, unw_fpreg_t);
33-
extern int __unw_resume(unw_cursor_t *);
33+
_LIBUNWIND_TRACE_NO_INLINE
34+
extern int __unw_resume_with_frames_walked(unw_cursor_t *, unsigned);
35+
// `__unw_resume` is a legacy function. Use `__unw_resume_with_frames_walked` instead.
36+
_LIBUNWIND_TRACE_NO_INLINE
37+
extern int __unw_resume(unw_cursor_t *);
3438

3539
#ifdef __arm__
3640
/* Save VFP registers in FSTMX format (instead of FSTMD). */

lldb/packages/Python/lldbsuite/test/decorators.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,31 @@ def is_out_of_tree_debugserver():
647647
return skipTestIfFn(is_out_of_tree_debugserver)(func)
648648

649649

650+
def skipIfOutOfTreeLibunwind(func):
651+
"""Decorate the item to skip tests if libunwind was not built in-tree."""
652+
653+
def is_out_of_tree_libunwind():
654+
if not configuration.llvm_tools_dir:
655+
return "out-of-tree libunwind"
656+
657+
# llvm_tools_dir is typically <build>/bin, so lib is a sibling.
658+
llvm_lib_dir = os.path.join(
659+
os.path.dirname(configuration.llvm_tools_dir), "lib"
660+
)
661+
662+
if not os.path.isdir(llvm_lib_dir):
663+
return "out-of-tree libunwind"
664+
665+
# Check for libunwind library (any extension).
666+
for filename in os.listdir(llvm_lib_dir):
667+
if filename.startswith("libunwind.") or filename.startswith("unwind."):
668+
return None
669+
670+
return "out-of-tree libunwind"
671+
672+
return skipTestIfFn(is_out_of_tree_libunwind)(func)
673+
674+
650675
def skipIfRemote(func):
651676
"""Decorate the item to skip tests if testing remotely."""
652677
return unittest.skipIf(lldb.remote_platform, "skip on remote platform")(func)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CXX_SOURCES := main.cpp
2+
3+
# Build with C++ exceptions enabled
4+
CXXFLAGS := -g -O0 -fexceptions
5+
6+
include Makefile.rules

0 commit comments

Comments
 (0)