mirror of
https://github.com/python/cpython.git
synced 2025-12-23 09:19:18 +00:00
GH-111744: Support opcode events in bdb (GH-111834)
This commit is contained in:
parent
f6b5d3bdc8
commit
f34e965e52
4 changed files with 72 additions and 17 deletions
72
Lib/bdb.py
72
Lib/bdb.py
|
|
@ -32,8 +32,10 @@ class Bdb:
|
|||
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()
|
||||
|
||||
|
|
@ -85,6 +87,9 @@ class Bdb:
|
|||
|
||||
The arg parameter depends on the previous event.
|
||||
"""
|
||||
|
||||
self.enterframe = frame
|
||||
|
||||
if self.quitting:
|
||||
return # None
|
||||
if event == 'line':
|
||||
|
|
@ -101,6 +106,8 @@ class Bdb:
|
|||
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
|
||||
|
||||
|
|
@ -187,6 +194,17 @@ class Bdb:
|
|||
|
||||
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.
|
||||
|
|
@ -273,7 +291,21 @@ class Bdb:
|
|||
"""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
|
||||
|
|
@ -286,6 +318,17 @@ class Bdb:
|
|||
# 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.
|
||||
|
|
@ -300,16 +343,14 @@ class Bdb:
|
|||
|
||||
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)
|
||||
|
|
@ -329,11 +370,12 @@ class Bdb:
|
|||
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()
|
||||
|
|
@ -353,9 +395,9 @@ class Bdb:
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -228,6 +228,10 @@ class Tracer(Bdb):
|
|||
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]
|
||||
|
|
@ -366,7 +370,7 @@ class Tracer(Bdb):
|
|||
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'):
|
||||
|
|
@ -610,6 +614,15 @@ class StateTestCase(BaseTestCase):
|
|||
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', ),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Support opcode events in :mod:`bdb`
|
||||
Loading…
Add table
Add a link
Reference in a new issue