Skip to content

Commit c63d324

Browse files
gaogaotiantianpull[bot]
authored andcommitted
GH-111744: Support opcode events in bdb (GH-111834)
1 parent b1a0118 commit c63d324

File tree

4 files changed

+72
-17
lines changed

4 files changed

+72
-17
lines changed

Lib/bdb.py

+57-15
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ def __init__(self, skip=None):
3232
self.skip = set(skip) if skip else None
3333
self.breaks = {}
3434
self.fncache = {}
35-
self.frame_trace_lines = {}
35+
self.frame_trace_lines_opcodes = {}
3636
self.frame_returning = None
37+
self.trace_opcodes = False
38+
self.enterframe = None
3739

3840
self._load_breaks()
3941

@@ -85,6 +87,9 @@ def trace_dispatch(self, frame, event, arg):
8587
8688
The arg parameter depends on the previous event.
8789
"""
90+
91+
self.enterframe = frame
92+
8893
if self.quitting:
8994
return # None
9095
if event == 'line':
@@ -101,6 +106,8 @@ def trace_dispatch(self, frame, event, arg):
101106
return self.trace_dispatch
102107
if event == 'c_return':
103108
return self.trace_dispatch
109+
if event == 'opcode':
110+
return self.dispatch_opcode(frame, arg)
104111
print('bdb.Bdb.dispatch: unknown debugging event:', repr(event))
105112
return self.trace_dispatch
106113

@@ -187,6 +194,17 @@ def dispatch_exception(self, frame, arg):
187194

188195
return self.trace_dispatch
189196

197+
def dispatch_opcode(self, frame, arg):
198+
"""Invoke user function and return trace function for opcode event.
199+
If the debugger stops on the current opcode, invoke
200+
self.user_opcode(). Raise BdbQuit if self.quitting is set.
201+
Return self.trace_dispatch to continue tracing in this scope.
202+
"""
203+
if self.stop_here(frame) or self.break_here(frame):
204+
self.user_opcode(frame)
205+
if self.quitting: raise BdbQuit
206+
return self.trace_dispatch
207+
190208
# Normally derived classes don't override the following
191209
# methods, but they may if they want to redefine the
192210
# definition of stopping and breakpoints.
@@ -273,7 +291,21 @@ def user_exception(self, frame, exc_info):
273291
"""Called when we stop on an exception."""
274292
pass
275293

276-
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
294+
def user_opcode(self, frame):
295+
"""Called when we are about to execute an opcode."""
296+
pass
297+
298+
def _set_trace_opcodes(self, trace_opcodes):
299+
if trace_opcodes != self.trace_opcodes:
300+
self.trace_opcodes = trace_opcodes
301+
frame = self.enterframe
302+
while frame is not None:
303+
frame.f_trace_opcodes = trace_opcodes
304+
if frame is self.botframe:
305+
break
306+
frame = frame.f_back
307+
308+
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False):
277309
"""Set the attributes for stopping.
278310
279311
If stoplineno is greater than or equal to 0, then stop at line
@@ -286,6 +318,17 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
286318
# stoplineno >= 0 means: stop at line >= the stoplineno
287319
# stoplineno -1 means: don't stop at all
288320
self.stoplineno = stoplineno
321+
self._set_trace_opcodes(opcode)
322+
323+
def _set_caller_tracefunc(self):
324+
# Issue #13183: pdb skips frames after hitting a breakpoint and running
325+
# step commands.
326+
# Restore the trace function in the caller (that may not have been set
327+
# for performance reasons) when returning from the current frame.
328+
if self.frame_returning:
329+
caller_frame = self.frame_returning.f_back
330+
if caller_frame and not caller_frame.f_trace:
331+
caller_frame.f_trace = self.trace_dispatch
289332

290333
# Derived classes and clients can call the following methods
291334
# to affect the stepping state.
@@ -300,16 +343,14 @@ def set_until(self, frame, lineno=None):
300343

