diff options
author | David Malcolm <dmalcolm@redhat.com> | 2011-02-15 18:12:07 -0500 |
---|---|---|
committer | David Malcolm <dmalcolm@redhat.com> | 2011-02-15 18:12:07 -0500 |
commit | 4f5566f9393ed9a3fdbe317b326ccdb724a183d2 (patch) | |
tree | fe3314bc643eca4a23a3d4b4593e2e5bef6b91a0 | |
parent | a8ab774aec55fc200859a1c3b9202080093d1d30 (diff) | |
download | triage-4f5566f9393ed9a3fdbe317b326ccdb724a183d2.tar.gz triage-4f5566f9393ed9a3fdbe317b326ccdb724a183d2.tar.xz triage-4f5566f9393ed9a3fdbe317b326ccdb724a183d2.zip |
Fix a bug in backtrace parsing; add extractor for python-level backtraces
-rw-r--r-- | backtrace.py | 72 | ||||
-rw-r--r-- | test_backtrace_parser.py | 30 |
2 files changed, 85 insertions, 17 deletions
diff --git a/backtrace.py b/backtrace.py index 57e907e..cebba2f 100644 --- a/backtrace.py +++ b/backtrace.py @@ -13,6 +13,26 @@ class Frame(object): 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 @@ -23,10 +43,30 @@ class Thread(object): 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) @@ -51,7 +91,7 @@ class Backtrace(object): self._frame = None continue - m = re.match('^#([0-9]+)\s+(?:0x([0-9a-f]+) in)? (\S+) (.*)$', line) + m = re.match('^#([0-9]+)\s+(?:0x([0-9a-f]+) in )?(\S+) (.*)$', line) if m: if debug: print 'STACK FRAME:', m.groups() @@ -64,18 +104,8 @@ class Backtrace(object): address, m.group(3), m.group(4)) - - if self._thread is None: - if debug: - print 'GOT CRASH SITE' - self._crash_site = f - self._frame = None - continue - else: - self._thread.frames[f.index] = f - self._thread.framelist.append(f) - self._frame = f - continue + self.__on_new_frame(f, debug) + continue if line.startswith(' '): if self._frame: @@ -83,6 +113,17 @@ class Backtrace(object): #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 @@ -96,3 +137,8 @@ class Backtrace(object): 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) diff --git a/test_backtrace_parser.py b/test_backtrace_parser.py index e2d831f..713eadb 100644 --- a/test_backtrace_parser.py +++ b/test_backtrace_parser.py @@ -3,14 +3,16 @@ from backtrace import Backtrace class TestParser(unittest.TestCase): def test_bz677051(self): - text = open('data/rhbz-677051-attachment-478410.txt', 'r').read() - bt = Backtrace(text) + bt = Backtrace.from_text_file('data/rhbz-677051-attachment-478410.txt') thread, frame = bt.get_crash_site() self.assertEquals(thread.index, 1) self.assertEquals(thread.extra, '0xb78516c0 (LWP 17355)') - #self.assertEquals(len(thread.frames), 59) - #self.assertEquals(len(thread.framelist), 59) + self.assertEquals(len(thread.frames), 60) + self.assertEquals(len(thread.framelist), 60) + + # Frame #13 wasn't handled by an earlier version of the parser: + self.assert_(13 in thread.frames) self.assertEquals(frame.index, 0) self.assertEquals(frame.address, 0x4d2ade0a) @@ -24,6 +26,26 @@ class TestParser(unittest.TestCase): " base = <optimized out>\n" " dict = <optimized out>\n" " h = <optimized out>") + + + + def test_python_backtrace(self): + bt = Backtrace.from_text_file('data/rhbz-677051-attachment-478410.txt') + + pybt = bt.as_python_backtrace(False) + + self.assertEquals(pybt, + '#4 /usr/lib/python2.7/site-packages/yum/sqlitesack.py:1362 searchPrco\n' + '#9 /usr/lib/python2.7/site-packages/yum/sqlitesack.py:46 newFunc\n' + '#13 /usr/lib/python2.7/site-packages/yum/sqlitesack.py:1400 searchProvides\n' + '#23 /usr/lib/python2.7/site-packages/yum/packageSack.py:598 _computeAggregateListResult\n' + '#27 /usr/lib/python2.7/site-packages/yum/packageSack.py:426 searchProvides\n' + '#30 /usr/lib/python2.7/site-packages/yum/__init__.py:2990 returnPackagesByDep\n' + '#33 /usr/lib/python2.7/site-packages/yum/__init__.py:3313 install\n' + '#37 /usr/sbin/yum-complete-transaction:199 main\n' + '#40 /usr/sbin/yum-complete-transaction:118 __init__\n' + '#51 /usr/sbin/yum-complete-transaction:256 <module>\n') + if __name__ == '__main__': unittest.main() |