]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-98894: Restore function entry/exit DTrace probes (#142397)
authorPablo Galindo Salgado <Pablogsal@gmail.com>
Tue, 5 May 2026 00:29:55 +0000 (01:29 +0100)
committerGitHub <noreply@github.com>
Tue, 5 May 2026 00:29:55 +0000 (00:29 +0000)
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.

15 files changed:
Lib/test/dtracedata/call_stack.py
Lib/test/dtracedata/call_stack.stp.expected
Lib/test/dtracedata/line.d [deleted file]
Lib/test/dtracedata/line.d.expected [deleted file]
Lib/test/dtracedata/line.py [deleted file]
Lib/test/test_dtrace.py
Misc/NEWS.d/next/Core_and_Builtins/2025-12-08-00-25-35.gh-issue-98894.hKWyfqNx.rst [new file with mode: 0644]
Modules/_testinternalcapi/test_cases.c.h
Modules/_testinternalcapi/test_targets.h
Python/bytecodes.c
Python/ceval.c
Python/ceval_macros.h
Python/executor_cases.c.h
Python/generated_cases.c.h
Python/opcode_targets.h

index ee9f3ae8d6c9f4ab6b606d49f25376e6c03d3a37..11c0369d4baa0451b324647463f9ac5bf3046196 100644 (file)
@@ -5,16 +5,16 @@ def function_1():
 def function_2():
     function_1()
 
-# CALL_FUNCTION_VAR
+# CALL with positional args
 def function_3(dummy, dummy2):
     pass
 
-# CALL_FUNCTION_KW
+# CALL_KW (keyword arguments)
 def function_4(**dummy):
     return 1
     return 2  # unreachable
 
-# CALL_FUNCTION_VAR_KW
+# CALL_FUNCTION_EX (unpacking)
 def function_5(dummy, dummy2, **dummy3):
     if False:
         return 7
index 32cf396f820629ef09980b410000637fee2fe0c2..044eae62018f5791e4aaa4691cc3f93b27e49dc4 100644 (file)
@@ -1,8 +1,11 @@
-function__entry:call_stack.py:start:23
 function__entry:call_stack.py:function_1:1
+function__entry:call_stack.py:function_3:9
+function__return:call_stack.py:function_3:10
 function__return:call_stack.py:function_1:2
 function__entry:call_stack.py:function_2:5
 function__entry:call_stack.py:function_1:1
+function__entry:call_stack.py:function_3:9
+function__return:call_stack.py:function_3:10
 function__return:call_stack.py:function_1:2
 function__return:call_stack.py:function_2:6
 function__entry:call_stack.py:function_3:9
@@ -11,4 +14,3 @@ function__entry:call_stack.py:function_4:13
 function__return:call_stack.py:function_4:14
 function__entry:call_stack.py:function_5:18
 function__return:call_stack.py:function_5:21
-function__return:call_stack.py:start:28
diff --git a/Lib/test/dtracedata/line.d b/Lib/test/dtracedata/line.d
deleted file mode 100644 (file)
index 03f22db..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-python$target:::line
-/(copyinstr(arg1)=="test_line")/
-{
-    printf("%d\t%s:%s:%s:%d\n", timestamp,
-        probename, basename(copyinstr(arg0)),
-        copyinstr(arg1), arg2);
-}
diff --git a/Lib/test/dtracedata/line.d.expected b/Lib/test/dtracedata/line.d.expected
deleted file mode 100644 (file)
index 9b16ce7..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-line:line.py:test_line:2
-line:line.py:test_line:3
-line:line.py:test_line:4
-line:line.py:test_line:5
-line:line.py:test_line:6
-line:line.py:test_line:7
-line:line.py:test_line:8
-line:line.py:test_line:9
-line:line.py:test_line:10
-line:line.py:test_line:11
-line:line.py:test_line:4
-line:line.py:test_line:5
-line:line.py:test_line:6
-line:line.py:test_line:7
-line:line.py:test_line:8
-line:line.py:test_line:10
-line:line.py:test_line:11
-line:line.py:test_line:4
-line:line.py:test_line:12
-line:line.py:test_line:13
diff --git a/Lib/test/dtracedata/line.py b/Lib/test/dtracedata/line.py
deleted file mode 100644 (file)
index 0930ff3..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-def test_line():
-    a = 1
-    print('# Preamble', a)
-    for i in range(2):
-        a = i
-        b = i+2
-        c = i+3
-        if c < 4:
-            a = c
-        d = a + b +c
-        print('#', a, b, c, d)
-    a = 1
-    print('# Epilogue', a)
-
-
-if __name__ == '__main__':
-    test_line()
index ba2fa99707cd469f6e45cb75ebdabe806258c67c..61320a472f3e02c1f967b5333eff9b1c98a96461 100644 (file)
@@ -33,11 +33,17 @@ def normalize_trace_output(output):
         result = [
             row.split("\t")
             for row in output.splitlines()
-            if row and not row.startswith('#')
+            if row and not row.startswith('#') and not row.startswith('@')
         ]
         result.sort(key=lambda row: int(row[0]))
         result = [row[1] for row in result]
-        return "\n".join(result)
+        # Normalize paths to basenames (bpftrace outputs full paths)
+        normalized = []
+        for line in result:
+            # Replace full paths with just the filename
+            line = re.sub(r'/[^:]+/([^/:]+\.py)', r'\1', line)
+            normalized.append(line)
+        return "\n".join(normalized)
     except (IndexError, ValueError):
         raise AssertionError(
             "tracer produced unparsable output:\n{}".format(output)
@@ -96,6 +102,8 @@ class TraceBackend:
 class DTraceBackend(TraceBackend):
     EXTENSION = ".d"
     COMMAND = ["dtrace", "-q", "-s"]
+    if sys.platform == "sunos5":
+        COMMAND.insert(2, "-Z")
 
 
 class SystemTapBackend(TraceBackend):
@@ -103,6 +111,177 @@ class SystemTapBackend(TraceBackend):
     COMMAND = ["stap", "-g"]
 
 
+class BPFTraceBackend(TraceBackend):
+    EXTENSION = ".bt"
+    COMMAND = ["bpftrace"]
+
+    # Inline bpftrace programs for each test case
+    PROGRAMS = {
+        "call_stack": """
+            usdt:{python}:python:function__entry {{
+                printf("%lld\\tfunction__entry:%s:%s:%d\\n",
+                       nsecs, str(arg0), str(arg1), arg2);
+            }}
+            usdt:{python}:python:function__return {{
+                printf("%lld\\tfunction__return:%s:%s:%d\\n",
+                       nsecs, str(arg0), str(arg1), arg2);
+            }}
+        """,
+        "gc": """
+            usdt:{python}:python:function__entry {{
+                if (str(arg1) == "start") {{ @tracing = 1; }}
+            }}
+            usdt:{python}:python:function__return {{
+                if (str(arg1) == "start") {{ @tracing = 0; }}
+            }}
+            usdt:{python}:python:gc__start {{
+                if (@tracing) {{
+                    printf("%lld\\tgc__start:%d\\n", nsecs, arg0);
+                }}
+            }}
+            usdt:{python}:python:gc__done {{
+                if (@tracing) {{
+                    printf("%lld\\tgc__done:%lld\\n", nsecs, arg0);
+                }}
+            }}
+            END {{ clear(@tracing); }}
+        """,
+    }
+
+    # Which test scripts to filter by filename (None = use @tracing flag)
+    FILTER_BY_FILENAME = {"call_stack": "call_stack.py"}
+
+    @staticmethod
+    def _filter_probe_rows(output):
+        return "\n".join(
+            line for line in output.splitlines()
+            if line.partition("\t")[0].isdigit()
+        )
+
+    # Expected outputs for each test case
+    # Note: bpftrace captures <module> entry/return and may have slight timing
+    # differences compared to SystemTap due to probe firing order
+    EXPECTED = {
+        "call_stack": """function__entry:call_stack.py:<module>:0
+function__entry:call_stack.py:start:23
+function__entry:call_stack.py:function_1:1
+function__entry:call_stack.py:function_3:9
+function__return:call_stack.py:function_3:10
+function__return:call_stack.py:function_1:2
+function__entry:call_stack.py:function_2:5
+function__entry:call_stack.py:function_1:1
+function__entry:call_stack.py:function_3:9
+function__return:call_stack.py:function_3:10
+function__return:call_stack.py:function_1:2
+function__return:call_stack.py:function_2:6
+function__entry:call_stack.py:function_3:9
+function__return:call_stack.py:function_3:10
+function__entry:call_stack.py:function_4:13
+function__return:call_stack.py:function_4:14
+function__entry:call_stack.py:function_5:18
+function__return:call_stack.py:function_5:21
+function__return:call_stack.py:start:28
+function__return:call_stack.py:<module>:30""",
+        "gc": """gc__start:0
+gc__done:0
+gc__start:1
+gc__done:0
+gc__start:2
+gc__done:0
+gc__start:2
+gc__done:1""",
+    }
+
+    def run_case(self, name, optimize_python=None):
+        if name not in self.PROGRAMS:
+            raise unittest.SkipTest(f"No bpftrace program for {name}")
+
+        python_file = abspath(name + ".py")
+        python_flags = []
+        if optimize_python:
+            python_flags.extend(["-O"] * optimize_python)
+
+        subcommand = [sys.executable] + python_flags + [python_file]
+        program = self.PROGRAMS[name].format(python=sys.executable)
+
+        try:
+            proc = subprocess.Popen(
+                ["bpftrace", "-e", program, "-c", " ".join(subcommand)],
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+                universal_newlines=True,
+            )
+            stdout, stderr = proc.communicate(timeout=60)
+        except subprocess.TimeoutExpired:
+            proc.kill()
+            raise AssertionError("bpftrace timed out")
+        except (FileNotFoundError, PermissionError) as e:
+            raise unittest.SkipTest(f"bpftrace not available: {e}")
+
+        if proc.returncode != 0:
+            raise AssertionError(
+                f"bpftrace failed with code {proc.returncode}:\n{stderr}"
+            )
+
+        stdout = self._filter_probe_rows(stdout)
+
+        # Filter output by filename if specified (bpftrace captures everything)
+        if name in self.FILTER_BY_FILENAME:
+            filter_filename = self.FILTER_BY_FILENAME[name]
+            filtered_lines = [
+                line for line in stdout.splitlines()
+                if filter_filename in line
+            ]
+            stdout = "\n".join(filtered_lines)
+
+        actual_output = normalize_trace_output(stdout)
+        expected_output = self.EXPECTED[name].strip()
+
+        return (expected_output, actual_output)
+
+    def assert_usable(self):
+        # Check if bpftrace is available and can attach to USDT probes
+        program = f'usdt:{sys.executable}:python:function__entry {{ printf("probe: success\\n"); exit(); }}'
+        try:
+            proc = subprocess.Popen(
+                ["bpftrace", "-e", program, "-c", f"{sys.executable} -c pass"],
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+                universal_newlines=True,
+            )
+            stdout, stderr = proc.communicate(timeout=10)
+        except subprocess.TimeoutExpired:
+            proc.kill()
+            proc.communicate()  # Clean up
+            raise unittest.SkipTest("bpftrace timed out during usability check")
+        except OSError as e:
+            raise unittest.SkipTest(f"bpftrace not available: {e}")
+
+        # Check for permission errors (bpftrace usually requires root)
+        if proc.returncode != 0:
+            raise unittest.SkipTest(
+                f"bpftrace(1) failed with code {proc.returncode}: {stderr}"
+            )
+
+        if "probe: success" not in stdout:
+            raise unittest.SkipTest(
+                f"bpftrace(1) failed: stdout={stdout!r} stderr={stderr!r}"
+            )
+
+
+class BPFTraceOutputTests(unittest.TestCase):
+    def test_filter_probe_rows_ignores_warnings(self):
+        output = """stdin:1-19: WARNING: found external warnings
+HINT: include/vmlinux.h:1439:3: warning: declaration does not declare anything
+4623214882928\tgc__start:0
+4623214885575\tgc__done:0
+"""
+        self.assertEqual(
+            BPFTraceBackend._filter_probe_rows(output),
+            "4623214882928\tgc__start:0\n4623214885575\tgc__done:0",
+        )
+
+
 @unittest.skipIf(MS_WINDOWS, "Tests not compliant with trace on Windows.")
 class TraceTests:
     # unittest.TestCase options
@@ -127,7 +306,8 @@ class TraceTests:
     def test_verify_call_opcodes(self):
         """Ensure our call stack test hits all function call opcodes"""
 
-        opcodes = set(["CALL_FUNCTION", "CALL_FUNCTION_EX", "CALL_FUNCTION_KW"])
+        # Modern Python uses CALL, CALL_KW, and CALL_FUNCTION_EX
+        opcodes = set(["CALL", "CALL_FUNCTION_EX", "CALL_KW"])
 
         with open(abspath("call_stack.py")) as f:
             code_string = f.read()
@@ -152,9 +332,6 @@ class TraceTests:
     def test_gc(self):
         self.run_case("gc")
 
-    def test_line(self):
-        self.run_case("line")
-
 
 class DTraceNormalTests(TraceTests, unittest.TestCase):
     backend = DTraceBackend()
@@ -175,6 +352,17 @@ class SystemTapOptimizedTests(TraceTests, unittest.TestCase):
     backend = SystemTapBackend()
     optimize_python = 2
 
+
+class BPFTraceNormalTests(TraceTests, unittest.TestCase):
+    backend = BPFTraceBackend()
+    optimize_python = 0
+
+
+class BPFTraceOptimizedTests(TraceTests, unittest.TestCase):
+    backend = BPFTraceBackend()
+    optimize_python = 2
+
+
 class CheckDtraceProbes(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
@@ -235,6 +423,8 @@ class CheckDtraceProbes(unittest.TestCase):
             "Name: audit",
             "Name: gc__start",
             "Name: gc__done",
+            "Name: function__entry",
+            "Name: function__return",
         ]
 
         for probe_name in available_probe_names:
@@ -247,8 +437,6 @@ class CheckDtraceProbes(unittest.TestCase):
 
         # Missing probes will be added in the future.
         missing_probe_names = [
-            "Name: function__entry",
-            "Name: function__return",
             "Name: line",
         ]
 
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-08-00-25-35.gh-issue-98894.hKWyfqNx.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-08-00-25-35.gh-issue-98894.hKWyfqNx.rst
new file mode 100644 (file)
index 0000000..09ccf19
--- /dev/null
@@ -0,0 +1,2 @@
+Restore ``function__entry`` and ``function__return`` DTrace/SystemTap probes
+that were broken since Python 3.11.
index c65767e42da2e9068e8acc4d4a475eb77feed74a..46627c33d2a7c90d1c491de7142d2ce1d43434a5 100644 (file)
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 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;
                 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);
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 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;
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 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);
@@ -13079,6 +13096,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);
@@ -13111,8 +13135,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());
index d99b618c8393fcb6a9cb935947ce37634df4156f..43d4656a4b7f7e390eee506f309d194f476aee8f 100644 (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);
 
index daa989eb32ca1f3256606f64e517dd0283005d80..8b411829214b5218a357d7a275a87c4316cade89 100644 (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:
index 28087ba58d4855b537087ec2f74d98d207ce6a26..060e948e6b01c9f0db59717363e72f45f0657ae8 100644 (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)
 {
index a4e9980589e4a30102a1ebc324fefa8d00935103..be3d5fb0a69ad492d7d3f3e649dc739f5439357b 100644 (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. */
index 29c901b2bae7238c455ae1587a86a3b0411306ec..76caff06ca61f791870ad61a29d8166b18bf2fc5 100644 (file)
             _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;
             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);
             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());
index 7fea3ddfc6f559022c7654cf863422cb70c43e27..0419991ca7761ad2d4c9499c6a19846083d8eaf7 100644 (file)
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 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;
                 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);
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 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;
                 tstate->py_recursion_remaining--;
                 LOAD_SP();
                 LOAD_IP(0);
+                DTRACE_FUNCTION_ENTRY();
                 LLTRACE_RESUME_FRAME();
             }
             DISPATCH();
                 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());
index d99b618c8393fcb6a9cb935947ce37634df4156f..43d4656a4b7f7e390eee506f309d194f476aee8f 100644 (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);