#!/usr/bin/env python3 # Copyright (C) 2017 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Like llvm-symbolizer for UI JS/TS sources. This script is used to "symbolize" UI crashes. It takes a crash, typically copied from a bug reports, of the form: (https://ui.perfetto.dev/v12.1.269/frontend_bundle.js:7639:61) at foo() (https://ui.perfetto.dev/v12.1.269/frontend_bundle.js:9235:29) at bar() it fetches the corresponding source maps and emits in output a translated crash report, of the form: https://android.googlesource.com/platform/external/perfetto/+/de4db33f/ui/src/foo.ts#61 at foo() https://android.googlesource.com/platform/external/perfetto/+/de4db33f/ui/src/baz.ts#300 at bar() """ import logging import re import sys import tempfile import urllib.request import ssl import os try: import sourcemap except: print('Run `pip3 install sourcemap` and try again') sys.exit(1) GERRIT_BASE_URL = 'https://android.googlesource.com/platform/external/perfetto/' def fetch_url_cached(url): normalized = re.sub('[^a-zA-Z0-9-._]', '_', url) local_file = os.path.join(tempfile.gettempdir(), normalized) if os.path.exists(local_file): logging.debug('Using %s', local_file) with open(local_file, 'r') as f: return f.read() context = ssl._create_unverified_context() logging.info('Fetching %s', url) resp = urllib.request.urlopen(url, context=context) contents = resp.read().decode() with open(local_file, 'w') as f: f.write(contents) return contents def Main(): if len(sys.argv) > 1: with open(sys.argv[1], 'r') as f: txt = f.read() else: if sys.stdin.isatty(): print('Paste the crash log and press CTRL-D\n') txt = sys.stdin.read() # Look for the GIT commitish appended in crash reports. This is not required # for resolving the sourcemaps but helps generating better links. matches = re.findall(r'https://ui.perfetto.dev/(.*-)([a-f0-9]{6,})\n', txt) if not matches: logging.fatal('Could not determine the version.' 'The crash report should have a line like: ' '"UI: https://ui.perfetto.dev/v12.3-abcdef"') return 1 dir_name = matches[0][0] + matches[0][1] git_rev = matches[0][1] base_url = 'https://commondatastorage.googleapis.com/ui.perfetto.dev/' + dir_name + '/' matches = re.findall(r'(\((.+[.]js):(\d+):(\d+)\))', txt) maps_by_url = {} sym_lines = '' for entry in matches: whole_token, script_url, line, col = entry if '/' not in script_url: script_url = base_url + script_url map_url = script_url + '.map' if map_url in maps_by_url: srcmap = maps_by_url[map_url] else: map_file_contents = fetch_url_cached(map_url) srcmap = sourcemap.loads(map_file_contents) maps_by_url[map_url] = srcmap sym = srcmap.lookup(int(line), int(col)) src = sym.src.replace('../../', '') sym_url = '%s#%s' % (src, sym.src_line) if src.startswith('../out/ui/'): src = src.replace('../out/ui/', 'ui/') sym_url = GERRIT_BASE_URL + '/+/%s/%s#%d' % (git_rev, src, sym.src_line) sym_lines += sym_url + '\n' txt = txt.replace(whole_token, sym_url) print(txt) print('\nResolved symbols:\n' + sym_lines) if __name__ == '__main__': logging.basicConfig(level=logging.INFO) sys.exit(Main())