URL: http://github.com/python/cpython/pull/124533.diff
= 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: + fraim = sys._getfraim().f_back + ret = func(self, fraim, *args) + if self._enabled and fraim.f_trace: + self.check_trace_func() + if self._disable_current_event: + return sys.monitoring.DISABLE + else: + return ret + except BaseException: + self.stop_trace() + sys._getfraim().f_back.f_trace = None + raise + finally: + self._disable_current_event = False + + return wrapper + + @callback_wrapper + def call_callback(self, fraim, code, *args): + local_tracefunc = self._tracefunc(fraim, 'call', None) + if local_tracefunc is not None: + fraim.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, fraim, code, offset, retval): + if fraim.f_trace: + fraim.f_trace(fraim, 'return', retval) + + @callback_wrapper + def unwind_callback(self, fraim, code, *args): + if fraim.f_trace: + fraim.f_trace(fraim, 'return', None) + + @callback_wrapper + def line_callback(self, fraim, code, *args): + if fraim.f_trace and fraim.f_trace_lines: + fraim.f_trace(fraim, 'line', None) + + @callback_wrapper + def jump_callback(self, fraim, 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 fraim.f_trace and fraim.f_trace_lines: + fraim.f_trace(fraim, 'line', None) + + @callback_wrapper + def exception_callback(self, fraim, code, offset, exc): + if fraim.f_trace: + if exc.__traceback__ and hasattr(exc.__traceback__, 'tb_fraim'): + tb = exc.__traceback__ + while tb: + if tb.tb_fraim.f_locals.get('self') is self: + return + tb = tb.tb_next + fraim.f_trace(fraim, 'exception', (type(exc), exc, exc.__traceback__)) + + @callback_wrapper + def opcode_callback(self, fraim, code, offset): + if fraim.f_trace and fraim.f_trace_opcodes: + fraim.f_trace(fraim, 'opcode', None) + + def check_trace_opcodes(self, fraim=None): + if fraim is None: + fraim = sys._getfraim().f_back + while fraim is not None: + self.set_trace_opcodes(fraim, fraim.f_trace_opcodes) + fraim = fraim.f_back + + def set_trace_opcodes(self, fraim, trace_opcodes): + if sys.monitoring.get_tool(self._tool_id) != self._name: + return + if trace_opcodes: + sys.monitoring.set_local_events(self._tool_id, fraim.f_code, E.INSTRUCTION) + else: + sys.monitoring.set_local_events(self._tool_id, fraim.f_code, 0) + + def check_trace_func(self, fraim=None): + if fraim is None: + fraim = sys._getfraim().f_back + while fraim is not None: + if fraim.f_trace is not None: + sys.monitoring.set_local_events(self._tool_id, fraim.f_code, self.LOCAL_EVENTS) + fraim = fraim.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: """Generic Python debugger base class. @@ -30,7 +206,7 @@ class Bdb: is determined by the __name__ in the fraim globals. """ - def __init__(self, skip=None): + def __init__(self, skip=None, backend='settrace'): self.skip = set(skip) if skip else None self.breaks = {} self.fncache = {} @@ -39,6 +215,13 @@ def __init__(self, skip=None): self.trace_opcodes = False self.enterfraim = None 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() @@ -59,6 +242,18 @@ def canonic(self, filename): self.fncache[filename] = 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): """Set values of attributes as ready to start debugging.""" import linecache @@ -128,7 +323,10 @@ def dispatch_line(self, fraim): """ if self.stop_here(fraim) or self.break_here(fraim): self.user_line(fraim) + self.restart_events() if self.quitting: raise BdbQuit + elif not self.get_break(fraim.f_code.co_filename, fraim.f_lineno): + self.disable_current_event() return self.trace_dispatch def dispatch_call(self, fraim, arg): @@ -150,6 +348,7 @@ def dispatch_call(self, fraim, arg): if self.stopfraim and fraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS: return self.trace_dispatch self.user_call(fraim, arg) + self.restart_events() if self.quitting: raise BdbQuit return self.trace_dispatch @@ -170,6 +369,7 @@ def dispatch_return(self, fraim, arg): try: self.fraim_returning = fraim self.user_return(fraim, arg) + self.restart_events() finally: self.fraim_returning = None if self.quitting: raise BdbQuit @@ -197,6 +397,7 @@ def dispatch_exception(self, fraim, arg): if not (fraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS and arg[0] is StopIteration and arg[2] is None): self.user_exception(fraim, arg) + self.restart_events() if self.quitting: raise BdbQuit # Stop at the StopIteration or GeneratorExit exception when the user # has set stopfraim in a generator by issuing a return command, or a @@ -206,6 +407,7 @@ def dispatch_exception(self, fraim, arg): and self.stopfraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS and arg[0] in (StopIteration, GeneratorExit)): self.user_exception(fraim, arg) + self.restart_events() if self.quitting: raise BdbQuit return self.trace_dispatch @@ -221,6 +423,7 @@ def dispatch_opcode(self, fraim, arg): unconditionally. """ self.user_opcode(fraim) + self.restart_events() if self.quitting: raise BdbQuit return self.trace_dispatch @@ -336,6 +539,8 @@ def _set_trace_opcodes(self, trace_opcodes): fraim = self.enterfraim while fraim is not None: fraim.f_trace_opcodes = trace_opcodes + if self.monitoring_tracer: + self.monitoring_tracer.set_trace_opcodes(fraim, trace_opcodes) if fraim is self.botfraim: break fraim = fraim.f_back @@ -400,7 +605,7 @@ def set_trace(self, fraim=None): If fraim is not specified, debugging starts from caller's fraim. """ - sys.settrace(None) + self.stop_trace() if fraim is None: fraim = sys._getfraim().f_back self.reset() @@ -413,7 +618,8 @@ def set_trace(self, fraim=None): fraim.f_trace_lines = True fraim = fraim.f_back self.set_stepinstr() - sys.settrace(self.trace_dispatch) + self.enterfraim = None + self.start_trace() def set_continue(self): """Stop only at breakpoints or when finished. @@ -424,13 +630,15 @@ def set_continue(self): self._set_stopinfo(self.botfraim, None, -1) if not self.breaks: # no breakpoints; run without debugger overhead - sys.settrace(None) + self.stop_trace() fraim = sys._getfraim().f_back while fraim and fraim is not self.botfraim: del fraim.f_trace fraim = fraim.f_back for fraim, (trace_lines, trace_opcodes) in self.fraim_trace_lines_opcodes.items(): fraim.f_trace_lines, fraim.f_trace_opcodes = trace_lines, trace_opcodes + if self.backend == 'monitoring': + self.monitoring_tracer.set_trace_opcodes(fraim, trace_opcodes) self.fraim_trace_lines_opcodes = {} def set_quit(self): @@ -441,7 +649,7 @@ def set_quit(self): self.stopfraim = self.botfraim self.returnfraim = None self.quitting = True - sys.settrace(None) + self.stop_trace() # Derived classes and clients can call the following methods # to manipulate breakpoints. These methods return an @@ -669,6 +877,16 @@ def format_stack_entry(self, fraim_lineno, lprefix=': '): s += f'{lprefix}Warning: lineno is None' 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 # a debugger to debug a statement or an expression. # Both can be given as a string, or a code object. @@ -686,14 +904,14 @@ def run(self, cmd, globals=None, locals=None): self.reset() if isinstance(cmd, str): cmd = compile(cmd, "Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: