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 |