From e662eb6a5f4b23910d7ee6a423a598f697cf03ae Mon Sep 17 00:00:00 2001 From: Capri XXI Date: Fri, 19 Jun 2026 01:37:48 +0800 Subject: [PATCH] Document LLVM libunwind JIT frame warning --- Lib/test/test_c_stack_unwind.py | 5 ++ Python/jit_unwind.c | 85 +++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/Lib/test/test_c_stack_unwind.py b/Lib/test/test_c_stack_unwind.py index 91bf44e463473d..cf6113db221ea3 100644 --- a/Lib/test/test_c_stack_unwind.py +++ b/Lib/test/test_c_stack_unwind.py @@ -27,6 +27,9 @@ STACK_DEPTH = 10 +BAD_DYNAMIC_FDE_WARNING = ( + "libunwind: __unw_add_dynamic_fde: bad fde: FDE is really a CIE" +) def _manual_unwind_expected(machine): @@ -218,6 +221,8 @@ def _run_unwind_helper(helper_name, unwinder_name, **env): # Surface the output for debugging/visibility when running this test if proc.stdout: print(proc.stdout, end="") + if BAD_DYNAMIC_FDE_WARNING in proc.stderr: + raise RuntimeError(proc.stderr) if proc.returncode: raise RuntimeError( f"unwind helper failed (rc={proc.returncode}): {proc.stderr or proc.stdout}" diff --git a/Python/jit_unwind.c b/Python/jit_unwind.c index 0941ed593ff7d1..48a357a5eaf135 100644 --- a/Python/jit_unwind.c +++ b/Python/jit_unwind.c @@ -31,6 +31,18 @@ */ void __register_frame(const void *); void __deregister_frame(const void *); + +# if defined(__linux__) && defined(__clang__) && \ + (__clang_major__ >= 22) && (__clang_major__ <= 23) +# define PY_JIT_GNU_BACKTRACE_REGISTER_FDE +# endif + +# if defined(PY_JIT_GNU_BACKTRACE_REGISTER_FDE) +typedef struct JitGnuBacktraceRegistration { + uint8_t *eh_frame; + const void *registered_frame; +} JitGnuBacktraceRegistration; +# endif #endif #include #include @@ -1008,6 +1020,50 @@ _PyJitUnwind_GdbUnregisterCode(void *handle) } #if defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) +#if defined(PY_JIT_GNU_BACKTRACE_REGISTER_FDE) +static const void * +gnu_backtrace_registration_frame(const uint8_t *eh_frame, size_t eh_frame_size) +{ + /* + * LLVM libunwind 22 and 23 implement __register_frame as a single-FDE + * registration API. The compiler-version check above is intentionally a + * narrow toolchain guard requested for those releases; it is not a + * general runtime unwinder capability probe. + */ + uint32_t cie_length; + if (eh_frame == NULL || eh_frame_size < 2 * sizeof(uint32_t)) { + return NULL; + } + memcpy(&cie_length, eh_frame, sizeof(cie_length)); + if (cie_length == 0 || cie_length == UINT32_MAX) { + return NULL; + } + + size_t fde_offset = sizeof(uint32_t) + (size_t)cie_length; + if (fde_offset > eh_frame_size - 2 * sizeof(uint32_t)) { + return NULL; + } + + uint32_t fde_length; + memcpy(&fde_length, eh_frame + fde_offset, sizeof(fde_length)); + if (fde_length == 0 || fde_length == UINT32_MAX) { + return NULL; + } + if ((size_t)fde_length > eh_frame_size - fde_offset - sizeof(uint32_t)) { + return NULL; + } + + uint32_t cie_pointer; + memcpy(&cie_pointer, eh_frame + fde_offset + sizeof(fde_length), + sizeof(cie_pointer)); + if (cie_pointer == 0) { + return NULL; + } + + return eh_frame + fde_offset; +} +#endif + void * _PyJitUnwind_GnuBacktraceRegisterCode(const void *code_addr, size_t code_size) { @@ -1044,8 +1100,29 @@ _PyJitUnwind_GnuBacktraceRegisterCode(const void *code_addr, size_t code_size) return NULL; } +#if defined(PY_JIT_GNU_BACKTRACE_REGISTER_FDE) + const void *registered_frame = gnu_backtrace_registration_frame( + eh_frame, eh_frame_size); + if (registered_frame == NULL) { + PyMem_RawFree(eh_frame); + return NULL; + } + + JitGnuBacktraceRegistration *registration = + PyMem_RawMalloc(sizeof(*registration)); + if (registration == NULL) { + PyMem_RawFree(eh_frame); + return NULL; + } + registration->eh_frame = eh_frame; + registration->registered_frame = registered_frame; + + __register_frame(registered_frame); + return registration; +#else __register_frame(eh_frame); return eh_frame; +#endif } void @@ -1054,8 +1131,16 @@ _PyJitUnwind_GnuBacktraceUnregisterCode(void *handle) if (handle == NULL) { return; } +#if defined(PY_JIT_GNU_BACKTRACE_REGISTER_FDE) + JitGnuBacktraceRegistration *registration = + (JitGnuBacktraceRegistration *)handle; + __deregister_frame(registration->registered_frame); + PyMem_RawFree(registration->eh_frame); + PyMem_RawFree(registration); +#else __deregister_frame(handle); PyMem_RawFree(handle); +#endif } #endif // defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND)