99
1010JIT_SAMPLE_SCRIPT = os .path .join (os .path .dirname (__file__ ), "gdb_jit_sample.py" )
1111# In batch GDB, break in builtin_id() while it is running under JIT,
12- # then repeatedly "finish" until the selected frame is the JIT entry .
13- # That gives a deterministic backtrace starting with py::jit_entry:< jit> .
12+ # then repeatedly "finish" until the selected frame is the JIT executor .
13+ # That gives a deterministic backtrace starting with py::jit:executor .
1414#
1515# builtin_id() sits only a few helper frames above the JIT entry on this path.
1616# This bound is just a generous upper limit so the test fails clearly if the
1717# expected stack shape changes.
1818MAX_FINISH_STEPS = 20
19- # Break directly on the lazy shim entry in the binary, then single-step just
20- # enough to let it install the compiled JIT entry and set a temporary
21- # breakpoint on the resulting address.
22- MAX_ENTRY_SETUP_STEPS = 20
2319# After landing on the JIT entry frame, single-step a bounded number of
2420# instructions further into the blob so the backtrace is taken from JIT code
2521# itself rather than the immediate helper-return site. The exact number of
2824# JIT region, instead of asserting against a misleading backtrace.
2925MAX_JIT_ENTRY_STEPS = 4
3026EVAL_FRAME_RE = r"(_PyEval_EvalFrameDefault|_PyEval_Vector)"
27+ JIT_ENTRY_RE = r"_PyJIT_Entry"
28+ JIT_EXECUTOR_FRAME = "py::jit:executor"
3129BACKTRACE_FRAME_RE = re .compile (r"^#\d+\s+.*$" , re .MULTILINE )
3230
33- FINISH_TO_JIT_ENTRY = (
31+ FINISH_TO_JIT_EXECUTOR = (
3432 "python exec(\" import gdb\\ n"
35- "target = 'py::jit_entry:<jit>' \\ n"
33+ f "target = { JIT_EXECUTOR_FRAME !r } \\ n"
3634 f"for _ in range({ MAX_FINISH_STEPS } ):\\ n"
3735 " frame = gdb.selected_frame()\\ n"
3836 " if frame is not None and frame.name() == target:\\ n"
4139 "else:\\ n"
4240 " raise RuntimeError('did not reach %s' % target)\\ n\" )"
4341)
44- BREAK_IN_COMPILED_JIT_ENTRY = (
42+ STEP_INSIDE_JIT_EXECUTOR = (
4543 "python exec(\" import gdb\\ n"
46- "lazy = int(gdb.parse_and_eval('(void*)_Py_LazyJitShim'))\\ n"
47- f"for _ in range({ MAX_ENTRY_SETUP_STEPS } ):\\ n"
48- " entry = int(gdb.parse_and_eval('(void*)_Py_jit_entry'))\\ n"
49- " if entry != lazy:\\ n"
50- " gdb.execute('tbreak *0x%x' % entry)\\ n"
51- " break\\ n"
52- " gdb.execute('next')\\ n"
53- "else:\\ n"
54- " raise RuntimeError('compiled JIT entry was not installed')\\ n\" )"
55- )
56- STEP_INSIDE_JIT_ENTRY = (
57- "python exec(\" import gdb\\ n"
58- "target = 'py::jit_entry:<jit>'\\ n"
44+ f"target = { JIT_EXECUTOR_FRAME !r} \\ n"
5945 f"for _ in range({ MAX_JIT_ENTRY_STEPS } ):\\ n"
6046 " frame = gdb.selected_frame()\\ n"
6147 " if frame is None or frame.name() != target:\\ n"
@@ -103,10 +89,12 @@ def _extract_backtrace_frames(self, gdb_output):
10389 def _assert_jit_backtrace_shape (self , gdb_output , * , anchor_at_top ):
10490 # Shape assertions applied to every JIT backtrace we produce:
10591 # 1. The synthetic JIT symbol appears exactly once. A second
106- # py::jit_entry:< jit> frame would mean the unwinder is
92+ # py::jit:executor frame would mean the unwinder is
10793 # materializing two native frames for a single logical JIT
10894 # region, or failing to unwind out of the region entirely.
109- # 2. At least one _PyEval_EvalFrameDefault / _PyEval_Vector
95+ # 2. The linked shim frame appears exactly once after the
96+ # synthetic JIT frame and before the eval loop.
97+ # 3. At least one _PyEval_EvalFrameDefault / _PyEval_Vector
11098 # frame appears after the JIT frame, proving the unwinder
11199 # climbs back out of the JIT region into the eval loop.
112100 # Helper frames from inside the JITted region may still
@@ -116,11 +104,18 @@ def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top):
116104 frames = self ._extract_backtrace_frames (gdb_output )
117105 backtrace = "\n " .join (frames )
118106
119- jit_frames = [frame for frame in frames if "py::jit_entry:<jit>" in frame ]
107+ jit_frames = [frame for frame in frames if JIT_EXECUTOR_FRAME in frame ]
120108 jit_count = len (jit_frames )
121109 self .assertEqual (
122110 jit_count , 1 ,
123- f"expected exactly 1 py::jit_entry:<jit> frame, got { jit_count } \n "
111+ f"expected exactly 1 { JIT_EXECUTOR_FRAME } frame, got { jit_count } \n "
112+ f"backtrace:\n { backtrace } " ,
113+ )
114+ jit_entry_frames = [frame for frame in frames if re .search (JIT_ENTRY_RE , frame )]
115+ jit_entry_count = len (jit_entry_frames )
116+ self .assertEqual (
117+ jit_entry_count , 1 ,
118+ f"expected exactly 1 _PyJIT_Entry frame, got { jit_entry_count } \n "
124119 f"backtrace:\n { backtrace } " ,
125120 )
126121 eval_frames = [frame for frame in frames if re .search (EVAL_FRAME_RE , frame )]
@@ -131,7 +126,15 @@ def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top):
131126 f"backtrace:\n { backtrace } " ,
132127 )
133128 jit_frame_index = next (
134- i for i , frame in enumerate (frames ) if "py::jit_entry:<jit>" in frame
129+ i for i , frame in enumerate (frames ) if JIT_EXECUTOR_FRAME in frame
130+ )
131+ jit_entry_index = next (
132+ i for i , frame in enumerate (frames ) if re .search (JIT_ENTRY_RE , frame )
133+ )
134+ self .assertGreater (
135+ jit_entry_index , jit_frame_index ,
136+ "expected _PyJIT_Entry after the synthetic JIT frame\n "
137+ f"backtrace:\n { backtrace } " ,
135138 )
136139 eval_after_jit = any (
137140 re .search (EVAL_FRAME_RE , frame )
@@ -142,10 +145,23 @@ def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top):
142145 f"expected an eval frame after the JIT frame\n "
143146 f"backtrace:\n { backtrace } " ,
144147 )
148+ eval_after_entry = any (
149+ re .search (EVAL_FRAME_RE , frame )
150+ for frame in frames [jit_entry_index + 1 :]
151+ )
152+ self .assertTrue (
153+ eval_after_entry ,
154+ "expected an eval frame after _PyJIT_Entry\n "
155+ f"backtrace:\n { backtrace } " ,
156+ )
145157 relevant_end = max (
146158 i
147159 for i , frame in enumerate (frames )
148- if "py::jit_entry:<jit>" in frame or re .search (EVAL_FRAME_RE , frame )
160+ if (
161+ JIT_EXECUTOR_FRAME in frame
162+ or re .search (JIT_ENTRY_RE , frame )
163+ or re .search (EVAL_FRAME_RE , frame )
164+ )
149165 )
150166 truncated_frames = [
151167 frame for frame in frames [: relevant_end + 1 ]
@@ -159,46 +175,29 @@ def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top):
159175 if anchor_at_top :
160176 self .assertRegex (
161177 frames [0 ],
162- re .compile (r "^#0\s+py::jit_entry:<jit> " ),
178+ re .compile (rf "^#0\s+{ re . escape ( JIT_EXECUTOR_FRAME ) } " ),
163179 )
164180
165- def test_bt_shows_compiled_jit_entry (self ):
166- gdb_output = self .get_stack_trace (
167- script = JIT_SAMPLE_SCRIPT ,
168- breakpoint = "_Py_LazyJitShim" ,
169- cmds_after_breakpoint = [
170- BREAK_IN_COMPILED_JIT_ENTRY ,
171- "continue" ,
172- "bt" ,
173- ],
174- PYTHON_JIT = "1" ,
175- )
176- # GDB registers the compiled JIT entry and per-trace JIT regions under
177- # the same synthetic symbol name; breaking at the entry PC pins the
178- # JIT frame at #0.
179- self ._assert_jit_backtrace_shape (gdb_output , anchor_at_top = True )
180-
181181 def test_bt_unwinds_through_jit_frames (self ):
182182 gdb_output = self .get_stack_trace (
183183 script = JIT_SAMPLE_SCRIPT ,
184184 cmds_after_breakpoint = ["bt" ],
185185 PYTHON_JIT = "1" ,
186186 )
187187 # The executor should appear as a named JIT frame and unwind back into
188- # the eval loop. Whether GDB also materializes a separate shim frame is
189- # an implementation detail of the synthetic executor CFI.
188+ # the eval loop.
190189 self ._assert_jit_backtrace_shape (gdb_output , anchor_at_top = False )
191190
192- def test_bt_unwinds_from_inside_jit_entry (self ):
191+ def test_bt_unwinds_from_inside_jit_executor (self ):
193192 gdb_output = self .get_stack_trace (
194193 script = JIT_SAMPLE_SCRIPT ,
195194 cmds_after_breakpoint = [
196- FINISH_TO_JIT_ENTRY ,
197- STEP_INSIDE_JIT_ENTRY ,
195+ FINISH_TO_JIT_EXECUTOR ,
196+ STEP_INSIDE_JIT_EXECUTOR ,
198197 "bt" ,
199198 ],
200199 PYTHON_JIT = "1" ,
201200 )
202- # Once the selected PC is inside the JIT entry , we require that GDB
201+ # Once the selected PC is inside the JIT executor , we require that GDB
203202 # identifies the JIT frame at #0 and keeps unwinding into _PyEval_*.
204203 self ._assert_jit_backtrace_shape (gdb_output , anchor_at_top = True )
0 commit comments