pipe_dump_to_json_dump()   F
last analyzed

Complexity

Conditions 9

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
dl 0
loc 36
rs 3
c 0
b 0
f 0
1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this
3
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5
"""This module provides a function that will transate Minidump Stackwalk pipe
6
dump into a json format.
7
8
{
9
  # "status": string, // OK | ERROR_* | SYMBOL_SUPPLIER_INTERRUPTED
10
  "system_info": {
11
    "os": string,
12
    "os_ver": string,
13
    "cpu_arch": string, // x86 | amd64 | arm | ppc | sparc
14
    "cpu_info": string,
15
    "cpu_count": int
16
  },
17
  "crash_info": {
18
    "type": string,
19
    "crash_address": string, // 0x[[:xdigit:]]+
20
    "crashing_thread": int // | null
21
  }
22
  "main_module": int, // index into modules
23
  "modules": [
24
         // zero or more
25
    {
26
      "base_addr": string, // 0x[[:xdigit:]]+
27
      "debug_file": string,
28
      "debug_id": string, // [[:xdigit:]]{33}
29
      "end_addr": string, // 0x[[:xdigit:]]+
30
      "filename": string,
31
      "version": string
32
    }
33
  ],
34
  "thread_count": int,
35
  "threads": [
36
    // for i in range(thread_count)
37
    {
38
      "frame_count": int,
39
      "frames": [
40
        // for i in range(frame_count)
41
        {
42
          "frame": int,
43
          "module": string,   // optional
44
          "function": string, // optional
45
          "file": string,     // optional
46
          "line": int,        // optional
47
          "offset": string,         // 0x[[:xdigit:]]+
48
          "module_offset": string,  // 0x[[:xdigit:]]+ , optional
49
          "function_offset": string // 0x[[:xdigit:]]+ , optional
50
        }
51
      ]
52
    }
53
  ],
54
  // repeated here for ease of searching
55
  // (max 10 frames)
56
  "crashing_thread": {
57
    "threads_index": int,
58
    "total_frames": int,
59
    "frames": [
60
      // for i in range(length)
61
      {
62
        // as per "frames" entries from "threads" above
63
      }
64
    ]
65
  }
66
}
67
"""
68
69
70
class DotDict(dict):
71
    __getattr__ = dict.__getitem__
72
    __setattr__ = dict.__setitem__
73
    __delattr__ = dict.__delitem__
74
75
76
class DotDictWithPut(DotDict):
77
    #--------------------------------------------------------------------------
78
    def put_if_not_none(self, key, value):
79
        if value is not None and value != '':
80
            self[key] = value
81
82
83
def pipe_dump_to_json_dump(pipe_dump_iterable):
84
    """given a list (or any iterable) of strings representing a MDSW pipe dump,
85
    this function will convert it into a json format."""
86
    json_dump = DotDict()
87
    crashing_thread = None
88
    module_counter = 0
89
    for a_line in pipe_dump_iterable:
90
        parts = a_line.split('|')
91
        if parts[0] == 'OS':
92
            _extract_OS_info(parts, json_dump)
93
        elif parts[0] == 'CPU':
94
            _extract_CPU_info(parts, json_dump)
95
        elif parts[0] == 'Crash':
96
            crashing_thread = _extract_crash_info(parts, json_dump)
97
        elif parts[0] == 'Module':
98
            _extract_module_info(parts, json_dump, module_counter)
99
            module_counter += 1
100
        else:
101
            try:
102
                thread_number = int(parts[0])
103
            except (ValueError, IndexError):
104
                continue  # unknow line type, ignore it
105
            _extract_frame_info(parts, json_dump)
106
    try:
107
        json_dump.thread_count = len(json_dump.threads)
108
    except KeyError:  # no threads were over found, 'threads' key was not made
109
        json_dump.thread_count = 0
110
    if crashing_thread is not None:
111
        crashing_thread_frames = DotDict()
112
        crashing_thread_frames.threads_index = crashing_thread
113
        crashing_thread_frames.total_frames = \
114
            len(json_dump.threads[crashing_thread].frames)
115
        crashing_thread_frames.frames = \
116
            json_dump.threads[crashing_thread].frames[:10]
117
        json_dump.crashing_thread = crashing_thread_frames
118
    return json_dump
119
120
121
#------------------------------------------------------------------------------
122
def _get(indexable_container, index, default):
123
    """like 'get' on a dict, but it works on lists, too"""
124
    try:
125
        return indexable_container[index]
126
    except (IndexError, KeyError):
127
        return default
128
129
130
#------------------------------------------------------------------------------
131
def _get_int(indexable_container, index, default):
132
    """try to get an int from an indexable container.  If that fails
133
    return the default"""
134
    try:
135
        return int(indexable_container[index])
136
    # exceptions separated to make case coverage clearer
137
    except (IndexError, KeyError):
138
        # item not found in the container
139
        return default
140
    except ValueError:
141
        # conversion to integer has failed
