1#!/usr/bin/env python3 2# Copyright (C) 2017 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Like llvm-symbolizer for UI JS/TS sources. 16 17This script is used to "symbolize" UI crashes. It takes a crash, typically 18copied from a bug reports, of the form: 19 20(https://ui.perfetto.dev/v12.1.269/frontend_bundle.js:7639:61) at foo() 21(https://ui.perfetto.dev/v12.1.269/frontend_bundle.js:9235:29) at bar() 22 23it fetches the corresponding source maps and emits in output a translated 24crash report, of the form: 25 26https://android.googlesource.com/platform/external/perfetto/+/de4db33f/ui/src/foo.ts#61 at foo() 27https://android.googlesource.com/platform/external/perfetto/+/de4db33f/ui/src/baz.ts#300 at bar() 28""" 29 30import logging 31import re 32import sys 33import tempfile 34import urllib.request 35import ssl 36import os 37 38try: 39 import sourcemap 40except: 41 print('Run `pip3 install sourcemap` and try again') 42 sys.exit(1) 43 44GERRIT_BASE_URL = 'https://android.googlesource.com/platform/external/perfetto/' 45 46 47def fetch_url_cached(url): 48 normalized = re.sub('[^a-zA-Z0-9-._]', '_', url) 49 local_file = os.path.join(tempfile.gettempdir(), normalized) 50 if os.path.exists(local_file): 51 logging.debug('Using %s', local_file) 52 with open(local_file, 'r') as f: 53 return f.read() 54 context = ssl._create_unverified_context() 55 logging.info('Fetching %s', url) 56 resp = urllib.request.urlopen(url, context=context) 57 contents = resp.read().decode() 58 with open(local_file, 'w') as f: 59 f.write(contents) 60 return contents 61 62 63def Main(): 64 if len(sys.argv) > 1: 65 with open(sys.argv[1], 'r') as f: 66 txt = f.read() 67 else: 68 if sys.stdin.isatty(): 69 print('Paste the crash log and press CTRL-D\n') 70 txt = sys.stdin.read() 71 72 # Look for the GIT commitish appended in crash reports. This is not required 73 # for resolving the sourcemaps but helps generating better links. 74 matches = re.findall(r'https://ui.perfetto.dev/(.*-)([a-f0-9]{6,})\n', txt) 75 if not matches: 76 logging.fatal('Could not determine the version.' 77 'The crash report should have a line like: ' 78 '"UI: https://ui.perfetto.dev/v12.3-abcdef"') 79 return 1 80 81 dir_name = matches[0][0] + matches[0][1] 82 git_rev = matches[0][1] 83 base_url = 'https://commondatastorage.googleapis.com/ui.perfetto.dev/' + dir_name + '/' 84 matches = re.findall(r'(\((.+[.]js):(\d+):(\d+)\))', txt) 85 maps_by_url = {} 86 sym_lines = '' 87 for entry in matches: 88 whole_token, script_url, line, col = entry 89 if '/' not in script_url: 90 script_url = base_url + script_url 91 map_url = script_url + '.map' 92 if map_url in maps_by_url: 93 srcmap = maps_by_url[map_url] 94 else: 95 map_file_contents = fetch_url_cached(map_url) 96 srcmap = sourcemap.loads(map_file_contents) 97 maps_by_url[map_url] = srcmap 98 sym = srcmap.lookup(int(line), int(col)) 99 src = sym.src.replace('../../', '') 100 sym_url = '%s#%s' % (src, sym.src_line) 101 if src.startswith('../out/ui/'): 102 src = src.replace('../out/ui/', 'ui/') 103 sym_url = GERRIT_BASE_URL + '/+/%s/%s#%d' % (git_rev, src, sym.src_line) 104 sym_lines += sym_url + '\n' 105 txt = txt.replace(whole_token, sym_url) 106 107 print(txt) 108 print('\nResolved symbols:\n' + sym_lines) 109 110 111if __name__ == '__main__': 112 logging.basicConfig(level=logging.INFO) 113 sys.exit(Main()) 114