summaryrefslogtreecommitdiffstats
path: root/backtrace.py
blob: cebba2fa113fed2459a2884a7493e59a2afc0c6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
'''
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)