Skip to content

Commit cf35502

Browse files
authored
[libunwind] Fix execution flow imbalance when using C++ Exceptions (#165066)
1 parent 3e6442a commit cf35502

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)