Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e0afed7

Browse files
gh-103615: Use local events for opcode tracing (GH-109472)
* Use local monitoring for opcode trace * Remove f_opcode_trace_set * Add test for setting f_trace_opcodes after settrace
1 parent 2bc01cc commit e0afed7

File tree

9 files changed

+114
-8
lines changed

9 files changed

+114
-8
lines changed

Include/internal/pycore_ceval.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ PyAPI_FUNC(int) _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyO
2222

2323
extern int _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg);
2424

25+
extern int _PyEval_SetOpcodeTrace(PyFrameObject *f, bool enable);
26+
2527
// Helper to look up a builtin object
2628
// Export for 'array' shared extension
2729
PyAPI_FUNC(PyObject*) _PyEval_GetBuiltin(PyObject *);

Include/internal/pycore_instruments.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ typedef uint32_t _PyMonitoringEventSet;
6363
PyObject *_PyMonitoring_RegisterCallback(int tool_id, int event_id, PyObject *obj);
6464

6565
int _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events);
66+
int _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet events);
67+
int _PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet *events);
6668

6769
extern int
6870
_Py_call_instrumentation(PyThreadState *tstate, int event,

Include/internal/pycore_interp.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ struct _is {
200200
uint32_t next_func_version;
201201

202202
_Py_GlobalMonitors monitors;
203-
bool f_opcode_trace_set;
204203
bool sys_profile_initialized;
205204
bool sys_trace_initialized;
206205
Py_ssize_t sys_profiling_threads; /* Count of threads with c_profilefunc set */

Lib/test/test_sys_settrace.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
import asyncio
1010
from test.support import import_helper
1111
import contextlib
12+
import os
13+
import tempfile
14+
import textwrap
15+
import subprocess
1216
import warnings
1317

1418
support.requires_working_socket(module=True)
@@ -1802,6 +1806,39 @@ def compare_events(self, line_offset, events, expected_events):
18021806
def make_tracer():
18031807
return Tracer(trace_opcode_events=True)
18041808

1809+
def test_trace_opcodes_after_settrace(self):
1810+
"""Make sure setting f_trace_opcodes after starting trace works even
1811+
if it's the first time f_trace_opcodes is being set. GH-103615"""
1812+
1813+
code = textwrap.dedent("""
1814+
import sys
1815+
1816+
def opcode_trace_func(frame, event, arg):
1817+
if event == "opcode":
1818+
print("opcode trace triggered")
1819+
return opcode_trace_func
1820+
1821+
sys.settrace(opcode_trace_func)
1822+
sys._getframe().f_trace = opcode_trace_func
1823+
sys._getframe().f_trace_opcodes = True
1824+
a = 1
1825+
""")
1826+
1827+
# We can't use context manager because Windows can't execute a file while
1828+
# it's being written
1829+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.py')
1830+
tmp.write(code.encode('utf-8'))
1831+
tmp.close()
1832+
try:
1833+
p = subprocess.Popen([sys.executable, tmp.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1834+
p.wait()
1835+
out = p.stdout.read()
1836+
finally:
1837+
os.remove(tmp.name)
1838+
p.stdout.close()
1839+
p.stderr.close()
1840+
self.assertIn(b"opcode trace triggered", out)
1841+
18051842

18061843
class RaisingTraceFuncTestCase(unittest.TestCase):
18071844
def setUp(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use local events for opcode tracing

Objects/frameobject.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,13 @@ frame_settrace_opcodes(PyFrameObject *f, PyObject* value, void *Py_UNUSED(ignore
127127
}
128128
if (value == Py_True) {
129129
f->f_trace_opcodes = 1;
130-
_PyInterpreterState_GET()->f_opcode_trace_set = true;
130+
if (f->f_trace) {
131+
return _PyEval_SetOpcodeTrace(f, true);
132+
}
131133
}
132134
else {
133135
f->f_trace_opcodes = 0;
136+
return _PyEval_SetOpcodeTrace(f, false);
134137
}
135138
return 0;
136139
}
@@ -842,6 +845,9 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
842845
}
843846
if (v != f->f_trace) {
844847
Py_XSETREF(f->f_trace, Py_XNewRef(v));
848+
if (v != NULL && f->f_trace_opcodes) {
849+
return _PyEval_SetOpcodeTrace(f, true);
850+
}
845851
}
846852
return 0;
847853
}

Python/instrumentation.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,6 +1833,23 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
18331833
return 0;
18341834
}
18351835

1836+
int
1837+
_PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet *events)
1838+
{
1839+
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
1840+
PyInterpreterState *interp = _PyInterpreterState_GET();
1841+
if (check_tool(interp, tool_id)) {
1842+
return -1;
1843+
}
1844+
if (code->_co_monitoring == NULL) {
1845+
*events = 0;
1846+
return 0;
1847+
}
1848+
_Py_LocalMonitors *local = &code->_co_monitoring->local_monitors;
1849+
*events = get_local_events(local, tool_id);
1850+
return 0;
1851+
}
1852+
18361853
/*[clinic input]
18371854
module monitoring
18381855
[clinic start generated code]*/

Python/legacy_tracing.c

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,35 @@ sys_profile_call_or_return(
117117
Py_RETURN_NONE;
118118
}
119119

120+
int
121+
_PyEval_SetOpcodeTrace(
122+
PyFrameObject *frame,
123+
bool enable
124+
) {
125+
assert(frame != NULL);
126+
assert(PyCode_Check(frame->f_frame->f_executable));
127+
128+
PyCodeObject *code = (PyCodeObject *)frame->f_frame->f_executable;
129+
_PyMonitoringEventSet events = 0;
130+
131+
if (_PyMonitoring_GetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, &events) < 0) {
132+
return -1;
133+
}
134+
135+
if (enable) {
136+
if (events & (1 << PY_MONITORING_EVENT_INSTRUCTION)) {
137+
return 0;
138+
}
139+
events |= (1 << PY_MONITORING_EVENT_INSTRUCTION);
140+
} else {
141+
if (!(events & (1 << PY_MONITORING_EVENT_INSTRUCTION))) {
142+
return 0;
143+
}
144+
events &= (~(1 << PY_MONITORING_EVENT_INSTRUCTION));
145+
}
146+
return _PyMonitoring_SetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, events);
147+
}
148+
120149
static PyObject *
121150
call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
122151
{
@@ -130,6 +159,12 @@ call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
130159
"Missing frame when calling trace function.");
131160
return NULL;
132161
}
162+
if (frame->f_trace_opcodes) {
163+
if (_PyEval_SetOpcodeTrace(frame, true) != 0) {
164+
return NULL;
165+
}
166+
}
167+
133168
Py_INCREF(frame);
134169
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, arg);
135170
Py_DECREF(frame);
@@ -230,11 +265,14 @@ sys_trace_instruction_func(
230265
"Missing frame when calling trace function.");
231266
return NULL;
232267
}
233-
if (!frame->f_trace_opcodes) {
268+
PyThreadState *tstate = _PyThreadState_GET();
269+
if (!tstate->c_tracefunc || !frame->f_trace_opcodes) {
270+
if (_PyEval_SetOpcodeTrace(frame, false) != 0) {
271+
return NULL;
272+
}
234273
Py_RETURN_NONE;
235274
}
236275
Py_INCREF(frame);
237-
PyThreadState *tstate = _PyThreadState_GET();
238276
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, Py_None);
239277
frame->f_lineno = 0;
240278
Py_DECREF(frame);
@@ -531,9 +569,15 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
531569
(1 << PY_MONITORING_EVENT_PY_UNWIND) | (1 << PY_MONITORING_EVENT_PY_THROW) |
532570
(1 << PY_MONITORING_EVENT_STOP_ITERATION) |
533571
(1 << PY_MONITORING_EVENT_EXCEPTION_HANDLED);
534-
if (tstate->interp->f_opcode_trace_set) {
535-
events |= (1 << PY_MONITORING_EVENT_INSTRUCTION);
572+
573+
PyFrameObject* frame = PyEval_GetFrame();
574+
if (frame->f_trace_opcodes) {
575+
int ret = _PyEval_SetOpcodeTrace(frame, true);
576+
if (ret != 0) {
577+
return ret;
578+
}
536579
}
537580
}
581+
538582
return _PyMonitoring_SetEvents(PY_MONITORING_SYS_TRACE_ID, events);
539583
}

Python/pystate.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,6 @@ init_interpreter(PyInterpreterState *interp,
708708
/* Fix the self-referential, statically initialized fields. */
709709
interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp);
710710
}
711-
interp->f_opcode_trace_set = false;
712711

713712
interp->_initialized = 1;
714713
return _PyStatus_OK();
@@ -958,7 +957,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
958957
interp->code_watchers[i] = NULL;
959958
}
960959
interp->active_code_watchers = 0;
961-
interp->f_opcode_trace_set = false;
962960
// XXX Once we have one allocator per interpreter (i.e.
963961
// per-interpreter GC) we must ensure that all of the interpreter's
964962
// objects have been cleaned up at the point.

0 commit comments

Comments
 (0)