Chromium Code Reviews| Index: tools/trace/symbolize.py |
| diff --git a/tools/trace/symbolize.py b/tools/trace/symbolize.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..aaa22084e5cf911a329e6b503ffaa0c2e81c7050 |
| --- /dev/null |
| +++ b/tools/trace/symbolize.py |
| @@ -0,0 +1,281 @@ |
| +#!/usr/bin/env python |
|
Primiano Tucci (use gerrit)
2016/04/01 15:56:28
not sure whether this is the right folder for this
|
| +# Copyright (c) 2016 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 argparse |
| +import bisect |
| +import collections |
| +import gzip |
| +import json |
| +import os |
| +import posixpath |
| +import sys |
| + |
| +# This is how tools/binary_size/run_binary_size_analysis.py does it. |
| +# See http://crbug.com/375725 |
| +elf_symbolizer_path = os.path.abspath(os.path.join( |
| + os.path.dirname(__file__), |
| + '..', |
| + '..', |
| + 'build', |
| + 'android', |
| + 'pylib')) |
| +sys.path.append(elf_symbolizer_path) |
| +import symbols.elf_symbolizer as elf_symbolizer # pylint: disable=F0401 |
| + |
| + |
| +class ProcessMemoryMaps(object): |
| + Region = collections.namedtuple( |
| + 'Region', |
| + ['start_address', 'end_address', 'file_name']) |
| + |
| + def __init__(self, value): |
| + self._regions = [] |
| + for region_value in value['vm_regions']: |
| + start_address = long(region_value['sa'], 16) |
| + size = long(region_value['sz'], 16) |
| + self._regions.append(self.Region( |
| + start_address, |
| + start_address + size, |
| + region_value['mf'])) |
| + |
| + self._regions.sort() |
| + |
| + # Remove duplicates; check for overlaps |
| + region_index = 0 |
| + while region_index < len(self._regions): |
| + region = self._regions[region_index] |
| + if region_index != 0: |
| + last_region = self._regions[region_index - 1] |
| + if last_region == region: |
| + del self._regions[region_index] |
| + continue |
| + |
| + assert region.start_address >= last_region.end_address, \ |
| + 'Regions {} and {} overlap.'.format(last_region, region) |
| + region_index += 1 |
| + |
| + @property |
| + def regions(self): |
| + return self._regions |
| + |
| + def FindRegion(self, address): |
| + if not self._regions: |
| + return None |
| + region_index = bisect.bisect_left( |
| + self._regions, self.Region(address, None, None)) |
| + region = self._regions[region_index] |
| + if region.start_address > address and region_index != 0: |
| + region_index -= 1 |
| + region = self._regions[region_index] |
| + if address >= region.start_address and address < region.end_address: |
| + return region |
| + return None |
| + |
| + |
| +class StackFrames(object): |
| + def __init__(self, value): |
| + self._frames_value = value |
| + |
| + def CollectPCs(self): |
| + return [pc for _, pc in self._IteratePCs()] |
| + |
| + def SymbolizePCs(self, pc_symbol_map): |
| + symbolized = False |
| + for frame, pc in self._IteratePCs(): |
| + if pc in pc_symbol_map: |
| + frame['name'] = pc_symbol_map[pc] |
| + symbolized = True |
| + return symbolized |
| + |
| + # Details |
| + |
| + _PC_TAG = 'pc:' |
| + |
| + def _ParsePC(self, name): |
| + if not name.startswith(self._PC_TAG): |
| + return None |
| + return long(name[len(self._PC_TAG):], 16) |
| + |
| + def _IteratePCs(self): |
| + for frame in self._frames_value.itervalues(): |
| + pc = self._ParsePC(frame['name']) |
| + if pc is not None: |
| + yield (frame, pc) |
| + |
| + |
| +class Symbolizer(object): |
| + def __init__(self, binary_path, addr2line_path): |
| + self._elf_symbolizer = None |
| + self._pc_symbol_map = {} |
| + self._failed_pcs = set() |
| + self._queued_pcs = set() |
| + |
| + if os.path.isfile(binary_path): |
| + self._elf_symbolizer = elf_symbolizer.ELFSymbolizer( |
| + binary_path, |
| + addr2line_path, |
| + self._SymbolizerCallback) |
| + |
| + @property |
| + def pc_symbol_map(self): |
| + return self._pc_symbol_map |
| + |
| + @property |
| + def failed_pcs(self): |
| + return self._failed_pcs |
| + |
| + def SymbolizeAsync(self, pc, region): |
| + if self._elf_symbolizer is None: |
| + self._failed_pcs.add(pc) |
| + return |
| + |
| + if pc not in self._queued_pcs: |
| + self._elf_symbolizer.SymbolizeAsync(int(pc - region.start_address), pc) |
| + self._queued_pcs.add(pc) |
| + |
| + def Join(self): |
| + if self._elf_symbolizer is None: |
| + return |
| + |
| + self._elf_symbolizer.Join() |
| + |
| + # Details |
| + |
| + def _SymbolizerCallback(self, sym_info, pc): |
| + self._queued_pcs.remove(pc) |
| + if sym_info.name: |
| + self._pc_symbol_map[pc] = sym_info.name |
| + else: |
| + self._failed_pcs.add(pc) |
| + |
| + |
| +class Process(object): |
| + def __init__(self, pid): |
| + self.pid = pid |
| + self.name = None |
| + self.mmaps = None |
| + self.stack_frames = None |
| + |
| + |
| +def CollectProcesses(trace): |
| + process_map = {} |
| + |
| + for event in trace['traceEvents']: |
| + name = event.get('name') |
| + if not name: |
| + continue |
| + |
| + pid = event['pid'] |
| + process = process_map.get(pid) |
| + if process is None: |
| + process = Process(pid) |
| + process_map[pid] = process |
| + |
| + if name == 'process_name': |
| + process.name = event['args']['name'] |
| + elif name == 'stackFrames': |
| + value = event['args']['stackFrames'] |
| + process.stack_frames = StackFrames(value) |
| + elif name == 'periodic_interval': |
| + value = event['args']['dumps'].get('process_mmaps') |
| + if value: |
| + process.mmaps = ProcessMemoryMaps(value) |
| + |
| + processes = [] |
| + for process in process_map.itervalues(): |
| + if process.mmaps is not None and process.stack_frames is not None: |
| + processes.append(process) |
| + |
| + return processes |
| + |
| + |
| +def SymbolizeProcess(process, addr2line_path): |
| + pcs = process.stack_frames.CollectPCs() |
| + if not pcs: |
| + return False |
| + |
| + print 'Symbolizing {} ({})...'.format(process.name, process.pid) |
| + |
| + def _SubPrintf(message, *args): |
| + print (' ' + message).format(*args) |
| + |
| + symbolizer_map = {} |
| + unresolved_pcs = set() |
| + for pc in pcs: |
| + region = process.mmaps.FindRegion(pc) |
| + if region is None: |
| + unresolved_pcs.add(pc) |
| + continue |
| + symbolizer = symbolizer_map.get(region.file_name) |
| + if symbolizer is None: |
| + symbolizer = Symbolizer(region.file_name, addr2line_path) |
| + symbolizer_map[region.file_name] = symbolizer |
| + symbolizer.SymbolizeAsync(pc, region) |
| + |
| + if unresolved_pcs: |
| + _SubPrintf('{} PCs were not resolved.', len(unresolved_pcs)) |
| + |
| + pc_symbol_map = {} |
| + for file_path, symbolizer in symbolizer_map.iteritems(): |
| + symbolizer.Join() |
| + _SubPrintf('{}: {} PCs symbolized ({} failed)', |
| + file_path, |
| + len(symbolizer.pc_symbol_map), |
| + len(symbolizer.failed_pcs)) |
| + pc_symbol_map.update(symbolizer.pc_symbol_map) |
| + |
| + return process.stack_frames.SymbolizePCs(pc_symbol_map) |
| + |
| + |
| +def FindInSystemPath(binary_name): |
| + paths = os.environ["PATH"].split(os.pathsep) |
| + for path in paths: |
| + binary_path = os.path.join(path, binary_name) |
| + if os.path.isfile(binary_path): |
| + return binary_path |
| + return None |
| + |
| + |
| +def main(): |
| + BACKUP_FILE_TAG = '.BACKUP' |
| + |
| + parser = argparse.ArgumentParser() |
| + parser.add_argument('file', nargs=1, |
| + help='Trace file to symbolize (.json or .json.gz)') |
| + parser.add_argument('--no-backup', action='store_true', |
| + help="Don't create {} files".format(BACKUP_FILE_TAG)) |
| + options = parser.parse_args() |
| + |
| + trace_file_path = options.file[0] |
| + def _OpenTraceFile(mode): |
| + if trace_file_path.endswith('.gz'): |
| + return gzip.open(trace_file_path, mode) |
| + else: |
| + return open(trace_file_path, mode) |
| + |
| + with _OpenTraceFile('rb') as trace_file: |
| + trace = json.load(trace_file) |
| + |
| + processes = CollectProcesses(trace) |
| + |
| + addr2line_path = FindInSystemPath('addr2line') |
| + |
| + update_trace = False |
| + for process in processes: |
| + if SymbolizeProcess(process, addr2line_path): |
| + update_trace = True |
| + |
| + if update_trace: |
| + if not options.no_backup: |
| + print 'Backing up trace file...' |
| + os.rename(trace_file_path, trace_file_path + BACKUP_FILE_TAG) |
| + |
| + print 'Updating trace file...' |
| + with _OpenTraceFile('wb') as trace_file: |
| + json.dump(trace, trace_file) |
| + |
| + |
| +if __name__ == '__main__': |
| + main() |