mirror of
https://github.com/python/cpython.git
synced 2025-12-04 00:30:19 +00:00
gh-120144: Make it possible to use sys.monitoring for bdb and make it default for pdb (#124533)
This commit is contained in:
parent
f48887fb97
commit
a936af924e
7 changed files with 435 additions and 21 deletions
|
|
@ -118,7 +118,7 @@ The :mod:`bdb` module also defines two classes:
|
||||||
|
|
||||||
Count of the number of times a :class:`Breakpoint` has been hit.
|
Count of the number of times a :class:`Breakpoint` has been hit.
|
||||||
|
|
||||||
.. class:: Bdb(skip=None)
|
.. class:: Bdb(skip=None, backend='settrace')
|
||||||
|
|
||||||
The :class:`Bdb` class acts as a generic Python debugger base class.
|
The :class:`Bdb` class acts as a generic Python debugger base class.
|
||||||
|
|
||||||
|
|
@ -132,9 +132,22 @@ The :mod:`bdb` module also defines two classes:
|
||||||
frame is considered to originate in a certain module is determined
|
frame is considered to originate in a certain module is determined
|
||||||
by the ``__name__`` in the frame globals.
|
by the ``__name__`` in the frame globals.
|
||||||
|
|
||||||
|
The *backend* argument specifies the backend to use for :class:`Bdb`. It
|
||||||
|
can be either ``'settrace'`` or ``'monitoring'``. ``'settrace'`` uses
|
||||||
|
:func:`sys.settrace` which has the best backward compatibility. The
|
||||||
|
``'monitoring'`` backend uses the new :mod:`sys.monitoring` that was
|
||||||
|
introduced in Python 3.12, which can be much more efficient because it
|
||||||
|
can disable unused events. We are trying to keep the exact interfaces
|
||||||
|
for both backends, but there are some differences. The debugger developers
|
||||||
|
are encouraged to use the ``'monitoring'`` backend to achieve better
|
||||||
|
performance.
|
||||||
|
|
||||||
.. versionchanged:: 3.1
|
.. versionchanged:: 3.1
|
||||||
Added the *skip* parameter.
|
Added the *skip* parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.14
|
||||||
|
Added the *backend* parameter.
|
||||||
|
|
||||||
The following methods of :class:`Bdb` normally don't need to be overridden.
|
The following methods of :class:`Bdb` normally don't need to be overridden.
|
||||||
|
|
||||||
.. method:: canonic(filename)
|
.. method:: canonic(filename)
|
||||||
|
|
@ -146,6 +159,20 @@ The :mod:`bdb` module also defines two classes:
|
||||||
<os.path.abspath>`. A *filename* with angle brackets, such as ``"<stdin>"``
|
<os.path.abspath>`. A *filename* with angle brackets, such as ``"<stdin>"``
|
||||||
generated in interactive mode, is returned unchanged.
|
generated in interactive mode, is returned unchanged.
|
||||||
|
|
||||||
|
.. method:: start_trace(self)
|
||||||
|
|
||||||
|
Start tracing. For ``'settrace'`` backend, this method is equivalent to
|
||||||
|
``sys.settrace(self.trace_dispatch)``
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
|
.. method:: stop_trace(self)
|
||||||
|
|
||||||
|
Stop tracing. For ``'settrace'`` backend, this method is equivalent to
|
||||||
|
``sys.settrace(None)``
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
.. method:: reset()
|
.. method:: reset()
|
||||||
|
|
||||||
Set the :attr:`!botframe`, :attr:`!stopframe`, :attr:`!returnframe` and
|
Set the :attr:`!botframe`, :attr:`!stopframe`, :attr:`!returnframe` and
|
||||||
|
|
@ -364,6 +391,28 @@ The :mod:`bdb` module also defines two classes:
|
||||||
Return all breakpoints that are set.
|
Return all breakpoints that are set.
|
||||||
|
|
||||||
|
|
||||||
|
Derived classes and clients can call the following methods to disable and
|
||||||
|
restart events to achieve better performance. These methods only work
|
||||||
|
when using the ``'monitoring'`` backend.
|
||||||
|
|
||||||
|
.. method:: disable_current_event()
|
||||||
|
|
||||||
|
Disable the current event until the next time :func:`restart_events` is
|
||||||
|
called. This is helpful when the debugger is not interested in the current
|
||||||
|
line.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
|
.. method:: restart_events()
|
||||||
|
|
||||||
|
Restart all the disabled events. This function is automatically called in
|
||||||
|
``dispatch_*`` methods after ``user_*`` methods are called. If the
|
||||||
|
``dispatch_*`` methods are not overridden, the disabled events will be
|
||||||
|
restarted after each user interaction.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
|
|
||||||
Derived classes and clients can call the following methods to get a data
|
Derived classes and clients can call the following methods to get a data
|
||||||
structure representing a stack trace.
|
structure representing a stack trace.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,13 +203,32 @@ slightly different way:
|
||||||
Enter post-mortem debugging of the exception found in
|
Enter post-mortem debugging of the exception found in
|
||||||
:data:`sys.last_exc`.
|
:data:`sys.last_exc`.
|
||||||
|
|
||||||
|
.. function:: set_default_backend(backend)
|
||||||
|
|
||||||
|
There are two supported backends for pdb: ``'settrace'`` and ``'monitoring'``.
|
||||||
|
See :class:`bdb.Bdb` for details. The user can set the default backend to
|
||||||
|
use if none is specified when instantiating :class:`Pdb`. If no backend is
|
||||||
|
specified, the default is ``'settrace'``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
:func:`breakpoint` and :func:`set_trace` will not be affected by this
|
||||||
|
function. They always use ``'monitoring'`` backend.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
|
.. function:: get_default_backend()
|
||||||
|
|
||||||
|
Returns the default backend for pdb.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
The ``run*`` functions and :func:`set_trace` are aliases for instantiating the
|
The ``run*`` functions and :func:`set_trace` are aliases for instantiating the
|
||||||
:class:`Pdb` class and calling the method of the same name. If you want to
|
:class:`Pdb` class and calling the method of the same name. If you want to
|
||||||
access further features, you have to do this yourself:
|
access further features, you have to do this yourself:
|
||||||
|
|
||||||
.. class:: Pdb(completekey='tab', stdin=None, stdout=None, skip=None, \
|
.. class:: Pdb(completekey='tab', stdin=None, stdout=None, skip=None, \
|
||||||
nosigint=False, readrc=True, mode=None)
|
nosigint=False, readrc=True, mode=None, backend=None)
|
||||||
|
|
||||||
:class:`Pdb` is the debugger class.
|
:class:`Pdb` is the debugger class.
|
||||||
|
|
||||||
|
|
@ -235,6 +254,10 @@ access further features, you have to do this yourself:
|
||||||
or ``None`` (for backwards compatible behaviour, as before the *mode*
|
or ``None`` (for backwards compatible behaviour, as before the *mode*
|
||||||
argument was added).
|
argument was added).
|
||||||
|
|
||||||
|
The *backend* argument specifies the backend to use for the debugger. If ``None``
|
||||||
|
is passed, the default backend will be used. See :func:`set_default_backend`.
|
||||||
|
Otherwise the supported backends are ``'settrace'`` and ``'monitoring'``.
|
||||||
|
|
||||||
Example call to enable tracing with *skip*::
|
Example call to enable tracing with *skip*::
|
||||||
|
|
||||||
import pdb; pdb.Pdb(skip=['django.*']).set_trace()
|
import pdb; pdb.Pdb(skip=['django.*']).set_trace()
|
||||||
|
|
@ -254,6 +277,9 @@ access further features, you have to do this yourself:
|
||||||
.. versionadded:: 3.14
|
.. versionadded:: 3.14
|
||||||
Added the *mode* argument.
|
Added the *mode* argument.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
Added the *backend* argument.
|
||||||
|
|
||||||
.. versionchanged:: 3.14
|
.. versionchanged:: 3.14
|
||||||
Inline breakpoints like :func:`breakpoint` or :func:`pdb.set_trace` will
|
Inline breakpoints like :func:`breakpoint` or :func:`pdb.set_trace` will
|
||||||
always stop the program at calling frame, ignoring the *skip* pattern (if any).
|
always stop the program at calling frame, ignoring the *skip* pattern (if any).
|
||||||
|
|
|
||||||
|
|
@ -423,6 +423,11 @@ ast
|
||||||
that the root node type is appropriate.
|
that the root node type is appropriate.
|
||||||
(Contributed by Irit Katriel in :gh:`130139`.)
|
(Contributed by Irit Katriel in :gh:`130139`.)
|
||||||
|
|
||||||
|
bdb
|
||||||
|
---
|
||||||
|
|
||||||
|
* The :mod:`bdb` module now supports the :mod:`sys.monitoring` backend.
|
||||||
|
(Contributed by Tian Gao in :gh:`124533`.)
|
||||||
|
|
||||||
calendar
|
calendar
|
||||||
--------
|
--------
|
||||||
|
|
@ -843,6 +848,13 @@ pdb
|
||||||
* ``$_asynctask`` is added to access the current asyncio task if applicable.
|
* ``$_asynctask`` is added to access the current asyncio task if applicable.
|
||||||
(Contributed by Tian Gao in :gh:`124367`.)
|
(Contributed by Tian Gao in :gh:`124367`.)
|
||||||
|
|
||||||
|
* :mod:`pdb` now supports two backends: :func:`sys.settrace` and
|
||||||
|
:mod:`sys.monitoring`. Using :mod:`pdb` CLI or :func:`breakpoint` will
|
||||||
|
always use the :mod:`sys.monitoring` backend. Explicitly instantiating
|
||||||
|
:class:`pdb.Pdb` and its derived classes will use the :func:`sys.settrace`
|
||||||
|
backend by default, which is configurable.
|
||||||
|
(Contributed by Tian Gao in :gh:`124533`.)
|
||||||
|
|
||||||
|
|
||||||
pickle
|
pickle
|
||||||
------
|
------
|
||||||
|
|
|
||||||
240
Lib/bdb.py
240
Lib/bdb.py
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
import os
|
import os
|
||||||
import weakref
|
import weakref
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
@ -16,6 +17,181 @@ class BdbQuit(Exception):
|
||||||
"""Exception to give up completely."""
|
"""Exception to give up completely."""
|
||||||
|
|
||||||
|
|
||||||
|
E = sys.monitoring.events
|
||||||
|
|
||||||
|
class _MonitoringTracer:
|
||||||
|
EVENT_CALLBACK_MAP = {
|
||||||
|
E.PY_START: 'call',
|
||||||
|
E.PY_RESUME: 'call',
|
||||||
|
E.PY_THROW: 'call',
|
||||||
|
E.LINE: 'line',
|
||||||
|
E.JUMP: 'jump',
|
||||||
|
E.PY_RETURN: 'return',
|
||||||
|
E.PY_YIELD: 'return',
|
||||||
|
E.PY_UNWIND: 'unwind',
|
||||||
|
E.RAISE: 'exception',
|
||||||
|
E.STOP_ITERATION: 'exception',
|
||||||
|
E.INSTRUCTION: 'opcode',
|
||||||
|
}
|
||||||
|
|
||||||
|
GLOBAL_EVENTS = E.PY_START | E.PY_RESUME | E.PY_THROW | E.PY_UNWIND | E.RAISE
|
||||||
|
LOCAL_EVENTS = E.LINE | E.JUMP | E.PY_RETURN | E.PY_YIELD | E.STOP_ITERATION
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._tool_id = sys.monitoring.DEBUGGER_ID
|
||||||
|
self._name = 'bdbtracer'
|
||||||
|
self._tracefunc = None
|
||||||
|
self._disable_current_event = False
|
||||||
|
self._tracing_thread = None
|
||||||
|
self._enabled = False
|
||||||
|
|
||||||
|
def start_trace(self, tracefunc):
|
||||||
|
self._tracefunc = tracefunc
|
||||||
|
self._tracing_thread = threading.current_thread()
|
||||||
|
curr_tool = sys.monitoring.get_tool(self._tool_id)
|
||||||
|
if curr_tool is None:
|
||||||
|
sys.monitoring.use_tool_id(self._tool_id, self._name)
|
||||||
|
elif curr_tool == self._name:
|
||||||
|
sys.monitoring.clear_tool_id(self._tool_id)
|
||||||
|
else:
|
||||||
|
raise ValueError('Another debugger is using the monitoring tool')
|
||||||
|
E = sys.monitoring.events
|
||||||
|
all_events = 0
|
||||||
|
for event, cb_name in self.EVENT_CALLBACK_MAP.items():
|
||||||
|
callback = getattr(self, f'{cb_name}_callback')
|
||||||
|
sys.monitoring.register_callback(self._tool_id, event, callback)
|
||||||
|
if event != E.INSTRUCTION:
|
||||||
|
all_events |= event
|
||||||
|
self.check_trace_func()
|
||||||
|
self.check_trace_opcodes()
|
||||||
|
sys.monitoring.set_events(self._tool_id, self.GLOBAL_EVENTS)
|
||||||
|
self._enabled = True
|
||||||
|
|
||||||
|
def stop_trace(self):
|
||||||
|
self._enabled = False
|
||||||
|
self._tracing_thread = None
|
||||||
|
curr_tool = sys.monitoring.get_tool(self._tool_id)
|
||||||
|
if curr_tool != self._name:
|
||||||
|
return
|
||||||
|
sys.monitoring.clear_tool_id(self._tool_id)
|
||||||
|
self.check_trace_opcodes()
|
||||||
|
sys.monitoring.free_tool_id(self._tool_id)
|
||||||
|
|
||||||
|
def disable_current_event(self):
|
||||||
|
self._disable_current_event = True
|
||||||
|
|
||||||
|
def restart_events(self):
|
||||||
|
if sys.monitoring.get_tool(self._tool_id) == self._name:
|
||||||
|
sys.monitoring.restart_events()
|
||||||
|
|
||||||
|
def callback_wrapper(func):
|
||||||
|
import functools
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(self, *args):
|
||||||
|
if self._tracing_thread != threading.current_thread():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
frame = sys._getframe().f_back
|
||||||
|
ret = func(self, frame, *args)
|
||||||
|
if self._enabled and frame.f_trace:
|
||||||
|
self.check_trace_func()
|
||||||
|
if self._disable_current_event:
|
||||||
|
return sys.monitoring.DISABLE
|
||||||
|
else:
|
||||||
|
return ret
|
||||||
|
except BaseException:
|
||||||
|
self.stop_trace()
|
||||||
|
sys._getframe().f_back.f_trace = None
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
self._disable_current_event = False
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@callback_wrapper
|
||||||
|
def call_callback(self, frame, code, *args):
|
||||||
|
local_tracefunc = self._tracefunc(frame, 'call', None)
|
||||||
|
if local_tracefunc is not None:
|
||||||
|
frame.f_trace = local_tracefunc
|
||||||
|
if self._enabled:
|
||||||
|
sys.monitoring.set_local_events(self._tool_id, code, self.LOCAL_EVENTS)
|
||||||
|
|
||||||
|
@callback_wrapper
|
||||||
|
def return_callback(self, frame, code, offset, retval):
|
||||||
|
if frame.f_trace:
|
||||||
|
frame.f_trace(frame, 'return', retval)
|
||||||
|
|
||||||
|
@callback_wrapper
|
||||||
|
def unwind_callback(self, frame, code, *args):
|
||||||
|
if frame.f_trace:
|
||||||
|
frame.f_trace(frame, 'return', None)
|
||||||
|
|
||||||
|
@callback_wrapper
|
||||||
|
def line_callback(self, frame, code, *args):
|
||||||
|
if frame.f_trace and frame.f_trace_lines:
|
||||||
|
frame.f_trace(frame, 'line', None)
|
||||||
|
|
||||||
|
@callback_wrapper
|
||||||
|
def jump_callback(self, frame, code, inst_offset, dest_offset):
|
||||||
|
if dest_offset > inst_offset:
|
||||||
|
return sys.monitoring.DISABLE
|
||||||
|
inst_lineno = self._get_lineno(code, inst_offset)
|
||||||
|
dest_lineno = self._get_lineno(code, dest_offset)
|
||||||
|
if inst_lineno != dest_lineno:
|
||||||
|
return sys.monitoring.DISABLE
|
||||||
|
if frame.f_trace and frame.f_trace_lines:
|
||||||
|
frame.f_trace(frame, 'line', None)
|
||||||
|
|
||||||
|
@callback_wrapper
|
||||||
|
def exception_callback(self, frame, code, offset, exc):
|
||||||
|
if frame.f_trace:
|
||||||
|
if exc.__traceback__ and hasattr(exc.__traceback__, 'tb_frame'):
|
||||||
|
tb = exc.__traceback__
|
||||||
|
while tb:
|
||||||
|
if tb.tb_frame.f_locals.get('self') is self:
|
||||||
|
return
|
||||||
|
tb = tb.tb_next
|
||||||
|
frame.f_trace(frame, 'exception', (type(exc), exc, exc.__traceback__))
|
||||||
|
|
||||||
|
@callback_wrapper
|
||||||
|
def opcode_callback(self, frame, code, offset):
|
||||||
|
if frame.f_trace and frame.f_trace_opcodes:
|
||||||
|
frame.f_trace(frame, 'opcode', None)
|
||||||
|
|
||||||
|
def check_trace_opcodes(self, frame=None):
|
||||||
|
if frame is None:
|
||||||
|
frame = sys._getframe().f_back
|
||||||
|
while frame is not None:
|
||||||
|
self.set_trace_opcodes(frame, frame.f_trace_opcodes)
|
||||||
|
frame = frame.f_back
|
||||||
|
|
||||||
|
def set_trace_opcodes(self, frame, trace_opcodes):
|
||||||
|
if sys.monitoring.get_tool(self._tool_id) != self._name:
|
||||||
|
return
|
||||||
|
if trace_opcodes:
|
||||||
|
sys.monitoring.set_local_events(self._tool_id, frame.f_code, E.INSTRUCTION)
|
||||||
|
else:
|
||||||
|
sys.monitoring.set_local_events(self._tool_id, frame.f_code, 0)
|
||||||
|
|
||||||
|
def check_trace_func(self, frame=None):
|
||||||
|
if frame is None:
|
||||||
|
frame = sys._getframe().f_back
|
||||||
|
while frame is not None:
|
||||||
|
if frame.f_trace is not None:
|
||||||
|
sys.monitoring.set_local_events(self._tool_id, frame.f_code, self.LOCAL_EVENTS)
|
||||||
|
frame = frame.f_back
|
||||||
|
|
||||||
|
def _get_lineno(self, code, offset):
|
||||||
|
import dis
|
||||||
|
last_lineno = None
|
||||||
|
for start, lineno in dis.findlinestarts(code):
|
||||||
|
if offset < start:
|
||||||
|
return last_lineno
|
||||||
|
last_lineno = lineno
|
||||||
|
return last_lineno
|
||||||
|
|
||||||
|
|
||||||
class Bdb:
|
class Bdb:
|
||||||
"""Generic Python debugger base class.
|
"""Generic Python debugger base class.
|
||||||
|
|
||||||
|
|
@ -30,7 +206,7 @@ class Bdb:
|
||||||
is determined by the __name__ in the frame globals.
|
is determined by the __name__ in the frame globals.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, skip=None):
|
def __init__(self, skip=None, backend='settrace'):
|
||||||
self.skip = set(skip) if skip else None
|
self.skip = set(skip) if skip else None
|
||||||
self.breaks = {}
|
self.breaks = {}
|
||||||
self.fncache = {}
|
self.fncache = {}
|
||||||
|
|
@ -39,6 +215,13 @@ class Bdb:
|
||||||
self.trace_opcodes = False
|
self.trace_opcodes = False
|
||||||
self.enterframe = None
|
self.enterframe = None
|
||||||
self.code_linenos = weakref.WeakKeyDictionary()
|
self.code_linenos = weakref.WeakKeyDictionary()
|
||||||
|
self.backend = backend
|
||||||
|
if backend == 'monitoring':
|
||||||
|
self.monitoring_tracer = _MonitoringTracer()
|
||||||
|
elif backend == 'settrace':
|
||||||
|
self.monitoring_tracer = None
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid backend '{backend}'")
|
||||||
|
|
||||||
self._load_breaks()
|
self._load_breaks()
|
||||||
|
|
||||||
|
|
@ -59,6 +242,18 @@ class Bdb:
|
||||||
self.fncache[filename] = canonic
|
self.fncache[filename] = canonic
|
||||||
return canonic
|
return canonic
|
||||||
|
|
||||||
|
def start_trace(self):
|
||||||
|
if self.monitoring_tracer:
|
||||||
|
self.monitoring_tracer.start_trace(self.trace_dispatch)
|
||||||
|
else:
|
||||||
|
sys.settrace(self.trace_dispatch)
|
||||||
|
|
||||||
|
def stop_trace(self):
|
||||||
|
if self.monitoring_tracer:
|
||||||
|
self.monitoring_tracer.stop_trace()
|
||||||
|
else:
|
||||||
|
sys.settrace(None)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Set values of attributes as ready to start debugging."""
|
"""Set values of attributes as ready to start debugging."""
|
||||||
import linecache
|
import linecache
|
||||||
|
|
@ -128,7 +323,10 @@ class Bdb:
|
||||||
"""
|
"""
|
||||||
if self.stop_here(frame) or self.break_here(frame):
|
if self.stop_here(frame) or self.break_here(frame):
|
||||||
self.user_line(frame)
|
self.user_line(frame)
|
||||||
|
self.restart_events()
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
|
elif not self.get_break(frame.f_code.co_filename, frame.f_lineno):
|
||||||
|
self.disable_current_event()
|
||||||
return self.trace_dispatch
|
return self.trace_dispatch
|
||||||
|
|
||||||
def dispatch_call(self, frame, arg):
|
def dispatch_call(self, frame, arg):
|
||||||
|
|
@ -150,6 +348,7 @@ class Bdb:
|
||||||
if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
|
if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
|
||||||
return self.trace_dispatch
|
return self.trace_dispatch
|
||||||
self.user_call(frame, arg)
|
self.user_call(frame, arg)
|
||||||
|
self.restart_events()
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
return self.trace_dispatch
|
return self.trace_dispatch
|
||||||
|
|
||||||
|
|
@ -170,6 +369,7 @@ class Bdb:
|
||||||
try:
|
try:
|
||||||
self.frame_returning = frame
|
self.frame_returning = frame
|
||||||
self.user_return(frame, arg)
|
self.user_return(frame, arg)
|
||||||
|
self.restart_events()
|
||||||
finally:
|
finally:
|
||||||
self.frame_returning = None
|
self.frame_returning = None
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
|
|
@ -197,6 +397,7 @@ class Bdb:
|
||||||
if not (frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
|
if not (frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
|
||||||
and arg[0] is StopIteration and arg[2] is None):
|
and arg[0] is StopIteration and arg[2] is None):
|
||||||
self.user_exception(frame, arg)
|
self.user_exception(frame, arg)
|
||||||
|
self.restart_events()
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
# Stop at the StopIteration or GeneratorExit exception when the user
|
# Stop at the StopIteration or GeneratorExit exception when the user
|
||||||
# has set stopframe in a generator by issuing a return command, or a
|
# has set stopframe in a generator by issuing a return command, or a
|
||||||
|
|
@ -206,6 +407,7 @@ class Bdb:
|
||||||
and self.stopframe.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
|
and self.stopframe.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
|
||||||
and arg[0] in (StopIteration, GeneratorExit)):
|
and arg[0] in (StopIteration, GeneratorExit)):
|
||||||
self.user_exception(frame, arg)
|
self.user_exception(frame, arg)
|
||||||
|
self.restart_events()
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
|
|
||||||
return self.trace_dispatch
|
return self.trace_dispatch
|
||||||
|
|
@ -221,6 +423,7 @@ class Bdb:
|
||||||
unconditionally.
|
unconditionally.
|
||||||
"""
|
"""
|
||||||
self.user_opcode(frame)
|
self.user_opcode(frame)
|
||||||
|
self.restart_events()
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
return self.trace_dispatch
|
return self.trace_dispatch
|
||||||
|
|
||||||
|
|
@ -336,6 +539,8 @@ class Bdb:
|
||||||
frame = self.enterframe
|
frame = self.enterframe
|
||||||
while frame is not None:
|
while frame is not None:
|
||||||
frame.f_trace_opcodes = trace_opcodes
|
frame.f_trace_opcodes = trace_opcodes
|
||||||
|
if self.monitoring_tracer:
|
||||||
|
self.monitoring_tracer.set_trace_opcodes(frame, trace_opcodes)
|
||||||
if frame is self.botframe:
|
if frame is self.botframe:
|
||||||
break
|
break
|
||||||
frame = frame.f_back
|
frame = frame.f_back
|
||||||
|
|
@ -400,7 +605,7 @@ class Bdb:
|
||||||
|
|
||||||
If frame is not specified, debugging starts from caller's frame.
|
If frame is not specified, debugging starts from caller's frame.
|
||||||
"""
|
"""
|
||||||
sys.settrace(None)
|
self.stop_trace()
|
||||||
if frame is None:
|
if frame is None:
|
||||||
frame = sys._getframe().f_back
|
frame = sys._getframe().f_back
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
@ -413,7 +618,8 @@ class Bdb:
|
||||||
frame.f_trace_lines = True
|
frame.f_trace_lines = True
|
||||||
frame = frame.f_back
|
frame = frame.f_back
|
||||||
self.set_stepinstr()
|
self.set_stepinstr()
|
||||||
sys.settrace(self.trace_dispatch)
|
self.enterframe = None
|
||||||
|
self.start_trace()
|
||||||
|
|
||||||
def set_continue(self):
|
def set_continue(self):
|
||||||
"""Stop only at breakpoints or when finished.
|
"""Stop only at breakpoints or when finished.
|
||||||
|
|
@ -424,13 +630,15 @@ class Bdb:
|
||||||
self._set_stopinfo(self.botframe, None, -1)
|
self._set_stopinfo(self.botframe, None, -1)
|
||||||
if not self.breaks:
|
if not self.breaks:
|
||||||
# no breakpoints; run without debugger overhead
|
# no breakpoints; run without debugger overhead
|
||||||
sys.settrace(None)
|
self.stop_trace()
|
||||||
frame = sys._getframe().f_back
|
frame = sys._getframe().f_back
|
||||||
while frame and frame is not self.botframe:
|
while frame and frame is not self.botframe:
|
||||||
del frame.f_trace
|
del frame.f_trace
|
||||||
frame = frame.f_back
|
frame = frame.f_back
|
||||||
for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items():
|
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
|
frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes
|
||||||
|
if self.backend == 'monitoring':
|
||||||
|
self.monitoring_tracer.set_trace_opcodes(frame, trace_opcodes)
|
||||||
self.frame_trace_lines_opcodes = {}
|
self.frame_trace_lines_opcodes = {}
|
||||||
|
|
||||||
def set_quit(self):
|
def set_quit(self):
|
||||||
|
|
@ -441,7 +649,7 @@ class Bdb:
|
||||||
self.stopframe = self.botframe
|
self.stopframe = self.botframe
|
||||||
self.returnframe = None
|
self.returnframe = None
|
||||||
self.quitting = True
|
self.quitting = True
|
||||||
sys.settrace(None)
|
self.stop_trace()
|
||||||
|
|
||||||
# Derived classes and clients can call the following methods
|
# Derived classes and clients can call the following methods
|
||||||
# to manipulate breakpoints. These methods return an
|
# to manipulate breakpoints. These methods return an
|
||||||
|
|
@ -669,6 +877,16 @@ class Bdb:
|
||||||
s += f'{lprefix}Warning: lineno is None'
|
s += f'{lprefix}Warning: lineno is None'
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
def disable_current_event(self):
|
||||||
|
"""Disable the current event."""
|
||||||
|
if self.backend == 'monitoring':
|
||||||
|
self.monitoring_tracer.disable_current_event()
|
||||||
|
|
||||||
|
def restart_events(self):
|
||||||
|
"""Restart all events."""
|
||||||
|
if self.backend == 'monitoring':
|
||||||
|
self.monitoring_tracer.restart_events()
|
||||||
|
|
||||||
# The following methods can be called by clients to use
|
# The following methods can be called by clients to use
|
||||||
# a debugger to debug a statement or an expression.
|
# a debugger to debug a statement or an expression.
|
||||||
# Both can be given as a string, or a code object.
|
# Both can be given as a string, or a code object.
|
||||||
|
|
@ -686,14 +904,14 @@ class Bdb:
|
||||||
self.reset()
|
self.reset()
|
||||||
if isinstance(cmd, str):
|
if isinstance(cmd, str):
|
||||||
cmd = compile(cmd, "<string>", "exec")
|
cmd = compile(cmd, "<string>", "exec")
|
||||||
sys.settrace(self.trace_dispatch)
|
self.start_trace()
|
||||||
try:
|
try:
|
||||||
exec(cmd, globals, locals)
|
exec(cmd, globals, locals)
|
||||||
except BdbQuit:
|
except BdbQuit:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
self.quitting = True
|
self.quitting = True
|
||||||
sys.settrace(None)
|
self.stop_trace()
|
||||||
|
|
||||||
def runeval(self, expr, globals=None, locals=None):
|
def runeval(self, expr, globals=None, locals=None):
|
||||||
"""Debug an expression executed via the eval() function.
|
"""Debug an expression executed via the eval() function.
|
||||||
|
|
@ -706,14 +924,14 @@ class Bdb:
|
||||||
if locals is None:
|
if locals is None:
|
||||||
locals = globals
|
locals = globals
|
||||||
self.reset()
|
self.reset()
|
||||||
sys.settrace(self.trace_dispatch)
|
self.start_trace()
|
||||||
try:
|
try:
|
||||||
return eval(expr, globals, locals)
|
return eval(expr, globals, locals)
|
||||||
except BdbQuit:
|
except BdbQuit:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
self.quitting = True
|
self.quitting = True
|
||||||
sys.settrace(None)
|
self.stop_trace()
|
||||||
|
|
||||||
def runctx(self, cmd, globals, locals):
|
def runctx(self, cmd, globals, locals):
|
||||||
"""For backwards-compatibility. Defers to run()."""
|
"""For backwards-compatibility. Defers to run()."""
|
||||||
|
|
@ -728,7 +946,7 @@ class Bdb:
|
||||||
Return the result of the function call.
|
Return the result of the function call.
|
||||||
"""
|
"""
|
||||||
self.reset()
|
self.reset()
|
||||||
sys.settrace(self.trace_dispatch)
|
self.start_trace()
|
||||||
res = None
|
res = None
|
||||||
try:
|
try:
|
||||||
res = func(*args, **kwds)
|
res = func(*args, **kwds)
|
||||||
|
|
@ -736,7 +954,7 @@ class Bdb:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
self.quitting = True
|
self.quitting = True
|
||||||
sys.settrace(None)
|
self.stop_trace()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
31
Lib/pdb.py
31
Lib/pdb.py
|
|
@ -99,7 +99,7 @@ class Restart(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
__all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace",
|
__all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace",
|
||||||
"post_mortem", "help"]
|
"post_mortem", "set_default_backend", "get_default_backend", "help"]
|
||||||
|
|
||||||
|
|
||||||
def find_first_executable_line(code):
|
def find_first_executable_line(code):
|
||||||
|
|
@ -302,6 +302,23 @@ class _PdbInteractiveConsole(code.InteractiveConsole):
|
||||||
line_prefix = '\n-> ' # Probably a better default
|
line_prefix = '\n-> ' # Probably a better default
|
||||||
|
|
||||||
|
|
||||||
|
# The default backend to use for Pdb instances if not specified
|
||||||
|
# Should be either 'settrace' or 'monitoring'
|
||||||
|
_default_backend = 'settrace'
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_backend(backend):
|
||||||
|
"""Set the default backend to use for Pdb instances."""
|
||||||
|
global _default_backend
|
||||||
|
if backend not in ('settrace', 'monitoring'):
|
||||||
|
raise ValueError("Invalid backend: %s" % backend)
|
||||||
|
_default_backend = backend
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_backend():
|
||||||
|
"""Get the default backend to use for Pdb instances."""
|
||||||
|
return _default_backend
|
||||||
|
|
||||||
|
|
||||||
class Pdb(bdb.Bdb, cmd.Cmd):
|
class Pdb(bdb.Bdb, cmd.Cmd):
|
||||||
_previous_sigint_handler = None
|
_previous_sigint_handler = None
|
||||||
|
|
@ -315,8 +332,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
||||||
_last_pdb_instance = None
|
_last_pdb_instance = None
|
||||||
|
|
||||||
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
|
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
|
||||||
nosigint=False, readrc=True, mode=None):
|
nosigint=False, readrc=True, mode=None, backend=None):
|
||||||
bdb.Bdb.__init__(self, skip=skip)
|
bdb.Bdb.__init__(self, skip=skip, backend=backend if backend else get_default_backend())
|
||||||
cmd.Cmd.__init__(self, completekey, stdin, stdout)
|
cmd.Cmd.__init__(self, completekey, stdin, stdout)
|
||||||
sys.audit("pdb.Pdb")
|
sys.audit("pdb.Pdb")
|
||||||
if stdout:
|
if stdout:
|
||||||
|
|
@ -1768,7 +1785,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
||||||
if not arg:
|
if not arg:
|
||||||
self._print_invalid_arg(arg)
|
self._print_invalid_arg(arg)
|
||||||
return
|
return
|
||||||
sys.settrace(None)
|
self.stop_trace()
|
||||||
globals = self.curframe.f_globals
|
globals = self.curframe.f_globals
|
||||||
locals = self.curframe.f_locals
|
locals = self.curframe.f_locals
|
||||||
p = Pdb(self.completekey, self.stdin, self.stdout)
|
p = Pdb(self.completekey, self.stdin, self.stdout)
|
||||||
|
|
@ -1779,7 +1796,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
||||||
except Exception:
|
except Exception:
|
||||||
self._error_exc()
|
self._error_exc()
|
||||||
self.message("LEAVING RECURSIVE DEBUGGER")
|
self.message("LEAVING RECURSIVE DEBUGGER")
|
||||||
sys.settrace(self.trace_dispatch)
|
self.start_trace()
|
||||||
self.lastcmd = p.lastcmd
|
self.lastcmd = p.lastcmd
|
||||||
|
|
||||||
complete_debug = _complete_expression
|
complete_debug = _complete_expression
|
||||||
|
|
@ -2469,7 +2486,7 @@ def set_trace(*, header=None, commands=None):
|
||||||
if Pdb._last_pdb_instance is not None:
|
if Pdb._last_pdb_instance is not None:
|
||||||
pdb = Pdb._last_pdb_instance
|
pdb = Pdb._last_pdb_instance
|
||||||
else:
|
else:
|
||||||
pdb = Pdb(mode='inline')
|
pdb = Pdb(mode='inline', backend='monitoring')
|
||||||
if header is not None:
|
if header is not None:
|
||||||
pdb.message(header)
|
pdb.message(header)
|
||||||
pdb.set_trace(sys._getframe().f_back, commands=commands)
|
pdb.set_trace(sys._getframe().f_back, commands=commands)
|
||||||
|
|
@ -2600,7 +2617,7 @@ def main():
|
||||||
# modified by the script being debugged. It's a bad idea when it was
|
# modified by the script being debugged. It's a bad idea when it was
|
||||||
# changed by the user from the command line. There is a "restart" command
|
# changed by the user from the command line. There is a "restart" command
|
||||||
# which allows explicit specification of command line arguments.
|
# which allows explicit specification of command line arguments.
|
||||||
pdb = Pdb(mode='cli')
|
pdb = Pdb(mode='cli', backend='monitoring')
|
||||||
pdb.rcLines.extend(opts.commands)
|
pdb.rcLines.extend(opts.commands)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -364,6 +364,49 @@ def test_pdb_breakpoint_commands():
|
||||||
4
|
4
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def test_pdb_breakpoint_ignore_and_condition():
|
||||||
|
"""
|
||||||
|
>>> reset_Breakpoint()
|
||||||
|
|
||||||
|
>>> def test_function():
|
||||||
|
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
|
||||||
|
... for i in range(5):
|
||||||
|
... print(i)
|
||||||
|
|
||||||
|
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
|
||||||
|
... 'break 4',
|
||||||
|
... 'ignore 1 2', # ignore once
|
||||||
|
... 'continue',
|
||||||
|
... 'condition 1 i == 4',
|
||||||
|
... 'continue',
|
||||||
|
... 'clear 1',
|
||||||
|
... 'continue',
|
||||||
|
... ]):
|
||||||
|
... test_function()
|
||||||
|
> <doctest test.test_pdb.test_pdb_breakpoint_ignore_and_condition[1]>(2)test_function()
|
||||||
|
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
|
||||||
|
(Pdb) break 4
|
||||||
|
Breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoint_ignore_and_condition[1]>:4
|
||||||
|
(Pdb) ignore 1 2
|
||||||
|
Will ignore next 2 crossings of breakpoint 1.
|
||||||
|
(Pdb) continue
|
||||||
|
0
|
||||||
|
1
|
||||||
|
> <doctest test.test_pdb.test_pdb_breakpoint_ignore_and_condition[1]>(4)test_function()
|
||||||
|
-> print(i)
|
||||||
|
(Pdb) condition 1 i == 4
|
||||||
|
New condition set for breakpoint 1.
|
||||||
|
(Pdb) continue
|
||||||
|
2
|
||||||
|
3
|
||||||
|
> <doctest test.test_pdb.test_pdb_breakpoint_ignore_and_condition[1]>(4)test_function()
|
||||||
|
-> print(i)
|
||||||
|
(Pdb) clear 1
|
||||||
|
Deleted breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoint_ignore_and_condition[1]>:4
|
||||||
|
(Pdb) continue
|
||||||
|
4
|
||||||
|
"""
|
||||||
|
|
||||||
def test_pdb_breakpoint_on_annotated_function_def():
|
def test_pdb_breakpoint_on_annotated_function_def():
|
||||||
"""Test breakpoints on function definitions with annotation.
|
"""Test breakpoints on function definitions with annotation.
|
||||||
|
|
||||||
|
|
@ -488,6 +531,48 @@ def test_pdb_breakpoint_with_filename():
|
||||||
(Pdb) continue
|
(Pdb) continue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def test_pdb_breakpoint_on_disabled_line():
|
||||||
|
"""New breakpoint on once disabled line should work
|
||||||
|
|
||||||
|
>>> reset_Breakpoint()
|
||||||
|
>>> def test_function():
|
||||||
|
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
|
||||||
|
... for i in range(3):
|
||||||
|
... j = i * 2
|
||||||
|
... print(j)
|
||||||
|
|
||||||
|
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
|
||||||
|
... 'break 5',
|
||||||
|
... 'c',
|
||||||
|
... 'clear 1',
|
||||||
|
... 'break 4',
|
||||||
|
... 'c',
|
||||||
|
... 'clear 2',
|
||||||
|
... 'c'
|
||||||
|
... ]):
|
||||||
|
... test_function()
|
||||||
|
> <doctest test.test_pdb.test_pdb_breakpoint_on_disabled_line[1]>(2)test_function()
|
||||||
|
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
|
||||||
|
(Pdb) break 5
|
||||||
|
Breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoint_on_disabled_line[1]>:5
|
||||||
|
(Pdb) c
|
||||||
|
> <doctest test.test_pdb.test_pdb_breakpoint_on_disabled_line[1]>(5)test_function()
|
||||||
|
-> print(j)
|
||||||
|
(Pdb) clear 1
|
||||||
|
Deleted breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoint_on_disabled_line[1]>:5
|
||||||
|
(Pdb) break 4
|
||||||
|
Breakpoint 2 at <doctest test.test_pdb.test_pdb_breakpoint_on_disabled_line[1]>:4
|
||||||
|
(Pdb) c
|
||||||
|
0
|
||||||
|
> <doctest test.test_pdb.test_pdb_breakpoint_on_disabled_line[1]>(4)test_function()
|
||||||
|
-> j = i * 2
|
||||||
|
(Pdb) clear 2
|
||||||
|
Deleted breakpoint 2 at <doctest test.test_pdb.test_pdb_breakpoint_on_disabled_line[1]>:4
|
||||||
|
(Pdb) c
|
||||||
|
2
|
||||||
|
4
|
||||||
|
"""
|
||||||
|
|
||||||
def test_pdb_breakpoints_preserved_across_interactive_sessions():
|
def test_pdb_breakpoints_preserved_across_interactive_sessions():
|
||||||
"""Breakpoints are remembered between interactive sessions
|
"""Breakpoints are remembered between interactive sessions
|
||||||
|
|
||||||
|
|
@ -4585,7 +4670,13 @@ class PdbTestReadline(unittest.TestCase):
|
||||||
|
|
||||||
def load_tests(loader, tests, pattern):
|
def load_tests(loader, tests, pattern):
|
||||||
from test import test_pdb
|
from test import test_pdb
|
||||||
tests.addTest(doctest.DocTestSuite(test_pdb))
|
def setUpPdbBackend(backend):
|
||||||
|
def setUp(test):
|
||||||
|
import pdb
|
||||||
|
pdb.set_default_backend(backend)
|
||||||
|
return setUp
|
||||||
|
tests.addTest(doctest.DocTestSuite(test_pdb, setUp=setUpPdbBackend('monitoring')))
|
||||||
|
tests.addTest(doctest.DocTestSuite(test_pdb, setUp=setUpPdbBackend('settrace')))
|
||||||
return tests
|
return tests
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Add the optional backend of ``sys.monitoring`` to :mod:`bdb` and use it for :mod:`pdb`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue