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
|
|
|
|