Skip to content

Commit 669dfb9

Browse files
committed
GH-126910 jit_unwind: add GDB JIT interface and test
1 parent 5cd7ade commit 669dfb9

File tree

8 files changed

+379
-5
lines changed

8 files changed

+379
-5
lines changed

Include/internal/pycore_ceval.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ extern PyStatus _PyPerfTrampoline_AfterFork_Child(void);
108108
#ifdef PY_HAVE_PERF_TRAMPOLINE
109109
extern _PyPerf_Callbacks _Py_perfmap_callbacks;
110110
extern _PyPerf_Callbacks _Py_perfmap_jit_callbacks;
111+
extern void _PyPerfJit_WriteNamedCode(const void *code_addr,
112+
unsigned int code_size,
113+
const char *entry,
114+
const char *filename);
111115
#endif
112116

113117
static inline PyObject*

Include/internal/pycore_jit_unwind.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@ size_t _PyJitUnwind_EhFrameSize(int absolute_addr);
1111
/*
1212
* Build DWARF .eh_frame data for JIT code; returns size written or 0 on error.
1313
* absolute_addr selects the FDE address encoding:
14-
* - 0: PC-relative offsets.
15-
* - nonzero: absolute addresses.
14+
* - 0: PC-relative offsets (perf jitdump synthesized DSO).
15+
* - nonzero: absolute addresses (GDB JIT in-memory ELF).
1616
*/
1717
size_t _PyJitUnwind_BuildEhFrame(uint8_t *buffer, size_t buffer_size,
1818
const void *code_addr, size_t code_size,
1919
int absolute_addr);
2020

21+
void _PyJitUnwind_GdbRegisterCode(const void *code_addr,
22+
unsigned int code_size,
23+
const char *entry,
24+
const char *filename);
25+
2126
#endif // PY_HAVE_PERF_TRAMPOLINE
2227

2328
#endif // Py_CORE_JIT_UNWIND_H
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Sample script for use by test_gdb.test_jit
2+
3+
import operator
4+
import sys
5+
6+
7+
def jit_bt_hot(depth, warming_up_caller=False):
8+
if warming_up_caller:
9+
return
10+
if depth == 0:
11+
id(42)
12+
return
13+
14+
warming_up = True
15+
while warming_up:
16+
warming_up = sys._jit.is_enabled() & (not sys._jit.is_active())
17+
operator.call(jit_bt_hot, depth - 1, warming_up)
18+
19+
20+
jit_bt_hot(10)

Lib/test/test_gdb/test_jit.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import os
2+
import re
3+
import sys
4+
import unittest
5+
6+
from .util import setup_module, DebuggerTests
7+
8+
9+
JIT_SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), "gdb_jit_sample.py")
10+
11+
12+
def setUpModule():
13+
setup_module()
14+
15+
16+
@unittest.skipUnless(
17+
hasattr(sys, "_jit") and sys._jit.is_available(),
18+
"requires a JIT-enabled build",
19+
)
20+
class JitBacktraceTests(DebuggerTests):
21+
def test_bt_unwinds_through_jit_frames(self):
22+
gdb_output = self.get_stack_trace(
23+
script=JIT_SAMPLE_SCRIPT,
24+
cmds_after_breakpoint=["bt"],
25+
PYTHON_JIT="1",
26+
)
27+
self.assertIn("py::jit_executor:<jit>", gdb_output)
28+
self.assertIn("py::jit_shim:<jit>", gdb_output)
29+
self.assertRegex(
30+
gdb_output,
31+
re.compile(
32+
r"py::jit_executor:<jit>.*(_PyEval_EvalFrameDefault|_PyEval_Vector)",
33+
re.DOTALL,
34+
),
35+
)

Lib/test/test_gdb/util.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ def get_stack_trace(self, source=None, script=None,
160160
breakpoint=BREAKPOINT_FN,
161161
cmds_after_breakpoint=None,
162162
import_site=False,
163-
ignore_stderr=False):
163+
ignore_stderr=False,
164+
**env_vars):
164165
'''
165166
Run 'python -c SOURCE' under gdb with a breakpoint.
166167
@@ -239,7 +240,7 @@ def get_stack_trace(self, source=None, script=None,
239240
args += [script]
240241

241242
# Use "args" to invoke gdb, capturing stdout, stderr:
242-
out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
243+
out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED, **env_vars)
243244

244245
if not ignore_stderr:
245246
for line in err.splitlines():

Python/jit.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "pycore_interpframe.h"
1616
#include "pycore_interpolation.h"
1717
#include "pycore_intrinsics.h"
18+
#include "pycore_jit_unwind.h"
1819
#include "pycore_lazyimportobject.h"
1920
#include "pycore_list.h"
2021
#include "pycore_long.h"
@@ -60,6 +61,28 @@ jit_error(const char *message)
6061
PyErr_Format(PyExc_RuntimeWarning, "JIT %s (%d)", message, hint);
6162
}
6263

64+
static void
65+
jit_record_code(const void *code_addr, size_t code_size,
66+
const char *entry, const char *filename)
67+
{
68+
#ifdef PY_HAVE_PERF_TRAMPOLINE
69+
_PyPerf_Callbacks callbacks;
70+
_PyPerfTrampoline_GetCallbacks(&callbacks);
71+
if (callbacks.write_state == _Py_perfmap_jit_callbacks.write_state) {
72+
_PyPerfJit_WriteNamedCode(
73+
code_addr, (unsigned int)code_size, entry, filename);
74+
return;
75+
}
76+
_PyJitUnwind_GdbRegisterCode(
77+
code_addr, (unsigned int)code_size, entry, filename);
78+
#else
79+
(void)code_addr;
80+
(void)code_size;
81+
(void)entry;
82+
(void)filename;
83+
#endif
84+
}
85+
6386
static size_t _Py_jit_shim_size = 0;
6487

6588
static int
@@ -731,6 +754,10 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz
731754
}
732755
executor->jit_code = memory;
733756
executor->jit_size = total_size;
757+
jit_record_code(memory,
758+
code_size + state.trampolines.size,
759+
"jit_executor",
760+
"<jit>");
734761
return 0;
735762
}
736763

@@ -781,6 +808,10 @@ compile_shim(void)
781808
return NULL;
782809
}
783810
_Py_jit_shim_size = total_size;
811+
jit_record_code(memory,
812+
code_size + state.trampolines.size,
813+
"jit_shim",
814+
"<jit>");
784815
return (_PyJitEntryFuncPtr)memory;
785816
}
786817

0 commit comments

Comments
 (0)