summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2011-02-15 18:12:07 -0500
committerDavid Malcolm <dmalcolm@redhat.com>2011-02-15 18:12:07 -0500
commit4f5566f9393ed9a3fdbe317b326ccdb724a183d2 (patch)
treefe3314bc643eca4a23a3d4b4593e2e5bef6b91a0
parenta8ab774aec55fc200859a1c3b9202080093d1d30 (diff)
downloadtriage-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.py72
-rw-r--r--test_backtrace_parser.py30
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()