-
-
Notifications
You must be signed in to change notification settings - Fork 34.6k
gh-149202: Fix frame pointer unwinding on ppc64le and armv7/clang #149409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
c68855d
ad2c814
242a58e
9b74182
efb829e
a612c88
955261c
92ee35e
02617af
cfc9b62
4ad01d6
1db3418
b7cb2b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| Enable frame pointers by default for GCC-compatible CPython builds, including | ||
| ``-mno-omit-leaf-frame-pointer`` when the compiler supports it, so profilers | ||
| and debuggers can unwind native interpreter frames more reliably. Users can pass | ||
| ``--without-frame-pointers`` to opt out. | ||
| ``-mno-omit-leaf-frame-pointer``, ``-marm`` on 32-bit ARM, and/or ``-mbackchain`` | ||
| on s390x platforms when the compiler supports them, so profilers and debuggers | ||
| can unwind native interpreter frames more reliably. Users can pass | ||
| :option:`--without-frame-pointers` to ``./configure`` to opt out. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -63,6 +63,45 @@ | |
| static const uintptr_t min_frame_pointer_addr = 0x1000; | ||
| #define MAX_UNWIND_FRAMES 200 | ||
|
|
||
| #ifdef __s390x__ | ||
| // Linux's s390 "Stack Frame Layout" table documents that z/Architecture | ||
| // backchain frames start with the backchain at offset 0 and store "saved r14 | ||
| // of caller function" at offset 112. The same document's register table | ||
| // identifies r14 as the return-address register, so this backchain unwinder | ||
| // reads the return address from fp + 112. | ||
| // https://www.kernel.org/doc/html/v5.3/s390/debugging390.html#stack-frame-layout | ||
| // | ||
| // This is only for Linux s390x backchain frames. The s390x ELF ABI does not | ||
| // generally mandate where RA and FP are saved, or whether they are saved at all. | ||
| // https://sourceware.org/binutils/docs/sframe-spec.html#s390x | ||
| # define S390X_FRAME_RETURN_ADDRESS_OFFSET 112 | ||
| #endif | ||
|
|
||
| // The generic manual unwinder treats the frame pointer as a two-word record: | ||
| // fp[0] is the previous frame pointer and fp[1] is the return address. That is | ||
| // not true for every architecture, even with frame pointers enabled, so these | ||
| // offsets describe the actual slots used by each supported frame layout. | ||
| #if defined(__arm__) && !defined(__thumb__) && !defined(__clang__) | ||
| // GCC ARM mode keeps the caller's fp one word below fp and the saved LR at | ||
| // fp[0], so the return address is not in the generic fp[1] slot. | ||
| # define FRAME_POINTER_NEXT_OFFSET (-1) | ||
| # define FRAME_POINTER_RETURN_OFFSET 0 | ||
| #elif defined(__s390x__) | ||
| // s390x backchain frames keep the previous frame pointer at fp[0], but save the | ||
| // return-address register in the ABI register save area rather than fp[1]. | ||
| # define FRAME_POINTER_NEXT_OFFSET 0 | ||
| # define FRAME_POINTER_RETURN_OFFSET \ | ||
| (S390X_FRAME_RETURN_ADDRESS_OFFSET / (Py_ssize_t)sizeof(uintptr_t)) | ||
| #elif defined(__powerpc64__) || defined(__ppc64__) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vstinner as the powerpc expert: Are these the right macros to detect ppc64le?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, the Linux stack layout might not apply to Mac... but then again, the last ppc Mac came out 20 years ago. |
||
| // ppc64le puts the Condition Register between the two fields, see | ||
| // https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi-1.9.html#STACK | ||
| # define FRAME_POINTER_NEXT_OFFSET 0 | ||
| # define FRAME_POINTER_RETURN_OFFSET 2 | ||
| #else | ||
| # define FRAME_POINTER_NEXT_OFFSET 0 | ||
| # define FRAME_POINTER_RETURN_OFFSET 1 | ||
| #endif | ||
|
|
||
|
|
||
| static PyObject * | ||
| _get_current_module(void) | ||
|
|
@@ -329,15 +368,96 @@ get_jit_backend(PyObject *self, PyObject *Py_UNUSED(args)) | |
| #endif | ||
| } | ||
|
|
||
| static int | ||
| stack_address_is_valid(uintptr_t addr, uintptr_t stack_min, uintptr_t stack_max) | ||
| { | ||
| if (addr < min_frame_pointer_addr) { | ||
| return 0; | ||
| } | ||
| if (stack_min != 0 && (addr < stack_min || addr >= stack_max)) { | ||
| return 0; | ||
| } | ||
| return 1; | ||
| } | ||
|
|
||
| static int | ||
| frame_pointer_slot_is_valid(uintptr_t *frame_pointer, Py_ssize_t offset, | ||
| uintptr_t stack_min, uintptr_t stack_max) | ||
| { | ||
| uintptr_t fp_addr = (uintptr_t)frame_pointer; | ||
| uintptr_t slot_addr; | ||
| uintptr_t delta = (uintptr_t)Py_ABS(offset) * sizeof(uintptr_t); | ||
| if (offset < 0) { | ||
| if (fp_addr < delta) { | ||
| return 0; | ||
| } | ||
| slot_addr = fp_addr - delta; | ||
| } | ||
| else { | ||
| if (fp_addr > UINTPTR_MAX - delta) { | ||
| return 0; | ||
| } | ||
| slot_addr = fp_addr + delta; | ||
| } | ||
| if (!stack_address_is_valid(slot_addr, stack_min, stack_max)) { | ||
| return 0; | ||
| } | ||
| if (stack_max != 0) { | ||
| if (slot_addr > UINTPTR_MAX - sizeof(uintptr_t)) { | ||
| return 0; | ||
| } | ||
| if (slot_addr + sizeof(uintptr_t) > stack_max) { | ||
| return 0; | ||
| } | ||
| } | ||
| return 1; | ||
| } | ||
|
|
||
| static int | ||
| next_frame_pointer_is_valid(uintptr_t *frame_pointer, uintptr_t *next_fp, | ||
| uintptr_t stack_min, uintptr_t stack_max) | ||
| { | ||
| uintptr_t fp_addr = (uintptr_t)frame_pointer; | ||
| uintptr_t next_addr = (uintptr_t)next_fp; | ||
| if (!stack_address_is_valid(next_addr, stack_min, stack_max)) { | ||
| return 0; | ||
| } | ||
| if ((next_addr % sizeof(uintptr_t)) != 0) { | ||
| return 0; | ||
| } | ||
| #if _Py_STACK_GROWS_DOWN | ||
| return next_addr > fp_addr; | ||
| #else | ||
| return next_addr < fp_addr; | ||
| #endif | ||
| } | ||
|
|
||
| static PyObject * | ||
| manual_unwind_from_fp(uintptr_t *frame_pointer) | ||
| { | ||
| int stack_grows_down = _Py_STACK_GROWS_DOWN; | ||
| uintptr_t stack_min = 0; | ||
| uintptr_t stack_max = 0; | ||
|
|
||
| #ifdef __s390x__ | ||
| Py_BUILD_ASSERT(S390X_FRAME_RETURN_ADDRESS_OFFSET % sizeof(uintptr_t) == 0); | ||
| #endif | ||
|
|
||
| if (frame_pointer == NULL) { | ||
| return PyList_New(0); | ||
| } | ||
|
|
||
| PyThreadState *tstate = _PyThreadState_GET(); | ||
| if (tstate != NULL) { | ||
| _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; | ||
| #if _Py_STACK_GROWS_DOWN | ||
| stack_min = tstate_impl->c_stack_hard_limit; | ||
| stack_max = tstate_impl->c_stack_top; | ||
| #else | ||
| stack_min = tstate_impl->c_stack_top; | ||
| stack_max = tstate_impl->c_stack_hard_limit; | ||
| #endif | ||
| } | ||
|
|
||
| PyObject *result = PyList_New(0); | ||
| if (result == NULL) { | ||
| return NULL; | ||
|
|
@@ -357,7 +477,21 @@ manual_unwind_from_fp(uintptr_t *frame_pointer) | |
| MAX_UNWIND_FRAMES); | ||
| return NULL; | ||
| } | ||
| uintptr_t return_addr = frame_pointer[1]; | ||
| if (!stack_address_is_valid(fp_addr, stack_min, stack_max)) { | ||
| break; | ||
| } | ||
| if (!frame_pointer_slot_is_valid(frame_pointer, | ||
| FRAME_POINTER_NEXT_OFFSET, | ||
| stack_min, stack_max)) { | ||
| break; | ||
| } | ||
| if (!frame_pointer_slot_is_valid(frame_pointer, | ||
| FRAME_POINTER_RETURN_OFFSET, | ||
| stack_min, stack_max)) { | ||
| break; | ||
| } | ||
| uintptr_t *next_fp = (uintptr_t *)frame_pointer[FRAME_POINTER_NEXT_OFFSET]; | ||
| uintptr_t return_addr = frame_pointer[FRAME_POINTER_RETURN_OFFSET]; | ||
|
|
||
| PyObject *addr_obj = PyLong_FromUnsignedLongLong(return_addr); | ||
| if (addr_obj == NULL) { | ||
|
|
@@ -372,22 +506,10 @@ manual_unwind_from_fp(uintptr_t *frame_pointer) | |
| Py_DECREF(addr_obj); | ||
| depth++; | ||
|
|
||
| uintptr_t *next_fp = (uintptr_t *)frame_pointer[0]; | ||
| // Stop if the frame pointer is extremely low. | ||
| if ((uintptr_t)next_fp < min_frame_pointer_addr) { | ||
| if (!next_frame_pointer_is_valid(frame_pointer, next_fp, | ||
| stack_min, stack_max)) { | ||
| break; | ||
| } | ||
| uintptr_t next_addr = (uintptr_t)next_fp; | ||
| if (stack_grows_down) { | ||
| if (next_addr <= fp_addr) { | ||
| break; | ||
| } | ||
| } | ||
| else { | ||
| if (next_addr >= fp_addr) { | ||
| break; | ||
| } | ||
| } | ||
| frame_pointer = next_fp; | ||
| } | ||
|
|
||
|
|
@@ -3170,6 +3292,12 @@ module_exec(PyObject *module) | |
| return 1; | ||
| } | ||
|
|
||
| #ifdef _Py_WITH_FRAME_POINTERS | ||
| if (PyModule_AddIntMacro(module, _Py_WITH_FRAME_POINTERS) < 0) { | ||
| return 1; | ||
| } | ||
| #endif | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.