301344
def set_step(self):
302345
"""Stop after one line of code."""
303-
# Issue #13183: pdb skips frames after hitting a breakpoint and running
304-
# step commands.
305-
# Restore the trace function in the caller (that may not have been set
306-
# for performance reasons) when returning from the current frame.
307-
if self.frame_returning:
308-
caller_frame = self.frame_returning.f_back
309-
if caller_frame and not caller_frame.f_trace:
310-
caller_frame.f_trace = self.trace_dispatch
346+
self._set_caller_tracefunc()
311347
self._set_stopinfo(None, None)
312348

349+
def set_stepinstr(self):
350+
"""Stop before the next instruction."""
351+
self._set_caller_tracefunc()
352+
self._set_stopinfo(None, None, opcode=True)
353+
313354
def set_next(self, frame):
314355
"""Stop on the next line in or below the given frame."""
315356
self._set_stopinfo(frame, None)
@@ -329,11 +370,12 @@ def set_trace(self, frame=None):
329370
if frame is None:
330371
frame = sys._getframe().f_back
331372
self.reset()
373+
self.enterframe = frame
332374
while frame:
333375
frame.f_trace = self.trace_dispatch
334376
self.botframe = frame
335-
# We need f_trace_liens == True for the debugger to work
336-
self.frame_trace_lines[frame] = frame.f_trace_lines
377+
self.frame_trace_lines_opcodes[frame] = (frame.f_trace_lines, frame.f_trace_opcodes)
378+
# We need f_trace_lines == True for the debugger to work
337379
frame.f_trace_lines = True
338380
frame = frame.f_back
339381
self.set_step()
@@ -353,9 +395,9 @@ def set_continue(self):
353395
while frame and frame is not self.botframe:
354396
del frame.f_trace
355397
frame = frame.f_back
356-
for frame, prev_trace_lines in self.frame_trace_lines.items():
357-
frame.f_trace_lines = prev_trace_lines
358-
self.frame_trace_lines = {}
398+
for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items():
399+
frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes
400+
self.frame_trace_lines_opcodes = {}
359401

360402
def set_quit(self):
361403
"""Set quitting attribute to True.

Lib/test/test_bdb.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ def user_exception(self, frame, exc_info):
228228
self.process_event('exception', frame)
229229
self.next_set_method()
230230

231+
def user_opcode(self, frame):
232+
self.process_event('opcode', frame)
233+
self.next_set_method()
234+
231235
def do_clear(self, arg):
232236
# The temporary breakpoints are deleted in user_line().
233237
bp_list = [self.currentbp]
@@ -366,7 +370,7 @@ def next_set_method(self):
366370
set_method = getattr(self, 'set_' + set_type)
367371

368372
# The following set methods give back control to the tracer.
369-
if set_type in ('step', 'continue', 'quit'):
373+
if set_type in ('step', 'stepinstr', 'continue', 'quit'):
370374
set_method()
371375
return
372376
elif set_type in ('next', 'return'):
@@ -610,6 +614,15 @@ def test_step_next_on_last_statement(self):
610614
with TracerRun(self) as tracer:
611615
tracer.runcall(tfunc_main)
612616

617+
def test_stepinstr(self):
618+
self.expect_set = [
619+
('line', 2, 'tfunc_main'), ('stepinstr', ),
620+
('opcode', 2, 'tfunc_main'), ('next', ),
621+
('line', 3, 'tfunc_main'), ('quit', ),
622+
]
623+
with TracerRun(self) as tracer:
624+
tracer.runcall(tfunc_main)
625+
613626
def test_next(self):
614627
self.expect_set = [
615628
('line', 2, 'tfunc_main'), ('step', ),

Lib/test/test_pdb.py

-1
Original file line numberDiff line numberDiff line change
@@ -2456,7 +2456,6 @@ def test_pdb_issue_gh_108976():
24562456
... 'continue'
24572457
... ]):
24582458
... test_function()
2459-
bdb.Bdb.dispatch: unknown debugging event: 'opcode'
24602459
> <doctest test.test_pdb.test_pdb_issue_gh_108976[0]>(5)test_function()
24612460
-> a = 1
24622461
(Pdb) continue
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support opcode events in :mod:`bdb`

0 commit comments

Comments
 (0)