Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/python
      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.
      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, library_paths, platform):
     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 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.
     32     """
     33     self.main_nexe = main_nexe
     34     self.nmf_filename = nmf_filename
     35     if nmf_filename == '-':
     36       self.nmf_data = {}
     37     else:
     38       self.nmf_data = json.load(open(nmf_filename))
     39     self.addr2line = addr2line
     40     self.library_paths = library_paths
     41     self.platform = platform
     42 
     43   def _SelectModulePath(self, filename):
     44     """Select which path to get a module from.
     45 
     46     Args:
     47       filename: filename of a module (as appears in phdrs).
     48     Returns:
     49       Full local path to the file.
     50       Derived by consulting the manifest.
     51     """
     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)']:
     56       return self.main_nexe
     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.
     61     if nmf_url is None:
     62       return filename
     63     # Look for the module relative to the manifest (if any),
     64     # then in other search paths.
     65     paths = []
     66     if self.nmf_filename != '-':
     67       paths.append(os.path.dirname(self.nmf_filename))
     68     paths.extend(self.library_paths)
     69     for path in paths:
     70       pfilename = os.path.join(path, nmf_url)
     71       if os.path.exists(pfilename):
     72         return pfilename
     73     # If nothing else, try the path directly.
     74     return filename
     75 
     76   def _DecodeAddressSegment(self, segments, address):
     77     """Convert an address to a segment relative one, plus filename.
     78 
     79     Args:
     80       segments: a list of phdr segments.
     81       address: a process wide code address.
     82     Returns:
     83       A tuple of filename and segment relative address.
     84     """
     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)
     92 
     93   def _Addr2Line(self, segments, address):
     94     """Use addr2line to decode a code address.
     95 
     96     Args:
     97       segments: A list of phdr segments.
     98       address: a code address.
     99     Returns:
    100       A list of dicts containing: function, filename, lineno.
    101     """
    102     filename, address = self._DecodeAddressSegment(segments, address)
    103     filename = self._SelectModulePath(filename)
    104     if not os.path.exists(filename):
    105       return [{
    106           'function': 'Unknown_function',
    107           'filename': 'unknown_file',
    108           'lineno': -1,
    109       }]
    110     # Use address - 1 to get the call site instead of the line after.
    111     address -= 1
    112     cmd = [
    113         self.addr2line, '-f', '--inlines', '-e', filename, '0x%08x' % address,
    114     ]
    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
    120     results = []
    121     for index in xrange(len(lines) / 2):
    122       func = lines[index * 2]
    123       afilename, lineno = lines[index * 2 + 1].split(':', 1)
    124       results.append({
    125           'function': func,
    126           'filename': afilename,
    127           'lineno': int(lineno),
    128       })
    129     return results
    130 
    131   def Decode(self, text):
    132     core = json.loads(text)
    133     for frame in core['frames']:
    134       frame['scopes'] = self._Addr2Line(core['segments'], frame['prog_ctr'])
    135     return core
    136 
    137 
    138   def LoadAndDecode(self, core_path):
    139     """Given a core.json file, load and embellish with decoded addresses.
    140 
    141     Args:
    142       core_path: source file containing a dump.
    143     Returns:
    144       An embellished core dump dict (decoded code addresses).
    145     """
    146     core = json.load(open(core_path))
    147     for frame in core['frames']:
    148       frame['scopes'] = self._Addr2Line(core['segments'], frame['prog_ctr'])
    149     return core
    150 
    151   def StackTrace(self, info):
    152     """Convert a decoded core.json dump to a simple stack trace.
    153 
    154     Args:
    155       info: core.json info with decoded code addresses.
    156     Returns:
    157       A list of dicts with filename, lineno, function (deepest first).
    158     """
    159     trace = []
    160     for frame in info['frames']:
    161       for scope in frame['scopes']:
    162         trace.append(scope)
    163     return trace
    164 
    165   def PrintTrace(self, trace, out):
    166     """Print a trace to a file like object.
    167 
    168     Args:
    169       trace: A list of [filename, lineno, function] (deepest first).
    170       out: file like object to output the trace to.
    171     """
    172     for scope in trace:
    173       out.write('%s at %s:%d\n' % (
    174           scope['function'],
    175           scope['filename'],
    176           scope['lineno']))
    177 
    178 
    179 def Main(args):
    180   parser = optparse.OptionParser(
    181       usage='USAGE: %prog [options] <core.json>')
    182   parser.add_option('-m', '--main-nexe', dest='main_nexe',
    183                     help='nexe to resolve NaClMain references from')
    184   parser.add_option('-n', '--nmf', dest='nmf_filename', default='-',
    185                     help='nmf to resolve references from')
    186   parser.add_option('-a', '--addr2line', dest='addr2line',
    187                     help='path to appropriate addr2line')
    188   parser.add_option('-L', '--library-path', dest='library_paths',
    189                     action='append', default=[],
    190                     help='path to search for shared libraries')
    191   parser.add_option('-p', '--platform', dest='platform',
    192                     help='platform in a style match nmf files')
    193   options, args = parser.parse_args(args)
    194   if len(args) != 1:
    195     parser.print_help()
    196     sys.exit(1)
    197   decoder = CoreDecoder(
    198       main_nexe=options.main_nexe,
    199       nmf_filename=options.nmf_filename,
    200       addr2line=options.add2line,
    201       library_paths=options.library_paths,
    202       platform=options.platform)
    203   info = decoder.LoadAndDecode(args[0])
    204   trace = decoder.StackTrace(info)
    205   decoder.PrintTrace(trace, sys.stdout)
    206 
    207 
    208 if __name__ == '__main__':
    209   Main(sys.argv[1:])
    210