-
-
Notifications
You must be signed in to change notification settings - Fork 34.4k
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
Open
diegorusso
wants to merge
17
commits into
python:main
Choose a base branch
from
diegorusso:add-gdb-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
5cd7ade
GH-126910 jit_unwind: refactor EH frame generation
diegorusso 669dfb9
GH-126910 jit_unwind: add GDB JIT interface and test
diegorusso 255c0b3
Fix make smelly
diegorusso ac018d6
Add __jit_debug_descriptor to ignored.tsv
diegorusso b0bab8c
📜🤖 Added by blurb_it.
blurb-it[bot] a0dff1f
Fix check C globals
diegorusso 2b52588
Merge branch 'main' into add-gdb-support
diegorusso e44170e
Address Pablo's feedback
diegorusso 2e40f1d
Fix smelly
diegorusso d890add
Merge branch 'main' into add-gdb-support
diegorusso 965a543
Strengthen JIT GDB backtrace tests
diegorusso 17be0a2
Fix x86_64 unwind
diegorusso f47d763
Add comment for inviariant and fix CFI instructions
diegorusso 67ae6cb
Rename jit_executor/jit_shim to just jit_entry
diegorusso a18cb96
Address Pablo's feedback
diegorusso bdc8d12
Make the mutex private
diegorusso 6357698
Address Pablo's feedback
diegorusso File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| #ifndef Py_INTERNAL_JIT_UNWIND_H | ||
| #define Py_INTERNAL_JIT_UNWIND_H | ||
|
|
||
| #ifndef Py_BUILD_CORE | ||
| # error "this header requires Py_BUILD_CORE define" | ||
| #endif | ||
|
|
||
| #if defined(PY_HAVE_PERF_TRAMPOLINE) || (defined(__linux__) && defined(__ELF__)) | ||
|
|
||
| #include <stddef.h> | ||
|
|
||
| /* DWARF exception-handling pointer encodings shared by JIT unwind users. */ | ||
| enum { | ||
| DWRF_EH_PE_absptr = 0x00, | ||
| DWRF_EH_PE_omit = 0xff, | ||
|
|
||
| /* Data type encodings */ | ||
| DWRF_EH_PE_uleb128 = 0x01, | ||
| DWRF_EH_PE_udata2 = 0x02, | ||
| DWRF_EH_PE_udata4 = 0x03, | ||
| DWRF_EH_PE_udata8 = 0x04, | ||
| DWRF_EH_PE_sleb128 = 0x09, | ||
| DWRF_EH_PE_sdata2 = 0x0a, | ||
| DWRF_EH_PE_sdata4 = 0x0b, | ||
| DWRF_EH_PE_sdata8 = 0x0c, | ||
| DWRF_EH_PE_signed = 0x08, | ||
|
|
||
| /* Reference type encodings */ | ||
| DWRF_EH_PE_pcrel = 0x10, | ||
| DWRF_EH_PE_textrel = 0x20, | ||
| DWRF_EH_PE_datarel = 0x30, | ||
| DWRF_EH_PE_funcrel = 0x40, | ||
| DWRF_EH_PE_aligned = 0x50, | ||
| DWRF_EH_PE_indirect = 0x80 | ||
| }; | ||
|
|
||
| /* 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, | ||
| size_t code_size, | ||
| const char *entry, | ||
| const char *filename); | ||
|
|
||
| void _PyJitUnwind_GdbUnregisterCode(void *handle); | ||
|
|
||
| #endif // defined(PY_HAVE_PERF_TRAMPOLINE) || (defined(__linux__) && defined(__ELF__)) | ||
|
|
||
| #endif // Py_INTERNAL_JIT_UNWIND_H | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Sample script for use by test_gdb.test_jit | ||
|
|
||
| import _testinternalcapi | ||
| import operator | ||
|
|
||
|
|
||
| WARMUP_ITERATIONS = _testinternalcapi.TIER2_THRESHOLD + 10 | ||
|
|
||
|
|
||
| def jit_bt_hot(depth, warming_up_caller=False): | ||
| if depth == 0: | ||
| if not warming_up_caller: | ||
| id(42) | ||
| return | ||
|
|
||
| for iteration in range(WARMUP_ITERATIONS): | ||
| operator.call( | ||
| jit_bt_hot, | ||
| depth - 1, | ||
| warming_up_caller or iteration + 1 != WARMUP_ITERATIONS, | ||
| ) | ||
|
|
||
|
|
||
| # Warm the shared shim once without hitting builtin_id so the real run uses | ||
| # the steady-state shim path when GDB breaks inside id(42). | ||
| jit_bt_hot(1, warming_up_caller=True) | ||
| jit_bt_hot(1) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| 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") | ||
| # In batch GDB, break in builtin_id() while it is running under JIT, | ||
| # then repeatedly "finish" until the selected frame is the JIT entry. | ||
| # That gives a deterministic backtrace starting with py::jit_entry:<jit>. | ||
| # | ||
| # builtin_id() sits only a few helper frames above the JIT entry on this path. | ||
| # This bound is just a generous upper limit so the test fails clearly if the | ||
| # expected stack shape changes. | ||
| MAX_FINISH_STEPS = 20 | ||
| # Break directly on the lazy shim entry in the binary, then single-step just | ||
| # enough to let it install the compiled JIT entry and set a temporary | ||
| # breakpoint on the resulting address. | ||
| MAX_ENTRY_SETUP_STEPS = 20 | ||
| # After landing on the JIT entry frame, single-step a little further into the | ||
| # blob so the backtrace is taken from JIT code itself rather than the | ||
| # immediate helper-return site. | ||
| JIT_ENTRY_SINGLE_STEPS = 2 | ||
| EVAL_FRAME_RE = r"(_PyEval_EvalFrameDefault|_PyEval_Vector)" | ||
|
|
||
| FINISH_TO_JIT_ENTRY = ( | ||
| "python exec(\"import gdb\\n" | ||
| "target = 'py::jit_entry:<jit>'\\n" | ||
| f"for _ in range({MAX_FINISH_STEPS}):\\n" | ||
| " frame = gdb.selected_frame()\\n" | ||
| " if frame is not None and frame.name() == target:\\n" | ||
| " break\\n" | ||
| " gdb.execute('finish')\\n" | ||
| "else:\\n" | ||
| " raise RuntimeError('did not reach %s' % target)\\n\")" | ||
| ) | ||
| BREAK_IN_COMPILED_JIT_ENTRY = ( | ||
| "python exec(\"import gdb\\n" | ||
| "lazy = int(gdb.parse_and_eval('(void*)_Py_LazyJitShim'))\\n" | ||
| f"for _ in range({MAX_ENTRY_SETUP_STEPS}):\\n" | ||
| " entry = int(gdb.parse_and_eval('(void*)_Py_jit_entry'))\\n" | ||
| " if entry != lazy:\\n" | ||
| " gdb.execute('tbreak *0x%x' % entry)\\n" | ||
| " break\\n" | ||
| " gdb.execute('next')\\n" | ||
| "else:\\n" | ||
| " raise RuntimeError('compiled JIT entry was not installed')\\n\")" | ||
| ) | ||
|
|
||
|
|
||
| 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_shows_compiled_jit_entry(self): | ||
| gdb_output = self.get_stack_trace( | ||
| script=JIT_SAMPLE_SCRIPT, | ||
| breakpoint="_Py_LazyJitShim", | ||
| cmds_after_breakpoint=[ | ||
| BREAK_IN_COMPILED_JIT_ENTRY, | ||
| "continue", | ||
| "bt", | ||
| ], | ||
| PYTHON_JIT="1", | ||
| ) | ||
| # GDB registers the compiled JIT entry and per-trace JIT regions under | ||
| # the same synthetic symbol name. | ||
| self.assertRegex( | ||
| gdb_output, | ||
| re.compile( | ||
| rf"#0\s+py::jit_entry:<jit>.*{EVAL_FRAME_RE}", | ||
| re.DOTALL, | ||
| ), | ||
| ) | ||
|
|
||
| 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", | ||
| ) | ||
| # The executor should appear as a named JIT frame and unwind back into | ||
| # the eval loop. Whether GDB also materializes a separate shim frame is | ||
| # an implementation detail of the synthetic executor CFI. | ||
| self.assertRegex( | ||
| gdb_output, | ||
| re.compile( | ||
| rf"py::jit_entry:<jit>.*{EVAL_FRAME_RE}", | ||
| re.DOTALL, | ||
| ), | ||
| ) | ||
|
|
||
| def test_bt_unwinds_from_inside_jit_entry(self): | ||
| gdb_output = self.get_stack_trace( | ||
| script=JIT_SAMPLE_SCRIPT, | ||
| cmds_after_breakpoint=[ | ||
| FINISH_TO_JIT_ENTRY, | ||
| *(["si"] * JIT_ENTRY_SINGLE_STEPS), | ||
| "bt", | ||
| ], | ||
| PYTHON_JIT="1", | ||
| ) | ||
| # Once the selected PC is inside the JIT entry, we only require that | ||
| # GDB can identify the JIT frame and keep unwinding into _PyEval_*. | ||
| self.assertRegex( | ||
| gdb_output, | ||
| re.compile( | ||
| rf"#0\s+py::jit_entry:<jit>.*{EVAL_FRAME_RE}", | ||
| re.DOTALL, | ||
| ), | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
Misc/NEWS.d/next/Core_and_Builtins/2026-03-17-20-30-17.gh-issue-126910.NaUwmD.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
this is missing Py_BUILD_CORE guard no?
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.
yes, I've seen now the other headers files.