Skip to content

Commit 70baa9d

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 c25a4a9 commit 70baa9d

File tree

8 files changed

+140
-15
lines changed

8 files changed

+140
-15
lines changed

libunwind/src/Registers.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,7 +1827,8 @@ inline const char *Registers_ppc64::getRegisterName(int regNum) {
18271827
/// Registers_arm64 holds the register state of a thread in a 64-bit arm
18281828
/// process.
18291829
class _LIBUNWIND_HIDDEN Registers_arm64;
1830-
extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *);
1830+
extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *,
1831+
unsigned walkedFrames);
18311832

18321833
#if defined(_LIBUNWIND_USE_GCS)
18331834
extern "C" void *__libunwind_shstk_get_jump_target() {
@@ -1855,7 +1856,14 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18551856
v128 getVectorRegister(int num) const;
18561857
void setVectorRegister(int num, v128 value);
18571858
static const char *getRegisterName(int num);
1858-
void jumpto() { __libunwind_Registers_arm64_jumpto(this); }
1859+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1860+
_LIBUNWIND_TRACE_NO_INLINE
1861+
void returnto(unsigned walkedFrames) {
1862+
__libunwind_Registers_arm64_jumpto(this, walkedFrames);
1863+
}
1864+
#else
1865+
void jumpto() { __libunwind_Registers_arm64_jumpto(this, 0); }
1866+
#endif
18591867
static constexpr int lastDwarfRegNum() {
18601868
return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64;
18611869
}

libunwind/src/UnwindCursor.hpp

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,10 @@ 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
476+
virtual void jumpto() {
477+
_LIBUNWIND_ABORT("jumpto not implemented");
478+
}
476479
virtual bool isSignalFrame() {
477480
_LIBUNWIND_ABORT("isSignalFrame not implemented");
478481
}
@@ -489,6 +492,12 @@ class _LIBUNWIND_HIDDEN AbstractUnwindCursor {
489492
virtual void saveVFPAsX() { _LIBUNWIND_ABORT("saveVFPAsX not implemented"); }
490493
#endif
491494

495+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
496+
virtual void setWalkedFrames(unsigned) {
497+
_LIBUNWIND_ABORT("setWalkedFrames not implemented");
498+
}
499+
#endif
500+
492501
#ifdef _AIX
493502
virtual uintptr_t getDataRelBase() {
494503
_LIBUNWIND_ABORT("getDataRelBase not implemented");
@@ -965,6 +974,7 @@ class UnwindCursor : public AbstractUnwindCursor{
965974
virtual void setFloatReg(int, unw_fpreg_t);
966975
virtual int step(bool stage2 = false);
967976
virtual void getInfo(unw_proc_info_t *);
977+
_LIBUNWIND_TRACE_NO_INLINE
968978
virtual void jumpto();
969979
virtual bool isSignalFrame();
970980
virtual bool getFunctionName(char *buf, size_t len, unw_word_t *off);
@@ -974,6 +984,10 @@ class UnwindCursor : public AbstractUnwindCursor{
974984
virtual void saveVFPAsX();
975985
#endif
976986

987+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
988+
virtual void setWalkedFrames(unsigned);
989+
#endif
990+
977991
#ifdef _AIX
978992
virtual uintptr_t getDataRelBase();
979993
#endif
@@ -1356,6 +1370,9 @@ class UnwindCursor : public AbstractUnwindCursor{
13561370
defined(_LIBUNWIND_TARGET_HAIKU)
13571371
bool _isSigReturn = false;
13581372
#endif
1373+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1374+
uint32_t _walkedFrames;
1375+
#endif
13591376
};
13601377

13611378

@@ -1410,7 +1427,46 @@ void UnwindCursor<A, R>::setFloatReg(int regNum, unw_fpreg_t value) {
14101427
}
14111428

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

14161472
#ifdef __arm__
@@ -1419,6 +1475,13 @@ template <typename A, typename R> void UnwindCursor<A, R>::saveVFPAsX() {
14191475
}
14201476
#endif
14211477

1478+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1479+
template <typename A, typename R>
1480+
void UnwindCursor<A, R>::setWalkedFrames(unsigned walkedFrames) {
1481+
_walkedFrames = walkedFrames;
1482+
}
1483+
#endif
1484+
14221485
#ifdef _AIX
14231486
template <typename A, typename R>
14241487
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
@@ -643,13 +643,26 @@ Lnovec:
643643
#endif
644644

645645
//
646-
// extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *);
646+
// extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *, unsigned);
647647
//
648648
// On entry:
649649
// thread_state pointer is in x0
650+
// walked_frames counter is in x1
650651
//
651652
.p2align 2
652653
DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto)
654+
655+
#if defined(_LIBUNWIND_TRACE_RET_INJECT)
656+
cbz w1, 1f
657+
0:
658+
subs w1, w1, #1
659+
adr x16, #8
660+
ret x16
661+
662+
b.ne 0b
663+
1:
664+
#endif
665+
653666
// skip restore of x0,x1 for now
654667
ldp x2, x3, [x0, #0x010]
655668
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ 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+
_LIBUNWIND_TRACE_NO_INLINE
34+
extern int __unw_resume_with_frames_walked(unw_cursor_t *, unsigned);
35+
// Legacy function. Do not use.
36+
_LIBUNWIND_TRACE_NO_INLINE
3337
extern int __unw_resume(unw_cursor_t *);
3438

3539
#ifdef __arm__

0 commit comments

Comments
 (0)