-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
GH-126910: Add gdb support for unwinding JIT frames #146071
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
5cd7ade
669dfb9
255c0b3
ac018d6
b0bab8c
a0dff1f
2b52588
e44170e
2e40f1d
d890add
965a543
17be0a2
f47d763
67ae6cb
a18cb96
bdc8d12
6357698
0b07c57
93bbf99
3eeddb9
a9c6315
6ee9fbc
0193cb6
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 |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| #ifndef Py_CORE_JIT_UNWIND_H | ||
| #define Py_CORE_JIT_UNWIND_H | ||
|
|
||
|
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. this is missing Py_BUILD_CORE guard no?
Contributor
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. yes, I've seen now the other headers files. |
||
| #ifdef PY_HAVE_PERF_TRAMPOLINE | ||
|
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. The entire file is gated on PY_HAVE_PERF_TRAMPOLINE, but the GDB JIT interface is conceptually independent of perf no?
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. Oops yea you're right.
Contributor
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. for now, I'll add the bare minimum to address this but I already in mind some refactoring to do with another PR. Let's land this first and then I will refactor the code in light of adding libcc (for gnu backtrace)
Contributor
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. this solution won't be the best, but it will be improved in subsequent PRs. I don't want to keep changing this PR. |
||
|
|
||
| #include <stddef.h> | ||
|
|
||
| /* Return the size of the generated .eh_frame data for the given encoding. */ | ||
| size_t _PyJitUnwind_EhFrameSize(int absolute_addr); | ||
|
|
||
| /* | ||
| * Build DWARF .eh_frame data for JIT code; returns size written or 0 on error. | ||
| * absolute_addr selects the FDE address encoding: | ||
| * - 0: PC-relative offsets (perf jitdump synthesized DSO). | ||
| * - nonzero: absolute addresses (GDB JIT in-memory ELF). | ||
| */ | ||
| size_t _PyJitUnwind_BuildEhFrame(uint8_t *buffer, size_t buffer_size, | ||
| const void *code_addr, size_t code_size, | ||
| int absolute_addr); | ||
|
|
||
| void _PyJitUnwind_GdbRegisterCode(const void *code_addr, | ||
| unsigned int code_size, | ||
| const char *entry, | ||
| const char *filename); | ||
|
|
||
| #endif // PY_HAVE_PERF_TRAMPOLINE | ||
|
|
||
| #endif // Py_CORE_JIT_UNWIND_H | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Sample script for use by test_gdb.test_jit | ||
|
|
||
| import operator | ||
| import sys | ||
|
|
||
|
|
||
| def jit_bt_hot(depth, warming_up_caller=False): | ||
| if warming_up_caller: | ||
| return | ||
| if depth == 0: | ||
| id(42) | ||
| return | ||
|
|
||
| warming_up = True | ||
|
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. Could this loop hang? When Also, line 16 uses bitwise
Contributor
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. I've simplified the test, the loop is not more controlled and deterministic. |
||
| while warming_up: | ||
| warming_up = sys._jit.is_enabled() & (not sys._jit.is_active()) | ||
| operator.call(jit_bt_hot, depth - 1, warming_up) | ||
|
|
||
|
|
||
| jit_bt_hot(10) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import os | ||
| import re | ||
| import sys | ||
| import unittest | ||
|
|
||
| from .util import setup_module, DebuggerTests | ||
|
|
||
|
|
||
| JIT_SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), "gdb_jit_sample.py") | ||
|
|
||
|
|
||
| def setUpModule(): | ||
| setup_module() | ||
|
|
||
|
|
||
| @unittest.skipUnless( | ||
| hasattr(sys, "_jit") and sys._jit.is_available(), | ||
| "requires a JIT-enabled build", | ||
| ) | ||
| class JitBacktraceTests(DebuggerTests): | ||
| def test_bt_unwinds_through_jit_frames(self): | ||
| gdb_output = self.get_stack_trace( | ||
| script=JIT_SAMPLE_SCRIPT, | ||
| cmds_after_breakpoint=["bt"], | ||
| PYTHON_JIT="1", | ||
| ) | ||
| self.assertIn("py::jit_executor:<jit>", gdb_output) | ||
| self.assertIn("py::jit_shim:<jit>", gdb_output) | ||
| self.assertRegex( | ||
| gdb_output, | ||
| re.compile( | ||
| r"py::jit_executor:<jit>.*(_PyEval_EvalFrameDefault|_PyEval_Vector)", | ||
| re.DOTALL, | ||
| ), | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Add support for unwinding JIT frames using GDB. Patch by Diego Russo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
| #include "pycore_interpframe.h" | ||
| #include "pycore_interpolation.h" | ||
| #include "pycore_intrinsics.h" | ||
| #include "pycore_jit_unwind.h" | ||
| #include "pycore_lazyimportobject.h" | ||
| #include "pycore_list.h" | ||
| #include "pycore_long.h" | ||
|
|
@@ -60,6 +61,28 @@ jit_error(const char *message) | |
| PyErr_Format(PyExc_RuntimeWarning, "JIT %s (%d)", message, hint); | ||
| } | ||
|
|
||
| static void | ||
| jit_record_code(const void *code_addr, size_t code_size, | ||
|
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. I will leave this for the future but as this is unconditionally active I assume will have a perf cost we probably want top measure
Contributor
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. I'm measuring it.. it might take some time. |
||
| const char *entry, const char *filename) | ||
| { | ||
| #ifdef PY_HAVE_PERF_TRAMPOLINE | ||
| _PyPerf_Callbacks callbacks; | ||
| _PyPerfTrampoline_GetCallbacks(&callbacks); | ||
| if (callbacks.write_state == _Py_perfmap_jit_callbacks.write_state) { | ||
| _PyPerfJit_WriteNamedCode( | ||
| code_addr, (unsigned int)code_size, entry, filename); | ||
| return; | ||
| } | ||
| _PyJitUnwind_GdbRegisterCode( | ||
| code_addr, (unsigned int)code_size, entry, filename); | ||
|
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.
Contributor
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. This is now done. |
||
| #else | ||
| (void)code_addr; | ||
| (void)code_size; | ||
| (void)entry; | ||
| (void)filename; | ||
| #endif | ||
| } | ||
|
|
||
| static size_t _Py_jit_shim_size = 0; | ||
|
|
||
| static int | ||
|
|
@@ -731,6 +754,10 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz | |
| } | ||
| executor->jit_code = memory; | ||
| executor->jit_size = total_size; | ||
| jit_record_code(memory, | ||
| code_size + state.trampolines.size, | ||
| "jit_executor", | ||
| "<jit>"); | ||
| return 0; | ||
| } | ||
|
|
||
|
|
@@ -781,6 +808,10 @@ compile_shim(void) | |
| return NULL; | ||
| } | ||
| _Py_jit_shim_size = total_size; | ||
| jit_record_code(memory, | ||
| code_size + state.trampolines.size, | ||
| "jit_shim", | ||
| "<jit>"); | ||
| return (_PyJitEntryFuncPtr)memory; | ||
| } | ||
|
|
||
|
|
||
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.
should be Py_INTERNAL_JIT_UNWIND_H