Skip to content

Commit

Permalink
pythonGH-111744: Support opcode events in bdb (pythonGH-111834)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaogaotiantian authored May 4, 2024
1 parent f6b5d3b commit f34e965
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 17 deletions.
72 changes: 57 additions & 15 deletions Lib/bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ def __init__(self, skip=None):
self.skip = set(skip) if skip else None
self.breaks = {}
self.fncache = {}
self.frame_trace_lines = {}
self.frame_trace_lines_opcodes = {}
self.frame_returning = None
self.trace_opcodes = False
self.enterframe = None

self._load_breaks()

Expand Down Expand Up @@ -85,6 +87,9 @@ def trace_dispatch(self, frame, event, arg):
The arg parameter depends on the previous event.
"""

self.enterframe = frame

if self.quitting:
return # None
if event == 'line':
Expand All @@ -101,6 +106,8 @@ def trace_dispatch(self, frame, event, arg):
return self.trace_dispatch
if event == 'c_return':
return self.trace_dispatch
if event == 'opcode':
return self.dispatch_opcode(frame, arg)
print('bdb.Bdb.dispatch: unknown debugging event:', repr(event))
return self.trace_dispatch

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

return self.trace_dispatch

def dispatch_opcode(self, frame, arg):
"""Invoke user function and return trace function for opcode event.
If the debugger stops on the current opcode, invoke
self.user_opcode(). Raise BdbQuit if self.quitting is set.
Return self.trace_dispatch to continue tracing in this scope.
"""
if self.stop_here(frame) or self.break_here(frame):
self.user_opcode(frame)
if self.quitting: raise BdbQuit
return self.trace_dispatch

# Normally derived classes don't override the following
# methods, but they may if they want to redefine the
# definition of stopping and breakpoints.
Expand Down Expand Up @@ -273,7 +291,21 @@ def user_exception(self, frame, exc_info):
"""Called when we stop on an exception."""
pass

def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
def user_opcode(self, frame):
"""Called when we are about to execute an opcode."""
pass

def _set_trace_opcodes(self, trace_opcodes):
if trace_opcodes != self.trace_opcodes:
self.trace_opcodes = trace_opcodes
frame = self.enterframe
while frame is not None:
frame.f_trace_opcodes = trace_opcodes
if frame is self.botframe:
break
frame = frame.f_back

def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False):
"""Set the attributes for stopping.
If stoplineno is greater than or equal to 0, then stop at line
Expand All @@ -286,6 +318,17 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
# stoplineno >= 0 means: stop at line >= the stoplineno
# stoplineno -1 means: don't stop at all
self.stoplineno = stoplineno
self._set_trace_opcodes(opcode)

def _set_caller_tracefunc(self):
# Issue #13183: pdb skips frames after hitting a breakpoint and running
# step commands.
# Restore the trace function in the caller (that may not have been set
# for performance reasons) when returning from the current frame.
if self.frame_returning:
caller_frame = self.frame_returning.f_back
if caller_frame and not caller_frame.f_trace:
caller_frame.f_trace = self.trace_dispatch

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

def set_step(self):
"""Stop after one line of code."""
# Issue #13183: pdb skips frames after hitting a breakpoint and running
# step commands.
# Restore the trace function in the caller (that may not have been set
# for performance reasons) when returning from the current frame.
if self.frame_returning:
caller_frame = self.frame_returning.f_back
if caller_frame and not caller_frame.f_trace:
caller_frame.f_trace = self.trace_dispatch
self._set_caller_tracefunc()
self._set_stopinfo(None, None)

def set_stepinstr(self):
"""Stop before the next instruction."""
self._set_caller_tracefunc()
self._set_stopinfo(None, None, opcode=True)

def set_next(self, frame):
"""Stop on the next line in or below the given frame."""
self._set_stopinfo(frame, None)
Expand All @@ -329,11 +370,12 @@ def set_trace(self, frame=None):
if frame is None:
frame = sys._getframe().f_back
self.reset()
self.enterframe = frame
while frame:
frame.f_trace = self.trace_dispatch
self.botframe = frame
# We need f_trace_liens == True for the debugger to work
self.frame_trace_lines[frame] = frame.f_trace_lines
self.frame_trace_lines_opcodes[frame] = (frame.f_trace_lines, frame.f_trace_opcodes)
# We need f_trace_lines == True for the debugger to work
frame.f_trace_lines = True
frame = frame.f_back
self.set_step()
Expand All @@ -353,9 +395,9 @@ def set_continue(self):
while frame and frame is not self.botframe:
del frame.f_trace
frame = frame.f_back
for frame, prev_trace_lines in self.frame_trace_lines.items():
frame.f_trace_lines = prev_trace_lines
self.frame_trace_lines = {}
for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items():
frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes
self.frame_trace_lines_opcodes = {}

def set_quit(self):
"""Set quitting attribute to True.
Expand Down
15 changes: 14 additions & 1 deletion Lib/test/test_bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ def user_exception(self, frame, exc_info):
self.process_event('exception', frame)
self.next_set_method()

def user_opcode(self, frame):
self.process_event('opcode', frame)
self.next_set_method()

def do_clear(self, arg):
# The temporary breakpoints are deleted in user_line().
bp_list = [self.currentbp]
Expand Down Expand Up @@ -366,7 +370,7 @@ def next_set_method(self):
set_method = getattr(self, 'set_' + set_type)

# The following set methods give back control to the tracer.
if set_type in ('step', 'continue', 'quit'):
if set_type in ('step', 'stepinstr', 'continue', 'quit'):
set_method()
return
elif set_type in ('next', 'return'):
Expand Down Expand Up @@ -610,6 +614,15 @@ def test_step_next_on_last_statement(self):
with TracerRun(self) as tracer:
tracer.runcall(tfunc_main)

def test_stepinstr(self):
self.expect_set = [
('line', 2, 'tfunc_main'), ('stepinstr', ),
('opcode', 2, 'tfunc_main'), ('next', ),
('line', 3, 'tfunc_main'), ('quit', ),
]
with TracerRun(self) as tracer:
tracer.runcall(tfunc_main)

def test_next(self):
self.expect_set = [
('line', 2, 'tfunc_main'), ('step', ),
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2456,7 +2456,6 @@ def test_pdb_issue_gh_108976():
... 'continue'
... ]):
... test_function()
bdb.Bdb.dispatch: unknown debugging event: 'opcode'
> <doctest test.test_pdb.test_pdb_issue_gh_108976[0]>(5)test_function()
-> a = 1
(Pdb) continue
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support opcode events in :mod:`bdb`

0 comments on commit f34e965

Please sign in to comment.