OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 # Copyright (c) 2012 The Native Client Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Utility to decode a crash dump generated by untrusted_crash_dump.[ch] | |
7 | |
8 Currently this produces a simple stack trace. | |
9 """ | |
10 | |
11 import json | |
12 import optparse | |
13 import os | |
14 import posixpath | |
15 import subprocess | |
16 import sys | |
17 | |
18 | |
19 def SelectModulePath(options, filename): | |
20 """Select which path to get a module from. | |
21 | |
22 Args: | |
23 options: option object. | |
24 filename: filename of a module (as appears in phdrs). | |
25 Returns: | |
26 Full local path to the file. | |
27 Derived by consulting the manifest. | |
28 """ | |
29 # For some names try the main nexe. | |
30 # NaClMain is the argv[0] setup in sel_main.c | |
31 # (null) shows up in chrome. | |
32 if options.main_nexe and filename in ['NaClMain', '(null)']: | |
Mark Seaborn
2012/02/16 18:42:50
"if options.main_nexe is not None ..."
bradn
2012/02/16 20:13:10
Done.
| |
33 return options.main_nexe | |
34 filepart = posixpath.basename(filename) | |
35 nmf_entry = options.nmf_data.get('files', {}).get(filepart, {}) | |
36 # TODO(bradnelson): support x86-64 + arm. | |
37 nmf_url = nmf_entry.get('x86-32', {}).get('url') | |
38 # Try filename directly if not in manifest. | |
39 if nmf_url is None: | |
40 return filename | |
41 # Look for the module relative to the manifest (if any), then toolchain. | |
42 paths = [] | |
43 if options.nmf is not None: | |
44 paths.append(os.path.dirname(options.nmf)) | |
45 if options.toolchain_libs is not None: | |
46 paths.append(options.toolchain_libs) | |
47 for path in paths: | |
48 pfilename = os.path.join(path, nmf_url) | |
49 if os.path.exists(pfilename): | |
50 return pfilename | |
51 # If nothing else, try the path directly. | |
52 return filename | |
53 | |
54 | |
55 def DecodeAddressSegment(segments, address): | |
56 """Convert an address to a segment relative one, plus filename. | |
57 | |
58 Args: | |
59 segments: a list of phdr segments. | |
60 address: a process wide code address. | |
61 Returns: | |
62 A tuple of filename and segment relative address. | |
63 """ | |
64 for segment in segments: | |
65 for phdr in segment['dlpi_phdr']: | |
66 start = segment['dlpi_addr'] + phdr['p_vaddr'] | |
67 end = start + phdr['p_memsz'] | |
68 if address >= start and address < end: | |
69 return (segment['dlpi_name'], address - segment['dlpi_addr']) | |
70 return ('(null)', address) | |
71 | |
72 | |
73 def Addr2Line(options, segments, address): | |
74 """Use addr2line to decode a code address. | |
75 | |
76 Args: | |
77 options: option object. | |
78 segments: A list of phdr segments. | |
79 address: a code address. | |
80 Returns: | |
81 A list of dicts containing: function, filename, lineno. | |
82 """ | |
83 filename, address = DecodeAddressSegment(segments, address) | |
84 filename = SelectModulePath(options, filename) | |
85 if not os.path.exists(filename): | |
86 return [{ | |
87 'function': 'Unknown_function', | |
88 'filename': 'unknown_file', | |
89 'lineno': -1, | |
90 }] | |
91 cmd = [ | |
92 options.addr2line, '-f', '--inlines', '-e', filename, '0x%08x' % address, | |
93 ] | |
94 process = subprocess.Popen(cmd, stdout=subprocess.PIPE) | |
95 process_stdout, _ = process.communicate() | |
96 assert process.returncode == 0 | |
97 lines = process_stdout.splitlines() | |
98 assert len(lines) % 2 == 0 | |
99 results = [] | |
100 for index in range(len(lines) / 2): | |
Mark Seaborn
2012/02/16 18:42:50
Nit: xrange() is preferred over range() in general
bradn
2012/02/16 20:13:10
Done.
| |
101 func = lines[index * 2] | |
102 afilename, lineno = lines[index * 2 + 1].split(':') | |
Mark Seaborn
2012/02/16 18:42:50
Nit: split(':', 1) is preferable if you're expecti
bradn
2012/02/16 20:13:10
Done.
| |
103 results.append({ | |
104 'function': func, | |
105 'filename': afilename, | |
106 'lineno': int(lineno), | |
107 }) | |
108 return results | |
109 | |
110 | |
111 def LoadAndDecode(options, core_path): | |
112 """Given a core.json file, load and embellish with decoded addresses. | |
113 | |
114 Args: | |
115 options: options object. | |
116 core_path: source file containing a dump. | |
117 Returns: | |
118 | |
119 """ | |
120 if options.nmf: | |
121 options.nmf_data = json.load(open(options.nmf)) | |
122 else: | |
123 options.nmf_data = {} | |
124 core = json.load(open(core_path)) | |
125 for frame in core['frames']: | |
126 frame['scopes'] = Addr2Line( | |
127 options, core['segments'], frame['ip']) | |
128 return core | |
129 | |
130 | |
131 def StackTrace(info): | |
132 """Convert a decoded core.json dump to a simple stack trace. | |
133 | |
134 Args: | |
135 info: core.json info with decoded code addresses. | |
136 Returns: | |
137 A list of dicts with filename, lineno, function (deepest first). | |
138 """ | |
139 trace = [] | |
140 for frame in info['frames']: | |
141 for scope in frame['scopes']: | |
142 trace.append(scope) | |
143 return trace | |
144 | |
145 | |
146 def PrintTrace(trace, out): | |
147 """Print a trace to a file like object. | |
148 | |
149 Args: | |
150 trace: A list of [filename, lineno, function] (deepest first). | |
151 out: file like object to output the trace to. | |
152 """ | |
153 for scope in trace: | |
154 out.write('%s at %s:%d\n' % ( | |
155 scope['function'], | |
156 scope['filename'], | |
157 scope['lineno'])) | |
158 | |
159 | |
160 def Main(args): | |
161 parser = optparse.OptionParser( | |
162 usage='USAGE: %prog [options] <core.json>') | |
163 parser.add_option('-m', '--main-nexe', dest='main_nexe', | |
164 help='nexe to resolve NaClMain references from') | |
165 parser.add_option('-n', '--nmf', dest='nmf', | |
Mark Seaborn
2012/02/16 18:42:50
dest='nmf_filename' would be a little more descrip
bradn
2012/02/16 20:13:10
Done.
| |
166 help='nmf to resolve references from') | |
167 parser.add_option('-a', '--addr2line', dest='addr2line', | |
168 help='path to appropriate addr2line') | |
169 parser.add_option('-l', '--toolchain-libs', dest='toolchain_libs', | |
170 help='path to the toolchain libraries') | |
171 options, args = parser.parse_args(args) | |
172 if len(args) != 1: | |
173 parser.print_help() | |
174 sys.exit(1) | |
175 info = LoadAndDecode(options, args[0]) | |
176 trace = StackTrace(info) | |
177 PrintTrace(trace, sys.stdout) | |
178 | |
179 | |
180 if __name__ == '__main__': | |
181 Main(sys.argv[1:]) | |
OLD | NEW |