codekingpro/portable-devtools
114k
1"""Debugger basics"""2 3import fnmatch4import sys5import threading6import os7import weakref8from contextlib import contextmanager9from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR10 11__all__ = ["BdbQuit", "Bdb", "Breakpoint"]12 13GENERATOR_AND_COROUTINE_FLAGS = CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR14 15 16class BdbQuit(Exception):17 """Exception to give up completely."""18 19 20E = sys.monitoring.events21 22class _MonitoringTracer:23 EVENT_CALLBACK_MAP = {24 E.PY_START: 'call',25 E.PY_RESUME: 'call',26 E.PY_THROW: 'call',27 E.LINE: 'line',28 E.JUMP: 'jump',29 E.PY_RETURN: 'return',30 E.PY_YIELD: 'return',31 E.PY_UNWIND: 'unwind',32 E.RAISE: 'exception',33 E.STOP_ITERATION: 'exception',34 E.INSTRUCTION: 'opcode',35 }36 37 GLOBAL_EVENTS = E.PY_START | E.PY_RESUME | E.PY_THROW | E.PY_UNWIND | E.RAISE38 LOCAL_EVENTS = E.LINE | E.JUMP | E.PY_RETURN | E.PY_YIELD | E.STOP_ITERATION39 40 def __init__(self):41 self._tool_id = sys.monitoring.DEBUGGER_ID42 self._name = 'bdbtracer'43 self._tracefunc = None44 self._disable_current_event = False45 self._tracing_thread = None46 self._enabled = False47 48 def start_trace(self, tracefunc):49 self._tracefunc = tracefunc50 self._tracing_thread = threading.current_thread()51 curr_tool = sys.monitoring.get_tool(self._tool_id)52 if curr_tool is None:53 sys.monitoring.use_tool_id(self._tool_id, self._name)54 elif curr_tool == self._name:55 sys.monitoring.clear_tool_id(self._tool_id)56 else:57 raise ValueError('Another debugger is using the monitoring tool')58 E = sys.monitoring.events59 all_events = 060 for event, cb_name in self.EVENT_CALLBACK_MAP.items():61 callback = self.callback_wrapper(getattr(self, f'{cb_name}_callback'), event)62 sys.monitoring.register_callback(self._tool_id, event, callback)63 if event != E.INSTRUCTION:64 all_events |= event65 self.update_local_events()66 sys.monitoring.set_events(self._tool_id, self.GLOBAL_EVENTS)67 self._enabled = True68 69 def stop_trace(self):70 self._enabled = False71 self._tracing_thread = None72 curr_tool = sys.monitoring.get_tool(self._tool_id)73 if curr_tool != self._name:74 return75 sys.monitoring.clear_tool_id(self._tool_id)76 sys.monitoring.free_tool_id(self._tool_id)77 78 def disable_current_event(self):79 self._disable_current_event = True80 81 def restart_events(self):82 if sys.monitoring.get_tool(self._tool_id) == self._name:83 sys.monitoring.restart_events()84 85 def callback_wrapper(self, func, event):86 import functools87 88 @functools.wraps(func)89 def wrapper(*args):90 if self._tracing_thread != threading.current_thread():91 return92 try:93 frame = sys._getframe().f_back94 ret = func(frame, *args)95 if self._enabled and frame.f_trace:96 self.update_local_events()97 if (98 self._disable_current_event99 and event not in (E.PY_THROW, E.PY_UNWIND, E.RAISE)100 ):101 return sys.monitoring.DISABLE102 else:103 return ret104 except BaseException:105 self.stop_trace()106 sys._getframe().f_back.f_trace = None107 raise108 finally:109 self._disable_current_event = False110 111 return wrapper112 113 def call_callback(self, frame, code, *args):114 local_tracefunc = self._tracefunc(frame, 'call', None)115 if local_tracefunc is not None:116 frame.f_trace = local_tracefunc117 if self._enabled:118 sys.monitoring.set_local_events(self._tool_id, code, self.LOCAL_EVENTS)119 120 def return_callback(self, frame, code, offset, retval):121 if frame.f_trace:122 frame.f_trace(frame, 'return', retval)123 124 def unwind_callback(self, frame, code, *args):125 if frame.f_trace:126 frame.f_trace(frame, 'return', None)127 128 def line_callback(self, frame, code, *args):129 if frame.f_trace and frame.f_trace_lines:130 frame.f_trace(frame, 'line', None)131 132 def jump_callback(self, frame, code, inst_offset, dest_offset):133 if dest_offset > inst_offset:134 return sys.monitoring.DISABLE135 inst_lineno = self._get_lineno(code, inst_offset)136 dest_lineno = self._get_lineno(code, dest_offset)137 if inst_lineno != dest_lineno:138 return sys.monitoring.DISABLE139 if frame.f_trace and frame.f_trace_lines:140 frame.f_trace(frame, 'line', None)141 142 def exception_callback(self, frame, code, offset, exc):143 if frame.f_trace:144 if exc.__traceback__ and hasattr(exc.__traceback__, 'tb_frame'):145 tb = exc.__traceback__146 while tb:147 if tb.tb_frame.f_locals.get('self') is self:148 return149 tb = tb.tb_next150 frame.f_trace(frame, 'exception', (type(exc), exc, exc.__traceback__))151 152 def opcode_callback(self, frame, code, offset):153 if frame.f_trace and frame.f_trace_opcodes:154 frame.f_trace(frame, 'opcode', None)155 156 def update_local_events(self, frame=None):157 if sys.monitoring.get_tool(self._tool_id) != self._name:158 return159 if frame is None:160 frame = sys._getframe().f_back161 while frame is not None:162 if frame.f_trace is not None:163 if frame.f_trace_opcodes:164 events = self.LOCAL_EVENTS | E.INSTRUCTION165 else:166 events = self.LOCAL_EVENTS167 sys.monitoring.set_local_events(self._tool_id, frame.f_code, events)168 frame = frame.f_back169 170 def _get_lineno(self, code, offset):171 import dis172 last_lineno = None173 for start, lineno in dis.findlinestarts(code):174 if offset < start:175 return last_lineno176 last_lineno = lineno177 return last_lineno178 179 180class Bdb:181 """Generic Python debugger base class.182 183 This class takes care of details of the trace facility;184 a derived class should implement user interaction.185 The standard debugger class (pdb.Pdb) is an example.186 187 The optional skip argument must be an iterable of glob-style188 module name patterns. The debugger will not step into frames189 that originate in a module that matches one of these patterns.190 Whether a frame is considered to originate in a certain module191 is determined by the __name__ in the frame globals.192 """193 194 def __init__(self, skip=None, backend='settrace'):195 self.skip = set(skip) if skip else None196 self.breaks = {}197 self.fncache = {}198 self.frame_trace_lines_opcodes = {}199 self.frame_returning = None200 self.trace_opcodes = False201 self.enterframe = None202 self.cmdframe = None203 self.cmdlineno = None204 self.code_linenos = weakref.WeakKeyDictionary()205 self.backend = backend206 if backend == 'monitoring':207 self.monitoring_tracer = _MonitoringTracer()208 elif backend == 'settrace':209 self.monitoring_tracer = None210 else:211 raise ValueError(f"Invalid backend '{backend}'")212 213 self._load_breaks()214 215 def canonic(self, filename):216 """Return canonical form of filename.217 218 For real filenames, the canonical form is a case-normalized (on219 case insensitive filesystems) absolute path. 'Filenames' with220 angle brackets, such as "<stdin>", generated in interactive221 mode, are returned unchanged.222 """223 if filename == "<" + filename[1:-1] + ">":224 return filename225 canonic = self.fncache.get(filename)226 if not canonic:227 canonic = os.path.abspath(filename)228 canonic = os.path.normcase(canonic)229 self.fncache[filename] = canonic230 return canonic231 232 def start_trace(self):233 if self.monitoring_tracer:234 self.monitoring_tracer.start_trace(self.trace_dispatch)235 else:236 sys.settrace(self.trace_dispatch)237 238 def stop_trace(self):239 if self.monitoring_tracer:240 self.monitoring_tracer.stop_trace()241 else:242 sys.settrace(None)243 244 def reset(self):245 """Set values of attributes as ready to start debugging."""246 import linecache247 linecache.checkcache()248 self.botframe = None249 self._set_stopinfo(None, None)250 251 @contextmanager252 def set_enterframe(self, frame):253 self.enterframe = frame254 yield255 self.enterframe = None256 257 def trace_dispatch(self, frame, event, arg):258 """Dispatch a trace function for debugged frames based on the event.259 260 This function is installed as the trace function for debugged261 frames. Its return value is the new trace function, which is262 usually itself. The default implementation decides how to263 dispatch a frame, depending on the type of event (passed in as a264 string) that is about to be executed.265 266 The event can be one of the following:267 line: A new line of code is going to be executed.268 call: A function is about to be called or another code block269 is entered.270 return: A function or other code block is about to return.271 exception: An exception has occurred.272 c_call: A C function is about to be called.273 c_return: A C function has returned.274 c_exception: A C function has raised an exception.275 276 For the Python events, specialized functions (see the dispatch_*()277 methods) are called. For the C events, no action is taken.278 279 The arg parameter depends on the previous event.280 """281 282 with self.set_enterframe(frame):283 if self.quitting:284 return # None285 if event == 'line':286 return self.dispatch_line(frame)287 if event == 'call':288 return self.dispatch_call(frame, arg)289 if event == 'return':290 return self.dispatch_return(frame, arg)291 if event == 'exception':292 return self.dispatch_exception(frame, arg)293 if event == 'c_call':294 return self.trace_dispatch295 if event == 'c_exception':296 return self.trace_dispatch297 if event == 'c_return':298 return self.trace_dispatch299 if event == 'opcode':300 return self.dispatch_opcode(frame, arg)301 print('bdb.Bdb.dispatch: unknown debugging event:', repr(event))302 return self.trace_dispatch303 304 def dispatch_line(self, frame):305 """Invoke user function and return trace function for line event.306 307 If the debugger stops on the current line, invoke308 self.user_line(). Raise BdbQuit if self.quitting is set.309 Return self.trace_dispatch to continue tracing in this scope.310 """311 # GH-136057312 # For line events, we don't want to stop at the same line where313 # the latest next/step command was issued.314 if (self.stop_here(frame) or self.break_here(frame)) and not (315 self.cmdframe == frame and self.cmdlineno == frame.f_lineno316 ):317 self.user_line(frame)318 self.restart_events()319 if self.quitting: raise BdbQuit320 elif not self.get_break(frame.f_code.co_filename, frame.f_lineno):321 self.disable_current_event()322 return self.trace_dispatch323 324 def dispatch_call(self, frame, arg):325 """Invoke user function and return trace function for call event.326 327 If the debugger stops on this function call, invoke328 self.user_call(). Raise BdbQuit if self.quitting is set.329 Return self.trace_dispatch to continue tracing in this scope.330 """331 # XXX 'arg' is no longer used332 if self.botframe is None:333 # First call of dispatch since reset()334 self.botframe = frame.f_back # (CT) Note that this may also be None!335 return self.trace_dispatch336 if not (self.stop_here(frame) or self.break_anywhere(frame)):337 # We already know there's no breakpoint in this function338 # If it's a next/until/return command, we don't need any CALL event339 # and we don't need to set the f_trace on any new frame.340 # If it's a step command, it must either hit stop_here, or skip the341 # whole module. Either way, we don't need the CALL event here.342 self.disable_current_event()343 return # None344 # Ignore call events in generator except when stepping.345 if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:346 return self.trace_dispatch347 self.user_call(frame, arg)348 self.restart_events()349 if self.quitting: raise BdbQuit350 return self.trace_dispatch351 352 def dispatch_return(self, frame, arg):353 """Invoke user function and return trace function for return event.354 355 If the debugger stops on this function return, invoke356 self.user_return(). Raise BdbQuit if self.quitting is set.357 Return self.trace_dispatch to continue tracing in this scope.358 """359 if self.stop_here(frame) or frame == self.returnframe:360 # Ignore return events in generator except when stepping.361 if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:362 # It's possible to trigger a StopIteration exception in363 # the caller so we must set the trace function in the caller364 self._set_caller_tracefunc(frame)365 return self.trace_dispatch366 try:367 self.frame_returning = frame368 self.user_return(frame, arg)369 self.restart_events()370 finally:371 self.frame_returning = None372 if self.quitting: raise BdbQuit373 # The user issued a 'next' or 'until' command.374 if self.stopframe is frame and self.stoplineno != -1:375 self._set_stopinfo(None, None)376 # The previous frame might not have f_trace set, unless we are377 # issuing a command that does not expect to stop, we should set378 # f_trace379 if self.stoplineno != -1:380 self._set_caller_tracefunc(frame)381 return self.trace_dispatch382 383 def dispatch_exception(self, frame, arg):384 """Invoke user function and return trace function for exception event.385 386 If the debugger stops on this exception, invoke387 self.user_exception(). Raise BdbQuit if self.quitting is set.388 Return self.trace_dispatch to continue tracing in this scope.389 """390 if self.stop_here(frame):391 # When stepping with next/until/return in a generator frame, skip392 # the internal StopIteration exception (with no traceback)393 # triggered by a subiterator run with the 'yield from' statement.394 if not (frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS395 and arg[0] is StopIteration and arg[2] is None):396 self.user_exception(frame, arg)397 self.restart_events()398 if self.quitting: raise BdbQuit399 # Stop at the StopIteration or GeneratorExit exception when the user400 # has set stopframe in a generator by issuing a return command, or a401 # next/until command at the last statement in the generator before the402 # exception.403 elif (self.stopframe and frame is not self.stopframe404 and self.stopframe.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS405 and arg[0] in (StopIteration, GeneratorExit)):406 self.user_exception(frame, arg)407 self.restart_events()408 if self.quitting: raise BdbQuit409 410 return self.trace_dispatch411 412 def dispatch_opcode(self, frame, arg):413 """Invoke user function and return trace function for opcode event.414 If the debugger stops on the current opcode, invoke415 self.user_opcode(). Raise BdbQuit if self.quitting is set.416 Return self.trace_dispatch to continue tracing in this scope.417 418 Opcode event will always trigger the user callback. For now the only419 opcode event is from an inline set_trace() and we want to stop there420 unconditionally.421 """422 self.user_opcode(frame)423 self.restart_events()424 if self.quitting: raise BdbQuit425 return self.trace_dispatch426 427 # Normally derived classes don't override the following428 # methods, but they may if they want to redefine the429 # definition of stopping and breakpoints.430 431 def is_skipped_module(self, module_name):432 "Return True if module_name matches any skip pattern."433 if module_name is None: # some modules do not have names434 return False435 for pattern in self.skip:436 if fnmatch.fnmatch(module_name, pattern):437 return True438 return False439 440 def stop_here(self, frame):441 "Return True if frame is below the starting frame in the stack."442 # (CT) stopframe may now also be None, see dispatch_call.443 # (CT) the former test for None is therefore removed from here.444 if self.skip and \445 self.is_skipped_module(frame.f_globals.get('__name__')):446 return False447 if frame is self.stopframe:448 if self.stoplineno == -1:449 return False450 return frame.f_lineno >= self.stoplineno451 if not self.stopframe:452 return True453 return False454 455 def break_here(self, frame):456 """Return True if there is an effective breakpoint for this line.457 458 Check for line or function breakpoint and if in effect.459 Delete temporary breakpoints if effective() says to.460 """461 filename = self.canonic(frame.f_code.co_filename)462 if filename not in self.breaks:463 return False464 lineno = frame.f_lineno465 if lineno not in self.breaks[filename]:466 # The line itself has no breakpoint, but maybe the line is the467 # first line of a function with breakpoint set by function name.468 lineno = frame.f_code.co_firstlineno469 if lineno not in self.breaks[filename]:470 return False471 472 # flag says ok to delete temp. bp473 (bp, flag) = effective(filename, lineno, frame)474 if bp:475 self.currentbp = bp.number476 if (flag and bp.temporary):477 self.do_clear(str(bp.number))478 return True479 else:480 return False481 482 def do_clear(self, arg):483 """Remove temporary breakpoint.484 485 Must implement in derived classes or get NotImplementedError.486 """487 raise NotImplementedError("subclass of bdb must implement do_clear()")488 489 def break_anywhere(self, frame):490 """Return True if there is any breakpoint in that frame491 """492 filename = self.canonic(frame.f_code.co_filename)493 if filename not in self.breaks:494 return False495 for lineno in self.breaks[filename]:496 if self._lineno_in_frame(lineno, frame):497 return True498 return False499 500 def _lineno_in_frame(self, lineno, frame):501 """Return True if the line number is in the frame's code object.502 """503 code = frame.f_code504 if lineno < code.co_firstlineno:505 return False506 if code not in self.code_linenos:507 self.code_linenos[code] = set(lineno for _, _, lineno in code.co_lines())508 return lineno in self.code_linenos[code]509 510 # Derived classes should override the user_* methods511 # to gain control.512 513 def user_call(self, frame, argument_list):514 """Called if we might stop in a function."""515 pass516 517 def user_line(self, frame):518 """Called when we stop or break at a line."""519 pass520 521 def user_return(self, frame, return_value):522 """Called when a return trap is set here."""523 pass524 525 def user_exception(self, frame, exc_info):526 """Called when we stop on an exception."""527 pass528 529 def user_opcode(self, frame):530 """Called when we are about to execute an opcode."""531 pass532 533 def _set_trace_opcodes(self, trace_opcodes):534 if trace_opcodes != self.trace_opcodes:535 self.trace_opcodes = trace_opcodes536 frame = self.enterframe537 while frame is not None:538 frame.f_trace_opcodes = trace_opcodes539 if frame is self.botframe:540 break541 frame = frame.f_back542 if self.monitoring_tracer:543 self.monitoring_tracer.update_local_events()544 545 def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False,546 cmdframe=None, cmdlineno=None):547 """Set the attributes for stopping.548 549 If stoplineno is greater than or equal to 0, then stop at line550 greater than or equal to the stopline. If stoplineno is -1, then551 don't stop at all.552 """553 self.stopframe = stopframe554 self.returnframe = returnframe555 self.quitting = False556 # stoplineno >= 0 means: stop at line >= the stoplineno557 # stoplineno -1 means: don't stop at all558 self.stoplineno = stoplineno559 # cmdframe/cmdlineno is the frame/line number when the user issued560 # step/next commands.561 self.cmdframe = cmdframe562 self.cmdlineno = cmdlineno563 self._set_trace_opcodes(opcode)564 565 def _set_caller_tracefunc(self, current_frame):566 # Issue #13183: pdb skips frames after hitting a breakpoint and running567 # step commands.568 # Restore the trace function in the caller (that may not have been set569 # for performance reasons) when returning from the current frame, unless570 # the caller is the botframe.571 caller_frame = current_frame.f_back572 if caller_frame and not caller_frame.f_trace and caller_frame is not self.botframe:573 caller_frame.f_trace = self.trace_dispatch574 575 # Derived classes and clients can call the following methods576 # to affect the stepping state.577 578 def set_until(self, frame, lineno=None):579 """Stop when the line with the lineno greater than the current one is580 reached or when returning from current frame."""581 # the name "until" is borrowed from gdb582 if lineno is None:583 lineno = frame.f_lineno + 1584 self._set_stopinfo(frame, frame, lineno)585 586 def set_step(self):587 """Stop after one line of code."""588 # set_step() could be called from signal handler so enterframe might be None589 self._set_stopinfo(None, None, cmdframe=self.enterframe,590 cmdlineno=getattr(self.enterframe, 'f_lineno', None))591 592 def set_stepinstr(self):593 """Stop before the next instruction."""594 self._set_stopinfo(None, None, opcode=True)595 596 def set_next(self, frame):597 """Stop on the next line in or below the given frame."""598 self._set_stopinfo(frame, None, cmdframe=frame, cmdlineno=frame.f_lineno)599 600 def set_return(self, frame):601 """Stop when returning from the given frame."""602 if frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:603 self._set_stopinfo(frame, frame, -1)604 else:605 self._set_stopinfo(frame.f_back, frame)606 607 def set_trace(self, frame=None):608 """Start debugging from frame.609 610 If frame is not specified, debugging starts from caller's frame.611 """612 self.stop_trace()613 if frame is None:614 frame = sys._getframe().f_back615 self.reset()616 with self.set_enterframe(frame):617 while frame:618 frame.f_trace = self.trace_dispatch619 self.botframe = frame620 self.frame_trace_lines_opcodes[frame] = (frame.f_trace_lines, frame.f_trace_opcodes)621 # We need f_trace_lines == True for the debugger to work622 frame.f_trace_lines = True623 frame = frame.f_back624 self.set_stepinstr()625 self.enterframe = None626 self.start_trace()627 628 def set_continue(self):629 """Stop only at breakpoints or when finished.630 631 If there are no breakpoints, set the system trace function to None.632 """633 # Don't stop except at breakpoints or when finished634 self._set_stopinfo(self.botframe, None, -1)635 if not self.breaks:636 # no breakpoints; run without debugger overhead637 self.stop_trace()638 frame = sys._getframe().f_back639 while frame and frame is not self.botframe:640 del frame.f_trace641 frame = frame.f_back642 for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items():643 frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes644 if self.backend == 'monitoring':645 self.monitoring_tracer.update_local_events()646 self.frame_trace_lines_opcodes = {}647 648 def set_quit(self):649 """Set quitting attribute to True.650 651 Raises BdbQuit exception in the next call to a dispatch_*() method.652 """653 self.stopframe = self.botframe654 self.returnframe = None655 self.quitting = True656 self.stop_trace()657 658 # Derived classes and clients can call the following methods659 # to manipulate breakpoints. These methods return an660 # error message if something went wrong, None if all is well.661 # Set_break prints out the breakpoint line and file:lineno.662 # Call self.get_*break*() to see the breakpoints or better663 # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().664 665 def _add_to_breaks(self, filename, lineno):666 """Add breakpoint to breaks, if not already there."""667 bp_linenos = self.breaks.setdefault(filename, [])668 if lineno not in bp_linenos:669 bp_linenos.append(lineno)670 671 def set_break(self, filename, lineno, temporary=False, cond=None,672 funcname=None):673 """Set a new breakpoint for filename:lineno.674 675 If lineno doesn't exist for the filename, return an error message.676 The filename should be in canonical form.677 """678 filename = self.canonic(filename)679 import linecache # Import as late as possible680 line = linecache.getline(filename, lineno)681 if not line:682 return 'Line %s:%d does not exist' % (filename, lineno)683 self._add_to_breaks(filename, lineno)684 bp = Breakpoint(filename, lineno, temporary, cond, funcname)685 # After we set a new breakpoint, we need to search through all frames686 # and set f_trace to trace_dispatch if there could be a breakpoint in687 # that frame.688 frame = self.enterframe689 while frame:690 if self.break_anywhere(frame):691 frame.f_trace = self.trace_dispatch692 frame = frame.f_back693 return None694 695 def _load_breaks(self):696 """Apply all breakpoints (set in other instances) to this one.697 698 Populates this instance's breaks list from the Breakpoint class's699 list, which can have breakpoints set by another Bdb instance. This700 is necessary for interactive sessions to keep the breakpoints701 active across multiple calls to run().702 """703 for (filename, lineno) in Breakpoint.bplist.keys():704 self._add_to_breaks(filename, lineno)705 706 def _prune_breaks(self, filename, lineno):707 """Prune breakpoints for filename:lineno.708 709 A list of breakpoints is maintained in the Bdb instance and in710 the Breakpoint class. If a breakpoint in the Bdb instance no711 longer exists in the Breakpoint class, then it's removed from the712 Bdb instance.713 """714 if (filename, lineno) not in Breakpoint.bplist:715 self.breaks[filename].remove(lineno)716 if not self.breaks[filename]:717 del self.breaks[filename]718 719 def clear_break(self, filename, lineno):720 """Delete breakpoints for filename:lineno.721 722 If no breakpoints were set, return an error message.723 """724 filename = self.canonic(filename)725 if filename not in self.breaks:726 return 'There are no breakpoints in %s' % filename727 if lineno not in self.breaks[filename]:728 return 'There is no breakpoint at %s:%d' % (filename, lineno)729 # If there's only one bp in the list for that file,line730 # pair, then remove the breaks entry731 for bp in Breakpoint.bplist[filename, lineno][:]:732 bp.deleteMe()733 self._prune_breaks(filename, lineno)734 return None735 736 def clear_bpbynumber(self, arg):737 """Delete a breakpoint by its index in Breakpoint.bpbynumber.738 739 If arg is invalid, return an error message.740 """741 try:742 bp = self.get_bpbynumber(arg)743 except ValueError as err:744 return str(err)745 bp.deleteMe()746 self._prune_breaks(bp.file, bp.line)747 return None748 749 def clear_all_file_breaks(self, filename):750 """Delete all breakpoints in filename.751 752 If none were set, return an error message.753 """754 filename = self.canonic(filename)755 if filename not in self.breaks:756 return 'There are no breakpoints in %s' % filename757 for line in self.breaks[filename]:758 blist = Breakpoint.bplist[filename, line]759 for bp in blist:760 bp.deleteMe()761 del self.breaks[filename]762 return None763 764 def clear_all_breaks(self):765 """Delete all existing breakpoints.766 767 If none were set, return an error message.768 """769 if not self.breaks:770 return 'There are no breakpoints'771 for bp in Breakpoint.bpbynumber:772 if bp:773 bp.deleteMe()774 self.breaks = {}775 return None776 777 def get_bpbynumber(self, arg):778 """Return a breakpoint by its index in Breakpoint.bybpnumber.779 780 For invalid arg values or if the breakpoint doesn't exist,781 raise a ValueError.782 """783 if not arg:784 raise ValueError('Breakpoint number expected')785 try:786 number = int(arg)787 except ValueError:788 raise ValueError('Non-numeric breakpoint number %s' % arg) from None789 try:790 bp = Breakpoint.bpbynumber[number]791 except IndexError:792 raise ValueError('Breakpoint number %d out of range' % number) from None793 if bp is None:794 raise ValueError('Breakpoint %d already deleted' % number)795 return bp796 797 def get_break(self, filename, lineno):798 """Return True if there is a breakpoint for filename:lineno."""799 filename = self.canonic(filename)800 return filename in self.breaks and \801 lineno in self.breaks[filename]802 803 def get_breaks(self, filename, lineno):804 """Return all breakpoints for filename:lineno.805 806 If no breakpoints are set, return an empty list.807 """808 filename = self.canonic(filename)809 return filename in self.breaks and \810 lineno in self.breaks[filename] and \811 Breakpoint.bplist[filename, lineno] or []812 813 def get_file_breaks(self, filename):814 """Return all lines with breakpoints for filename.815 816 If no breakpoints are set, return an empty list.817 """818 filename = self.canonic(filename)819 if filename in self.breaks:820 return self.breaks[filename]821 else:822 return []823 824 def get_all_breaks(self):825 """Return all breakpoints that are set."""826 return self.breaks827 828 # Derived classes and clients can call the following method829 # to get a data structure representing a stack trace.830 831 def get_stack(self, f, t):832 """Return a list of (frame, lineno) in a stack trace and a size.833 834 List starts with original calling frame, if there is one.835 Size may be number of frames above or below f.836 """837 stack = []838 if t and t.tb_frame is f:839 t = t.tb_next840 while f is not None:841 stack.append((f, f.f_lineno))842 if f is self.botframe:843 break844 f = f.f_back845 stack.reverse()846 i = max(0, len(stack) - 1)847 while t is not None:848 stack.append((t.tb_frame, t.tb_lineno))849 t = t.tb_next850 if f is None:851 i = max(0, len(stack) - 1)852 return stack, i853 854 def format_stack_entry(self, frame_lineno, lprefix=': '):855 """Return a string with information about a stack entry.856 857 The stack entry frame_lineno is a (frame, lineno) tuple. The858 return string contains the canonical filename, the function name859 or '<lambda>', the input arguments, the return value, and the860 line of code (if it exists).861 862 """863 import linecache, reprlib864 frame, lineno = frame_lineno865 filename = self.canonic(frame.f_code.co_filename)866 s = '%s(%r)' % (filename, lineno)867 if frame.f_code.co_name:868 s += frame.f_code.co_name869 else:870 s += "<lambda>"871 s += '()'872 if '__return__' in frame.f_locals:873 rv = frame.f_locals['__return__']874 s += '->'875 s += reprlib.repr(rv)876 if lineno is not None:877 line = linecache.getline(filename, lineno, frame.f_globals)878 if line:879 s += lprefix + line.strip()880 else:881 s += f'{lprefix}Warning: lineno is None'882 return s883 884 def disable_current_event(self):885 """Disable the current event."""886 if self.backend == 'monitoring':887 self.monitoring_tracer.disable_current_event()888 889 def restart_events(self):890 """Restart all events."""891 if self.backend == 'monitoring':892 self.monitoring_tracer.restart_events()893 894 # The following methods can be called by clients to use895 # a debugger to debug a statement or an expression.896 # Both can be given as a string, or a code object.897 898 def run(self, cmd, globals=None, locals=None):899 """Debug a statement executed via the exec() function.900 901 globals defaults to __main__.dict; locals defaults to globals.902 """903 if globals is None:904 import __main__905 globals = __main__.__dict__906 if locals is None:907 locals = globals908 self.reset()909 if isinstance(cmd, str):910 cmd = compile(cmd, "<string>", "exec")911 self.start_trace()912 try:913 exec(cmd, globals, locals)914 except BdbQuit:915 pass916 finally:917 self.quitting = True918 self.stop_trace()919 920 def runeval(self, expr, globals=None, locals=None):921 """Debug an expression executed via the eval() function.922 923 globals defaults to __main__.dict; locals defaults to globals.924 """925 if globals is None:926 import __main__927 globals = __main__.__dict__928 if locals is None:929 locals = globals930 self.reset()931 self.start_trace()932 try:933 return eval(expr, globals, locals)934 except BdbQuit:935 pass936 finally:937 self.quitting = True938 self.stop_trace()939 940 def runctx(self, cmd, globals, locals):941 """For backwards-compatibility. Defers to run()."""942 # B/W compatibility943 self.run(cmd, globals, locals)944 945 # This method is more useful to debug a single function call.946 947 def runcall(self, func, /, *args, **kwds):948 """Debug a single function call.949 950 Return the result of the function call.951 """952 self.reset()953 self.start_trace()954 res = None955 try:956 res = func(*args, **kwds)957 except BdbQuit:958 pass959 finally:960 self.quitting = True961 self.stop_trace()962 return res963 964 965def set_trace():966 """Start debugging with a Bdb instance from the caller's frame."""967 Bdb().set_trace()968 969 970class Breakpoint:971 """Breakpoint class.972 973 Implements temporary breakpoints, ignore counts, disabling and974 (re)-enabling, and conditionals.975 976 Breakpoints are indexed by number through bpbynumber and by977 the (file, line) tuple using bplist. The former points to a978 single instance of class Breakpoint. The latter points to a979 list of such instances since there may be more than one980 breakpoint per line.981 982 When creating a breakpoint, its associated filename should be983 in canonical form. If funcname is defined, a breakpoint hit will be984 counted when the first line of that function is executed. A985 conditional breakpoint always counts a hit.986 """987 988 # XXX Keeping state in the class is a mistake -- this means989 # you cannot have more than one active Bdb instance.990 991 next = 1 # Next bp to be assigned992 bplist = {} # indexed by (file, lineno) tuple993 bpbynumber = [None] # Each entry is None or an instance of Bpt994 # index 0 is unused, except for marking an995 # effective break .... see effective()996 997 def __init__(self, file, line, temporary=False, cond=None, funcname=None):998 self.funcname = funcname999 # Needed if funcname is not None.1000 self.func_first_executable_line = None1001 self.file = file # This better be in canonical form!1002 self.line = line1003 self.temporary = temporary1004 self.cond = cond1005 self.enabled = True1006 self.ignore = 01007 self.hits = 01008 self.number = Breakpoint.next1009 Breakpoint.next += 11010 # Build the two lists1011 self.bpbynumber.append(self)1012 if (file, line) in self.bplist:1013 self.bplist[file, line].append(self)1014 else:1015 self.bplist[file, line] = [self]1016 1017 @staticmethod1018 def clearBreakpoints():1019 Breakpoint.next = 11020 Breakpoint.bplist = {}1021 Breakpoint.bpbynumber = [None]1022 1023 def deleteMe(self):1024 """Delete the breakpoint from the list associated to a file:line.1025 1026 If it is the last breakpoint in that position, it also deletes1027 the entry for the file:line.1028 """1029 1030 index = (self.file, self.line)1031 self.bpbynumber[self.number] = None # No longer in list1032 self.bplist[index].remove(self)1033 if not self.bplist[index]:1034 # No more bp for this f:l combo1035 del self.bplist[index]1036 1037 def enable(self):1038 """Mark the breakpoint as enabled."""1039 self.enabled = True1040 1041 def disable(self):1042 """Mark the breakpoint as disabled."""1043 self.enabled = False1044 1045 def bpprint(self, out=None):1046 """Print the output of bpformat().1047 1048 The optional out argument directs where the output is sent1049 and defaults to standard output.1050 """1051 if out is None:1052 out = sys.stdout1053 print(self.bpformat(), file=out)1054 1055 def bpformat(self):1056 """Return a string with information about the breakpoint.1057 1058 The information includes the breakpoint number, temporary1059 status, file:line position, break condition, number of times to1060 ignore, and number of times hit.1061 1062 """1063 if self.temporary:1064 disp = 'del '1065 else:1066 disp = 'keep '1067 if self.enabled:1068 disp = disp + 'yes '1069 else:1070 disp = disp + 'no '1071 ret = '%-4dbreakpoint %s at %s:%d' % (self.number, disp,1072 self.file, self.line)1073 if self.cond:1074 ret += '\n\tstop only if %s' % (self.cond,)1075 if self.ignore:1076 ret += '\n\tignore next %d hits' % (self.ignore,)1077 if self.hits:1078 if self.hits > 1:1079 ss = 's'1080 else:1081 ss = ''1082 ret += '\n\tbreakpoint already hit %d time%s' % (self.hits, ss)1083 return ret1084 1085 def __str__(self):1086 "Return a condensed description of the breakpoint."1087 return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line)1088 1089# -----------end of Breakpoint class----------1090 1091 1092def checkfuncname(b, frame):1093 """Return True if break should happen here.1094 1095 Whether a break should happen depends on the way that b (the breakpoint)1096 was set. If it was set via line number, check if b.line is the same as1097 the one in the frame. If it was set via function name, check if this is1098 the right function and if it is on the first executable line.1099 """1100 if not b.funcname:1101 # Breakpoint was set via line number.1102 if b.line != frame.f_lineno:1103 # Breakpoint was set at a line with a def statement and the function1104 # defined is called: don't break.1105 return False1106 return True1107 1108 # Breakpoint set via function name.1109 if frame.f_code.co_name != b.funcname:1110 # It's not a function call, but rather execution of def statement.1111 return False1112 1113 # We are in the right frame.1114 if not b.func_first_executable_line:1115 # The function is entered for the 1st time.1116 b.func_first_executable_line = frame.f_lineno1117 1118 if b.func_first_executable_line != frame.f_lineno:1119 # But we are not at the first line number: don't break.1120 return False1121 return True1122 1123 1124def effective(file, line, frame):1125 """Return (active breakpoint, delete temporary flag) or (None, None) as1126 breakpoint to act upon.1127 1128 The "active breakpoint" is the first entry in bplist[line, file] (which1129 must exist) that is enabled, for which checkfuncname is True, and that1130 has neither a False condition nor a positive ignore count. The flag,1131 meaning that a temporary breakpoint should be deleted, is False only1132 when the condiion cannot be evaluated (in which case, ignore count is1133 ignored).1134 1135 If no such entry exists, then (None, None) is returned.1136 """1137 possibles = Breakpoint.bplist[file, line]1138 for b in possibles:1139 if not b.enabled:1140 continue1141 if not checkfuncname(b, frame):1142 continue1143 # Count every hit when bp is enabled1144 b.hits += 11145 if not b.cond:1146 # If unconditional, and ignoring go on to next, else break1147 if b.ignore > 0:1148 b.ignore -= 11149 continue1150 else:1151 # breakpoint and marker that it's ok to delete if temporary1152 return (b, True)1153 else:1154 # Conditional bp.1155 # Ignore count applies only to those bpt hits where the1156 # condition evaluates to true.1157 try:1158 val = eval(b.cond, frame.f_globals, frame.f_locals)1159 if val:1160 if b.ignore > 0:1161 b.ignore -= 11162 # continue1163 else:1164 return (b, True)1165 # else:1166 # continue1167 except:1168 # if eval fails, most conservative thing is to stop on1169 # breakpoint regardless of ignore count. Don't delete1170 # temporary, as another hint to user.1171 return (b, False)1172 return (None, None)1173 1174 1175# -------------------- testing --------------------1176 1177class Tdb(Bdb):1178 def user_call(self, frame, args):1179 name = frame.f_code.co_name1180 if not name: name = '???'1181 print('+++ call', name, args)1182 def user_line(self, frame):1183 import linecache1184 name = frame.f_code.co_name1185 if not name: name = '???'1186 fn = self.canonic(frame.f_code.co_filename)1187 line = linecache.getline(fn, frame.f_lineno, frame.f_globals)1188 print('+++', fn, frame.f_lineno, name, ':', line.strip())1189 def user_return(self, frame, retval):1190 print('+++ return', retval)1191 def user_exception(self, frame, exc_stuff):1192 print('+++ exception', exc_stuff)1193 self.set_continue()1194 1195def foo(n):1196 print('foo(', n, ')')1197 x = bar(n*10)1198 print('bar returned', x)1199 1200def bar(a):