142
        return default
143
144
145
#------------------------------------------------------------------------------
146
def _extract_OS_info(os_line, json_dump):
147
    """given a pipe dump OS line, extract the parts and put them in their
148
    proper location within the json_dump"""
149
    system_info = DotDictWithPut()
150
    system_info.put_if_not_none('os', _get(os_line, 1, None))
151
    system_info.put_if_not_none('os_ver', _get(os_line, 2, None))
152
    if 'system_info' in json_dump:
153
        json_dump.system_info.update(system_info)
154
    else:
155
        json_dump.system_info = system_info
156
157
158
#------------------------------------------------------------------------------
159
def _extract_CPU_info(cpu_line, json_dump):
160
    """given a pipe dump CPU line, extract the parts and put them in their
161
    proper location within the json_dump"""
162
    system_info = DotDictWithPut()
163
    system_info.put_if_not_none('cpu_arch', _get(cpu_line, 1, None))
164
    system_info.put_if_not_none('cpu_info', _get(cpu_line, 2, None))
165
    system_info.put_if_not_none('cpu_count', _get_int(cpu_line, 3, None))
166
    if 'system_info' in json_dump:
167
        json_dump.system_info.update(system_info)
168
    else:
169
        json_dump.system_info = system_info
170
171
172
#------------------------------------------------------------------------------
173
def _extract_crash_info(crash_line, json_dump):
174
    """given a pipe dump CRASH line, extract the parts and put them in their
175
    proper location within the json_dump"""
176
    crash_info = DotDictWithPut()
177
    crash_info.put_if_not_none('type', _get(crash_line, 1, None))
178
    crash_info.put_if_not_none('crash_address', _get(crash_line, 2, None))
179
    crash_info.put_if_not_none('crashing_thread', _get_int(crash_line, 3, None))
180
    json_dump.crash_info = crash_info
181
    return crash_info.get('crashing_thread', None)
182
183
184
#------------------------------------------------------------------------------
185
def _extract_module_info(module_line, json_dump, module_counter):
186
    """given a pipe dump Module line, extract the parts and put them in their
187
    proper location within the json_dump"""
188
    module = DotDictWithPut()
189
    module.put_if_not_none('filename', _get(module_line, 1, None))
190
    module.put_if_not_none('version', _get(module_line, 2, None))
191
    module.put_if_not_none('debug_file', _get(module_line, 3, None))
192
    module.put_if_not_none('debug_id', _get(module_line, 4, None))
193
    module.put_if_not_none('base_addr', _get(module_line, 5, None))
194
    module.put_if_not_none('end_addr', _get(module_line, 6, None))
195
    is_main_module = _get_int(module_line, 7, 0)
196
    if is_main_module:
197
        json_dump.main_module = module_counter
198
    if 'modules' not in json_dump:
199
        json_dump.modules = []
200
    json_dump.modules.append(module)
201
202
203
#------------------------------------------------------------------------------
204
def _extract_frame_info(frame_line, json_dump):
205
    """given a pipe dump Frame line, extract the parts and put them in their
206
    proper location within the json_dump"""
207
    if 'threads' not in json_dump:
208
        json_dump.threads = []
209
    thread_number = _get_int(frame_line, 0, None)
210
    if thread_number is None:
211
        return
212
    if thread_number >= len(json_dump.threads):
213
        # threads are supposed to arrive in order.  We've not seen this thread
214
        # before, fill in a new entry in the 'threads' section of the json_dump
215
        # making sure that intervening missing threads have empty thread data
216
        for i in range(thread_number - len(json_dump.threads) + 1):
217
            thread = DotDict()
218
            thread.frame_count = 0
219
            thread.frames = []
220
            json_dump.threads.append(thread)
221
    # collect frame info from the pipe dump line
222
    tmp_frame = _get_int(frame_line, 1, None)
223
    tmp_module = _get(frame_line, 2, None)
224
    tmp_function = _get(frame_line, 3, None)
225
    tmp_file = _get(frame_line, 4, None)
226
    tmp_line = _get_int(frame_line, 5, None)
227
    tmp_offset = _get(frame_line, 6, None)
228
    frame = DotDictWithPut()
229
    frame.put_if_not_none('frame', tmp_frame)
230
    frame.put_if_not_none('filename', tmp_module)
231
    frame.put_if_not_none('function', tmp_function)
232
    frame.put_if_not_none('abs_path', tmp_file)
233
    frame.put_if_not_none('lineno', tmp_line)
234
    if tmp_file and tmp_line is not None:
235
        # skip offset entirely
236
        pass
237
    elif not tmp_file and tmp_function:
238
        frame.function_offset = tmp_offset
239
    elif not tmp_function and tmp_module:
240
        frame.module_offset = tmp_offset
241
    else:
242
        frame.offset = tmp_offset
243
    # save the frame info into the json
244
    json_dump.threads[thread_number].frames.append(frame)
245
    json_dump.threads[thread_number].frame_count += 1
246