bdb.py revision 54f0222547b1e92cd018ef132307a6f793dc9505
1"""Debugger basics""" 2 3import sys 4import os 5import types 6 7__all__ = ["BdbQuit","Bdb","Breakpoint"] 8 9class BdbQuit(Exception): 10 """Exception to give up completely""" 11 12 13class Bdb: 14 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): 23 self.breaks = {} 24 self.fncache = {} 25 26 def canonic(self, filename): 27 if filename == "<" + filename[1:-1] + ">": 28 return filename 29 canonic = self.fncache.get(filename) 30 if not canonic: 31 canonic = os.path.abspath(filename) 32 canonic = os.path.normcase(canonic) 33 self.fncache[filename] = canonic 34 return canonic 35 36 def reset(self): 37 import linecache 38 linecache.checkcache() 39 self.botframe = None 40 self.stopframe = None 41 self.returnframe = None 42 self.quitting = 0 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 print 'bdb.Bdb.dispatch: unknown debugging event:', `event` 56 return self.trace_dispatch 57 58 def dispatch_line(self, frame): 59 if self.stop_here(frame) or self.break_here(frame): 60 self.user_line(frame) 61 if self.quitting: raise BdbQuit 62 return self.trace_dispatch 63 64 def dispatch_call(self, frame, arg): 65 # XXX 'arg' is no longer used 66 if self.botframe is None: 67 # First call of dispatch since reset() 68 self.botframe = frame.f_back # (CT) Note that this may also be None! 69 return self.trace_dispatch 70 if not (self.stop_here(frame) or self.break_anywhere(frame)): 71 # No need to trace this function 72 return # None 73 self.user_call(frame, arg) 74 if self.quitting: raise BdbQuit 75 return self.trace_dispatch 76 77 def dispatch_return(self, frame, arg): 78 if self.stop_here(frame) or frame == self.returnframe: 79 self.user_return(frame, arg) 80 if self.quitting: raise BdbQuit 81 return self.trace_dispatch 82 83 def dispatch_exception(self, frame, arg): 84 if self.stop_here(frame): 85 self.user_exception(frame, arg) 86 if self.quitting: raise BdbQuit 87 return self.trace_dispatch 88 89 # Normally derived classes don't override the following 90 # methods, but they may if they want to redefine the 91 # definition of stopping and breakpoints. 92 93 def stop_here(self, frame): 94 # (CT) stopframe may now also be None, see dispatch_call. 95 # (CT) the former test for None is therefore removed from here. 96 if frame is self.stopframe: 97 return True 98 while frame is not None and frame is not self.stopframe: 99 if frame is self.botframe: 100 return True 101 frame = frame.f_back 102 return False 103 104 def break_here(self, frame): 105 filename = self.canonic(frame.f_code.co_filename) 106 if not filename in self.breaks: 107 return False 108 lineno = frame.f_lineno 109 if not lineno in self.breaks[filename]: 110 return False 111 # flag says ok to delete temp. bp 112 (bp, flag) = effective(filename, lineno, frame) 113 if bp: 114 self.currentbp = bp.number 115 if (flag and bp.temporary): 116 self.do_clear(str(bp.number)) 117 return True 118 else: 119 return False 120 121 def do_clear(self, arg): 122 raise NotImplementedError, "subclass of bdb must implement do_clear()" 123 124 def break_anywhere(self, frame): 125 return self.breaks.has_key( 126 self.canonic(frame.f_code.co_filename)) 127 128 # Derived classes should override the user_* methods 129 # to gain control. 130 131 def user_call(self, frame, argument_list): 132 """This method is called when there is the remote possibility 133 that we ever need to stop in this function.""" 134 pass 135 136 def user_line(self, frame): 137 """This method is called when we stop or break at this line.""" 138 pass 139 140 def user_return(self, frame, return_value): 141 """This method is called when a return trap is set here.""" 142 pass 143 144 def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): 145 """This method is called if an exception occurs, 146 but only if we are to stop at or just below this level.""" 147 pass 148 149 # Derived classes and clients can call the following methods 150 # to affect the stepping state. 151 152 def set_step(self): 153 """Stop after one line of code.""" 154 self.stopframe = None 155 self.returnframe = None 156 self.quitting = 0 157 158 def set_next(self, frame): 159 """Stop on the next line in or below the given frame.""" 160 self.stopframe = frame 161 self.returnframe = None 162 self.quitting = 0 163 164 def set_return(self, frame): 165 """Stop when returning from the given frame.""" 166 self.stopframe = frame.f_back 167 self.returnframe = frame 168 self.quitting = 0 169 170 def set_trace(self): 171 """Start debugging from here.""" 172 frame = sys._getframe().f_back 173 self.reset() 174 while frame: 175 frame.f_trace = self.trace_dispatch 176 self.botframe = frame 177 frame = frame.f_back 178 self.set_step() 179 sys.settrace(self.trace_dispatch) 180 181 def set_continue(self): 182 # Don't stop except at breakpoints or when finished 183 self.stopframe = self.botframe 184 self.returnframe = None 185 self.quitting = 0 186 if not self.breaks: 187 # no breakpoints; run without debugger overhead 188 sys.settrace(None) 189 frame = sys._getframe().f_back 190 while frame and frame is not self.botframe: 191 del frame.f_trace 192 frame = frame.f_back 193 194 def set_quit(self): 195 self.stopframe = self.botframe 196 self.returnframe = None 197 self.quitting = 1 198 sys.settrace(None) 199 200 # Derived classes and clients can call the following methods 201 # to manipulate breakpoints. These methods return an 202 # error message is something went wrong, None if all is well. 203 # Set_break prints out the breakpoint line and file:lineno. 204 # Call self.get_*break*() to see the breakpoints or better 205 # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint(). 206 207 def set_break(self, filename, lineno, temporary=0, cond = None): 208 filename = self.canonic(filename) 209 import linecache # Import as late as possible 210 line = linecache.getline(filename, lineno) 211 if not line: 212 return 'Line %s:%d does not exist' % (filename, 213 lineno) 214 if not filename in self.breaks: 215 self.breaks[filename] = [] 216 list = self.breaks[filename] 217 if not lineno in list: 218 list.append(lineno) 219 bp = Breakpoint(filename, lineno, temporary, cond) 220 221 def clear_break(self, filename, lineno): 222 filename = self.canonic(filename) 223 if not filename in self.breaks: 224 return 'There are no breakpoints in %s' % filename 225 if lineno not in self.breaks[filename]: 226 return 'There is no breakpoint at %s:%d' % (filename, 227 lineno) 228 # If there's only one bp in the list for that file,line 229 # pair, then remove the breaks entry 230 for bp in Breakpoint.bplist[filename, lineno][:]: 231 bp.deleteMe() 232 if not Breakpoint.bplist.has_key((filename, lineno)): 233 self.breaks[filename].remove(lineno) 234 if not self.breaks[filename]: 235 del self.breaks[filename] 236 237 def clear_bpbynumber(self, arg): 238 try: 239 number = int(arg) 240 except: 241 return 'Non-numeric breakpoint number (%s)' % arg 242 try: 243 bp = Breakpoint.bpbynumber[number] 244 except IndexError: 245 return 'Breakpoint number (%d) out of range' % number 246 if not bp: 247 return 'Breakpoint (%d) already deleted' % number 248 self.clear_break(bp.file, bp.line) 249 250 def clear_all_file_breaks(self, filename): 251 filename = self.canonic(filename) 252 if not filename in self.breaks: 253 return 'There are no breakpoints in %s' % filename 254 for line in self.breaks[filename]: 255 blist = Breakpoint.bplist[filename, line] 256 for bp in blist: 257 bp.deleteMe() 258 del self.breaks[filename] 259 260 def clear_all_breaks(self): 261 if not self.breaks: 262 return 'There are no breakpoints' 263 for bp in Breakpoint.bpbynumber: 264 if bp: 265 bp.deleteMe() 266 self.breaks = {} 267 268 def get_break(self, filename, lineno): 269 filename = self.canonic(filename) 270 return filename in self.breaks and \ 271 lineno in self.breaks[filename] 272 273 def get_breaks(self, filename, lineno): 274 filename = self.canonic(filename) 275 return filename in self.breaks and \ 276 lineno in self.breaks[filename] and \ 277 Breakpoint.bplist[filename, lineno] or [] 278 279 def get_file_breaks(self, filename): 280 filename = self.canonic(filename) 281 if filename in self.breaks: 282 return self.breaks[filename] 283 else: 284 return [] 285 286 def get_all_breaks(self): 287 return self.breaks 288 289 # Derived classes and clients can call the following method 290 # to get a data structure representing a stack trace. 291 292 def get_stack(self, f, t): 293 stack = [] 294 if t and t.tb_frame is f: 295 t = t.tb_next 296 while f is not None: 297 stack.append((f, f.f_lineno)) 298 if f is self.botframe: 299 break 300 f = f.f_back 301 stack.reverse() 302 i = max(0, len(stack) - 1) 303 while t is not None: 304 stack.append((t.tb_frame, t.tb_lineno)) 305 t = t.tb_next 306 return stack, i 307 308 # 309 310 def format_stack_entry(self, frame_lineno, lprefix=': '): 311 import linecache, repr 312 frame, lineno = frame_lineno 313 filename = self.canonic(frame.f_code.co_filename) 314 s = filename + '(' + `lineno` + ')' 315 if frame.f_code.co_name: 316 s = s + frame.f_code.co_name 317 else: 318 s = s + "<lambda>" 319 if '__args__' in frame.f_locals: 320 args = frame.f_locals['__args__'] 321 else: 322 args = None 323 if args: 324 s = s + repr.repr(args) 325 else: 326 s = s + '()' 327 if '__return__' in frame.f_locals: 328 rv = frame.f_locals['__return__'] 329 s = s + '->' 330 s = s + repr.repr(rv) 331 line = linecache.getline(filename, lineno) 332 if line: s = s + lprefix + line.strip() 333 return s 334 335 # The following two methods can be called by clients to use 336 # a debugger to debug a statement, given as a string. 337 338 def run(self, cmd, globals=None, locals=None): 339 if globals is None: 340 import __main__ 341 globals = __main__.__dict__ 342 if locals is None: 343 locals = globals 344 self.reset() 345 sys.settrace(self.trace_dispatch) 346 if not isinstance(cmd, types.CodeType): 347 cmd = cmd+'\n' 348 try: 349 try: 350 exec cmd in globals, locals 351 except BdbQuit: 352 pass 353 finally: 354 self.quitting = 1 355 sys.settrace(None) 356 357 def runeval(self, expr, globals=None, locals=None): 358 if globals is None: 359 import __main__ 360 globals = __main__.__dict__ 361 if locals is None: 362 locals = globals 363 self.reset() 364 sys.settrace(self.trace_dispatch) 365 if not isinstance(expr, types.CodeType): 366 expr = expr+'\n' 367 try: 368 try: 369 return eval(expr, globals, locals) 370 except BdbQuit: 371 pass 372 finally: 373 self.quitting = 1 374 sys.settrace(None) 375 376 def runctx(self, cmd, globals, locals): 377 # B/W compatibility 378 self.run(cmd, globals, locals) 379 380 # This method is more useful to debug a single function call. 381 382 def runcall(self, func, *args): 383 self.reset() 384 sys.settrace(self.trace_dispatch) 385 res = None 386 try: 387 try: 388 res = apply(func, args) 389 except BdbQuit: 390 pass 391 finally: 392 self.quitting = 1 393 sys.settrace(None) 394 return res 395 396 397def set_trace(): 398 Bdb().set_trace() 399 400 401class Breakpoint: 402 403 """Breakpoint class 404 405 Implements temporary breakpoints, ignore counts, disabling and 406 (re)-enabling, and conditionals. 407 408 Breakpoints are indexed by number through bpbynumber and by 409 the file,line tuple using bplist. The former points to a 410 single instance of class Breakpoint. The latter points to a 411 list of such instances since there may be more than one 412 breakpoint per line. 413 414 """ 415 416 # XXX Keeping state in the class is a mistake -- this means 417 # you cannot have more than one active Bdb instance. 418 419 next = 1 # Next bp to be assigned 420 bplist = {} # indexed by (file, lineno) tuple 421 bpbynumber = [None] # Each entry is None or an instance of Bpt 422 # index 0 is unused, except for marking an 423 # effective break .... see effective() 424 425 def __init__(self, file, line, temporary=0, cond = None): 426 self.file = file # This better be in canonical form! 427 self.line = line 428 self.temporary = temporary 429 self.cond = cond 430 self.enabled = 1 431 self.ignore = 0 432 self.hits = 0 433 self.number = Breakpoint.next 434 Breakpoint.next = Breakpoint.next + 1 435 # Build the two lists 436 self.bpbynumber.append(self) 437 if self.bplist.has_key((file, line)): 438 self.bplist[file, line].append(self) 439 else: 440 self.bplist[file, line] = [self] 441 442 443 def deleteMe(self): 444 index = (self.file, self.line) 445 self.bpbynumber[self.number] = None # No longer in list 446 self.bplist[index].remove(self) 447 if not self.bplist[index]: 448 # No more bp for this f:l combo 449 del self.bplist[index] 450 451 def enable(self): 452 self.enabled = 1 453 454 def disable(self): 455 self.enabled = 0 456 457 def bpprint(self): 458 if self.temporary: 459 disp = 'del ' 460 else: 461 disp = 'keep ' 462 if self.enabled: 463 disp = disp + 'yes' 464 else: 465 disp = disp + 'no ' 466 print '%-4dbreakpoint %s at %s:%d' % (self.number, disp, 467 self.file, self.line) 468 if self.cond: 469 print '\tstop only if %s' % (self.cond,) 470 if self.ignore: 471 print '\tignore next %d hits' % (self.ignore) 472 if (self.hits): 473 if (self.hits > 1): ss = 's' 474 else: ss = '' 475 print ('\tbreakpoint already hit %d time%s' % 476 (self.hits, ss)) 477 478# -----------end of Breakpoint class---------- 479 480# Determines if there is an effective (active) breakpoint at this 481# line of code. Returns breakpoint number or 0 if none 482def effective(file, line, frame): 483 """Determine which breakpoint for this file:line is to be acted upon. 484 485 Called only if we know there is a bpt at this 486 location. Returns breakpoint that was triggered and a flag 487 that indicates if it is ok to delete a temporary bp. 488 489 """ 490 possibles = Breakpoint.bplist[file,line] 491 for i in range(0, len(possibles)): 492 b = possibles[i] 493 if b.enabled == 0: 494 continue 495 # Count every hit when bp is enabled 496 b.hits = b.hits + 1 497 if not b.cond: 498 # If unconditional, and ignoring, 499 # go on to next, else break 500 if b.ignore > 0: 501 b.ignore = b.ignore -1 502 continue 503 else: 504 # breakpoint and marker that's ok 505 # to delete if temporary 506 return (b,1) 507 else: 508 # Conditional bp. 509 # Ignore count applies only to those bpt hits where the 510 # condition evaluates to true. 511 try: 512 val = eval(b.cond, frame.f_globals, 513 frame.f_locals) 514 if val: 515 if b.ignore > 0: 516 b.ignore = b.ignore -1 517 # continue 518 else: 519 return (b,1) 520 # else: 521 # continue 522 except: 523 # if eval fails, most conservative 524 # thing is to stop on breakpoint 525 # regardless of ignore count. 526 # Don't delete temporary, 527 # as another hint to user. 528 return (b,0) 529 return (None, None) 530 531# -------------------- testing -------------------- 532 533class Tdb(Bdb): 534 def user_call(self, frame, args): 535 name = frame.f_code.co_name 536 if not name: name = '???' 537 print '+++ call', name, args 538 def user_line(self, frame): 539 import linecache 540 name = frame.f_code.co_name 541 if not name: name = '???' 542 fn = self.canonic(frame.f_code.co_filename) 543 line = linecache.getline(fn, frame.f_lineno) 544 print '+++', fn, frame.f_lineno, name, ':', line.strip() 545 def user_return(self, frame, retval): 546 print '+++ return', retval 547 def user_exception(self, frame, exc_stuff): 548 print '+++ exception', exc_stuff 549 self.set_continue() 550 551def foo(n): 552 print 'foo(', n, ')' 553 x = bar(n*10) 554 print 'bar returned', x 555 556def bar(a): 557 print 'bar(', a, ')' 558 return a/2 559 560def test(): 561 t = Tdb() 562 t.run('import bdb; bdb.foo(10)') 563 564# end 565