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 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 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 def IsSymbolizableFile(file_path): | 55 def IsSymbolizableFile(file_path): |
| 56 result = subprocess.check_output(['file', '-0', file_path]) | 56 result = subprocess.check_output(['file', '-0', file_path]) |
| 57 type_string = result[result.find('\0') + 1:] | 57 type_string = result[result.find('\0') + 1:] |
| 58 return bool(re.match(r'\: (ELF|Mach-O) (32|64)-bit\b', type_string)) | 58 return bool(re.match(r'.*(ELF|Mach-O) (32|64)-bit\b.*', |
| 59 type_string, re.DOTALL)) | |
| 59 | 60 |
| 60 | 61 |
| 61 class ProcessMemoryMaps(object): | 62 class ProcessMemoryMaps(object): |
| 62 """Represents 'process_mmaps' trace file entry.""" | 63 """Represents 'process_mmaps' trace file entry.""" |
| 63 | 64 |
| 64 class Region(object): | 65 class Region(object): |
| 65 def __init__(self, start_address, size, file_path): | 66 def __init__(self, start_address, size, file_path): |
| 66 self._start_address = start_address | 67 self._start_address = start_address |
| 67 self._size = size | 68 self._size = size |
| 68 self._file_path = file_path | 69 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) | 265 symfile = symfile_by_path.get(region.file_path) |
| 265 if symfile is None: | 266 if symfile is None: |
| 266 symfile = SymbolizableFile(region.file_path) | 267 symfile = SymbolizableFile(region.file_path) |
| 267 symfile_by_path[symfile.path] = symfile | 268 symfile_by_path[symfile.path] = symfile |
| 268 | 269 |
| 269 relative_pc = frame.pc - region.start_address | 270 relative_pc = frame.pc - region.start_address |
| 270 symfile.frames_by_address[relative_pc].append(frame) | 271 symfile.frames_by_address[relative_pc].append(frame) |
| 271 return symfile_by_path.values() | 272 return symfile_by_path.values() |
| 272 | 273 |
| 273 | 274 |
| 274 def SymbolizeFiles(symfiles, addr2line_path): | 275 def SymbolizeFiles(symfiles, symbolizer_path): |
| 275 """Symbolizes each file in the given list of SymbolizableFiles | 276 """Symbolizes each file in the given list of SymbolizableFiles |
| 276 and updates stack frames with symbolization results.""" | 277 and updates stack frames with symbolization results.""" |
| 277 print 'Symbolizing...' | 278 print 'Symbolizing...' |
| 278 | 279 |
| 279 def _SubPrintf(message, *args): | 280 def _SubPrintf(message, *args): |
| 280 print (' ' + message).format(*args) | 281 print (' ' + message).format(*args) |
| 281 | 282 |
| 282 symbolized = False | 283 symbolized = False |
| 283 for symfile in symfiles: | 284 for symfile in symfiles: |
| 284 unsymbolized_name = '<{}>'.format( | 285 unsymbolized_name = '<{}>'.format( |
| 285 symfile.path if symfile.path else 'unnamed') | 286 symfile.path if symfile.path else 'unnamed') |
| 286 | 287 |
| 287 problem = None | 288 problem = None |
| 288 if not os.path.isabs(symfile.symbolizable_path): | 289 if not os.path.isabs(symfile.symbolizable_path): |
| 289 problem = 'not a file' | 290 problem = 'not a file' |
| 290 elif not os.path.isfile(symfile.symbolizable_path): | 291 elif not os.path.isfile(symfile.symbolizable_path): |
| 291 problem = "file doesn't exist" | 292 problem = "file doesn't exist" |
| 292 elif not IsSymbolizableFile(symfile.symbolizable_path): | 293 elif not IsSymbolizableFile(symfile.symbolizable_path): |
| 293 problem = 'file is not symbolizable' | 294 problem = 'file is not symbolizable' |
| 294 if problem: | 295 if problem: |
| 295 _SubPrintf("Won't symbolize {} PCs for '{}': {}.", | 296 _SubPrintf("Won't symbolize {} PCs for '{}': {}.", |
| 296 len(symfile.frames_by_address), | 297 len(symfile.frames_by_address), |
| 297 symfile.symbolizable_path, | 298 symfile.symbolizable_path, |
| 298 problem) | 299 problem) |
| 299 for frames in symfile.frames_by_address.itervalues(): | 300 for frames in symfile.frames_by_address.itervalues(): |
| 300 for frame in frames: | 301 for frame in frames: |
| 301 frame.name = unsymbolized_name | 302 frame.name = unsymbolized_name |
| 302 continue | 303 continue |
| 303 | 304 |
| 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 {}...', | 305 _SubPrintf('Symbolizing {} PCs from {}...', |
| 319 len(symfile.frames_by_address), | 306 len(symfile.frames_by_address), |
| 320 symfile.path) | 307 symfile.path) |
| 321 | 308 |
| 322 for address, frames in symfile.frames_by_address.iteritems(): | 309 if sys.platform == 'darwin': |
|
DmitrySkiba
2017/01/10 21:28:09
We have this check in two places - first when we d
erikchen
2017/02/04 02:34:56
Introducing a class to avoid an extra conditional
| |
| 323 # SymbolizeAsync() asserts that the type of address is int. We operate | 310 SymbolizeMac(symfile, symbolizer_path) |
| 324 # on longs (since they are raw pointers possibly from 64-bit processes). | 311 else: |
| 325 # It's OK to cast here because we're passing relative PC, which should | 312 SymbolizeLinuxAndAndroid(sym_file, symbolizer_path) |
| 326 # always fit into int. | |
| 327 symbolizer.SymbolizeAsync(int(address), frames) | |
| 328 | |
| 329 symbolizer.Join() | |
| 330 symbolized = True | 313 symbolized = True |
| 331 | 314 |
| 332 return symbolized | 315 return symbolized |
| 333 | 316 |
| 334 | 317 |
| 318 def SymbolizeLinuxAndAndroid(sym_file, symbolizer_path): | |
| 319 def _SymbolizerCallback(sym_info, frames): | |
| 320 # Unwind inline chain to the top. | |
| 321 while sym_info.inlined_by: | |
| 322 sym_info = sym_info.inlined_by | |
| 323 | |
| 324 symbolized_name = sym_info.name if sym_info.name else unsymbolized_name | |
| 325 for frame in frames: | |
| 326 frame.name = symbolized_name | |
| 327 | |
| 328 symbolizer = elf_symbolizer.ELFSymbolizer(symfile.symbolizable_path, | |
| 329 symbolizer_path, | |
| 330 _SymbolizerCallback, | |
| 331 inlines=True) | |
| 332 | |
| 333 for address, frames in symfile.frames_by_address.iteritems(): | |
| 334 # SymbolizeAsync() asserts that the type of address is int. We operate | |
| 335 # on longs (since they are raw pointers possibly from 64-bit processes). | |
| 336 # It's OK to cast here because we're passing relative PC, which should | |
| 337 # always fit into int. | |
| 338 symbolizer.SymbolizeAsync(int(address), frames) | |
| 339 | |
| 340 symbolizer.Join() | |
| 341 | |
| 342 | |
| 343 def SymbolizeMac(symfile, symbolizer_path): | |
| 344 cmd = [symbolizer_path, '-arch', 'x86_64', '-l', | |
| 345 '0x0', '-o' , symfile.symbolizable_path] | |
| 346 cmd.extend([hex(int(x)) for x in symfile.frames_by_address.keys()]) | |
|
DmitrySkiba
2017/01/10 21:28:09
What is the max command line length on macOS? This
erikchen
2017/02/04 02:34:56
By default, the max length is 260k characters. I'v
| |
| 347 output_array = subprocess.check_output(cmd).split('\n') | |
| 348 for i in range(len(symfile.frames_by_address.keys())): | |
| 349 for frame in symfile.frames_by_address.values()[i]: | |
| 350 frame.name = output_array[i] | |
| 351 | |
| 352 | |
| 335 def HaveFilesFromAndroid(symfiles): | 353 def HaveFilesFromAndroid(symfiles): |
| 336 return any(ANDROID_PATH_MATCHER.match(f.path) for f in symfiles) | 354 return any(ANDROID_PATH_MATCHER.match(f.path) for f in symfiles) |
| 337 | 355 |
| 338 | 356 |
| 339 def RemapAndroidFiles(symfiles, output_path): | 357 def RemapAndroidFiles(symfiles, output_path): |
| 340 for symfile in symfiles: | 358 for symfile in symfiles: |
| 341 match = ANDROID_PATH_MATCHER.match(symfile.path) | 359 match = ANDROID_PATH_MATCHER.match(symfile.path) |
| 342 if match: | 360 if match: |
| 343 name = match.group('name') | 361 name = match.group('name') |
| 344 symfile.symbolizable_path = os.path.join( | 362 symfile.symbolizable_path = os.path.join( |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 365 'as out/Debug. Only needed for Android.') | 383 'as out/Debug. Only needed for Android.') |
| 366 options = parser.parse_args() | 384 options = parser.parse_args() |
| 367 | 385 |
| 368 trace_file_path = options.file | 386 trace_file_path = options.file |
| 369 def _OpenTraceFile(mode): | 387 def _OpenTraceFile(mode): |
| 370 if trace_file_path.endswith('.gz'): | 388 if trace_file_path.endswith('.gz'): |
| 371 return gzip.open(trace_file_path, mode + 'b') | 389 return gzip.open(trace_file_path, mode + 'b') |
| 372 else: | 390 else: |
| 373 return open(trace_file_path, mode + 't') | 391 return open(trace_file_path, mode + 't') |
| 374 | 392 |
| 375 addr2line_path = FindInSystemPath('addr2line') | 393 if sys.platform == 'darwin': |
| 376 if addr2line_path is None: | 394 symbolizer = 'atos' |
| 377 sys.exit("Can't symbolize - no addr2line in PATH.") | 395 else: |
| 396 symbolizer = 'addr2line' | |
| 397 symbolizer_path = FindInSystemPath(symbolizer) | |
| 398 if symbolizer_path is None: | |
| 399 sys.exit("Can't symbolize - no %s in PATH." % symbolizer) | |
| 378 | 400 |
| 379 print 'Reading trace file...' | 401 print 'Reading trace file...' |
| 380 with _OpenTraceFile('r') as trace_file: | 402 with _OpenTraceFile('r') as trace_file: |
| 381 trace = json.load(trace_file) | 403 trace = json.load(trace_file) |
| 382 | 404 |
| 383 processes = CollectProcesses(trace) | 405 processes = CollectProcesses(trace) |
| 384 symfiles = ResolveSymbolizableFiles(processes) | 406 symfiles = ResolveSymbolizableFiles(processes) |
| 385 | 407 |
| 386 # Android trace files don't have any indication they are from Android. | 408 # Android trace files don't have any indication they are from Android. |
| 387 # So we're checking for Android-specific paths. | 409 # So we're checking for Android-specific paths. |
| 388 if HaveFilesFromAndroid(symfiles): | 410 if HaveFilesFromAndroid(symfiles): |
| 389 if not options.output_directory: | 411 if not options.output_directory: |
| 390 parser.error('The trace file appears to be from Android. Please ' | 412 parser.error('The trace file appears to be from Android. Please ' |
| 391 "specify output directory (e.g. 'out/Debug') to properly " | 413 "specify output directory (e.g. 'out/Debug') to properly " |
| 392 'symbolize it.') | 414 'symbolize it.') |
| 393 RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory)) | 415 RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory)) |
| 394 | 416 |
| 395 if SymbolizeFiles(symfiles, addr2line_path): | 417 if SymbolizeFiles(symfiles, symbolizer_path): |
| 396 if options.backup: | 418 if options.backup: |
| 397 backup_file_path = trace_file_path + BACKUP_FILE_TAG | 419 backup_file_path = trace_file_path + BACKUP_FILE_TAG |
| 398 print 'Backing up trace file to {}...'.format(backup_file_path) | 420 print 'Backing up trace file to {}...'.format(backup_file_path) |
| 399 os.rename(trace_file_path, backup_file_path) | 421 os.rename(trace_file_path, backup_file_path) |
| 400 | 422 |
| 401 print 'Updating trace file...' | 423 print 'Updating trace file...' |
| 402 with _OpenTraceFile('w') as trace_file: | 424 with _OpenTraceFile('w') as trace_file: |
| 403 json.dump(trace, trace_file) | 425 json.dump(trace, trace_file) |
| 404 else: | 426 else: |
| 405 print 'No PCs symbolized - not updating trace file.' | 427 print 'No PCs symbolized - not updating trace file.' |
| 406 | 428 |
| 407 | 429 |
| 408 if __name__ == '__main__': | 430 if __name__ == '__main__': |
| 409 main() | 431 main() |
| OLD | NEW |