''' Support for parsing strings containing gdb backtraces, turning them into classes ''' import re class Frame(object): ''' Class representing a stack frame of a thread within a backtrace ''' def __init__(self, index, address, function, info): self.index = index self.address = address self.function = function self.info = info def as_python_frame(self): '''For relevant frames, return (file, line, funcname, args) tuple; otherwise None''' if self.function != 'PyEval_EvalFrameEx': return None return PythonFrame(self) #result += '#%i %s\n' % (frame.index, frame.info) class PythonFrame(object): ''' Class representating an interpretation of a C stack frame as a Python stack frame ''' def __init__(self, c_frame): self.c_frame = c_frame m = re.match('\(f=Frame 0x[0-9a-f]+, for file (\S+), line ([0-9]+), in (\S+) \((.*), throwflag=0\) at (\S+)', c_frame.info) self.file = m.group(1) self.linenum = m.group(2) self.funcname = m.group(3) self.vars = m.group(4) class Thread(object): ''' Class representing a thread within a backtrace ''' def __init__(self, index, extra): self.index = index self.extra = extra self.frames = {} self.framelist = [] def as_python_backtrace(self, with_vars): '''Generate a textual guess at a python-level backtrace''' result = '' for frame in self.framelist: pyframe = frame.as_python_frame() if pyframe: result += '#%i %s:%s %s\n' % (frame.index, pyframe.file, pyframe.linenum, pyframe.funcname) if with_vars: result += ' %s\n' % (pyframe.vars, ) return result class Backtrace(object): ''' Class representing a parsed backtrace ''' @classmethod def from_text_file(cls, filename, debug=False): with open(filename, 'r') as f: text = f.read() return Backtrace(text, debug) def __init__(self, string, debug=False): ''' Parse the given string (from gdb) ''' self._string = string #if debug: # print string self._crash_site = None self._thread = None self._threads = {} self._frame = None for line in string.splitlines(): if debug: print 'GOT LINE: %r' % line m = re.match('^Thread ([0-9]+) \(Thread (.*)\):$', line) if m: if debug: print 'THREAD START:', m.groups() self._thread = Thread(int(m.group(1)), m.group(2)) self._threads[self._thread.index] = self._thread self._frame = None continue m = re.match('^#([0-9]+)\s+(?:0x([0-9a-f]+) in )?(\S+) (.*)$', line) if m: if debug: print 'STACK FRAME:', m.groups() #print m.groups() if m.group(2): address = int(m.group(2), 16) else: address = None f = Frame(int(m.group(1)), address, m.group(3), m.group(4)) self.__on_new_frame(f, debug) continue if line.startswith(' '): if self._frame: self._frame.info += '\n' + line #pprint(self._threads[5].frames[6].__dict__) def __on_new_frame(self, f, debug): if self._thread is None: if debug: print 'GOT CRASH SITE' self._crash_site = f self._frame = None else: self._thread.frames[f.index] = f self._thread.framelist.append(f) self._frame = f def get_crash_site(self): '''Get a (Thread, Frame) pair for where the crash happened (or None)''' debug = False if debug: print self._crash_site.__dict__ for t in self._threads.values(): if debug: print t if 0 in t.frames: if debug: print t.frames[0].__dict__ if t.frames[0].address == self._crash_site.address: return (t, t.frames[0]) def as_python_backtrace(self, with_vars): '''Generate a textual guess at a python-level backtrace''' thread, frame = self.get_crash_site() return thread.as_python_backtrace(with_vars)