# -*- coding: utf-8 -*- # -*- coding: cp1252 -*- """A Python debugger.""" # (See pdb.doc for documentation.) import sys import linecache import bdb from repr import Repr import os import re import pprint import pglobal # Create a custom safe Repr instance and increase its maxstring. # The default of 30 truncates error messages too easily. _repr = Repr() _repr.maxstring = 200 _saferepr = _repr.repr __all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", "post_mortem", "help"] def uniw32dia(suni, titre): import ctypes MessageBoxW = ctypes.windll.user32.MessageBoxW MessageBoxW.argtypes = ctypes.c_int, ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_int """OKONLY = &H0& OKCANCEL = &H1& ABORTRETRYIGNORE = &H2& YESNOCANCEL = &H3& YESNO = &H4& CRITICAL = &H10& QUESTION = &H20& EXCLAMATION = &H30& INFORMATION = &H40& """ return MessageBoxW(0, suni, titre, 4) #6 = OUI 7 = NON def find_function(funcname, filename): cre = re.compile(r'def\s+%s\s*[(]' % funcname) try: fp = open(filename) except IOError: return None # consumer of this info expects the first line to be 1 lineno = 1 answer = None while 1: line = fp.readline() if line == '': break if cre.match(line): answer = funcname, filename, lineno break lineno = lineno + 1 fp.close() return answer # Interaction prompt line will separate file and call info from code # text using value of line_prefix string. A newline and arrow may # be to your liking. You can set it once pdb is imported using the # command "pdb.line_prefix = '\n% '". # line_prefix = ': ' # Use this to get the old situation back line_prefix = '\n-> ' # Probably a better default class Pdb(bdb.Bdb): def __init__(self): bdb.Bdb.__init__(self) self.prompt = '(debug) ' self.aliases = {} # Try to load readline if it exists try: import readline except ImportError: pass # Read $HOME/.pdbrc and ./.pdbrc self.rcLines = [] if 'HOME' in os.environ: envHome = os.environ['HOME'] try: rcFile = open(os.path.join(envHome, ".pdbrc")) except IOError: pass else: for line in rcFile.readlines(): self.rcLines.append(line) rcFile.close() try: rcFile = open(".pdbrc") except IOError: pass else: for line in rcFile.readlines(): self.rcLines.append(line) rcFile.close() def reset(self): bdb.Bdb.reset(self) self.forget() def forget(self): self.lineno = None self.stack = [] self.curindex = 0 self.curframe = None def setup(self, f, t): self.forget() self.stack, self.curindex = self.get_stack(f, t) self.curframe = self.stack[self.curindex][0] self.execRcLines() # Can be executed earlier than 'setup' if desired def execRcLines(self): if self.rcLines: # Make local copy because of recursion rcLines = self.rcLines # executed only once self.rcLines = [] for line in rcLines: line = line[:-1] if len(line) > 0 and line[0] != '#': self.onecmd(line) # Override Bdb methods def user_call(self, frame, argument_list): #appel(call) d'une fonction """This method is called when there is the remote possibility that we ever need to stop in this function.""" if self.stop_here(frame): #print '--Call--' uniw32dia('*** Call ***\r\n','Fonction : '+frame.f_code.co_name+' ') print 'co_nlocals : ',frame.f_code.co_nlocals print dir(frame.f_code) print '-'*30 #self.interaction(frame, None) def user_line(self, frame): """This function is called when we stop or break at this line.""" #print frame.f_locals #print frame.f_globals #print frame.f_lasti #print dir(frame.f_back) #print frame.f_code #adresse fonction + ligne debut de la fonction #print frame.f_code.co_filename #print frame.f_code.co_names #noms des objets internes ? #print frame.f_code.co_firstlineno # premiere ligne de la fonction ? #print frame.f_code.co_consts # parametres ? #print frame.f_code.co_name # nom de la fonction courante #print frame.f_code.co_nlocals # import linecache name = frame.f_code.co_name if not name: name = '???' fn = self.canonic(frame.f_code.co_filename) # nom du fichier (e.g. ess.py) if fn=='': line = pglobal.GlobalSource[frame.f_lineno-1].strip() else: line = linecache.getline(fn, frame.f_lineno) svarl='' nb=0 for item in frame.f_locals.keys(): nb+=1 if nb<10: svarl=svarl+item+' ('+str(type(frame.f_locals[item]))+'): '+str(frame.f_locals[item])+'\r\n' vretour = uniw32dia(svarl+'\r\n'+line.strip() ,'Fonction : '+name+' ; ligne '+str(frame.f_lineno)+' ') if vretour==7: self.set_continue() #self.interaction(frame, None) def user_return(self, frame, return_value): """This function is called when a return trap is set here.""" frame.f_locals['__return__'] = return_value #print '--Return--' uniw32dia('*** Return ***\r\n' ,'Fonction : '+frame.f_code.co_name+' ') #self.interaction(frame, None) def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): """This function is called if an exception occurs, but only if we are to stop at or just below this level.""" frame.f_locals['__exception__'] = exc_type, exc_value if type(exc_type) == type(''): exc_type_name = exc_type else: exc_type_name = exc_type.__name__ print exc_type_name + ':', _saferepr(exc_value) self.interaction(frame, exc_traceback) # General interaction function def default(self, line): if line[:1] == '!': line = line[1:] locals = self.curframe.f_locals globals = self.curframe.f_globals try: code = compile(line + '\n', '', 'single') exec code in globals, locals except: t, v = sys.exc_info()[:2] if type(t) == type(''): exc_type_name = t else: exc_type_name = t.__name__ print '***', exc_type_name + ':', v def precmd(self, line): """Handle alias expansion and ';;' separator.""" if not line.strip(): return line args = line.split() while args[0] in self.aliases: line = self.aliases[args[0]] ii=1 for tmpArg in args[1:] : line=line.replace("%" + str(ii),tmpArg) ii = ii + 1 line = line.replace("%*", ' '.join(args[1:])) args = line.split() # split into ';;' separated commands # unless it's an alias command if args[0] != 'alias': marker = line.find(';;') if marker >= 0: # queue up everything after marker next = line[marker+2:].lstrip() self.cmdqueue.append(next) line = line[:marker].rstrip() return line # Command definitions, called by cmdloop() # The argument is the remaining string on the command line # Return true to exit from the command loop def do_break(self, arg, temporary = 0): # break [ ([filename:]lineno | function) [, "condition"] ] if not arg: if self.breaks: # There's at least one print "Num Type Disp Enb Where" for bp in bdb.Breakpoint.bpbynumber: if bp: bp.bpprint() return # parse arguments; comma has lowest precedence # and cannot occur in filename filename = None lineno = None cond = None comma = arg.find(',') if comma > 0: # parse stuff after comma: "condition" cond = arg[comma+1:].lstrip() arg = arg[:comma].rstrip() # parse stuff before comma: [filename:]lineno | function colon = arg.rfind(':') if colon >= 0: filename = arg[:colon].rstrip() f = self.lookupmodule(filename) if not f: print '*** ', `filename`, print 'not found from sys.path' return else: filename = f arg = arg[colon+1:].lstrip() try: lineno = int(arg) except ValueError, msg: print '*** Bad lineno:', arg return else: # no colon; can be lineno or function try: lineno = int(arg) except ValueError: try: func = eval(arg, self.curframe.f_globals, self.curframe.f_locals) except: func = arg try: if hasattr(func, 'im_func'): func = func.im_func code = func.func_code lineno = code.co_firstlineno filename = code.co_filename except: # last thing to try (ok, filename, ln) = self.lineinfo(arg) if not ok: print '*** The specified object', print `arg`, print 'is not a function' print ('or was not found ' 'along sys.path.') return lineno = int(ln) if not filename: filename = self.defaultFile() # Check for reasonable breakpoint line = self.checkline(filename, lineno) if line: # now set the break point err = self.set_break(filename, line, temporary, cond) if err: print '***', err else: bp = self.get_breaks(filename, line)[-1] print "Breakpoint %d at %s:%d" % (bp.number, bp.file, bp.line) # To be overridden in derived debuggers def defaultFile(self): """Produce a reasonable default.""" filename = self.curframe.f_code.co_filename if filename == '' and mainpyfile: filename = mainpyfile return filename do_b = do_break def do_tbreak(self, arg): self.do_break(arg, 1) def lineinfo(self, identifier): failed = (None, None, None) # Input is identifier, may be in single quotes idstring = identifier.split("'") if len(idstring) == 1: # not in single quotes id = idstring[0].strip() elif len(idstring) == 3: # quoted id = idstring[1].strip() else: return failed if id == '': return failed parts = id.split('.') # Protection for derived debuggers if parts[0] == 'self': del parts[0] if len(parts) == 0: return failed # Best first guess at file to look at fname = self.defaultFile() if len(parts) == 1: item = parts[0] else: #More than one part. #First is module, second is method/class f=self.lookupmodule(parts[0]) if f: fname = f item = parts[1] answer = find_function(item, fname) return answer or failed def checkline(self, filename, lineno): """Return line number of first line at or after input argument such that if the input points to a 'def', the returned line number is the first non-blank/non-comment line to follow. If the input points to a blank or comment line, return 0. At end of file, also return 0.""" line = linecache.getline(filename, lineno) if not line: print'End of file' return 0 line=line.strip() # Don't allow setting breakpoint at a blank line if (not line or (line[0] == '#') or (line[:3] == '"""') or line[:3] == "'''"): print '*** Blank or comment' return 0 # When a file is read in and a breakpoint is at # the 'def' statement, the system stops there at # code parse time. We don't want that, so all breakpoints # set at 'def' statements are moved one line onward if line[:3] == 'def': instr = '' brackets = 0 while 1: skipone = 0 for c in line: if instr: if skipone: skipone = 0 elif c == '\\': skipone = 1 elif c == instr: instr = '' elif c == '#': break elif c in ('"',"'"): instr = c elif c in ('(','{','['): brackets = brackets + 1 elif c in (')','}',']'): brackets = brackets - 1 lineno = lineno+1 line = linecache.getline(filename, lineno) if not line: print 'end of file' return 0 line = line.strip() if not line: continue # Blank line if brackets <= 0 and line[0] not in ('#','"',"'"): break return lineno def do_enable(self, arg): args = arg.split() for i in args: try: i = int(i) except ValueError: print 'Breakpoint index %r is not a number' % i continue if not (0 <= i < len(bdb.Breakpoint.bpbynumber)): print 'No breakpoint numbered', i continue bp = bdb.Breakpoint.bpbynumber[i] if bp: bp.enable() def do_disable(self, arg): args = arg.split() for i in args: try: i = int(i) except ValueError: print 'Breakpoint index %r is not a number' % i continue if not (0 <= i < len(bdb.Breakpoint.bpbynumber)): print 'No breakpoint numbered', i continue bp = bdb.Breakpoint.bpbynumber[i] if bp: bp.disable() def do_condition(self, arg): # arg is breakpoint number and condition args = arg.split(' ', 1) bpnum = int(args[0].strip()) try: cond = args[1] except: cond = None bp = bdb.Breakpoint.bpbynumber[bpnum] if bp: bp.cond = cond if not cond: print 'Breakpoint', bpnum, print 'is now unconditional.' def do_ignore(self,arg): """arg is bp number followed by ignore count.""" args = arg.split() bpnum = int(args[0].strip()) try: count = int(args[1].strip()) except: count = 0 bp = bdb.Breakpoint.bpbynumber[bpnum] if bp: bp.ignore = count if count > 0: reply = 'Will ignore next ' if count > 1: reply = reply + '%d crossings' % count else: reply = reply + '1 crossing' print reply + ' of breakpoint %d.' % bpnum else: print 'Will stop next time breakpoint', print bpnum, 'is reached.' def do_clear(self, arg): """Three possibilities, tried in this order: clear -> clear all breaks, ask for confirmation clear file:lineno -> clear all breaks at file:lineno clear bpno bpno ... -> clear breakpoints by number""" if not arg: try: reply = raw_input('Clear all breaks? ') except EOFError: reply = 'no' reply = reply.strip().lower() if reply in ('y', 'yes'): self.clear_all_breaks() return if ':' in arg: # Make sure it works for "clear C:\foo\bar.py:12" i = arg.rfind(':') filename = arg[:i] arg = arg[i+1:] try: lineno = int(arg) except: err = "Invalid line number (%s)" % arg else: err = self.clear_break(filename, lineno) if err: print '***', err return numberlist = arg.split() for i in numberlist: err = self.clear_bpbynumber(i) if err: print '***', err else: print 'Deleted breakpoint %s ' % (i,) do_cl = do_clear # 'c' is already an abbreviation for 'continue' def do_where(self, arg): self.print_stack_trace() do_w = do_where do_bt = do_where def do_up(self, arg): if self.curindex == 0: print '*** Oldest frame' else: self.curindex = self.curindex - 1 self.curframe = self.stack[self.curindex][0] self.print_stack_entry(self.stack[self.curindex]) self.lineno = None do_u = do_up def do_down(self, arg): if self.curindex + 1 == len(self.stack): print '*** Newest frame' else: self.curindex = self.curindex + 1 self.curframe = self.stack[self.curindex][0] self.print_stack_entry(self.stack[self.curindex]) self.lineno = None do_d = do_down def do_step(self, arg): self.set_step() return 1 do_s = do_step def do_next(self, arg): self.set_next(self.curframe) return 1 do_n = do_next def do_return(self, arg): self.set_return(self.curframe) return 1 do_r = do_return def do_continue(self, arg): self.set_continue() return 1 do_c = do_cont = do_continue def do_jump(self, arg): if self.curindex + 1 != len(self.stack): print "*** You can only jump within the bottom frame" return try: arg = int(arg) except ValueError: print "*** The 'jump' command requires a line number." else: try: # Do the jump, fix up our copy of the stack, and display the # new position self.curframe.f_lineno = arg self.stack[self.curindex] = self.stack[self.curindex][0], arg self.print_stack_entry(self.stack[self.curindex]) except ValueError, e: print '*** Jump failed:', e do_j = do_jump def do_debug(self, arg): #no sys.settrace(None) globals = self.curframe.f_globals locals = self.curframe.f_locals p = Pdb() p.prompt = "(%s) " % self.prompt.strip() print "ENTERING RECURSIVE DEBUGGER" sys.call_tracing(p.run, (arg, globals, locals)) print "LEAVING RECURSIVE DEBUGGER" sys.settrace(self.trace_dispatch) self.lastcmd = p.lastcmd def do_quit(self, arg): #use self.set_quit() return 1 do_q = do_quit do_exit = do_quit def do_EOF(self, arg): print self.set_quit() return 1 def do_args(self, arg): f = self.curframe co = f.f_code dict = f.f_locals n = co.co_argcount if co.co_flags & 4: n = n+1 if co.co_flags & 8: n = n+1 for i in range(n): name = co.co_varnames[i] print name, '=', if name in dict: print dict[name] else: print "*** undefined ***" do_a = do_args def do_retval(self, arg): if '__return__' in self.curframe.f_locals: print self.curframe.f_locals['__return__'] else: print '*** Not yet returned!' do_rv = do_retval def _getval(self, arg): try: return eval(arg, self.curframe.f_globals, self.curframe.f_locals) except: t, v = sys.exc_info()[:2] if isinstance(t, str): exc_type_name = t else: exc_type_name = t.__name__ print '***', exc_type_name + ':', `v` raise def do_p(self, arg): try: print repr(self._getval(arg)) except: pass def do_pp(self, arg): try: pprint.pprint(self._getval(arg)) except: pass def do_list(self, arg): self.lastcmd = 'list' last = None if arg: try: x = eval(arg, {}, {}) if type(x) == type(()): first, last = x first = int(first) last = int(last) if last < first: # Assume it's a count last = first + last else: first = max(1, int(x) - 5) except: print '*** Error in argument:', `arg` return elif self.lineno is None: first = max(1, self.curframe.f_lineno - 5) else: first = self.lineno + 1 if last is None: last = first + 10 filename = self.curframe.f_code.co_filename breaklist = self.get_file_breaks(filename) try: for lineno in range(first, last+1): line = linecache.getline(filename, lineno) if not line: print '[EOF]' break else: s = `lineno`.rjust(3) if len(s) < 4: s = s + ' ' if lineno in breaklist: s = s + 'B' else: s = s + ' ' if lineno == self.curframe.f_lineno: s = s + '->' print s + '\t' + line, self.lineno = lineno except KeyboardInterrupt: pass do_l = do_list def do_whatis(self, arg): try: value = eval(arg, self.curframe.f_globals, self.curframe.f_locals) except: t, v = sys.exc_info()[:2] if type(t) == type(''): exc_type_name = t else: exc_type_name = t.__name__ print '***', exc_type_name + ':', `v` return code = None # Is it a function? try: code = value.func_code except: pass if code: print 'Function', code.co_name return # Is it an instance method? try: code = value.im_func.func_code except: pass if code: print 'Method', code.co_name return # None of the above... print type(value) def do_alias(self, arg): args = arg.split() if len(args) == 0: keys = self.aliases.keys() keys.sort() for alias in keys: print "%s = %s" % (alias, self.aliases[alias]) return if args[0] in self.aliases and len(args) == 1: print "%s = %s" % (args[0], self.aliases[args[0]]) else: self.aliases[args[0]] = ' '.join(args[1:]) def do_unalias(self, arg): args = arg.split() if len(args) == 0: return if args[0] in self.aliases: del self.aliases[args[0]] # Print a traceback starting at the top stack frame. # The most recently entered frame is printed last; # this is different from dbx and gdb, but consistent with # the Python interpreter's stack trace. # It is also consistent with the up/down commands (which are # compatible with dbx and gdb: up moves towards 'main()' # and down moves towards the most recent stack frame). def print_stack_trace(self): try: for frame_lineno in self.stack: self.print_stack_entry(frame_lineno) except KeyboardInterrupt: pass def print_stack_entry(self, frame_lineno, prompt_prefix=line_prefix): frame, lineno = frame_lineno if frame is self.curframe: print '>', else: print ' ', print self.format_stack_entry(frame_lineno, prompt_prefix) # Simplified interface def run(statement, globals=None, locals=None): Pdb().run(statement, globals, locals) def runeval(expression, globals=None, locals=None): return Pdb().runeval(expression, globals, locals) def runctx(statement, globals, locals): # B/W compatibility run(statement, globals, locals) def runcall(*args): return Pdb().runcall(*args) def set_trace(): Pdb().set_trace() # Post-Mortem interface def post_mortem(t): p = Pdb() p.reset() while t.tb_next is not None: t = t.tb_next p.interaction(t.tb_frame, t) def pm(): post_mortem(sys.last_traceback) # Main program for testing TESTCMD = 'import x; x.main()' def test(): run(TESTCMD) # print help def help(): for dirname in sys.path: fullname = os.path.join(dirname, 'pdb.doc') if os.path.exists(fullname): sts = os.system('${PAGER-more} '+fullname) if sts: print '*** Pager exit status:', sts break else: print 'Sorry, can\'t find the help file "pdb.doc"', print 'along the Python search path' mainmodule = '' mainpyfile = '' # When invoked as main program, invoke the debugger on a script if __name__=='__main__': if not sys.argv[1:]: print "usage: pdb.py scriptfile [arg] ..." sys.exit(2) mainpyfile = filename = sys.argv[1] # Get script filename if not os.path.exists(filename): print 'Error:'+filename+'does not exist' sys.exit(1) mainmodule = os.path.basename(filename) del sys.argv[0] # Hide "pdb.py" from argument list # Insert script directory in front of module search path sys.path.insert(0, os.path.dirname(filename)) run('execfile(' + `filename` + ')')