Skip to content

Commit 77ede62

Browse files
Allow using sys.monitoring for bdb
1 parent 28efeef commit 77ede62

File tree

2 files changed

+171
-13
lines changed

2 files changed

+171
-13
lines changed

Lib/bdb.py

Lines changed: 169 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
import os
66
from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR
7+
from functools import partial
78

89
__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
910

@@ -14,6 +15,142 @@ class BdbQuit(Exception):
1415
"""Exception to give up completely."""
1516

1617

18+
E = sys.monitoring.events
19+
20+
class _MonitoringTracer:
21+
def __init__(self):
22+
self._tool_id = sys.monitoring.DEBUGGER_ID
23+
self._name = 'bdbtracer'
24+
self._tracefunc = None
25+
26+
def start_trace(self, tracefunc):
27+
self._tracefunc = tracefunc
28+
curr_tool = sys.monitoring.get_tool(self._tool_id)
29+
if curr_tool is None:
30+
sys.monitoring.use_tool_id(self._tool_id, self._name)
31+
elif curr_tool == self._name:
32+
sys.monitoring.set_events(self._tool_id, 0)
33+
else:
34+
raise ValueError('Another debugger is using the monitoring tool')
35+
E = sys.monitoring.events
36+
all_events = 0
37+
for event in (E.PY_START, E.PY_RESUME, E.PY_THROW):
38+
sys.monitoring.register_callback(self._tool_id, event, self.call_callback)
39+
all_events |= event
40+
for event in (E.LINE, ):
41+
sys.monitoring.register_callback(self._tool_id, event, self.line_callback)
42+
all_events |= event
43+
for event in (E.JUMP, ):
44+
sys.monitoring.register_callback(self._tool_id, event, self.jump_callback)
45+
all_events |= event
46+
for event in (E.PY_RETURN, E.PY_YIELD):
47+
sys.monitoring.register_callback(self._tool_id, event, self.return_callback)
48+
all_events |= event
49+
for event in (E.PY_UNWIND, ):
50+
sys.monitoring.register_callback(self._tool_id, event, self.unwind_callback)
51+
all_events |= event
52+
for event in (E.RAISE, E.STOP_ITERATION):
53+
sys.monitoring.register_callback(self._tool_id, event, self.exception_callback)
54+
all_events |= event
55+
for event in (E.INSTRUCTION, ):
56+
sys.monitoring.register_callback(self._tool_id, event, self.opcode_callback)
57+
self.check_trace_opcodes()
58+
sys.monitoring.set_events(self._tool_id, all_events)
59+
60+
def stop_trace(self):
61+
curr_tool = sys.monitoring.get_tool(self._tool_id)
62+
if curr_tool != self._name:
63+
return
64+
for event in (E.PY_START, E.PY_RESUME, E.PY_RETURN, E.PY_YIELD, E.RAISE, E.LINE,
65+
E.JUMP, E.PY_UNWIND, E.PY_THROW, E.STOP_ITERATION):
66+
sys.monitoring.register_callback(self._tool_id, event, None)
67+
sys.monitoring.set_events(self._tool_id, 0)
68+
self.check_trace_opcodes()
69+
sys.monitoring.free_tool_id(self._tool_id)
70+
71+
def callback_wrapper(func):
72+
def wrapper(self, *args):
73+
try:
74+
frame = sys._getframe().f_back
75+
return func(self, frame, *args)
76+
except Exception:
77+
self.stop_trace()
78+
raise
79+
return wrapper
80+
81+
@callback_wrapper
82+
def call_callback(self, frame, code, *args):
83+
local_tracefunc = self._tracefunc(frame, 'call', None)
84+
if local_tracefunc is not None:
85+
frame.f_trace = local_tracefunc
86+
87+
@callback_wrapper
88+
def return_callback(self, frame, code, offset, retval):
89+
if frame.f_trace:
90+
frame.f_trace(frame, 'return', retval)
91+
92+
@callback_wrapper
93+
def unwind_callback(self, frame, code, *args):
94+
if frame.f_trace:
95+
frame.f_trace(frame, 'return', None)
96+
97+
@callback_wrapper
98+
def line_callback(self, frame, code, *args):
99+
if frame.f_trace and frame.f_trace_lines:
100+
frame.f_trace(frame, 'line', None)
101+
102+
@callback_wrapper
103+
def jump_callback(self, frame, code, inst_offset, dest_offset):
104+
if dest_offset > inst_offset:
105+
return sys.monitoring.DISABLE
106+
inst_lineno = self._get_lineno(code, inst_offset)
107+
dest_lineno = self._get_lineno(code, dest_offset)
108+
if inst_lineno != dest_lineno:
109+
return sys.monitoring.DISABLE
110+
if frame.f_trace and frame.f_trace_lines:
111+
frame.f_trace(frame, 'line', None)
112+
113+
@callback_wrapper
114+
def exception_callback(self, frame, code, offset, exc):
115+
if frame.f_trace:
116+
if exc.__traceback__ and hasattr(exc.__traceback__, 'tb_frame'):
117+
tb = exc.__traceback__
118+
while tb:
119+
if tb.tb_frame.f_locals.get('self') is self:
120+
return
121+
tb = tb.tb_next
122+
frame.f_trace(frame, 'exception', (type(exc), exc, exc.__traceback__))
123+
124+
@callback_wrapper
125+
def opcode_callback(self, frame, code, offset):
126+
if frame.f_trace and frame.f_trace_opcodes:
127+
frame.f_trace(frame, 'opcode', None)
128+
129+
def check_trace_opcodes(self, frame=None):
130+
if frame is None:
131+
frame = sys._getframe().f_back
132+
while frame is not None:
133+
self.set_trace_opcodes(frame, frame.f_trace_opcodes)
134+
frame = frame.f_back
135+
136+
def set_trace_opcodes(self, frame, trace_opcodes):
137+
if sys.monitoring.get_tool(self._tool_id) != self._name:
138+
return
139+
if trace_opcodes:
140+
sys.monitoring.set_local_events(self._tool_id, frame.f_code, E.INSTRUCTION)
141+
else:
142+
sys.monitoring.set_local_events(self._tool_id, frame.f_code, 0)
143+
144+
def _get_lineno(self, code, offset):
145+
import dis
146+
last_lineno = None
147+
for start, lineno in dis.findlinestarts(code):
148+
if offset < start:
149+
return last_lineno
150+
last_lineno = lineno
151+
return last_lineno
152+
153+
17154
class Bdb:
18155
"""Generic Python debugger base class.
19156
@@ -28,14 +165,19 @@ class Bdb:
28165
is determined by the __name__ in the frame globals.
29166
"""
30167

