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 class CoreDecoder(object): |
| 20 """Class to process core dumps.""" |
| 21 |
| 22 def __init__(self, main_nexe, nmf_filename, |
| 23 addr2line, toolchain_libs): |
| 24 """Construct and object to process core dumps. |
| 25 |
| 26 Args: |
| 27 main_nexe: nexe to resolve NaClMain references from. |
| 28 nmf_filename: nmf to resovle references from. |
| 29 addr2line: path to appropriate addr2line. |
| 30 toolchain_libs: path to the toolchain libraries. |
| 31 """ |
| 32 self.main_nexe = main_nexe |
| 33 self.nmf_filename = nmf_filename |
| 34 if nmf_filename == '-': |
| 35 self.nmf_data = {} |
| 36 else: |
| 37 self.nmf_data = json.load(open(nmf_filename)) |
| 38 self.addr2line = addr2line |
| 39 self.toolchain_libs = toolchain_libs |
| 40 |
| 41 def _SelectModulePath(self, filename): |
| 42 """Select which path to get a module from. |
| 43 |
| 44 Args: |
| 45 filename: filename of a module (as appears in phdrs). |
| 46 Returns: |
| 47 Full local path to the file. |
| 48 Derived by consulting the manifest. |
| 49 """ |
| 50 # For some names try the main nexe. |
| 51 # NaClMain is the argv[0] setup in sel_main.c |
| 52 # (null) shows up in chrome. |
| 53 if self.main_nexe is not None and filename in ['NaClMain', '(null)']: |
| 54 return self.main_nexe |
| 55 filepart = posixpath.basename(filename) |
| 56 nmf_entry = self.nmf_data.get('files', {}).get(filepart, {}) |
| 57 # TODO(bradnelson): support x86-64 + arm. |
| 58 nmf_url = nmf_entry.get('x86-32', {}).get('url') |
| 59 # Try filename directly if not in manifest. |
| 60 if nmf_url is None: |
| 61 return filename |
| 62 # Look for the module relative to the manifest (if any), then toolchain. |
| 63 paths = [] |
| 64 if self.nmf_filename != '-': |
| 65 paths.append(os.path.dirname(self.nmf_filename)) |
| 66 if self.toolchain_libs is not None: |
| 67 paths.append(self.toolchain_libs) |
| 68 for path in paths: |
| 69 pfilename = os.path.join(path, nmf_url) |
| 70 if os.path.exists(pfilename): |
| 71 return pfilename |
| 72 # If nothing else, try the path directly. |
| 73 return filename |
| 74 |
| 75 def _DecodeAddressSegment(self, segments, address): |
| 76 """Convert an address to a segment relative one, plus filename. |
| 77 |
| 78 Args: |
| 79 segments: a list of phdr segments. |
| 80 address: a process wide code address. |
| 81 Returns: |
| 82 A tuple of filename and segment relative address. |
| 83 """ |
| 84 for segment in segments: |
| 85 for phdr in segment['dlpi_phdr']: |
| 86 start = segment['dlpi_addr'] + phdr['p_vaddr'] |
| 87 end = start + phdr['p_memsz'] |
| 88 if address >= start and address < end: |
| 89 return (segment['dlpi_name'], address - segment['dlpi_addr']) |
| 90 return ('(null)', address) |
| 91 |
| 92 def _Addr2Line(self, segments, address): |
| 93 """Use addr2line to decode a code address. |
| 94 |
| 95 Args: |
| 96 segments: A list of phdr segments. |
| 97 address: a code address. |
| 98 Returns: |
| 99 A list of dicts containing: function, filename, lineno. |
| 100 """ |
| 101 filename, address = self._DecodeAddressSegment(segments, address) |
| 102 filename = self._SelectModulePath(filename) |
| 103 if not os.path.exists(filename): |
| 104 return [{ |
| 105 'function': 'Unknown_function', |
| 106 'filename': 'unknown_file', |
| 107 'lineno': -1, |
| 108 }] |
| 109 # Use address - 1 to get the call site instead of the line after. |
| 110 address -= 1 |
| 111 cmd = [ |
| 112 self.addr2line, '-f', '--inlines', '-e', filename, '0x%08x' % address, |
| 113 ] |
| 114 process = subprocess.Popen(cmd, stdout=subprocess.PIPE) |
| 115 process_stdout, _ = process.communicate() |
| 116 assert process.returncode == 0 |
| 117 lines = process_stdout.splitlines() |
| 118 assert len(lines) % 2 == 0 |
| 119 results = [] |
| 120 for index in xrange(len(lines) / 2): |
| 121 func = lines[index * 2] |
| 122 afilename, lineno = lines[index * 2 + 1].split(':', 1) |
| 123 results.append({ |
| 124 'function': func, |
| 125 'filename': afilename, |
| 126 'lineno': int(lineno), |
| 127 }) |
| 128 return results |
| 129 |
| 130 def LoadAndDecode(self, core_path): |
| 131 """Given a core.json file, load and embellish with decoded addresses. |
| 132 |
| 133 Args: |
| 134 core_path: source file containing a dump. |
| 135 Returns: |
| 136 An embelished core dump dict (decoded code addresses). |
| 137 """ |
| 138 core = json.load(open(core_path)) |
| 139 for frame in core['frames']: |
| 140 frame['scopes'] = self._Addr2Line(core['segments'], frame['prog_ctr']) |
| 141 return core |
| 142 |
| 143 def StackTrace(self, info): |
| 144 """Convert a decoded core.json dump to a simple stack trace. |
| 145 |
| 146 Args: |
| 147 info: core.json info with decoded code addresses. |
| 148 Returns: |
| 149 A list of dicts with filename, lineno, function (deepest first). |
| 150 """ |
| 151 trace = [] |
| 152 for frame in info['frames']: |
| 153 for scope in frame['scopes']: |
| 154 trace.append(scope) |
| 155 return trace |
| 156 |
| 157 def PrintTrace(self, trace, out): |
| 158 """Print a trace to a file like object. |
| 159 |
| 160 Args: |
| 161 trace: A list of [filename, lineno, function] (deepest first). |
| 162 out: file like object to output the trace to. |
| 163 """ |
| 164 for scope in trace: |
| 165 out.write('%s at %s:%d\n' % ( |
| 166 scope['function'], |
| 167 scope['filename'], |
| 168 scope['lineno'])) |
| 169 |
| 170 |
| 171 def Main(args): |
| 172 parser = optparse.OptionParser( |
| 173 usage='USAGE: %prog [options] <core.json>') |
| 174 parser.add_option('-m', '--main-nexe', dest='main_nexe', |
| 175 help='nexe to resolve NaClMain references from') |
| 176 parser.add_option('-n', '--nmf', dest='nmf_filename', default='-', |
| 177 help='nmf to resolve references from') |
| 178 parser.add_option('-a', '--addr2line', dest='addr2line', |
| 179 help='path to appropriate addr2line') |
| 180 parser.add_option('-l', '--toolchain-libs', dest='toolchain_libs', |
| 181 help='path to the toolchain libraries') |
| 182 options, args = parser.parse_args(args) |
| 183 if len(args) != 1: |
| 184 parser.print_help() |
| 185 sys.exit(1) |
| 186 decoder = CoreDecoder( |
| 187 main_nexe=options.main_nexe, |
| 188 nmf_filename=options.nmf_filename, |
| 189 addr2line=options.add2line, |
| 190 toolchain_libs=options.toolchain_libs) |
| 191 info = decoder.LoadAndDecode(args[0]) |
| 192 trace = decoder.StackTrace(info) |
| 193 decoder.PrintTrace(trace, sys.stdout) |
| 194 |
| 195 |
| 196 if __name__ == '__main__': |
| 197 Main(sys.argv[1:]) |
OLD | NEW |