1 """Debugger basics""" 2 3 import fnmatch 4 import sys 5 import os 6 from inspect import CO_GENERATOR 7 8 __all__ = ["BdbQuit", "Bdb", "Breakpoint"] 9 10 class BdbQuit(Exception): 11 """Exception to give up completely.""" 12 13 14 class Bdb: 15 """Generic Python debugger base class. 16 17 This class takes care of details of the trace facility; 18 a derived class should implement user interaction. 19 The standard debugger class (pdb.Pdb) is an example. 20 """ 21 22 def __init__(self, skip=None): 23 self.skip = set(skip) if skip else None 24 self.breaks = {} 25 self.fncache = {} 26 self.frame_returning = None 27 28 def canonic(self, filename): 29 if filename == "<" + filename[1:-1] + ">": 30 return filename 31 canonic = self.fncache.get(filename) 32 if not canonic: 33 canonic = os.path.abspath(filename) 34 canonic = os.path.normcase(canonic) 35 self.fncache[filename] = canonic 36 return canonic 37 38 def reset(self): 39 import linecache 40 linecache.checkcache() 41 self.botframe = None 42 self._set_stopinfo(None, None) 43 44 def trace_dispatch(self, frame, event, arg): 45 if self.quitting: 46 return # None 47 if event == 'line': 48 return self.dispatch_line(frame) 49 if event == 'call': 50 return self.dispatch_call(frame, arg) 51 if event == 'return': 52 return self.dispatch_return(frame, arg) 53 if event == 'exception': 54 return self.dispatch_exception(frame, arg) 55 if event == 'c_call': 56 return self.trace_dispatch 57 if event == 'c_exception': 58 return self.trace_dispatch 59 if event == 'c_return': 60 return self.trace_dispatch 61 print('bdb.Bdb.dispatch: unknown debugging event:', repr(event)) 62 return self.trace_dispatch 63 64 def dispatch_line(self, frame): 65 if self.stop_here(frame) or self.break_here(frame): 66 self.user_line(frame) 67 if self.quitting: raise BdbQuit 68 return self.trace_dispatch 69 70 def dispatch_call(self, frame, arg): 71 # XXX 'arg' is no longer used 72 if self.botframe is None: 73 # First call of dispatch since reset() 74 self.botframe = frame.f_back # (CT) Note that this may also be None! 75 return self.trace_dispatch 76 if not (self.stop_here(frame) or self.break_anywhere(frame)): 77 # No need to trace this function 78 return # None 79 # Ignore call events in generator except when stepping. 80 if self.stopframe and frame.f_code.co_flags & CO_GENERATOR: 81 return self.trace_dispatch 82 self.user_call(frame, arg) 83 if self.quitting: raise BdbQuit 84 return self.trace_dispatch 85 86 def dispatch_return(self, frame, arg): 87 if self.stop_here(frame) or frame == self.returnframe: 88 # Ignore return events in generator except when stepping. 89 if self.stopframe and frame.f_code.co_flags & CO_GENERATOR: 90 return self.trace_dispatch 91 try: 92 self.frame_returning = frame 93 self.user_return(frame, arg) 94 finally: 95 self.frame_returning = None 96 if self.quitting: raise BdbQuit 97 # The user issued a 'next' or 'until' command. 98 if self.stopframe is frame and self.stoplineno != -1: 99 self._set_stopinfo(None, None) 100 return self.trace_dispatch 101 102 def dispatch_exception(self, frame, arg): 103 if self.stop_here(frame): 104 # When stepping with next/until/return in a generator frame, skip 105 # the internal StopIteration exception (with no traceback) 106 # triggered by a subiterator run with the 'yield from' statement. 107 if not (frame.f_code.co_flags & CO_GENERATOR 108 and arg[0] is StopIteration and arg[2] is None): 109 self.user_exception(frame, arg) 110 if self.quitting: raise BdbQuit 111 # Stop at the StopIteration or GeneratorExit exception when the user 112 # has set stopframe in a generator by issuing a return command, or a 113 # next/until command at the last statement in the generator before the 114 # exception. 115 elif (self.stopframe and frame is not self.stopframe 116 and self.stopframe.f_code.co_flags & CO_GENERATOR 117 and arg[0] in (StopIteration, GeneratorExit)): 118 self.user_exception(frame, arg) 119 if self.quitting: raise BdbQuit 120 121 return self.trace_dispatch 122 123 # Normally derived classes don't override the following 124 # methods, but they may if they want to redefine the 125 # definition of stopping and breakpoints. 126 127 def is_skipped_module(self, module_name): 128 for pattern in self.skip: 129 if fnmatch.fnmatch(module_name, pattern): 130 return True 131 return False 132 133 def stop_here(self, frame): 134 # (CT) stopframe may now also be None, see dispatch_call. 135 # (CT) the former test for None is therefore removed from here. 136 if self.skip and \ 137 self.is_skipped_module(frame.f_globals.get('__name__')): 138 return False 139 if frame is self.stopframe: 140 if self.stoplineno == -1: 141 return False 142 return frame.f_lineno >= self.stoplineno 143 if not self.stopframe: 144 return True 145 return False 146 147 def break_here(self, frame): 148 filename = self.canonic(frame.f_code.co_filename) 149 if filename not in self.breaks: 150 return False 151 lineno = frame.f_lineno 152 if lineno not in self.breaks[filename]: 153 # The line itself has no breakpoint, but maybe the line is the 154 # first line of a function with breakpoint set by function name. 155 lineno = frame.f_code.co_firstlineno 156 if lineno not in self.breaks[filename]: 157 return False 158 159 # flag says ok to delete temp. bp 160 (bp, flag) = effective(filename, lineno, frame) 161 if bp: 162 self.currentbp = bp.number 163 if (flag and bp.temporary): 164 self.do_clear(str(bp.number)) 165 return True 166 else: 167 return False 168 169 def do_clear(self, arg): 170 raise NotImplementedError("subclass of bdb must implement do_clear()") 171 172 def break_anywhere(self, frame): 173 return self.canonic(frame.f_code.co_filename) in self.breaks 174 175 # Derived classes should override the user_* methods 176 # to gain control. 177 178 def user_call(self, frame, argument_list): 179 """This method is called when there is the remote possibility 180 that we ever need to stop in this function.""" 181 pass 182 183 def user_line(self, frame): 184 """This method is called when we stop or break at this line.""" 185 pass 186 187 def user_return(self, frame, return_value): 188 """This method is called when a return trap is set here.""" 189 pass 190 191 def user_exception(self, frame, exc_info): 192 """This method is called if an exception occurs, 193 but only if we are to stop at or just below this level.""" 194 pass 195 196 def _set_stopinfo(self, stopframe, returnframe, stoplineno=0): 197 self.stopframe = stopframe 198 self.returnframe = returnframe 199 self.quitting = False 200 # stoplineno >= 0 means: stop at line >= the stoplineno 201 # stoplineno -1 means: don't stop at all 202 self.stoplineno = stoplineno 203 204 # Derived classes and clients can call the following methods 205 # to affect the stepping state. 206 207 def set_until(self, frame, lineno=None): 208 """Stop when the line with the line no greater than the current one is 209 reached or when returning from current frame""" 210 # the name "until" is borrowed from gdb 211 if lineno is None: 212 lineno = frame.f_lineno + 1 213 self._set_stopinfo(frame, frame, lineno) 214 215 def set_step(self): 216 """Stop after one line of code.""" 217 # Issue #13183: pdb skips frames after hitting a breakpoint and running 218 # step commands. 219 # Restore the trace function in the caller (that may not have been set 220 # for performance reasons) when returning from the current frame. 221 if self.frame_returning: 222 caller_frame = self.frame_returning.f_back 223 if caller_frame and not caller_frame.f_trace: 224 caller_frame.f_trace = self.trace_dispatch 225 self._set_stopinfo(None, None) 226 227 def set_next(self, frame): 228 """Stop on the next line in or below the given frame.""" 229 self._set_stopinfo(frame, None) 230 231 def set_return(self, frame): 232 """Stop when returning from the given frame.""" 233 if frame.f_code.co_flags & CO_GENERATOR: 234 self._set_stopinfo(frame, None, -1) 235 else: 236 self._set_stopinfo(frame.f_back, frame) 237 238 def set_trace(self, frame=None): 239 """Start debugging from `frame`. 240 241 If frame is not specified, debugging starts from caller's frame. 242 """ 243 if frame is None: 244 frame = sys._getframe().f_back 245 self.reset() 246 while frame: 247 frame.f_trace = self.trace_dispatch 248 self.botframe = frame 249 frame = frame.f_back 250 self.set_step() 251 sys.settrace(self.trace_dispatch) 252 253 def set_continue(self): 254 # Don't stop except at breakpoints or when finished 255 self._set_stopinfo(self.botframe, None, -1) 256 if not self.breaks: 257 # no breakpoints; run without debugger overhead 258 sys.settrace(None) 259 frame = sys._getframe().f_back 260 while frame and frame is not self.botframe: 261 del frame.f_trace 262 frame = frame.f_back 263 264 def set_quit(self): 265 self.stopframe = self.botframe 266 self.returnframe = None 267 self.quitting = True 268 sys.settrace(None) 269 270 # Derived classes and clients can call the following methods 271 # to manipulate breakpoints. These methods return an 272 # error message is something went wrong, None if all is well. 273 # Set_break prints out the breakpoint line and file:lineno. 274 # Call self.get_*break*() to see the breakpoints or better 275 # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint(). 276 277 def set_break(self, filename, lineno, temporary=False, cond=None, 278 funcname=None): 279 filename = self.canonic(filename) 280 import linecache # Import as late as possible 281 line = linecache.getline(filename, lineno) 282 if not line: 283 return 'Line %s:%d does not exist' % (filename, lineno) 284 list = self.breaks.setdefault(filename, []) 285 if lineno not in list: 286 list.append(lineno) 287 bp = Breakpoint(filename, lineno, temporary, cond, funcname) 288 289 def _prune_breaks(self, filename, lineno): 290 if (filename, lineno) not in Breakpoint.bplist: 291 self.breaks[filename].remove(lineno) 292 if not self.breaks[filename]: 293 del self.breaks[filename] 294 295 def clear_break(self, filename, lineno): 296 filename = self.canonic(filename) 297 if filename not in self.breaks: 298 return 'There are no breakpoints in %s' % filename 299 if lineno not in self.breaks[filename]: 300 return 'There is no breakpoint at %s:%d' % (filename, lineno) 301 # If there's only one bp in the list for that file,line 302 # pair, then remove the breaks entry 303 for bp in Breakpoint.bplist[filename, lineno][:]: 304 bp.deleteMe() 305 self._prune_breaks(filename, lineno) 306 307 def clear_bpbynumber(self, arg): 308 try: 309 bp = self.get_bpbynumber(arg) 310 except ValueError as err: 311 return str(err) 312 bp.deleteMe() 313 self._prune_breaks(bp.file, bp.line) 314 315 def clear_all_file_breaks(self, filename): 316 filename = self.canonic(filename) 317 if filename not in self.breaks: 318 return 'There are no breakpoints in %s' % filename 319 for line in self.breaks[filename]: 320 blist = Breakpoint.bplist[filename, line] 321 for bp in blist: 322 bp.deleteMe() 323 del self.breaks[filename] 324 325 def clear_all_breaks(self): 326 if not self.breaks: 327 return 'There are no breakpoints' 328 for bp in Breakpoint.bpbynumber: 329 if bp: 330 bp.deleteMe() 331 self.breaks = {} 332 333 def get_bpbynumber(self, arg): 334 if not arg: 335 raise ValueError('Breakpoint number expected') 336 try: 337 number = int(arg) 338 except ValueError: 339 raise ValueError('Non-numeric breakpoint number %s' % arg) 340 try: 341 bp = Breakpoint.bpbynumber[number] 342 except IndexError: 343 raise ValueError('Breakpoint number %d out of range' % number) 344 if bp is None: 345 raise ValueError('Breakpoint %d already deleted' % number) 346 return bp 347 348 def get_break(self, filename, lineno): 349 filename = self.canonic(filename) 350 return filename in self.breaks and \ 351 lineno in self.breaks[filename] 352 353 def get_breaks(self, filename, lineno): 354 filename = self.canonic(filename) 355 return filename in self.breaks and \ 356 lineno in self.breaks[filename] and \ 357 Breakpoint.bplist[filename, lineno] or [] 358 359 def get_file_breaks(self, filename): 360 filename = self.canonic(filename) 361 if filename in self.breaks: 362 return self.breaks[filename] 363 else: 364 return [] 365 366 def get_all_breaks(self): 367 return self.breaks 368 369 # Derived classes and clients can call the following method 370 # to get a data structure representing a stack trace. 371 372 def get_stack(self, f, t): 373 stack = [] 374 if t and t.tb_frame is f: 375 t = t.tb_next 376 while f is not None: 377 stack.append((f, f.f_lineno)) 378 if f is self.botframe: 379 break 380 f = f.f_back 381 stack.reverse() 382 i = max(0, len(stack) - 1) 383 while t is not None: 384 stack.append((t.tb_frame, t.tb_lineno)) 385 t = t.tb_next 386 if f is None: 387 i = max(0, len(stack) - 1) 388 return stack, i 389 390 def format_stack_entry(self, frame_lineno, lprefix=': '): 391 import linecache, reprlib 392 frame, lineno = frame_lineno 393 filename = self.canonic(frame.f_code.co_filename) 394 s = '%s(%r)' % (filename, lineno) 395 if frame.f_code.co_name: 396 s += frame.f_code.co_name 397 else: 398 s += "<lambda>" 399 if '__args__' in frame.f_locals: 400 args = frame.f_locals['__args__'] 401 else: 402 args = None 403 if args: 404 s += reprlib.repr(args) 405 else: 406 s += '()' 407 if '__return__' in frame.f_locals: 408 rv = frame.f_locals['__return__'] 409 s += '->' 410 s += reprlib.repr(rv) 411 line = linecache.getline(filename, lineno, frame.f_globals) 412 if line: 413 s += lprefix + line.strip() 414 return s 415 416 # The following methods can be called by clients to use 417 # a debugger to debug a statement or an expression. 418 # Both can be given as a string, or a code object. 419 420 def run(self, cmd, globals=None, locals=None): 421 if globals is None: 422 import __main__ 423 globals = __main__.__dict__ 424 if locals is None: 425 locals = globals 426 self.reset() 427 if isinstance(cmd, str): 428 cmd = compile(cmd, "<string>", "exec") 429 sys.settrace(self.trace_dispatch) 430 try: 431 exec(cmd, globals, locals) 432 except BdbQuit: 433 pass 434 finally: 435 self.quitting = True 436 sys.settrace(None) 437 438 def runeval(self, expr, globals=None, locals=None): 439 if globals is None: 440 import __main__ 441 globals = __main__.__dict__ 442 if locals is None: 443 locals = globals 444 self.reset() 445 sys.settrace(self.trace_dispatch) 446 try: 447 return eval(expr, globals, locals) 448 except BdbQuit: 449 pass 450 finally: 451 self.quitting = True 452 sys.settrace(None) 453 454 def runctx(self, cmd, globals, locals): 455 # B/W compatibility 456 self.run(cmd, globals, locals) 457 458 # This method is more useful to debug a single function call. 459 460 def runcall(self, func, *args, **kwds): 461 self.reset() 462 sys.settrace(self.trace_dispatch) 463 res = None 464 try: 465 res = func(*args, **kwds) 466 except BdbQuit: 467 pass 468 finally: 469 self.quitting = True 470 sys.settrace(None) 471 return res 472 473 474 def set_trace(): 475 Bdb().set_trace() 476 477 478 class Breakpoint: 479 """Breakpoint class. 480 481 Implements temporary breakpoints, ignore counts, disabling and 482 (re)-enabling, and conditionals. 483 484 Breakpoints are indexed by number through bpbynumber and by 485 the file,line tuple using bplist. The former points to a 486 single instance of class Breakpoint. The latter points to a 487 list of such instances since there may be more than one 488 breakpoint per line. 489 490 """ 491 492 # XXX Keeping state in the class is a mistake -- this means 493 # you cannot have more than one active Bdb instance. 494 495 next = 1 # Next bp to be assigned 496 bplist = {} # indexed by (file, lineno) tuple 497 bpbynumber = [None] # Each entry is None or an instance of Bpt 498 # index 0 is unused, except for marking an 499 # effective break .... see effective() 500 501 def __init__(self, file, line, temporary=False, cond=None, funcname=None): 502 self.funcname = funcname 503 # Needed if funcname is not None. 504 self.func_first_executable_line = None 505 self.file = file # This better be in canonical form! 506 self.line = line 507 self.temporary = temporary 508 self.cond = cond 509 self.enabled = True 510 self.ignore = 0 511 self.hits = 0 512 self.number = Breakpoint.next 513 Breakpoint.next += 1 514 # Build the two lists 515 self.bpbynumber.append(self) 516 if (file, line) in self.bplist: 517 self.bplist[file, line].append(self) 518 else: 519 self.bplist[file, line] = [self] 520 521 def deleteMe(self): 522 index = (self.file, self.line) 523 self.bpbynumber[self.number] = None # No longer in list 524 self.bplist[index].remove(self) 525 if not self.bplist[index]: 526 # No more bp for this f:l combo 527 del self.bplist[index] 528 529 def enable(self): 530 self.enabled = True 531 532 def disable(self): 533 self.enabled = False 534 535 def bpprint(self, out=None): 536 if out is None: 537 out = sys.stdout 538 print(self.bpformat(), file=out) 539 540 def bpformat(self): 541 if self.temporary: 542 disp = 'del ' 543 else: 544 disp = 'keep ' 545 if self.enabled: 546 disp = disp + 'yes ' 547 else: 548 disp = disp + 'no ' 549 ret = '%-4dbreakpoint %s at %s:%d' % (self.number, disp, 550 self.file, self.line) 551 if self.cond: 552 ret += '\n\tstop only if %s' % (self.cond,) 553 if self.ignore: 554 ret += '\n\tignore next %d hits' % (self.ignore,) 555 if self.hits: 556 if self.hits > 1: 557 ss = 's' 558 else: 559 ss = '' 560 ret += '\n\tbreakpoint already hit %d time%s' % (self.hits, ss) 561 return ret 562 563 def __str__(self): 564 return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line) 565 566 # -----------end of Breakpoint class---------- 567 568 def checkfuncname(b, frame): 569 """Check whether we should break here because of `b.funcname`.""" 570 if not b.funcname: 571 # Breakpoint was set via line number. 572 if b.line != frame.f_lineno: 573 # Breakpoint was set at a line with a def statement and the function 574 # defined is called: don't break. 575 return False 576 return True 577 578 # Breakpoint set via function name. 579 580 if frame.f_code.co_name != b.funcname: 581 # It's not a function call, but rather execution of def statement. 582 return False 583 584 # We are in the right frame. 585 if not b.func_first_executable_line: 586 # The function is entered for the 1st time. 587 b.func_first_executable_line = frame.f_lineno 588 589 if b.func_first_executable_line != frame.f_lineno: 590 # But we are not at the first line number: don't break. 591 return False 592 return True 593 594 # Determines if there is an effective (active) breakpoint at this 595 # line of code. Returns breakpoint number or 0 if none 596 def effective(file, line, frame): 597 """Determine which breakpoint for this file:line is to be acted upon. 598 599 Called only if we know there is a bpt at this 600 location. Returns breakpoint that was triggered and a flag 601 that indicates if it is ok to delete a temporary bp. 602 603 """ 604 possibles = Breakpoint.bplist[file, line] 605 for b in possibles: 606 if not b.enabled: 607 continue 608 if not checkfuncname(b, frame): 609 continue 610 # Count every hit when bp is enabled 611 b.hits += 1 612 if not b.cond: 613 # If unconditional, and ignoring go on to next, else break 614 if b.ignore > 0: 615 b.ignore -= 1 616 continue 617 else: 618 # breakpoint and marker that it's ok to delete if temporary 619 return (b, True) 620 else: 621 # Conditional bp. 622 # Ignore count applies only to those bpt hits where the 623 # condition evaluates to true. 624 try: 625 val = eval(b.cond, frame.f_globals, frame.f_locals) 626 if val: 627 if b.ignore > 0: 628 b.ignore -= 1 629 # continue 630 else: 631 return (b, True) 632 # else: 633 # continue 634 except: 635 # if eval fails, most conservative thing is to stop on 636 # breakpoint regardless of ignore count. Don't delete 637 # temporary, as another hint to user. 638 return (b, False) 639 return (None, None) 640 641 642 # -------------------- testing -------------------- 643 644 class Tdb(Bdb): 645 def user_call(self, frame, args): 646 name = frame.f_code.co_name 647 if not name: name = '???' 648 print('+++ call', name, args) 649 def user_line(self, frame): 650 import linecache 651 name = frame.f_code.co_name 652 if not name: name = '???' 653 fn = self.canonic(frame.f_code.co_filename) 654 line = linecache.getline(fn, frame.f_lineno, frame.f_globals) 655 print('+++', fn, frame.f_lineno, name, ':', line.strip()) 656 def user_return(self, frame, retval): 657 print('+++ return', retval) 658 def user_exception(self, frame, exc_stuff): 659 print('+++ exception', exc_stuff) 660 self.set_continue() 661 662 def foo(n): 663 print('foo(', n, ')') 664 x = bar(n*10) 665 print('bar returned', x) 666 667 def bar(a): 668 print('bar(', a, ')') 669 return a/2 670 671 def test(): 672 t = Tdb() 673 t.run('import bdb; bdb.foo(10)') 674