1515# This bound is just a generous upper limit so the test fails clearly if the
1616# expected stack shape changes.
1717MAX_FINISH_STEPS = 20
18+ # Break directly on the lazy shim entry in the binary, then single-step just
19+ # enough to let it install the compiled shim and set a temporary breakpoint on
20+ # the resulting JIT entry address.
21+ MAX_SHIM_SETUP_STEPS = 20
1822# After landing on the executor frame, single-step a little further into the
1923# blob so the backtrace is taken from executor code itself rather than the
2024# immediate helper-return site.
2125EXECUTOR_SINGLE_STEPS = 2
26+ EVAL_FRAME_RE = r"(_PyEval_EvalFrameDefault|_PyEval_Vector)"
2227
2328FINISH_TO_JIT_EXECUTOR = (
2429 "python exec(\" import gdb\\ n"
3136 "else:\\ n"
3237 " raise RuntimeError('did not reach %s' % target)\\ n\" )"
3338)
39+ BREAK_IN_COMPILED_SHIM = (
40+ "python exec(\" import gdb\\ n"
41+ "lazy = int(gdb.parse_and_eval('(void*)_Py_LazyJitShim'))\\ n"
42+ f"for _ in range({ MAX_SHIM_SETUP_STEPS } ):\\ n"
43+ " entry = int(gdb.parse_and_eval('(void*)_Py_jit_entry'))\\ n"
44+ " if entry != lazy:\\ n"
45+ " gdb.execute('tbreak *0x%x' % entry)\\ n"
46+ " break\\ n"
47+ " gdb.execute('next')\\ n"
48+ "else:\\ n"
49+ " raise RuntimeError('compiled shim was not installed')\\ n\" )"
50+ )
3451
3552
3653def setUpModule ():
@@ -42,17 +59,38 @@ def setUpModule():
4259 "requires a JIT-enabled build" ,
4360)
4461class JitBacktraceTests (DebuggerTests ):
62+ def test_bt_shows_compiled_jit_shim (self ):
63+ gdb_output = self .get_stack_trace (
64+ script = JIT_SAMPLE_SCRIPT ,
65+ breakpoint = "_Py_LazyJitShim" ,
66+ cmds_after_breakpoint = [
67+ BREAK_IN_COMPILED_SHIM ,
68+ "continue" ,
69+ "bt" ,
70+ ],
71+ PYTHON_JIT = "1" ,
72+ )
73+ self .assertRegex (
74+ gdb_output ,
75+ re .compile (
76+ rf"#0\s+py::jit_shim:<jit>.*{ EVAL_FRAME_RE } " ,
77+ re .DOTALL ,
78+ ),
79+ )
80+
4581 def test_bt_unwinds_through_jit_frames (self ):
4682 gdb_output = self .get_stack_trace (
4783 script = JIT_SAMPLE_SCRIPT ,
4884 cmds_after_breakpoint = ["bt" ],
4985 PYTHON_JIT = "1" ,
5086 )
87+ # The executor should appear as a named JIT frame and unwind back into
88+ # the eval loop. Whether GDB also materializes a separate shim frame is
89+ # an implementation detail of the synthetic executor CFI.
5190 self .assertRegex (
5291 gdb_output ,
5392 re .compile (
54- r"py::jit_executor:<jit>.*py::jit_shim:<jit>.*"
55- r"(_PyEval_EvalFrameDefault|_PyEval_Vector)" ,
93+ rf"py::jit_executor:<jit>.*{ EVAL_FRAME_RE } " ,
5694 re .DOTALL ,
5795 ),
5896 )
@@ -67,11 +105,12 @@ def test_bt_unwinds_from_inside_jit_executor(self):
67105 ],
68106 PYTHON_JIT = "1" ,
69107 )
108+ # Once the selected PC is inside the executor, we only require that
109+ # GDB can identify the JIT frame and keep unwinding into _PyEval_*.
70110 self .assertRegex (
71111 gdb_output ,
72112 re .compile (
73- r"#0\s+py::jit_executor:<jit>.*#1\s+py::jit_shim:<jit>.*"
74- r"(_PyEval_EvalFrameDefault|_PyEval_Vector)" ,
113+ rf"#0\s+py::jit_executor:<jit>.*{ EVAL_FRAME_RE } " ,
75114 re .DOTALL ,
76115 ),
77116 )
0 commit comments