31-
def __init__(self, skip=None):
168+
def __init__(self, skip=None, backend='monitoring'):
32169
self.skip = set(skip) if skip else None
33170
self.breaks = {}
34171
self.fncache = {}
35172
self.frame_trace_lines_opcodes = {}
36173
self.frame_returning = None
37174
self.trace_opcodes = False
38175
self.enterframe = None
176+
self.backend = backend
177+
if backend == 'monitoring':
178+
self.monitoring_tracer = _MonitoringTracer()
179+
else:
180+
self.monitoring_tracer = None
39181

40182
self._load_breaks()
41183

@@ -56,6 +198,18 @@ def canonic(self, filename):
56198
self.fncache[filename] = canonic
57199
return canonic
58200

201+
def start_trace(self, trace_dispatch):
202+
if self.backend == 'monitoring':
203+
self.monitoring_tracer.start_trace(trace_dispatch)
204+
else:
205+
sys.settrace(self.trace_dispatch)
206+
207+
def stop_trace(self):
208+
if self.backend == 'monitoring':
209+
self.monitoring_tracer.stop_trace()
210+
else:
211+
sys.settrace(None)
212+
59213
def reset(self):
60214
"""Set values of attributes as ready to start debugging."""
61215
import linecache
@@ -306,6 +460,8 @@ def _set_trace_opcodes(self, trace_opcodes):
306460
frame = self.enterframe
307461
while frame is not None:
308462
frame.f_trace_opcodes = trace_opcodes
463+
if self.backend == 'monitoring':
464+
self.monitoring_tracer.set_trace_opcodes(frame, trace_opcodes)
309465
if frame is self.botframe:
310466
break
311467
frame = frame.f_back
@@ -369,7 +525,7 @@ def set_trace(self, frame=None):
369525
370526
If frame is not specified, debugging starts from caller's frame.
371527
"""
372-
sys.settrace(None)
528+
self.stop_trace()
373529
if frame is None:
374530
frame = sys._getframe().f_back
375531
self.reset()
@@ -382,7 +538,7 @@ def set_trace(self, frame=None):
382538
frame.f_trace_lines = True
383539
frame = frame.f_back
384540
self.set_stepinstr()
385-
sys.settrace(self.trace_dispatch)
541+
self.start_trace(self.trace_dispatch)
386542

387543
def set_continue(self):
388544
"""Stop only at breakpoints or when finished.
@@ -393,13 +549,15 @@ def set_continue(self):
393549
self._set_stopinfo(self.botframe, None, -1)
394550
if not self.breaks:
395551
# no breakpoints; run without debugger overhead
396-
sys.settrace(None)
552+
self.stop_trace()
397553
frame = sys._getframe().f_back
398554
while frame and frame is not self.botframe:
399555
del frame.f_trace
400556
frame = frame.f_back
401557
for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items():
402558
frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes
559+
if self.backend == 'monitoring':
560+
self.monitoring_tracer.set_trace_opcodes(frame, trace_opcodes)
403561
self.frame_trace_lines_opcodes = {}
404562

