Chromium Code Reviews| 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 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 45 | 45 |
| 46 def FindInSystemPath(binary_name): | 46 def FindInSystemPath(binary_name): |
| 47 paths = os.environ['PATH'].split(os.pathsep) | 47 paths = os.environ['PATH'].split(os.pathsep) |
| 48 for path in paths: | 48 for path in paths: |
| 49 binary_path = os.path.join(path, binary_name) | 49 binary_path = os.path.join(path, binary_name) |
| 50 if os.path.isfile(binary_path): | 50 if os.path.isfile(binary_path): |
| 51 return binary_path | 51 return binary_path |
| 52 return None | 52 return None |
| 53 | 53 |
| 54 | 54 |
| 55 class Symbolizer(object): | |
| 56 # Encapsulates platform-specific symbolization logic. | |
| 57 def __init__(self): | |
| 58 self.is_mac = sys.platform == 'darwin' | |
| 59 if self.is_mac: | |
| 60 self.binary = 'atos' | |
| 61 else: | |
| 62 self.binary = 'addr2line' | |
| 63 self.symbolizer_path = FindInSystemPath(self.binary) | |
| 64 | |
| 65 def _SymbolizeLinuxAndAndroid(self, sym_file): | |
|
DmitrySkiba
2017/02/06 19:28:33
Argument should be 'symfile' (without underscore).
erikchen
2017/02/11 02:45:23
Done.
| |
| 66 def _SymbolizerCallback(sym_info, frames): | |
| 67 # Unwind inline chain to the top. | |
| 68 while sym_info.inlined_by: | |
| 69 sym_info = sym_info.inlined_by | |
| 70 | |
| 71 symbolized_name = sym_info.name if sym_info.name else unsymbolized_name | |
| 72 for frame in frames: | |
| 73 frame.name = symbolized_name | |
| 74 | |
| 75 symbolizer = elf_symbolizer.ELFSymbolizer(symfile.symbolizable_path, | |
| 76 self.symbolizer_path, | |
| 77 _SymbolizerCallback, | |
| 78 inlines=True) | |
| 79 | |
| 80 for address, frames in symfile.frames_by_address.iteritems(): | |
| 81 # SymbolizeAsync() asserts that the type of address is int. We operate | |
| 82 # on longs (since they are raw pointers possibly from 64-bit processes). | |
| 83 # It's OK to cast here because we're passing relative PC, which should | |
| 84 # always fit into int. | |
| 85 symbolizer.SymbolizeAsync(int(address), frames) | |
| 86 | |
| 87 symbolizer.Join() | |
| 88 | |
| 89 def _SymbolizeMac(self, symfile): | |
| 90 chars_max = int(subprocess.check_output("getconf ARG_MAX", shell=True)) | |
| 91 | |
| 92 # 16 for the address, 2 for "0x", 1 for the space | |
| 93 chars_per_address = 19 | |
| 94 chars_for_other_arguments = 2000 | |
|
DmitrySkiba
2017/02/06 19:28:34
Since symfile doesn't change in this function, we
erikchen
2017/02/11 02:45:23
Done.
| |
| 95 | |
| 96 # The maximum number of inputs that can be processed at once is limited by | |
| 97 # ARG_MAX. This currently evalutes to ~13000 on macOS. | |
| 98 max_inputs = (chars_max - chars_for_other_arguments) / chars_per_address | |
| 99 | |
| 100 all_keys = symfile.frames_by_address.keys() | |
| 101 while len(all_keys): | |
| 102 input_count = min(len(all_keys), max_inputs) | |
| 103 keys_to_process = all_keys[0:input_count] | |
| 104 | |
| 105 cmd = [self.symbolizer_path, '-arch', 'x86_64', '-l', | |
| 106 '0x0', '-o' , symfile.symbolizable_path] | |
| 107 cmd.extend([hex(int(x)) for x in keys_to_process]) | |
| 108 output_array = subprocess.check_output(cmd).split('\n') | |
| 109 for i in range(len(keys_to_process)): | |
| 110 for frame in symfile.frames_by_address.values()[i]: | |
|
DmitrySkiba
2017/02/06 19:28:34
|i| should include offset (number of keys already
erikchen
2017/02/11 02:45:23
Good catch, thanks. Done.
| |
| 111 frame.name = output_array[i] | |
|
DmitrySkiba
2017/02/06 19:28:34
Hmm, according to the docs, atos output can includ
erikchen
2017/02/11 02:45:23
what parsing logic are you referring to? Something
| |
| 112 all_keys = all_keys[input_count:] | |
| 113 | |
| 114 def Symbolize(self, symfile): | |
| 115 if self.is_mac: | |
| 116 self._SymbolizeMac(symfile) | |
| 117 else: | |
| 118 self._SymbolizeLinuxAndAndroid(symfile) | |
| 119 | |
| 120 | |
| 55 def IsSymbolizableFile(file_path): | 121 def IsSymbolizableFile(file_path): |
| 56 result = subprocess.check_output(['file', '-0', file_path]) | 122 result = subprocess.check_output(['file', '-0', file_path]) |
| 57 type_string = result[result.find('\0') + 1:] | 123 type_string = result[result.find('\0') + 1:] |
| 58 return bool(re.match(r'\: (ELF|Mach-O) (32|64)-bit\b', type_string)) | 124 return bool(re.match(r'.*(ELF|Mach-O) (32|64)-bit\b.*', |
| 125 type_string, re.DOTALL)) | |
| 59 | 126 |
| 60 | 127 |
| 61 class ProcessMemoryMaps(object): | 128 class ProcessMemoryMaps(object): |
| 62 """Represents 'process_mmaps' trace file entry.""" | 129 """Represents 'process_mmaps' trace file entry.""" |
| 63 | 130 |
| 64 class Region(object): | 131 class Region(object): |
| 65 def __init__(self, start_address, size, file_path): | 132 def __init__(self, start_address, size, file_path): |
| 66 self._start_address = start_address | 133 self._start_address = start_address |
| 67 self._size = size | 134 self._size = size |
| 68 self._file_path = file_path | 135 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) | 331 symfile = symfile_by_path.get(region.file_path) |
| 265 if symfile is None: | 332 if symfile is None: |
| 266 symfile = SymbolizableFile(region.file_path) | 333 symfile = SymbolizableFile(region.file_path) |
| 267 symfile_by_path[symfile.path] = symfile | 334 symfile_by_path[symfile.path] = symfile |
| 268 | 335 |
| 269 relative_pc = frame.pc - region.start_address | 336 relative_pc = frame.pc - region.start_address |
| 270 symfile.frames_by_address[relative_pc].append(frame) | 337 symfile.frames_by_address[relative_pc].append(frame) |
| 271 return symfile_by_path.values() | 338 return symfile_by_path.values() |
| 272 | 339 |
| 273 | 340 |
| 274 def SymbolizeFiles(symfiles, addr2line_path): | 341 def SymbolizeFiles(symfiles, symbolizer): |
| 275 """Symbolizes each file in the given list of SymbolizableFiles | 342 """Symbolizes each file in the given list of SymbolizableFiles |
| 276 and updates stack frames with symbolization results.""" | 343 and updates stack frames with symbolization results.""" |
| 277 print 'Symbolizing...' | 344 print 'Symbolizing...' |
| 278 | 345 |
| 279 def _SubPrintf(message, *args): | 346 def _SubPrintf(message, *args): |
| 280 print (' ' + message).format(*args) | 347 print (' ' + message).format(*args) |
| 281 | 348 |
| 282 symbolized = False | 349 symbolized = False |
| 283 for symfile in symfiles: | 350 for symfile in symfiles: |
| 284 unsymbolized_name = '<{}>'.format( | 351 unsymbolized_name = '<{}>'.format( |
| 285 symfile.path if symfile.path else 'unnamed') | 352 symfile.path if symfile.path else 'unnamed') |
| 286 | 353 |
| 287 problem = None | 354 problem = None |
| 288 if not os.path.isabs(symfile.symbolizable_path): | 355 if not os.path.isabs(symfile.symbolizable_path): |
| 289 problem = 'not a file' | 356 problem = 'not a file' |
| 290 elif not os.path.isfile(symfile.symbolizable_path): | 357 elif not os.path.isfile(symfile.symbolizable_path): |
| 291 problem = "file doesn't exist" | 358 problem = "file doesn't exist" |
| 292 elif not IsSymbolizableFile(symfile.symbolizable_path): | 359 elif not IsSymbolizableFile(symfile.symbolizable_path): |
| 293 problem = 'file is not symbolizable' | 360 problem = 'file is not symbolizable' |
| 294 if problem: | 361 if problem: |
| 295 _SubPrintf("Won't symbolize {} PCs for '{}': {}.", | 362 _SubPrintf("Won't symbolize {} PCs for '{}': {}.", |
| 296 len(symfile.frames_by_address), | 363 len(symfile.frames_by_address), |
| 297 symfile.symbolizable_path, | 364 symfile.symbolizable_path, |
| 298 problem) | 365 problem) |
| 299 for frames in symfile.frames_by_address.itervalues(): | 366 for frames in symfile.frames_by_address.itervalues(): |
| 300 for frame in frames: | 367 for frame in frames: |
| 301 frame.name = unsymbolized_name | 368 frame.name = unsymbolized_name |
| 302 continue | 369 continue |
| 303 | 370 |
| 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 {}...', | 371 _SubPrintf('Symbolizing {} PCs from {}...', |
| 319 len(symfile.frames_by_address), | 372 len(symfile.frames_by_address), |
| 320 symfile.path) | 373 symfile.path) |
| 321 | 374 |
| 322 for address, frames in symfile.frames_by_address.iteritems(): | 375 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 | 376 symbolized = True |
| 331 | 377 |
| 332 return symbolized | 378 return symbolized |
| 333 | 379 |
| 334 | 380 |
| 335 def HaveFilesFromAndroid(symfiles): | 381 def HaveFilesFromAndroid(symfiles): |
| 336 return any(ANDROID_PATH_MATCHER.match(f.path) for f in symfiles) | 382 return any(ANDROID_PATH_MATCHER.match(f.path) for f in symfiles) |
| 337 | 383 |
| 338 | 384 |
| 339 def RemapAndroidFiles(symfiles, output_path): | 385 def RemapAndroidFiles(symfiles, output_path): |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 365 'as out/Debug. Only needed for Android.') | 411 'as out/Debug. Only needed for Android.') |
| 366 options = parser.parse_args() | 412 options = parser.parse_args() |
| 367 | 413 |
| 368 trace_file_path = options.file | 414 trace_file_path = options.file |
| 369 def _OpenTraceFile(mode): | 415 def _OpenTraceFile(mode): |
| 370 if trace_file_path.endswith('.gz'): | 416 if trace_file_path.endswith('.gz'): |
| 371 return gzip.open(trace_file_path, mode + 'b') | 417 return gzip.open(trace_file_path, mode + 'b') |
| 372 else: | 418 else: |
| 373 return open(trace_file_path, mode + 't') | 419 return open(trace_file_path, mode + 't') |
| 374 | 420 |
| 375 addr2line_path = FindInSystemPath('addr2line') | 421 symbolizer = Symbolizer() |
| 376 if addr2line_path is None: | 422 if symbolizer.symbolizer_path is None: |
| 377 sys.exit("Can't symbolize - no addr2line in PATH.") | 423 sys.exit("Can't symbolize - no %s in PATH." % symbolizer.binary) |
| 378 | 424 |
| 379 print 'Reading trace file...' | 425 print 'Reading trace file...' |
| 380 with _OpenTraceFile('r') as trace_file: | 426 with _OpenTraceFile('r') as trace_file: |
| 381 trace = json.load(trace_file) | 427 trace = json.load(trace_file) |
| 382 | 428 |
| 383 processes = CollectProcesses(trace) | 429 processes = CollectProcesses(trace) |
| 384 symfiles = ResolveSymbolizableFiles(processes) | 430 symfiles = ResolveSymbolizableFiles(processes) |
| 385 | 431 |
| 386 # Android trace files don't have any indication they are from Android. | 432 # Android trace files don't have any indication they are from Android. |
| 387 # So we're checking for Android-specific paths. | 433 # So we're checking for Android-specific paths. |
| 388 if HaveFilesFromAndroid(symfiles): | 434 if HaveFilesFromAndroid(symfiles): |
| 389 if not options.output_directory: | 435 if not options.output_directory: |
| 390 parser.error('The trace file appears to be from Android. Please ' | 436 parser.error('The trace file appears to be from Android. Please ' |
| 391 "specify output directory (e.g. 'out/Debug') to properly " | 437 "specify output directory (e.g. 'out/Debug') to properly " |
| 392 'symbolize it.') | 438 'symbolize it.') |
| 393 RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory)) | 439 RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory)) |
| 394 | 440 |
| 395 if SymbolizeFiles(symfiles, addr2line_path): | 441 if SymbolizeFiles(symfiles, symbolizer): |
| 396 if options.backup: | 442 if options.backup: |
| 397 backup_file_path = trace_file_path + BACKUP_FILE_TAG | 443 backup_file_path = trace_file_path + BACKUP_FILE_TAG |
| 398 print 'Backing up trace file to {}...'.format(backup_file_path) | 444 print 'Backing up trace file to {}...'.format(backup_file_path) |
| 399 os.rename(trace_file_path, backup_file_path) | 445 os.rename(trace_file_path, backup_file_path) |
| 400 | 446 |
| 401 print 'Updating trace file...' | 447 print 'Updating trace file...' |
| 402 with _OpenTraceFile('w') as trace_file: | 448 with _OpenTraceFile('w') as trace_file: |
| 403 json.dump(trace, trace_file) | 449 json.dump(trace, trace_file) |
| 404 else: | 450 else: |
| 405 print 'No PCs symbolized - not updating trace file.' | 451 print 'No PCs symbolized - not updating trace file.' |
| 406 | 452 |
| 407 | 453 |
| 408 if __name__ == '__main__': | 454 if __name__ == '__main__': |
| 409 main() | 455 main() |
| OLD | NEW |