| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. | 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import argparse | 6 import argparse |
| 7 import bisect | 7 import bisect |
| 8 import collections | 8 import collections |
| 9 import gzip | 9 import gzip |
| 10 import json | 10 import json |
| 11 import os | 11 import os |
| 12 import re | 12 import re |
| 13 import subprocess | 13 import subprocess |
| 14 import sys | 14 import sys |
| 15 | 15 |
| 16 _SYMBOLS_PATH = os.path.abspath(os.path.join( | 16 _SYMBOLS_PATH = os.path.abspath(os.path.join( |
| 17 os.path.dirname(os.path.realpath(__file__)), | 17 os.path.dirname(os.path.realpath(__file__)), |
| 18 '..', | 18 '..', |
| 19 'third_party', | 19 'third_party', |
| 20 'symbols')) | 20 'symbols')) |
| 21 sys.path.append(_SYMBOLS_PATH) | 21 sys.path.append(_SYMBOLS_PATH) |
| 22 # pylint: disable=import-error | 22 # pylint: disable=import-error |
| 23 import symbols.elf_symbolizer as elf_symbolizer | 23 import symbols.elf_symbolizer as elf_symbolizer |
| 24 | 24 |
| 25 import symbolize_trace_atos_regex |
| 26 |
| 25 | 27 |
| 26 # Relevant trace event phases from Chromium's | 28 # Relevant trace event phases from Chromium's |
| 27 # src/base/trace_event/common/trace_event_common.h. | 29 # src/base/trace_event/common/trace_event_common.h. |
| 28 TRACE_EVENT_PHASE_METADATA = 'M' | 30 TRACE_EVENT_PHASE_METADATA = 'M' |
| 29 TRACE_EVENT_PHASE_MEMORY_DUMP = 'v' | 31 TRACE_EVENT_PHASE_MEMORY_DUMP = 'v' |
| 30 | 32 |
| 31 | 33 |
| 32 # Matches Android library paths, supports both K (/data/app-lib/<>/lib.so) | 34 # Matches Android library paths, supports both K (/data/app-lib/<>/lib.so) |
| 33 # as well as L+ (/data/app/<>/lib/<>/lib.so). Library name is available | 35 # as well as L+ (/data/app/<>/lib/<>/lib.so). Library name is available |
| 34 # via 'name' group. | 36 # via 'name' group. |
| (...skipping 10 matching lines...) Expand all Loading... |
| 45 | 47 |
| 46 def FindInSystemPath(binary_name): | 48 def FindInSystemPath(binary_name): |
| 47 paths = os.environ['PATH'].split(os.pathsep) | 49 paths = os.environ['PATH'].split(os.pathsep) |
| 48 for path in paths: | 50 for path in paths: |
| 49 binary_path = os.path.join(path, binary_name) | 51 binary_path = os.path.join(path, binary_name) |
| 50 if os.path.isfile(binary_path): | 52 if os.path.isfile(binary_path): |
| 51 return binary_path | 53 return binary_path |
| 52 return None | 54 return None |
| 53 | 55 |
| 54 | 56 |
| 57 class Symbolizer(object): |
| 58 # Encapsulates platform-specific symbolization logic. |
| 59 def __init__(self): |
| 60 self.is_mac = sys.platform == 'darwin' |
| 61 if self.is_mac: |
| 62 self.binary = 'atos' |
| 63 self._matcher = symbolize_trace_atos_regex.AtosRegexMatcher() |
| 64 else: |
| 65 self.binary = 'addr2line' |
| 66 self.symbolizer_path = FindInSystemPath(self.binary) |
| 67 |
| 68 def _SymbolizeLinuxAndAndroid(self, symfile): |
| 69 def _SymbolizerCallback(sym_info, frames): |
| 70 # Unwind inline chain to the top. |
| 71 while sym_info.inlined_by: |
| 72 sym_info = sym_info.inlined_by |
| 73 |
| 74 symbolized_name = sym_info.name if sym_info.name else unsymbolized_name |
| 75 for frame in frames: |
| 76 frame.name = symbolized_name |
| 77 |
| 78 symbolizer = elf_symbolizer.ELFSymbolizer(symfile.symbolizable_path, |
| 79 self.symbolizer_path, |
| 80 _SymbolizerCallback, |
| 81 inlines=True) |
| 82 |
| 83 for address, frames in symfile.frames_by_address.iteritems(): |
| 84 # SymbolizeAsync() asserts that the type of address is int. We operate |
| 85 # on longs (since they are raw pointers possibly from 64-bit processes). |
| 86 # It's OK to cast here because we're passing relative PC, which should |
| 87 # always fit into int. |
| 88 symbolizer.SymbolizeAsync(int(address), frames) |
| 89 |
| 90 symbolizer.Join() |
| 91 |
| 92 def _SymbolizeMac(self, symfile): |
| 93 chars_max = int(subprocess.check_output("getconf ARG_MAX", shell=True)) |
| 94 |
| 95 # 16 for the address, 2 for "0x", 1 for the space |
| 96 chars_per_address = 19 |
| 97 |
| 98 cmd_base = [self.symbolizer_path, '-arch', 'x86_64', '-l', |
| 99 '0x0', '-o' , symfile.symbolizable_path] |
| 100 chars_for_other_arguments = len(' '.join(cmd_base)) + 1 |
| 101 |
| 102 # The maximum number of inputs that can be processed at once is limited by |
| 103 # ARG_MAX. This currently evalutes to ~13000 on macOS. |
| 104 max_inputs = (chars_max - chars_for_other_arguments) / chars_per_address |
| 105 |
| 106 all_keys = symfile.frames_by_address.keys() |
| 107 processed_keys_count = 0 |
| 108 while len(all_keys): |
| 109 input_count = min(len(all_keys), max_inputs) |
| 110 keys_to_process = all_keys[0:input_count] |
| 111 |
| 112 cmd = list(cmd_base) |
| 113 cmd.extend([hex(int(x)) for x in keys_to_process]) |
| 114 output_array = subprocess.check_output(cmd).split('\n') |
| 115 for i in range(len(keys_to_process)): |
| 116 for frame in symfile.frames_by_address.values()[i + processed_keys_count
]: |
| 117 frame.name = self._matcher.Match(output_array[i]) |
| 118 processed_keys_count += len(keys_to_process) |
| 119 all_keys = all_keys[input_count:] |
| 120 |
| 121 def Symbolize(self, symfile): |
| 122 if self.is_mac: |
| 123 self._SymbolizeMac(symfile) |
| 124 else: |
| 125 self._SymbolizeLinuxAndAndroid(symfile) |
| 126 |
| 127 |
| 55 def IsSymbolizableFile(file_path): | 128 def IsSymbolizableFile(file_path): |
| 56 result = subprocess.check_output(['file', '-0', file_path]) | 129 result = subprocess.check_output(['file', '-0', file_path]) |
| 57 type_string = result[result.find('\0') + 1:] | 130 type_string = result[result.find('\0') + 1:] |
| 58 return bool(re.match(r'\: (ELF|Mach-O) (32|64)-bit\b', type_string)) | 131 return bool(re.match(r'.*(ELF|Mach-O) (32|64)-bit\b.*', |
| 132 type_string, re.DOTALL)) |
| 59 | 133 |
| 60 | 134 |
| 61 class ProcessMemoryMaps(object): | 135 class ProcessMemoryMaps(object): |
| 62 """Represents 'process_mmaps' trace file entry.""" | 136 """Represents 'process_mmaps' trace file entry.""" |
| 63 | 137 |
| 64 class Region(object): | 138 class Region(object): |
| 65 def __init__(self, start_address, size, file_path): | 139 def __init__(self, start_address, size, file_path): |
| 66 self._start_address = start_address | 140 self._start_address = start_address |
| 67 self._size = size | 141 self._size = size |
| 68 self._file_path = file_path | 142 self._file_path = file_path |
| (...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 264 symfile = symfile_by_path.get(region.file_path) | 338 symfile = symfile_by_path.get(region.file_path) |
| 265 if symfile is None: | 339 if symfile is None: |
| 266 symfile = SymbolizableFile(region.file_path) | 340 symfile = SymbolizableFile(region.file_path) |
| 267 symfile_by_path[symfile.path] = symfile | 341 symfile_by_path[symfile.path] = symfile |
| 268 | 342 |
| 269 relative_pc = frame.pc - region.start_address | 343 relative_pc = frame.pc - region.start_address |
| 270 symfile.frames_by_address[relative_pc].append(frame) | 344 symfile.frames_by_address[relative_pc].append(frame) |
| 271 return symfile_by_path.values() | 345 return symfile_by_path.values() |
| 272 | 346 |
| 273 | 347 |
| 274 def SymbolizeFiles(symfiles, addr2line_path): | 348 def SymbolizeFiles(symfiles, symbolizer): |
| 275 """Symbolizes each file in the given list of SymbolizableFiles | 349 """Symbolizes each file in the given list of SymbolizableFiles |
| 276 and updates stack frames with symbolization results.""" | 350 and updates stack frames with symbolization results.""" |
| 277 print 'Symbolizing...' | 351 print 'Symbolizing...' |
| 278 | 352 |
| 279 def _SubPrintf(message, *args): | 353 def _SubPrintf(message, *args): |
| 280 print (' ' + message).format(*args) | 354 print (' ' + message).format(*args) |
| 281 | 355 |
| 282 symbolized = False | 356 symbolized = False |
| 283 for symfile in symfiles: | 357 for symfile in symfiles: |
| 284 unsymbolized_name = '<{}>'.format( | 358 unsymbolized_name = '<{}>'.format( |
| 285 symfile.path if symfile.path else 'unnamed') | 359 symfile.path if symfile.path else 'unnamed') |
| 286 | 360 |
| 287 problem = None | 361 problem = None |
| 288 if not os.path.isabs(symfile.symbolizable_path): | 362 if not os.path.isabs(symfile.symbolizable_path): |
| 289 problem = 'not a file' | 363 problem = 'not a file' |
| 290 elif not os.path.isfile(symfile.symbolizable_path): | 364 elif not os.path.isfile(symfile.symbolizable_path): |
| 291 problem = "file doesn't exist" | 365 problem = "file doesn't exist" |
| 292 elif not IsSymbolizableFile(symfile.symbolizable_path): | 366 elif not IsSymbolizableFile(symfile.symbolizable_path): |
| 293 problem = 'file is not symbolizable' | 367 problem = 'file is not symbolizable' |
| 294 if problem: | 368 if problem: |
| 295 _SubPrintf("Won't symbolize {} PCs for '{}': {}.", | 369 _SubPrintf("Won't symbolize {} PCs for '{}': {}.", |
| 296 len(symfile.frames_by_address), | 370 len(symfile.frames_by_address), |
| 297 symfile.symbolizable_path, | 371 symfile.symbolizable_path, |
| 298 problem) | 372 problem) |
| 299 for frames in symfile.frames_by_address.itervalues(): | 373 for frames in symfile.frames_by_address.itervalues(): |
| 300 for frame in frames: | 374 for frame in frames: |
| 301 frame.name = unsymbolized_name | 375 frame.name = unsymbolized_name |
| 302 continue | 376 continue |
| 303 | 377 |
| 304 def _SymbolizerCallback(sym_info, frames): | |
| 305 # Unwind inline chain to the top. | |
| 306 while sym_info.inlined_by: | |
| 307 sym_info = sym_info.inlined_by | |
| 308 | |
| 309 symbolized_name = sym_info.name if sym_info.name else unsymbolized_name | |
| 310 for frame in frames: | |
| 311 frame.name = symbolized_name | |
| 312 | |
| 313 symbolizer = elf_symbolizer.ELFSymbolizer(symfile.symbolizable_path, | |
| 314 addr2line_path, | |
| 315 _SymbolizerCallback, | |
| 316 inlines=True) | |
| 317 | |
| 318 _SubPrintf('Symbolizing {} PCs from {}...', | 378 _SubPrintf('Symbolizing {} PCs from {}...', |
| 319 len(symfile.frames_by_address), | 379 len(symfile.frames_by_address), |
| 320 symfile.path) | 380 symfile.path) |
| 321 | 381 |
| 322 for address, frames in symfile.frames_by_address.iteritems(): | 382 symbolizer.Symbolize(symfile) |
| 323 # SymbolizeAsync() asserts that the type of address is int. We operate | |
| 324 # on longs (since they are raw pointers possibly from 64-bit processes). | |
| 325 # It's OK to cast here because we're passing relative PC, which should | |
| 326 # always fit into int. | |
| 327 symbolizer.SymbolizeAsync(int(address), frames) | |
| 328 | |
| 329 symbolizer.Join() | |
| 330 symbolized = True | 383 symbolized = True |
| 331 | 384 |
| 332 return symbolized | 385 return symbolized |
| 333 | 386 |
| 334 | 387 |
| 335 def HaveFilesFromAndroid(symfiles): | 388 def HaveFilesFromAndroid(symfiles): |
| 336 return any(ANDROID_PATH_MATCHER.match(f.path) for f in symfiles) | 389 return any(ANDROID_PATH_MATCHER.match(f.path) for f in symfiles) |
| 337 | 390 |
| 338 | 391 |
| 339 def RemapAndroidFiles(symfiles, output_path): | 392 def RemapAndroidFiles(symfiles, output_path): |
| (...skipping 25 matching lines...) Expand all Loading... |
| 365 'as out/Debug. Only needed for Android.') | 418 'as out/Debug. Only needed for Android.') |
| 366 options = parser.parse_args() | 419 options = parser.parse_args() |
| 367 | 420 |
| 368 trace_file_path = options.file | 421 trace_file_path = options.file |
| 369 def _OpenTraceFile(mode): | 422 def _OpenTraceFile(mode): |
| 370 if trace_file_path.endswith('.gz'): | 423 if trace_file_path.endswith('.gz'): |
| 371 return gzip.open(trace_file_path, mode + 'b') | 424 return gzip.open(trace_file_path, mode + 'b') |
| 372 else: | 425 else: |
| 373 return open(trace_file_path, mode + 't') | 426 return open(trace_file_path, mode + 't') |
| 374 | 427 |
| 375 addr2line_path = FindInSystemPath('addr2line') | 428 symbolizer = Symbolizer() |
| 376 if addr2line_path is None: | 429 if symbolizer.symbolizer_path is None: |
| 377 sys.exit("Can't symbolize - no addr2line in PATH.") | 430 sys.exit("Can't symbolize - no %s in PATH." % symbolizer.binary) |
| 378 | 431 |
| 379 print 'Reading trace file...' | 432 print 'Reading trace file...' |
| 380 with _OpenTraceFile('r') as trace_file: | 433 with _OpenTraceFile('r') as trace_file: |
| 381 trace = json.load(trace_file) | 434 trace = json.load(trace_file) |
| 382 | 435 |
| 383 processes = CollectProcesses(trace) | 436 processes = CollectProcesses(trace) |
| 384 symfiles = ResolveSymbolizableFiles(processes) | 437 symfiles = ResolveSymbolizableFiles(processes) |
| 385 | 438 |
| 386 # Android trace files don't have any indication they are from Android. | 439 # Android trace files don't have any indication they are from Android. |
| 387 # So we're checking for Android-specific paths. | 440 # So we're checking for Android-specific paths. |
| 388 if HaveFilesFromAndroid(symfiles): | 441 if HaveFilesFromAndroid(symfiles): |
| 389 if not options.output_directory: | 442 if not options.output_directory: |
| 390 parser.error('The trace file appears to be from Android. Please ' | 443 parser.error('The trace file appears to be from Android. Please ' |
| 391 "specify output directory (e.g. 'out/Debug') to properly " | 444 "specify output directory (e.g. 'out/Debug') to properly " |
| 392 'symbolize it.') | 445 'symbolize it.') |
| 393 RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory)) | 446 RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory)) |
| 394 | 447 |
| 395 if SymbolizeFiles(symfiles, addr2line_path): | 448 if SymbolizeFiles(symfiles, symbolizer): |
| 396 if options.backup: | 449 if options.backup: |
| 397 backup_file_path = trace_file_path + BACKUP_FILE_TAG | 450 backup_file_path = trace_file_path + BACKUP_FILE_TAG |
| 398 print 'Backing up trace file to {}...'.format(backup_file_path) | 451 print 'Backing up trace file to {}...'.format(backup_file_path) |
| 399 os.rename(trace_file_path, backup_file_path) | 452 os.rename(trace_file_path, backup_file_path) |
| 400 | 453 |
| 401 print 'Updating trace file...' | 454 print 'Updating trace file...' |
| 402 with _OpenTraceFile('w') as trace_file: | 455 with _OpenTraceFile('w') as trace_file: |
| 403 json.dump(trace, trace_file) | 456 json.dump(trace, trace_file) |
| 404 else: | 457 else: |
| 405 print 'No PCs symbolized - not updating trace file.' | 458 print 'No PCs symbolized - not updating trace file.' |
| 406 | 459 |
| 407 | 460 |
| 408 if __name__ == '__main__': | 461 if __name__ == '__main__': |
| 409 main() | 462 main() |
| OLD | NEW |