--- threaddbg.py 2006/08/07 02:25:13 1.3 +++ threaddbg.py 2006/08/19 20:57:38 @@ -9,9 +9,23 @@ ### - import bdb, inspect, os, pydb, sys - +from bdb import Breakpoint, checkfuncname +from pydb.pydbbdb import checkline import thread, threading +class TBreakpoint(Breakpoint): + + """Per-thread Breakpoint class.""" + + next = 1 + bplist = {} + bpbynumber = [None] + + def __init__(self, file, line, temporary=0, cond=None, funcname=None, + threadname=None): + Breakpoint.__init__(self, file, line, temporary, cond, funcname) + self.threadname = threadname + class threadDbg(pydb.Pdb): def __init__(self, completekey='tab', stdin=None, stdout=None): @@ -20,6 +34,8 @@ self.add_hook() self.stack = self.curframe = self.botframe = None + self.t_breaks = {'MainThread': {}} + # desired_thread is the thread we want to switch to after issuing # a "thread " command. self.desired_thread=None @@ -193,6 +209,7 @@ # Record in my own table a list of thread names if not thread_name in self.traced.keys(): self.traced[thread_name] = thread.get_ident() + self.t_breaks[thread_name] = {} # See if there was a request to switch to a specific thread while self.desired_thread not in (None, @@ -205,11 +222,9 @@ self.threading_lock.acquire() if self.desired_thread != None: - print "Hey we just did a switch to %s!" % self.desired_thread self.threading_cond.acquire() self.threading_cond.notifyAll() self.threading_cond.release() - print "notify done from %s" % threading.currentThread().getName() self.desired_thread = None @@ -256,3 +271,249 @@ statement = 'execfile( "%s")' % filename self.run(statement, globals=globals_, locals=locals_) + def info_breakpoints(self, arg): + threadname = threading.currentThread().getName() + if not self.breaks and not self.t_breaks[threadname]: + self.msg('No breakpoints') + return + + have_breaks = False + if self.breaks: + pydb.Pdb.info_breakpoints(self, arg) + have_breaks = True + if self.t_breaks[threadname]: + self.msg("\nThread-specific breakpoints:") + self.msg("Num Type Disp Enb Where Thread") + for bp in TBreakpoint.bpbynumber: + if bp: + self.tbpprint(bp) + else: + if not have_breaks: self.msg("No breakpoints.") + + info_breakpoints.__doc__ = pydb.Pdb.info_breakpoints.__doc__ + + def set_break(self, filename, lineno, temporary=0, cond=None, funcname=None, + threadname=None): + if threadname is None: + pydb.Pdb.set_break(self, filename, lineno, temporary, cond, funcname) + else: + filename = self.canonic(filename) + import linecache + line = linecache.getline(filename, lineno) + if not line: + return 'Line %s:%d does not exist' % (filename, lineno) + + if not filename in self.t_breaks: + self.t_breaks[threadname][filename] = [] + blist = self.t_breaks[threadname][filename] + if not lineno in blist: + blist.append(lineno) + bp = TBreakpoint(filename, lineno, temporary, cond, funcname, threadname) + + def do_break(self, arg, temporary=0): + if 'thread' not in arg: + # We only deal with thread-spefic breakpoints. pydb handles + # all others. + pydb.Pdb.do_break(self, arg) + return + + if not self.curframe: + self.msg("No stack.") + return + # Pull the 'thread' command and threadname out of the args + threadname = arg[arg.find('thread')+7:] + print threadname.__repr__() + arg = arg[:arg.find('thread')-1] + cond = None + funcname = None + if not arg: + if self.lineno is None: + lineno = max(1, inspect.getlineno(self.curframe)) + else: + lineno = self.lineno + 1 + filename = self.curframe.f_code.co_filename + else: + filename = None + lineno = None + comma = arg.find(',') + if comma > 0: + cond = arg[comma+1:].lstrip() + arg = arg[:comma].rstrip() + (funcname, filename, lineno) = self.parse_fileops(arg) + + if not filename: + filename = self.defaultFile() + + line = checkline(self, filename, lineno) + if line: + try: + err = self.set_break(filename, line, temporary, cond, funcname, threadname) + except TypeError: + err = self.set_break(filename, line, temporary, cond, threadname) + + if err: self.errmsg(err) + else: + bp = self.get_breaks(filename, line, threadname)[-1] + self.msg("Breakpoint %d set in file %s, line %d, thread %s." + % (bp.number, self.filename(bp.file), bp.line, bp.threadname)) + + do_break.__doc__ = pydb.Pdb.do_break.__doc__ + + # XXX This is an _exact_ copy of __parse_fileops from gdb.py + # It's only included because of the name mangling + def parse_fileops(self, arg): + colon = arg.find(':') + if colon >= 0: + filename = arg[:colon].rstrip() + f = self.lookupmodule(filename) + if not f: + self.errmsg("%s not found on sys.path" % + self.saferepr(filename)) + return (None, None, None) + else: + filename = f + arg = arg[colon+1:].lstrip() + try: + lineno = int(arg) + except TypeError: + self.errmsg("Bad lineno: %s", str(arg)) + return (None, filename, None) + return (None, filename, lineno) + else: + # no colon: can be lineno or function + return self.get_brkpt_lineno(arg) + + # Likewise, this method is only included because of the namespace mangling + def get_brkpt_lineno(self, arg): + funcname, filename = (None, None) + try: + lineno = int(arg) + filename = self.curframe.f_code.co_filename + 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: + (ok, filename, ln) = self.lineinfo(arg) + if not ok: + self.errmsg(('The specified object %s is not' + +' a fucntion, or not found' + +' along sys.path or not line given.') + % str(repr(arg))) + return (None, None, None) + funcname = ok + lineno = int(ln) + return (funcname, filename, lineno) + + def get_breaks(self, filename, lineno, threadname=None): + if threadname is None: + return pydb.Pdb.get_breaks(self, filename, lineno) + filename = self.canonic(filename) + return filename in self.t_breaks[threadname] and \ + lineno in self.t_breaks[threadname][filename] and \ + TBreakpoint.bplist[filename, lineno] or [] + + def tbpprint(self, bp, out=None): + if bp.temporary: + disp = 'del ' + else: + disp = 'keep ' + if bp.enabled: + disp = disp + 'y ' + else: + disp = disp + 'n ' + self.msg('%-4dbreakpoint %s at %s:%d in %s' % + (bp.number, disp, self.filename(bp.file), bp.line, bp.threadname), out) + if bp.cond: + self.msg('\tstop only if %s' % (bp.cond)) + if bp.ignore: + self.msg('\tignore next %d hits' % (bp.ignore), out) + if (bp.hits): + if (bp.hits > 1): ss = 's' + else: ss = '' + self.msg('\tbreakpoint already hit %d time%s' % + (bp.hits, ss), out) + + def break_here(self, frame): + if pydb.Pdb.break_here(self, frame): + return True + + threadname = threading.currentThread().getName() + filename = self.canonic(frame.f_code.co_filename) + if not filename in self.t_breaks[threadname]: + return False + lineno = frame.f_lineno + if not lineno in self.t_breaks[threadname][filename]: + # The line itself has no breakpoint, but maybe the line is the + # first line of a function with breakpoint set by function name. + lineno = frame.f_code.co_firstlineno + if not lineno in self.t_breaks[threadname][filename]: + return False + + # flag says ok to delete temp. bp + (bp, flag) = effective(filename, lineno, frame) + if bp: + self.currentbp = bp.number + if (flag and bp.temporary): + self.do_clear(str(bp.number)) + return True + else: + return False + +def effective(file, line, frame): + """Determine which breakpoint for this file:line is to be acted upon. + + Called only if we know there is a bpt at this + location. Returns breakpoint that was triggered and a flag + that indicates if it is ok to delete a temporary bp. + + """ + possibles = TBreakpoint.bplist[file,line] + for i in range(0, len(possibles)): + b = possibles[i] + if b.enabled == 0: + continue + if not checkfuncname(b, frame): + continue + # Count every hit when bp is enabled + b.hits = b.hits + 1 + if not b.cond: + # If unconditional, and ignoring, + # go on to next, else break + if b.ignore > 0: + b.ignore = b.ignore -1 + continue + else: + # breakpoint and marker that's ok + # to delete if temporary + return (b,1) + else: + # Conditional bp. + # Ignore count applies only to those bpt hits where the + # condition evaluates to true. + try: + val = eval(b.cond, frame.f_globals, + frame.f_locals) + if val: + if b.ignore > 0: + b.ignore = b.ignore -1 + # continue + else: + return (b,1) + except: + # if eval fails, most conservative + # thing is to stop on breakpoint + # regardless of ignore count. + # Don't delete temporary, + # as another hint to user. + return (b,0) + return (None, None) +