405563
def set_quit(self):
@@ -410,7 +568,7 @@ def set_quit(self):
410568
self.stopframe = self.botframe
411569
self.returnframe = None
412570
self.quitting = True
413-
sys.settrace(None)
571+
self.stop_trace()
414572

415573
# Derived classes and clients can call the following methods
416574
# to manipulate breakpoints. These methods return an
@@ -647,14 +805,14 @@ def run(self, cmd, globals=None, locals=None):
647805
self.reset()
648806
if isinstance(cmd, str):
649807
cmd = compile(cmd, "<string>", "exec")
650-
sys.settrace(self.trace_dispatch)
808+
self.start_trace(self.trace_dispatch)
651809
try:
652810
exec(cmd, globals, locals)
653811
except BdbQuit:
654812
pass
655813
finally:
656814
self.quitting = True
657-
sys.settrace(None)
815+
self.stop_trace()
658816

659817
def runeval(self, expr, globals=None, locals=None):
660818
"""Debug an expression executed via the eval() function.
@@ -667,14 +825,14 @@ def runeval(self, expr, globals=None, locals=None):
667825
if locals is None:
668826
locals = globals
669827
self.reset()
670-
sys.settrace(self.trace_dispatch)
828+
self.start_trace(self.trace_dispatch)
671829
try:
672830
return eval(expr, globals, locals)
673831
except BdbQuit:
674832
pass
675833
finally:
676834
self.quitting = True
677-
sys.settrace(None)
835+
self.stop_trace()
678836

679837
def runctx(self, cmd, globals, locals):
680838
"""For backwards-compatibility. Defers to run()."""
@@ -689,15 +847,15 @@ def runcall(self, func, /, *args, **kwds):
689847
Return the result of the function call.
690848
"""
691849
self.reset()
692-
sys.settrace(self.trace_dispatch)
850+
self.start_trace(self.trace_dispatch)
693851
res = None
694852
try:
695853
res = func(*args, **kwds)
696854
except BdbQuit:
697855
pass
698856
finally:
699857
self.quitting = True
700-
sys.settrace(None)
858+
self.stop_trace()
701859
return res
702860

703861

Lib/pdb.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,7 +1702,7 @@ def do_debug(self, arg):
17021702
argument (which is an arbitrary expression or statement to be
17031703
executed in the current environment).
17041704
"""
1705-
sys.settrace(None)
1705+
self.stop_trace()
17061706
globals = self.curframe.f_globals
17071707
locals = self.curframe_locals
17081708
p = Pdb(self.completekey, self.stdin, self.stdout)
@@ -1713,7 +1713,7 @@ def do_debug(self, arg):
17131713
except Exception:
17141714
self._error_exc()
17151715
self.message("LEAVING RECURSIVE DEBUGGER")
1716-
sys.settrace(self.trace_dispatch)
1716+
self.start_trace(self.trace_dispatch)
17171717
self.lastcmd = p.lastcmd
17181718

17191719
complete_debug = _complete_expression

0 commit comments

Comments
 (0)