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 """ | 6 """ |
| 7 This script processes trace files and symbolizes stack frames generated by | 7 This script processes trace files and symbolizes stack frames generated by |
| 8 Chrome's native heap profiler. This script assumes that the Chrome binary | 8 Chrome's native heap profiler. This script assumes that the Chrome binary |
| 9 referenced in the trace contains symbols, and is the same binary used to emit | 9 referenced in the trace contains symbols, and is the same binary used to emit |
| 10 the trace. | 10 the trace. |
| (...skipping 967 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 978 """Holds file path, addresses to symbolize and stack frames to update. | 978 """Holds file path, addresses to symbolize and stack frames to update. |
| 979 | 979 |
| 980 This class is a link between ELFSymbolizer and a trace file: it specifies | 980 This class is a link between ELFSymbolizer and a trace file: it specifies |
| 981 what to symbolize (addresses) and what to update with the symbolization | 981 what to symbolize (addresses) and what to update with the symbolization |
| 982 result (frames). | 982 result (frames). |
| 983 """ | 983 """ |
| 984 def __init__(self, file_path): | 984 def __init__(self, file_path): |
| 985 self.path = file_path | 985 self.path = file_path |
| 986 self.symbolizable_path = file_path # path to use for symbolization | 986 self.symbolizable_path = file_path # path to use for symbolization |
| 987 self.frames_by_address = collections.defaultdict(list) | 987 self.frames_by_address = collections.defaultdict(list) |
| 988 self.skip_symbolization = False | |
| 988 | 989 |
| 989 | 990 |
| 990 def ResolveSymbolizableFiles(processes): | 991 def ResolveSymbolizableFiles(processes): |
| 991 """Resolves and groups PCs into list of SymbolizableFiles. | 992 """Resolves and groups PCs into list of SymbolizableFiles. |
| 992 | 993 |
| 993 As part of the grouping process, this function resolves PC from each stack | 994 As part of the grouping process, this function resolves PC from each stack |
| 994 frame to the corresponding mmap region. Stack frames that failed to resolve | 995 frame to the corresponding mmap region. Stack frames that failed to resolve |
| 995 are symbolized with '<unresolved>'. | 996 are symbolized with '<unresolved>'. |
| 996 """ | 997 """ |
| 997 symfile_by_path = {} | 998 symfile_by_path = {} |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1059 # SymbolizeAsync() asserts that the type of address is int. We operate | 1060 # SymbolizeAsync() asserts that the type of address is int. We operate |
| 1060 # on longs (since they are raw pointers possibly from 64-bit processes). | 1061 # on longs (since they are raw pointers possibly from 64-bit processes). |
| 1061 # It's OK to cast here because we're passing relative PC, which should | 1062 # It's OK to cast here because we're passing relative PC, which should |
| 1062 # always fit into int. | 1063 # always fit into int. |
| 1063 symbolizer.SymbolizeAsync(int(address), frames) | 1064 symbolizer.SymbolizeAsync(int(address), frames) |
| 1064 | 1065 |
| 1065 symbolizer.Join() | 1066 symbolizer.Join() |
| 1066 | 1067 |
| 1067 | 1068 |
| 1068 def _SymbolizeMac(self, symfile): | 1069 def _SymbolizeMac(self, symfile): |
| 1070 if symfile.skip_symbolization: | |
| 1071 for address, frames in symfile.frames_by_address.iteritems(): | |
| 1072 unsymbolized_name = ('<' + symfile.symbolizable_path + '> + ' + | |
|
etienneb
2017/06/20 03:28:09
I wonder if we should not use only basename instea
erikchen
2017/06/23 00:33:45
Done.
| |
| 1073 str(address)) | |
| 1074 for frame in frames: | |
| 1075 frame.name = unsymbolized_name | |
| 1076 return | |
| 1077 | |
| 1069 load_address = (symbolize_trace_macho_reader. | 1078 load_address = (symbolize_trace_macho_reader. |
| 1070 ReadMachOTextLoadAddress(symfile.symbolizable_path)) | 1079 ReadMachOTextLoadAddress(symfile.symbolizable_path)) |
| 1071 assert load_address is not None | 1080 assert load_address is not None |
| 1072 | 1081 |
| 1073 address_os_file, address_file_path = tempfile.mkstemp() | 1082 address_os_file, address_file_path = tempfile.mkstemp() |
| 1074 try: | 1083 try: |
| 1075 with os.fdopen(address_os_file, 'w') as address_file: | 1084 with os.fdopen(address_os_file, 'w') as address_file: |
| 1076 for address in symfile.frames_by_address.iterkeys(): | 1085 for address in symfile.frames_by_address.iterkeys(): |
| 1077 address_file.write('{:x} '.format(address + load_address)) | 1086 address_file.write('{:x} '.format(address + load_address)) |
| 1078 | 1087 |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 1095 service that handles communication with the NT symbol servers. This | 1104 service that handles communication with the NT symbol servers. This |
| 1096 creates an explicit serialization (and therefor lock contention) of | 1105 creates an explicit serialization (and therefor lock contention) of |
| 1097 any process using the symbol API for files do not have a local PDB. | 1106 any process using the symbol API for files do not have a local PDB. |
| 1098 | 1107 |
| 1099 Thus, even though the windows symbolizer binary can be make command line | 1108 Thus, even though the windows symbolizer binary can be make command line |
| 1100 compatible with the POSIX addr2line interface, parallelizing the | 1109 compatible with the POSIX addr2line interface, parallelizing the |
| 1101 symbolization does not yield the same performance effects. Running | 1110 symbolization does not yield the same performance effects. Running |
| 1102 just one symbolizer seems good enough for now. Can optimize later | 1111 just one symbolizer seems good enough for now. Can optimize later |
| 1103 if this becomes a bottleneck. | 1112 if this becomes a bottleneck. |
| 1104 """ | 1113 """ |
| 1114 if symfile.skip_symbolization: | |
| 1115 for address, frames in symfile.frames_by_address.iteritems(): | |
| 1116 unsymbolized_name = ('<' + symfile.symbolizable_path + '> + ' + | |
| 1117 str(address)) | |
|
etienneb
2017/06/20 03:28:09
nit: address should be hexa!?
erikchen
2017/06/23 00:33:45
Done.
| |
| 1118 for frame in frames: | |
| 1119 frame.name = unsymbolized_name | |
| 1120 return | |
| 1121 | |
| 1105 cmd = [self.symbolizer_path, '--functions', '--demangle', '--exe', | 1122 cmd = [self.symbolizer_path, '--functions', '--demangle', '--exe', |
| 1106 symfile.symbolizable_path] | 1123 symfile.symbolizable_path] |
| 1107 | 1124 |
| 1108 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, | 1125 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, |
| 1109 stderr=sys.stderr) | 1126 stderr=sys.stderr) |
| 1110 addrs = ["%x" % relative_pc for relative_pc in | 1127 addrs = ["%x" % relative_pc for relative_pc in |
| 1111 symfile.frames_by_address.keys()] | 1128 symfile.frames_by_address.keys()] |
| 1112 (stdout_data, stderr_data) = proc.communicate('\n'.join(addrs)) | 1129 (stdout_data, stderr_data) = proc.communicate('\n'.join(addrs)) |
| 1113 stdout_data = stdout_data.split('\n') | 1130 stdout_data = stdout_data.split('\n') |
| 1114 | 1131 |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1205 name = match.group('name') | 1222 name = match.group('name') |
| 1206 symfile.symbolizable_path = os.path.join( | 1223 symfile.symbolizable_path = os.path.join( |
| 1207 output_path, ANDROID_UNSTRIPPED_SUBPATH, name) | 1224 output_path, ANDROID_UNSTRIPPED_SUBPATH, name) |
| 1208 else: | 1225 else: |
| 1209 # Clobber file path to trigger "not a file" problem in SymbolizeFiles(). | 1226 # Clobber file path to trigger "not a file" problem in SymbolizeFiles(). |
| 1210 # Without this, files won't be symbolized with "file not found" problem, | 1227 # Without this, files won't be symbolized with "file not found" problem, |
| 1211 # which is not accurate. | 1228 # which is not accurate. |
| 1212 symfile.symbolizable_path = 'android://{}'.format(symfile.path) | 1229 symfile.symbolizable_path = 'android://{}'.format(symfile.path) |
| 1213 | 1230 |
| 1214 | 1231 |
| 1215 def RemapMacFiles(symfiles, symbol_base_directory, version): | 1232 def RemapMacFiles(symfiles, symbol_base_directory, version, |
| 1233 only_symbolize_chrome_symbols): | |
| 1216 suffix = ("Google Chrome Framework.dSYM/Contents/Resources/DWARF/" | 1234 suffix = ("Google Chrome Framework.dSYM/Contents/Resources/DWARF/" |
| 1217 "Google Chrome Framework") | 1235 "Google Chrome Framework") |
| 1218 symbol_sub_dir = os.path.join(symbol_base_directory, version) | 1236 symbol_sub_dir = os.path.join(symbol_base_directory, version) |
| 1219 symbolizable_path = os.path.join(symbol_sub_dir, suffix) | 1237 symbolizable_path = os.path.join(symbol_sub_dir, suffix) |
| 1220 | 1238 |
| 1221 for symfile in symfiles: | 1239 for symfile in symfiles: |
| 1222 if symfile.path.endswith("Google Chrome Framework"): | 1240 if symfile.path.endswith("Google Chrome Framework"): |
| 1223 symfile.symbolizable_path = symbolizable_path | 1241 symfile.symbolizable_path = symbolizable_path |
| 1242 elif only_symbolize_chrome_symbols: | |
| 1243 symfile.skip_symbolization = True | |
| 1224 | 1244 |
| 1225 def RemapWinFiles(symfiles, symbol_base_directory, version, is64bit): | 1245 def RemapWinFiles(symfiles, symbol_base_directory, version, is64bit, |
| 1246 only_symbolize_chrome_symbols): | |
| 1226 folder = "win64" if is64bit else "win" | 1247 folder = "win64" if is64bit else "win" |
| 1227 symbol_sub_dir = os.path.join(symbol_base_directory, | 1248 symbol_sub_dir = os.path.join(symbol_base_directory, |
| 1228 "chrome-" + folder + "-" + version) | 1249 "chrome-" + folder + "-" + version) |
| 1229 for symfile in symfiles: | 1250 for symfile in symfiles: |
| 1230 image = os.path.join(symbol_sub_dir, os.path.basename(symfile.path)) | 1251 image = os.path.join(symbol_sub_dir, os.path.basename(symfile.path)) |
| 1231 symbols = image + ".pdb" | 1252 symbols = image + ".pdb" |
| 1232 if os.path.isfile(image) and os.path.isfile(symbols): | 1253 if os.path.isfile(image) and os.path.isfile(symbols): |
| 1233 symfile.symbolizable_path = image | 1254 symfile.symbolizable_path = image |
| 1255 elif only_symbolize_chrome_symbols: | |
| 1256 symfile.skip_symbolization = True | |
| 1234 | 1257 |
| 1235 def Symbolize(options, trace, symbolizer): | 1258 def Symbolize(options, trace, symbolizer): |
| 1236 symfiles = ResolveSymbolizableFiles(trace.processes) | 1259 symfiles = ResolveSymbolizableFiles(trace.processes) |
| 1237 | 1260 |
| 1238 # Android trace files don't have any indication they are from Android. | 1261 # Android trace files don't have any indication they are from Android. |
| 1239 # So we're checking for Android-specific paths. | 1262 # So we're checking for Android-specific paths. |
| 1240 if HaveFilesFromAndroid(symfiles): | 1263 if HaveFilesFromAndroid(symfiles): |
| 1241 if not options.output_directory: | 1264 if not options.output_directory: |
| 1242 sys.exit('The trace file appears to be from Android. Please ' | 1265 sys.exit('The trace file appears to be from Android. Please ' |
| 1243 'specify output directory to properly symbolize it.') | 1266 'specify output directory to properly symbolize it.') |
| 1244 RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory)) | 1267 RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory)) |
| 1245 | 1268 |
| 1246 | 1269 |
| 1247 if not trace.is_chromium: | 1270 if not trace.is_chromium: |
| 1248 if symbolizer.is_mac: | 1271 if symbolizer.is_mac: |
| 1249 RemapMacFiles(symfiles, options.symbol_base_directory, trace.version) | 1272 RemapMacFiles(symfiles, options.symbol_base_directory, trace.version, |
| 1273 options.only_symbolize_chrome_symbols) | |
| 1250 if symbolizer.is_win: | 1274 if symbolizer.is_win: |
| 1251 RemapWinFiles(symfiles, options.symbol_base_directory, trace.version, | 1275 RemapWinFiles(symfiles, options.symbol_base_directory, trace.version, |
| 1252 trace.is_64bit) | 1276 trace.is_64bit, options.only_symbolize_chrome_symbols) |
| 1253 | 1277 |
| 1254 SymbolizeFiles(symfiles, symbolizer) | 1278 SymbolizeFiles(symfiles, symbolizer) |
| 1255 | 1279 |
| 1256 | 1280 |
| 1257 def OpenTraceFile(file_path, mode): | 1281 def OpenTraceFile(file_path, mode): |
| 1258 if file_path.endswith('.gz'): | 1282 if file_path.endswith('.gz'): |
| 1259 return gzip.open(file_path, mode + 'b') | 1283 return gzip.open(file_path, mode + 'b') |
| 1260 else: | 1284 else: |
| 1261 return open(file_path, mode + 't') | 1285 return open(file_path, mode + 't') |
| 1262 | 1286 |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1331 os.path.join(symbol_base_directory, | 1355 os.path.join(symbol_base_directory, |
| 1332 "chrome-" + folder + "-" + version + ".zip"), | 1356 "chrome-" + folder + "-" + version + ".zip"), |
| 1333 gcs_folder + "chrome-" + folder + "-pgo.zip", | 1357 gcs_folder + "chrome-" + folder + "-pgo.zip", |
| 1334 symbol_sub_dir) | 1358 symbol_sub_dir) |
| 1335 | 1359 |
| 1336 return True | 1360 return True |
| 1337 | 1361 |
| 1338 # Suffix used for backup files. | 1362 # Suffix used for backup files. |
| 1339 BACKUP_FILE_TAG = '.BACKUP' | 1363 BACKUP_FILE_TAG = '.BACKUP' |
| 1340 | 1364 |
| 1341 def main(): | 1365 def main(args): |
| 1342 parser = argparse.ArgumentParser() | 1366 parser = argparse.ArgumentParser() |
| 1343 parser.add_argument( | 1367 parser.add_argument( |
| 1344 'file', | 1368 'file', |
| 1345 help='Trace file to symbolize (.json or .json.gz)') | 1369 help='Trace file to symbolize (.json or .json.gz)') |
| 1346 | 1370 |
| 1347 parser.add_argument( | 1371 parser.add_argument( |
| 1348 '--no-backup', dest='backup', default='true', action='store_false', | 1372 '--no-backup', dest='backup', default='true', action='store_false', |
| 1349 help="Don't create {} files".format(BACKUP_FILE_TAG)) | 1373 help="Don't create {} files".format(BACKUP_FILE_TAG)) |
| 1350 | 1374 |
| 1351 parser.add_argument( | 1375 parser.add_argument( |
| 1352 '--output-directory', | 1376 '--output-directory', |
| 1353 help='The path to the build output directory, such as out/Debug.') | 1377 help='The path to the build output directory, such as out/Debug.') |
| 1354 | 1378 |
| 1379 parser.add_argument( | |
|
etienneb
2017/06/20 03:28:09
For not chromium trace (traces from user computers
erikchen
2017/06/23 00:33:45
Done.
| |
| 1380 '--only-symbolize-chrome-symbols', | |
| 1381 action='store_true', | |
| 1382 help='Prevents symbolization of non-Chrome [system] symbols.') | |
| 1383 | |
| 1355 home_dir = os.path.expanduser('~') | 1384 home_dir = os.path.expanduser('~') |
| 1356 default_dir = os.path.join(home_dir, "symbols") | 1385 default_dir = os.path.join(home_dir, "symbols") |
| 1357 parser.add_argument( | 1386 parser.add_argument( |
| 1358 '--symbol-base-directory', | 1387 '--symbol-base-directory', |
| 1359 default=default_dir, | 1388 default=default_dir, |
| 1360 help='Directory where symbols are downloaded and cached.') | 1389 help='Directory where symbols are downloaded and cached.') |
| 1361 | 1390 |
| 1362 symbolizer = Symbolizer() | 1391 symbolizer = Symbolizer() |
| 1363 if symbolizer.symbolizer_path is None: | 1392 if symbolizer.symbolizer_path is None: |
| 1364 sys.exit("Can't symbolize - no %s in PATH." % symbolizer.binary) | 1393 sys.exit("Can't symbolize - no %s in PATH." % symbolizer.binary) |
| 1365 | 1394 |
| 1366 options = parser.parse_args() | 1395 options = parser.parse_args(args) |
| 1367 | 1396 |
| 1368 trace_file_path = options.file | 1397 trace_file_path = options.file |
| 1369 | 1398 |
| 1370 print 'Reading trace file...' | 1399 print 'Reading trace file...' |
| 1371 with OpenTraceFile(trace_file_path, 'r') as trace_file: | 1400 with OpenTraceFile(trace_file_path, 'r') as trace_file: |
| 1372 trace = Trace(json.load(trace_file)) | 1401 trace = Trace(json.load(trace_file)) |
| 1373 | 1402 |
| 1374 # Perform some sanity checks. | 1403 # Perform some sanity checks. |
| 1375 if trace.is_win and sys.platform != 'win32': | 1404 if trace.is_win and sys.platform != 'win32': |
| 1376 print "Cannot symbolize a windows trace on this architecture!" | 1405 print "Cannot symbolize a windows trace on this architecture!" |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 1403 os.rename(trace_file_path, backup_file_path) | 1432 os.rename(trace_file_path, backup_file_path) |
| 1404 | 1433 |
| 1405 print 'Updating the trace file...' | 1434 print 'Updating the trace file...' |
| 1406 with OpenTraceFile(trace_file_path, 'w') as trace_file: | 1435 with OpenTraceFile(trace_file_path, 'w') as trace_file: |
| 1407 json.dump(trace.node, trace_file) | 1436 json.dump(trace.node, trace_file) |
| 1408 else: | 1437 else: |
| 1409 print 'No modifications were made - not updating the trace file.' | 1438 print 'No modifications were made - not updating the trace file.' |
| 1410 | 1439 |
| 1411 | 1440 |
| 1412 if __name__ == '__main__': | 1441 if __name__ == '__main__': |
| 1413 main() | 1442 main(sys.argv[1:]) |
| OLD | NEW |