gh-98894: Restore function entry/exit DTrace probes (#142397)

The function__entry and function__return probes stopped working in Python 3.11
when the interpreter was restructured around the new bytecode system. This change
restores these probes by adding DTRACE_FUNCTION_ENTRY() at the start_frame label
in bytecodes.c and DTRACE_FUNCTION_RETURN() in the RETURN_VALUE and YIELD_VALUE
instructions. The helper functions are defined in ceval.c and extract the
filename, function name, and line number from the frame before firing the probe.

This builds on the approach from https://github.com/python/cpython/pull/125019
but avoids modifying the JIT template since the JIT does not currently support
DTrace. The macros are conditionally compiled with WITH_DTRACE and are no-ops
otherwise. The tests have been updated to use modern opcode names (CALL, CALL_KW,
CALL_FUNCTION_EX) and a new bpftrace backend was added for Linux CI alongside
the existing SystemTap tests. Line probe tests were removed since that probe
was never restored after 3.11.
This commit is contained in:
Pablo Galindo Salgado 2026-05-05 01:29:55 +01:00 committed by GitHub
parent 8c796782fc
commit 9a268e3e33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 319 additions and 60 deletions

View file

@ -1568,6 +1568,7 @@ dummy_func(
DEAD(retval);
SAVE_STACK();
assert(STACK_LEVEL() == 0);
DTRACE_FUNCTION_RETURN();
_Py_LeaveRecursiveCallPy(tstate);
// GH-99729: We need to unlink the frame *before* clearing it:
_PyInterpreterFrame *dying = frame;
@ -1760,6 +1761,7 @@ dummy_func(
_PyStackRef temp = retval;
DEAD(retval);
SAVE_STACK();
DTRACE_FUNCTION_RETURN();
tstate->exc_info = gen->gi_exc_state.previous_item;
gen->gi_exc_state.previous_item = NULL;
_Py_LeaveRecursiveCallPy(tstate);
@ -4569,6 +4571,7 @@ dummy_func(
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
@ -6469,6 +6472,12 @@ dummy_func(
}
spilled label(exit_unwind) {
assert(_PyErr_Occurred(tstate));
DTRACE_FUNCTION_RETURN();
goto exit_unwind_notrace;
}
spilled label(exit_unwind_notrace) {
assert(_PyErr_Occurred(tstate));
_Py_LeaveRecursiveCallPy(tstate);
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
@ -6501,8 +6510,9 @@ dummy_func(
spilled label(start_frame) {
int too_deep = _Py_EnterRecursivePy(tstate);
if (too_deep) {
goto exit_unwind;
goto exit_unwind_notrace;
}
DTRACE_FUNCTION_ENTRY();
next_instr = frame->instr_ptr;
#ifdef Py_DEBUG
int lltrace = maybe_lltrace_resume_frame(frame, GLOBALS());
@ -6601,6 +6611,7 @@ dummy_func(
error:
exception_unwind:
exit_unwind:
exit_unwind_notrace:
handle_eval_breaker:
resume_frame:
start_frame:

View file

@ -1174,6 +1174,38 @@ _Py_ReachedRecursionLimit(PyThreadState *tstate) {
#define DONT_SLP_VECTORIZE
#endif
#ifdef WITH_DTRACE
static void
dtrace_function_entry(_PyInterpreterFrame *frame)
{
const char *filename;
const char *funcname;
int lineno;
PyCodeObject *code = _PyFrame_GetCode(frame);
filename = PyUnicode_AsUTF8(code->co_filename);
funcname = PyUnicode_AsUTF8(code->co_name);
lineno = PyUnstable_InterpreterFrame_GetLine(frame);
PyDTrace_FUNCTION_ENTRY(filename, funcname, lineno);
}
static void
dtrace_function_return(_PyInterpreterFrame *frame)
{
const char *filename;
const char *funcname;
int lineno;
PyCodeObject *code = _PyFrame_GetCode(frame);
filename = PyUnicode_AsUTF8(code->co_filename);
funcname = PyUnicode_AsUTF8(code->co_name);
lineno = PyUnstable_InterpreterFrame_GetLine(frame);
PyDTrace_FUNCTION_RETURN(filename, funcname, lineno);
}
#endif
PyObject* _Py_HOT_FUNCTION DONT_SLP_VECTORIZE
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
{

View file

@ -328,11 +328,24 @@ GETITEM(PyObject *v, Py_ssize_t i) {
#define CONSTS() _PyFrame_GetCode(frame)->co_consts
#define NAMES() _PyFrame_GetCode(frame)->co_names
#if defined(WITH_DTRACE) && !defined(Py_BUILD_CORE_MODULE)
static void dtrace_function_entry(_PyInterpreterFrame *);
static void dtrace_function_return(_PyInterpreterFrame *);
#define DTRACE_FUNCTION_ENTRY() \
if (PyDTrace_FUNCTION_ENTRY_ENABLED()) { \
dtrace_function_entry(frame); \
}
#define DTRACE_FUNCTION_RETURN() \
if (PyDTrace_FUNCTION_RETURN_ENABLED()) { \
dtrace_function_return(frame); \
}
#else
#define DTRACE_FUNCTION_ENTRY() ((void)0)
#define DTRACE_FUNCTION_RETURN() ((void)0)
#endif
/* This takes a uint16_t instead of a _Py_BackoffCounter,
* because it is used directly on the cache entry in generated code,
* which is always an integral type. */

View file

@ -8623,6 +8623,7 @@
_PyStackRef temp = retval;
_PyFrame_SetStackPointer(frame, stack_pointer);
assert(STACK_LEVEL() == 0);
DTRACE_FUNCTION_RETURN();
_Py_LeaveRecursiveCallPy(tstate);
_PyInterpreterFrame *dying = frame;
frame = tstate->current_frame = dying->previous;
@ -8830,6 +8831,7 @@
assert(oparg == 0 || oparg == 1);
_PyStackRef temp = retval;
_PyFrame_SetStackPointer(frame, stack_pointer);
DTRACE_FUNCTION_RETURN();
tstate->exc_info = gen->gi_exc_state.previous_item;
gen->gi_exc_state.previous_item = NULL;
_Py_LeaveRecursiveCallPy(tstate);
@ -16840,6 +16842,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
SET_CURRENT_CACHED_VALUES(0);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());

View file

@ -779,6 +779,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -2005,6 +2006,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -2148,6 +2150,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -2276,6 +2279,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -2871,6 +2875,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -3465,6 +3470,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -3652,6 +3658,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -4489,6 +4496,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -4589,6 +4597,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -6174,6 +6183,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -7864,6 +7874,7 @@
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
assert(STACK_LEVEL() == 0);
DTRACE_FUNCTION_RETURN();
_Py_LeaveRecursiveCallPy(tstate);
_PyInterpreterFrame *dying = frame;
frame = tstate->current_frame = dying->previous;
@ -7926,6 +7937,7 @@
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
DTRACE_FUNCTION_RETURN();
tstate->exc_info = gen->gi_exc_state.previous_item;
gen->gi_exc_state.previous_item = NULL;
_Py_LeaveRecursiveCallPy(tstate);
@ -8532,6 +8544,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -9068,6 +9081,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -11047,6 +11061,7 @@
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
assert(STACK_LEVEL() == 0);
DTRACE_FUNCTION_RETURN();
_Py_LeaveRecursiveCallPy(tstate);
_PyInterpreterFrame *dying = frame;
frame = tstate->current_frame = dying->previous;
@ -11244,6 +11259,7 @@
tstate->py_recursion_remaining--;
LOAD_SP();
LOAD_IP(0);
DTRACE_FUNCTION_ENTRY();
LLTRACE_RESUME_FRAME();
}
DISPATCH();
@ -12930,6 +12946,7 @@
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
DTRACE_FUNCTION_RETURN();
tstate->exc_info = gen->gi_exc_state.previous_item;
gen->gi_exc_state.previous_item = NULL;
_Py_LeaveRecursiveCallPy(tstate);
@ -13076,6 +13093,13 @@ JUMP_TO_LABEL(error);
}
LABEL(exit_unwind)
{
assert(_PyErr_Occurred(tstate));
DTRACE_FUNCTION_RETURN();
JUMP_TO_LABEL(exit_unwind_notrace);
}
LABEL(exit_unwind_notrace)
{
assert(_PyErr_Occurred(tstate));
_Py_LeaveRecursiveCallPy(tstate);
@ -13108,8 +13132,9 @@ JUMP_TO_LABEL(error);
{
int too_deep = _Py_EnterRecursivePy(tstate);
if (too_deep) {
JUMP_TO_LABEL(exit_unwind);
JUMP_TO_LABEL(exit_unwind_notrace);
}
DTRACE_FUNCTION_ENTRY();
next_instr = frame->instr_ptr;
#ifdef Py_DEBUG
int lltrace = maybe_lltrace_resume_frame(frame, GLOBALS());

View file

@ -527,6 +527,7 @@ static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_pop_1_error(TAIL_CALL_PARAMS);
static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_error(TAIL_CALL_PARAMS);
static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_exception_unwind(TAIL_CALL_PARAMS);
static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_exit_unwind(TAIL_CALL_PARAMS);
static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_exit_unwind_notrace(TAIL_CALL_PARAMS);
static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_start_frame(TAIL_CALL_PARAMS);
static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_stop_tracing(TAIL_CALL_PARAMS);