Skip to content
29 changes: 29 additions & 0 deletions Doc/library/pdb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,14 @@ can be overridden by the local file.
Execute the current line, stop at the first possible occasion (either in a
function that is called or on the next line in the current function).

.. pdbcommand:: si | stepi

Execute the current bytecode instruction, stop at the first possible occasion
(either in a function that is called or on the next instruction in the
current function).

.. versionadded:: 3.12

.. pdbcommand:: n(ext)

Continue execution until the next line in the current function is reached or
Expand All @@ -387,6 +395,13 @@ can be overridden by the local file.
executes called functions at (nearly) full speed, only stopping at the next
line in the current function.)

.. pdbcommand:: ni | nexti

Continue execution until the next bytecode instruction in the current function
is reached or it returns.

.. versionadded:: 3.12

.. pdbcommand:: unt(il) [lineno]

Without argument, continue execution until the line with a number greater
Expand Down Expand Up @@ -433,13 +448,27 @@ can be overridden by the local file.
.. versionadded:: 3.2
The ``>>`` marker.

.. pdbcommand:: li | listi [first[, last]]

Similar to :pdbcmd:`list`, but also display bytecode instructions with
the source code

.. versionadded:: 3.12

.. pdbcommand:: ll | longlist

List all source code for the current function or frame. Interesting lines
are marked as for :pdbcmd:`list`.

.. versionadded:: 3.2

.. pdbcommand:: lli | longlisti

Similar to :pdbcmd:`ll`, but also display bytecode instructions with
the source code

.. versionadded:: 3.12

.. pdbcommand:: a(rgs)

Print the argument list of the current function.
Expand Down
85 changes: 74 additions & 11 deletions Lib/bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def __init__(self, skip=None):
self.skip = set(skip) if skip else None
self.breaks = {}
self.fncache = {}
self._curframe = None
self.lasti = -1
self.trace_opcodes = False
self.frame_returning = None

self._load_breaks()
Expand Down Expand Up @@ -75,6 +78,7 @@ def trace_dispatch(self, frame, event, arg):
is entered.
return: A function or other code block is about to return.
exception: An exception has occurred.
opcode: An opcode is going to be executed.
c_call: A C function is about to be called.
c_return: A C function has returned.
c_exception: A C function has raised an exception.
Expand All @@ -84,6 +88,8 @@ def trace_dispatch(self, frame, event, arg):

The arg parameter depends on the previous event.
"""
self._curframe = frame

if self.quitting:
return # None
if event == 'line':
Expand All @@ -94,6 +100,8 @@ def trace_dispatch(self, frame, event, arg):
return self.dispatch_return(frame, arg)
if event == 'exception':
return self.dispatch_exception(frame, arg)
if event == 'opcode':
return self.dispatch_opcode(frame)
if event == 'c_call':
return self.trace_dispatch
if event == 'c_exception':
Expand All @@ -115,13 +123,30 @@ def dispatch_line(self, frame):
if self.quitting: raise BdbQuit
return self.trace_dispatch

def dispatch_opcode(self, frame):
"""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

def dispatch_call(self, frame, arg):
"""Invoke user function and return trace function for call event.

If the debugger stops on this function call, invoke
self.user_call(). Raise BdbQuit if self.quitting is set.
Return self.trace_dispatch to continue tracing in this scope.
"""
if self.trace_opcodes:
frame.f_trace_opcodes = True
else:
frame.f_trace_opcodes = False

# XXX 'arg' is no longer used
if self.botframe is None:
# First call of dispatch since reset()
Expand Down Expand Up @@ -209,9 +234,15 @@ def stop_here(self, frame):
if frame is self.stopframe:
if self.stoplineno == -1:
return False
return frame.f_lineno >= self.stoplineno
if self.trace_opcodes:
return self.lasti != frame.f_lasti
else:
return frame.f_lineno >= self.stoplineno
if not self.stopframe:
return True
if self.trace_opcodes:
return self.lasti != frame.f_lasti
else:
return True
return False

def break_here(self, frame):
Expand Down Expand Up @@ -272,7 +303,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._curframe
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, lasti=None):
"""Set the attributes for stopping.

If stoplineno is greater than or equal to 0, then stop at line
Expand All @@ -285,6 +330,22 @@ 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
if lasti:
# We are stopping at opcode level
self._set_trace_opcodes(True)
self.lasti = lasti
else:
self._set_trace_opcodes(False)

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 @@ -299,20 +360,22 @@ 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_stepi(self, frame):
"""Stop after one opcode."""
self._set_caller_tracefunc()
self._set_stopinfo(None, None, lasti=frame.f_lasti)

def set_next(self, frame):
"""Stop on the next line in or below the given frame."""
self._set_stopinfo(frame, None)

def set_nexti(self, frame):
"""Stop on the next line in or below the given frame."""
self._set_stopinfo(frame, None, lasti=frame.f_lasti)

def set_return(self, frame):
"""Stop when returning from the given frame."""
if frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
Expand Down
Loading