Skip to content

Commit 8a67173

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 8a67173

File tree

8 files changed

+146
-16
lines changed

8 files changed

+146
-16
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+
__attribute__((noinline, disable_tail_calls)) void
1861+
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: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,13 @@ 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+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
476+
__attribute__((noinline, disable_tail_calls))
477+
#endif
478+
virtual void
479+
jumpto() {
480+
_LIBUNWIND_ABORT("jumpto not implemented");
481+
}
476482
virtual bool isSignalFrame() {
477483
_LIBUNWIND_ABORT("isSignalFrame not implemented");
478484
}
@@ -489,6 +495,12 @@ class _LIBUNWIND_HIDDEN AbstractUnwindCursor {
489495
virtual void saveVFPAsX() { _LIBUNWIND_ABORT("saveVFPAsX not implemented"); }
490496
#endif
491497

498+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
499+
virtual void setWalkedFrames(unsigned) {
500+
_LIBUNWIND_ABORT("setWalkedFrames not implemented");
501+
}
502+
#endif
503+
492504
#ifdef _AIX
493505
virtual uintptr_t getDataRelBase() {
494506
_LIBUNWIND_ABORT("getDataRelBase not implemented");
@@ -965,7 +977,11 @@ class UnwindCursor : public AbstractUnwindCursor{
965977
virtual void setFloatReg(int, unw_fpreg_t);
966978
virtual int step(bool stage2 = false);
967979
virtual void getInfo(unw_proc_info_t *);
968-
virtual void jumpto();
980+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
981+
__attribute__((noinline, disable_tail_calls))
982+
#endif
983+
virtual void
984+
jumpto();
969985
virtual bool isSignalFrame();
970986
virtual bool getFunctionName(char *buf, size_t len, unw_word_t *off);
971987
virtual void setInfoBasedOnIPRegister(bool isReturnAddress = false);
@@ -974,6 +990,10 @@ class UnwindCursor : public AbstractUnwindCursor{
974990
virtual void saveVFPAsX();
975991
#endif
976992

993+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
994+
virtual void setWalkedFrames(unsigned);
995+
#endif
996+
977997
#ifdef _AIX
978998
virtual uintptr_t getDataRelBase();
979999
#endif
@@ -1356,6 +1376,9 @@ class UnwindCursor : public AbstractUnwindCursor{
13561376
defined(_LIBUNWIND_TARGET_HAIKU)
13571377
bool _isSigReturn = false;
13581378
#endif
1379+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1380+
uint32_t _walkedFrames;
1381+
#endif
13591382
};
13601383

13611384

@@ -1410,7 +1433,46 @@ void UnwindCursor<A, R>::setFloatReg(int regNum, unw_fpreg_t value) {
14101433
}
14111434

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

14161478
#ifdef __arm__
@@ -1419,6 +1481,13 @@ template <typename A, typename R> void UnwindCursor<A, R>::saveVFPAsX() {
14191481
}
14201482
#endif
14211483

1484+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
1485+
template <typename A, typename R>
1486+
void UnwindCursor<A, R>::setWalkedFrames(unsigned walkedFrames) {
1487+
_walkedFrames = walkedFrames;
1488+
}
1489+
#endif
1490+
14221491
#ifdef _AIX
14231492
template <typename A, typename R>
14241493
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+
#elif defined(_LIBUNWIND_TRACE_RET_INJECT)
209+
__attribute__((noinline, disable_tail_calls))
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+
#elif defined(_LIBUNWIND_TRACE_RET_INJECT)
355+
__attribute__((noinline, disable_tail_calls))
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: 3 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

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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ 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+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
34+
__attribute__((noinline, disable_tail_calls))
35+
#endif
36+
extern int
37+
__unw_resume_with_frames_walked(unw_cursor_t *, unsigned);
38+
// Legacy function. Do not use.
39+
#ifdef _LIBUNWIND_TRACE_RET_INJECT
40+
__attribute__((noinline, disable_tail_calls))
41+
#endif
3342
extern int __unw_resume(unw_cursor_t *);
3443

3544
#ifdef __arm__

0 commit comments

Comments
 (0)