mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-28 00:06:09 +03:00
231 lines
8.5 KiB
Python
231 lines
8.5 KiB
Python
# Copyright 2018 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
# Matches the coarse syntax of a backtrace entry.
|
|
_BACKTRACE_PREFIX_RE = re.compile(r'(\[[0-9.]+\] )?bt#(?P<frame_id>\d+): ')
|
|
|
|
# Matches the specific fields of a backtrace entry.
|
|
# Back-trace line matcher/parser assumes that 'pc' is always present, and
|
|
# expects that 'sp' and ('binary','pc_offset') may also be provided.
|
|
_BACKTRACE_ENTRY_RE = re.compile(
|
|
r'pc 0(?:x[0-9a-f]+)?' +
|
|
r'(?: sp 0x[0-9a-f]+)?' +
|
|
r'(?: \((?P<binary>\S+),(?P<pc_offset>0x[0-9a-f]+)\))?$')
|
|
|
|
|
|
def _GetUnstrippedPath(path):
|
|
"""If there is a binary located at |path|, returns a path to its unstripped
|
|
source.
|
|
|
|
Returns None if |path| isn't a binary or doesn't exist in the lib.unstripped
|
|
or exe.unstripped directories."""
|
|
|
|
if path.endswith('.so'):
|
|
maybe_unstripped_path = os.path.normpath(
|
|
os.path.join(path, os.path.pardir, 'lib.unstripped',
|
|
os.path.basename(path)))
|
|
else:
|
|
maybe_unstripped_path = os.path.normpath(
|
|
os.path.join(path, os.path.pardir, 'exe.unstripped',
|
|
os.path.basename(path)))
|
|
|
|
if not os.path.exists(maybe_unstripped_path):
|
|
return None
|
|
|
|
with open(maybe_unstripped_path, 'rb') as f:
|
|
file_tag = f.read(4)
|
|
if file_tag != '\x7fELF':
|
|
logging.warn('Expected an ELF binary: ' + maybe_unstripped_path)
|
|
return None
|
|
|
|
return maybe_unstripped_path
|
|
|
|
|
|
def FilterStream(stream, package_name, manifest_path, output_dir):
|
|
"""Looks for backtrace lines from an iterable |stream| and symbolizes them.
|
|
Yields a stream of strings with symbolized entries replaced."""
|
|
|
|
return _SymbolizerFilter(package_name,
|
|
manifest_path,
|
|
output_dir).SymbolizeStream(stream)
|
|
|
|
|
|
class _SymbolizerFilter(object):
|
|
"""Adds backtrace symbolization capabilities to a process output stream."""
|
|
|
|
def __init__(self, package_name, manifest_path, output_dir):
|
|
self._symbols_mapping = {}
|
|
self._output_dir = output_dir
|
|
self._package_name = package_name
|
|
|
|
# Compute remote/local path mappings using the manifest data.
|
|
for next_line in open(manifest_path):
|
|
target, source = next_line.strip().split('=')
|
|
stripped_binary_path = _GetUnstrippedPath(os.path.join(output_dir,
|
|
source))
|
|
if not stripped_binary_path:
|
|
continue
|
|
|
|
self._symbols_mapping[os.path.basename(target)] = stripped_binary_path
|
|
self._symbols_mapping[target] = stripped_binary_path
|
|
if target == 'bin/app':
|
|
self._symbols_mapping[package_name] = stripped_binary_path
|
|
logging.debug('Symbols: %s -> %s' % (source, target))
|
|
|
|
def _SymbolizeEntries(self, entries):
|
|
"""Symbolizes the parsed backtrace |entries| by calling addr2line.
|
|
|
|
Returns a set of (frame_id, result) pairs."""
|
|
|
|
filename_re = re.compile(r'at ([-._a-zA-Z0-9/+]+):(\d+)')
|
|
|
|
# Use addr2line to symbolize all the |pc_offset|s in |entries| in one go.
|
|
# Entries with no |debug_binary| are also processed here, so that we get
|
|
# consistent output in that case, with the cannot-symbolize case.
|
|
addr2line_output = None
|
|
if entries[0].has_key('debug_binary'):
|
|
addr2line_args = (['addr2line', '-Cipf', '-p',
|
|
'--exe=' + entries[0]['debug_binary']] +
|
|
map(lambda entry: entry['pc_offset'], entries))
|
|
addr2line_output = subprocess.check_output(addr2line_args).splitlines()
|
|
assert addr2line_output
|
|
|
|
results = {}
|
|
for entry in entries:
|
|
raw, frame_id = entry['raw'], entry['frame_id']
|
|
prefix = '#%s: ' % frame_id
|
|
|
|
if not addr2line_output:
|
|
# Either there was no addr2line output, or too little of it.
|
|
filtered_line = raw
|
|
else:
|
|
output_line = addr2line_output.pop(0)
|
|
|
|
# Relativize path to the current working (output) directory if we see
|
|
# a filename.
|
|
def RelativizePath(m):
|
|
relpath = os.path.relpath(os.path.normpath(m.group(1)))
|
|
return 'at ' + relpath + ':' + m.group(2)
|
|
filtered_line = filename_re.sub(RelativizePath, output_line)
|
|
|
|
if '??' in filtered_line.split():
|
|
# If symbolization fails just output the raw backtrace.
|
|
filtered_line = raw
|
|
else:
|
|
# Release builds may inline things, resulting in "(inlined by)" lines.
|
|
inlined_by_prefix = " (inlined by)"
|
|
while (addr2line_output and
|
|
addr2line_output[0].startswith(inlined_by_prefix)):
|
|
inlined_by_line = \
|
|
'\n' + (' ' * len(prefix)) + addr2line_output.pop(0)
|
|
filtered_line += filename_re.sub(RelativizePath, inlined_by_line)
|
|
|
|
results[entry['frame_id']] = prefix + filtered_line
|
|
|
|
return results
|
|
|
|
def _LookupDebugBinary(self, entry):
|
|
"""Looks up the binary listed in |entry| in the |_symbols_mapping|.
|
|
Returns the corresponding host-side binary's filename, or None."""
|
|
|
|
binary = entry['binary']
|
|
if not binary:
|
|
return None
|
|
|
|
app_prefix = 'app:'
|
|
if binary.startswith(app_prefix):
|
|
binary = binary[len(app_prefix):]
|
|
|
|
# We change directory into /system/ before running the target executable, so
|
|
# all paths are relative to "/system/", and will typically start with "./".
|
|
# Some crashes still uses the full filesystem path, so cope with that, too.
|
|
pkg_prefix = '/pkg/'
|
|
cwd_prefix = './'
|
|
if binary.startswith(cwd_prefix):
|
|
binary = binary[len(cwd_prefix):]
|
|
elif binary.startswith(pkg_prefix):
|
|
binary = binary[len(pkg_prefix):]
|
|
# Allow other paths to pass-through; sometimes neither prefix is present.
|
|
|
|
if binary in self._symbols_mapping:
|
|
return self._symbols_mapping[binary]
|
|
|
|
# |binary| may be truncated by the crashlogger, so if there is a unique
|
|
# match for the truncated name in |symbols_mapping|, use that instead.
|
|
matches = filter(lambda x: x.startswith(binary),
|
|
self._symbols_mapping.keys())
|
|
if len(matches) == 1:
|
|
return self._symbols_mapping[matches[0]]
|
|
|
|
return None
|
|
|
|
def _SymbolizeBacktrace(self, backtrace):
|
|
"""Group |backtrace| entries according to the associated binary, and locate
|
|
the path to the debug symbols for that binary, if any."""
|
|
|
|
batches = {}
|
|
|
|
for entry in backtrace:
|
|
debug_binary = self._LookupDebugBinary(entry)
|
|
if debug_binary:
|
|
entry['debug_binary'] = debug_binary
|
|
batches.setdefault(debug_binary, []).append(entry)
|
|
|
|
# Run _SymbolizeEntries on each batch and collate the results.
|
|
symbolized = {}
|
|
for batch in batches.itervalues():
|
|
symbolized.update(self._SymbolizeEntries(batch))
|
|
|
|
# Map each entry to its symbolized form, by frame-id, and return the list.
|
|
return map(lambda entry: symbolized[entry['frame_id']], backtrace)
|
|
|
|
def SymbolizeStream(self, stream):
|
|
"""Creates a symbolized logging stream object using the output from
|
|
|stream|."""
|
|
|
|
# A buffer of backtrace entries awaiting symbolization, stored as dicts:
|
|
# raw: The original back-trace line that followed the prefix.
|
|
# frame_id: backtrace frame number (starting at 0).
|
|
# binary: path to executable code corresponding to the current frame.
|
|
# pc_offset: memory offset within the executable.
|
|
backtrace_entries = []
|
|
|
|
# Read from the stream until we hit EOF.
|
|
for line in stream:
|
|
line = line.rstrip()
|
|
|
|
# Look for the back-trace prefix, otherwise just emit the line.
|
|
matched = _BACKTRACE_PREFIX_RE.match(line)
|
|
if not matched:
|
|
yield line
|
|
continue
|
|
backtrace_line = line[matched.end():]
|
|
|
|
# If this was the end of a back-trace then symbolize and emit it.
|
|
frame_id = matched.group('frame_id')
|
|
if backtrace_line == 'end':
|
|
if backtrace_entries:
|
|
for processed in self._SymbolizeBacktrace(backtrace_entries):
|
|
yield processed
|
|
backtrace_entries = []
|
|
continue
|
|
|
|
# Parse the program-counter offset, etc into |backtrace_entries|.
|
|
matched = _BACKTRACE_ENTRY_RE.match(backtrace_line)
|
|
if matched:
|
|
# |binary| and |pc_offset| will be None if not present.
|
|
backtrace_entries.append(
|
|
{'raw': backtrace_line, 'frame_id': frame_id,
|
|
'binary': matched.group('binary'),
|
|
'pc_offset': matched.group('pc_offset')})
|
|
else:
|
|
backtrace_entries.append(
|
|
{'raw': backtrace_line, 'frame_id': frame_id,
|
|
'binary': None, 'pc_offset': None})
|