summaryrefslogtreecommitdiffstats
path: root/native_client_sdk/src/tools/decode_dump.py
blob: e2717ff2094cc29b6e4ba929ca11786a69486afe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Utility to decode a crash dump generated by untrusted_crash_dump.[ch]

Currently this produces a simple stack trace.
"""

import json
import optparse
import os
import posixpath
import subprocess
import sys


class CoreDecoder(object):
  """Class to process core dumps."""

  def __init__(self, main_nexe, nmf_filename,
               addr2line, library_paths, platform):
    """Construct and object to process core dumps.

    Args:
      main_nexe: nexe to resolve NaClMain references from.
      nmf_filename: nmf to resovle references from.
      addr2line: path to appropriate addr2line.
      library_paths: list of paths to search for libraries.
      platform: platform string to use in nmf files.
    """
    self.main_nexe = main_nexe
    self.nmf_filename = nmf_filename
    if nmf_filename == '-':
      self.nmf_data = {}
    else:
      self.nmf_data = json.load(open(nmf_filename))
    self.addr2line = addr2line
    self.library_paths = library_paths
    self.platform = platform

  def _SelectModulePath(self, filename):
    """Select which path to get a module from.

    Args:
      filename: filename of a module (as appears in phdrs).
    Returns:
      Full local path to the file.
      Derived by consulting the manifest.
    """
    # For some names try the main nexe.
    # NaClMain is the argv[0] setup in sel_main.c
    # (null) shows up in chrome.
    if self.main_nexe is not None and filename in ['NaClMain', '(null)']:
      return self.main_nexe
    filepart = posixpath.basename(filename)
    nmf_entry = self.nmf_data.get('files', {}).get(filepart, {})
    nmf_url = nmf_entry.get(self.platform, {}).get('url')
    # Try filename directly if not in manifest.
    if nmf_url is None:
      return filename
    # Look for the module relative to the manifest (if any),
    # then in other search paths.
    paths = []
    if self.nmf_filename != '-':
      paths.append(os.path.dirname(self.nmf_filename))
    paths.extend(self.library_paths)
    for path in paths:
      pfilename = os.path.join(path, nmf_url)
      if os.path.exists(pfilename):
        return pfilename
    # If nothing else, try the path directly.
    return filename

  def _DecodeAddressSegment(self, segments, address):
    """Convert an address to a segment relative one, plus filename.

    Args:
      segments: a list of phdr segments.
      address: a process wide code address.
    Returns:
      A tuple of filename and segment relative address.
    """
    for segment in segments:
      for phdr in segment['dlpi_phdr']:
        start = segment['dlpi_addr'] + phdr['p_vaddr']
        end = start + phdr['p_memsz']
        if address >= start and address < end:
          return (segment['dlpi_name'], address - segment['dlpi_addr'])
    return ('(null)', address)

  def _Addr2Line(self, segments, address):
    """Use addr2line to decode a code address.

    Args:
      segments: A list of phdr segments.
      address: a code address.
    Returns:
      A list of dicts containing: function, filename, lineno.
    """
    filename, address = self._DecodeAddressSegment(segments, address)
    filename = self._SelectModulePath(filename)
    if not os.path.exists(filename):
      return [{
          'function': 'Unknown_function',
          'filename': 'unknown_file',
          'lineno': -1,
      }]
    # Use address - 1 to get the call site instead of the line after.
    address -= 1
    cmd = [
        self.addr2line, '-f', '--inlines', '-e', filename, '0x%08x' % address,
    ]
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    process_stdout, _ = process.communicate()
    assert process.returncode == 0
    lines = process_stdout.splitlines()
    assert len(lines) % 2 == 0
    results = []
    for index in xrange(len(lines) / 2):
      func = lines[index * 2]
      afilename, lineno = lines[index * 2 + 1].split(':', 1)
      results.append({
          'function': func,
          'filename': afilename,
          'lineno': int(lineno),
      })
    return results

  def Decode(self, text):
    core = json.loads(text)
    for frame in core['frames']:
      frame['scopes'] = self._Addr2Line(core['segments'], frame['prog_ctr'])
    return core


  def LoadAndDecode(self, core_path):
    """Given a core.json file, load and embellish with decoded addresses.

    Args:
      core_path: source file containing a dump.
    Returns:
      An embelished core dump dict (decoded code addresses).
    """
    core = json.load(open(core_path))
    for frame in core['frames']:
      frame['scopes'] = self._Addr2Line(core['segments'], frame['prog_ctr'])
    return core

  def StackTrace(self, info):
    """Convert a decoded core.json dump to a simple stack trace.

    Args:
      info: core.json info with decoded code addresses.
    Returns:
      A list of dicts with filename, lineno, function (deepest first).
    """
    trace = []
    for frame in info['frames']:
      for scope in frame['scopes']:
        trace.append(scope)
    return trace

  def PrintTrace(self, trace, out):
    """Print a trace to a file like object.

    Args:
      trace: A list of [filename, lineno, function] (deepest first).
      out: file like object to output the trace to.
    """
    for scope in trace:
      out.write('%s at %s:%d\n' % (
          scope['function'],
          scope['filename'],
          scope['lineno']))


def Main(args):
  parser = optparse.OptionParser(
      usage='USAGE: %prog [options] <core.json>')
  parser.add_option('-m', '--main-nexe', dest='main_nexe',
                    help='nexe to resolve NaClMain references from')
  parser.add_option('-n', '--nmf', dest='nmf_filename', default='-',
                    help='nmf to resolve references from')
  parser.add_option('-a', '--addr2line', dest='addr2line',
                    help='path to appropriate addr2line')
  parser.add_option('-L', '--library-path', dest='library_paths',
                    action='append', default=[],
                    help='path to search for shared libraries')
  parser.add_option('-p', '--platform', dest='platform',
                    help='platform in a style match nmf files')
  options, args = parser.parse_args(args)
  if len(args) != 1:
    parser.print_help()
    sys.exit(1)
  decoder = CoreDecoder(
      main_nexe=options.main_nexe,
      nmf_filename=options.nmf_filename,
      addr2line=options.add2line,
      library_paths=options.library_paths,
      platform=options.platform)
  info = decoder.LoadAndDecode(args[0])
  trace = decoder.StackTrace(info)
  decoder.PrintTrace(trace, sys.stdout)


if __name__ == '__main__':
  Main(sys.argv[1:])