2 # Copyright (c) 2012 The Chromium 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.
6 """Utility to decode a crash dump generated by untrusted_crash_dump.[ch]
8 Currently this produces a simple stack trace.
19 class CoreDecoder(object):
20 """Class to process core dumps."""
22 def __init__(self
, main_nexe
, nmf_filename
,
23 addr2line
, library_paths
, platform
):
24 """Construct and object to process core dumps.
27 main_nexe: nexe to resolve NaClMain references from.
28 nmf_filename: nmf to resolve references from.
29 addr2line: path to appropriate addr2line.
30 library_paths: list of paths to search for libraries.
31 platform: platform string to use in nmf files.
33 self
.main_nexe
= main_nexe
34 self
.nmf_filename
= nmf_filename
35 if nmf_filename
== '-':
38 self
.nmf_data
= json
.load(open(nmf_filename
))
39 self
.addr2line
= addr2line
40 self
.library_paths
= library_paths
41 self
.platform
= platform
43 def _SelectModulePath(self
, filename
):
44 """Select which path to get a module from.
47 filename: filename of a module (as appears in phdrs).
49 Full local path to the file.
50 Derived by consulting the manifest.
52 # For some names try the main nexe.
53 # NaClMain is the argv[0] setup in sel_main.c
54 # (null) shows up in chrome.
55 if self
.main_nexe
is not None and filename
in ['NaClMain', '(null)']:
57 filepart
= posixpath
.basename(filename
)
58 nmf_entry
= self
.nmf_data
.get('files', {}).get(filepart
, {})
59 nmf_url
= nmf_entry
.get(self
.platform
, {}).get('url')
60 # Try filename directly if not in manifest.
63 # Look for the module relative to the manifest (if any),
64 # then in other search paths.
66 if self
.nmf_filename
!= '-':
67 paths
.append(os
.path
.dirname(self
.nmf_filename
))
68 paths
.extend(self
.library_paths
)
70 pfilename
= os
.path
.join(path
, nmf_url
)
71 if os
.path
.exists(pfilename
):
73 # If nothing else, try the path directly.
76 def _DecodeAddressSegment(self
, segments
, address
):
77 """Convert an address to a segment relative one, plus filename.
80 segments: a list of phdr segments.
81 address: a process wide code address.
83 A tuple of filename and segment relative address.
85 for segment
in segments
:
86 for phdr
in segment
['dlpi_phdr']:
87 start
= segment
['dlpi_addr'] + phdr
['p_vaddr']
88 end
= start
+ phdr
['p_memsz']
89 if address
>= start
and address
< end
:
90 return (segment
['dlpi_name'], address
- segment
['dlpi_addr'])
91 return ('(null)', address
)
93 def _Addr2Line(self
, segments
, address
):
94 """Use addr2line to decode a code address.
97 segments: A list of phdr segments.
98 address: a code address.
100 A list of dicts containing: function, filename, lineno.
102 filename
, address
= self
._DecodeAddressSegment
(segments
, address
)
103 filename
= self
._SelectModulePath
(filename
)
104 if not os
.path
.exists(filename
):
106 'function': 'Unknown_function',
107 'filename': 'unknown_file',
110 # Use address - 1 to get the call site instead of the line after.
113 self
.addr2line
, '-f', '--inlines', '-e', filename
, '0x%08x' % address
,
115 process
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
116 process_stdout
, _
= process
.communicate()
117 assert process
.returncode
== 0
118 lines
= process_stdout
.splitlines()
119 assert len(lines
) % 2 == 0
121 for index
in xrange(len(lines
) / 2):
122 func
= lines
[index
* 2]
123 afilename
, lineno
= lines
[index
* 2 + 1].split(':', 1)
126 'filename': afilename
,
127 'lineno': int(lineno
),
131 def Decode(self
, text
):
132 core
= json
.loads(text
)
133 for frame
in core
['frames']:
134 frame
['scopes'] = self
._Addr
2Line
(core
['segments'], frame
['prog_ctr'])
137 def LoadAndDecode(self
, core_path
):
138 """Given a core.json file, load and embellish with decoded addresses.
141 core_path: source file containing a dump.
143 An embellished core dump dict (decoded code addresses).
145 core
= json
.load(open(core_path
))
146 for frame
in core
['frames']:
147 frame
['scopes'] = self
._Addr
2Line
(core
['segments'], frame
['prog_ctr'])
150 def StackTrace(self
, info
):
151 """Convert a decoded core.json dump to a simple stack trace.
154 info: core.json info with decoded code addresses.
156 A list of dicts with filename, lineno, function (deepest first).
159 for frame
in info
['frames']:
160 for scope
in frame
['scopes']:
164 def PrintTrace(self
, trace
, out
):
165 """Print a trace to a file like object.
168 trace: A list of [filename, lineno, function] (deepest first).
169 out: file like object to output the trace to.
172 out
.write('%s at %s:%d\n' % (
179 parser
= argparse
.ArgumentParser(description
=__doc__
)
180 parser
.add_argument('-m', '--main-nexe',
181 help='nexe to resolve NaClMain references from')
182 parser
.add_argument('-n', '--nmf', default
='-',
183 help='nmf to resolve references from')
184 parser
.add_argument('-a', '--addr2line',
185 help='path to appropriate addr2line')
186 parser
.add_argument('-L', '--library-path', dest
='library_paths',
187 action
='append', default
=[],
188 help='path to search for shared libraries')
189 parser
.add_argument('-p', '--platform',
190 help='platform in a style match nmf files')
191 parser
.add_argument('core_json')
192 options
= parser
.parse_args(args
)
193 decoder
= CoreDecoder(
194 main_nexe
=options
.main_nexe
,
195 nmf_filename
=options
.nmf
,
196 addr2line
=options
.addr2line
,
197 library_paths
=options
.library_paths
,
198 platform
=options
.platform
)
199 info
= decoder
.LoadAndDecode(options
.core_json
)
200 trace
= decoder
.StackTrace(info
)
201 decoder
.PrintTrace(trace
, sys
.stdout
)
205 if __name__
== '__main__':
206 sys
.exit(main(sys
.argv
[1:]))