OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 | 2 |
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 from third_party import asan_symbolize | 7 from third_party import asan_symbolize |
8 | 8 |
9 import argparse | 9 import argparse |
10 import base64 | 10 import base64 |
11 import json | 11 import json |
12 import os | 12 import os |
| 13 import re |
| 14 import subprocess |
13 import sys | 15 import sys |
14 | 16 |
15 class LineBuffered(object): | 17 class LineBuffered(object): |
16 """Disable buffering on a file object.""" | 18 """Disable buffering on a file object.""" |
17 def __init__(self, stream): | 19 def __init__(self, stream): |
18 self.stream = stream | 20 self.stream = stream |
19 | 21 |
20 def write(self, data): | 22 def write(self, data): |
21 self.stream.write(data) | 23 self.stream.write(data) |
22 if '\n' in data: | 24 if '\n' in data: |
(...skipping 18 matching lines...) Expand all Loading... |
41 script_dir = os.path.dirname(os.path.abspath(__file__)) | 43 script_dir = os.path.dirname(os.path.abspath(__file__)) |
42 # Assume this script resides three levels below src/ (i.e. | 44 # Assume this script resides three levels below src/ (i.e. |
43 # src/tools/valgrind/asan/). | 45 # src/tools/valgrind/asan/). |
44 src_root = os.path.join(script_dir, "..", "..", "..") | 46 src_root = os.path.join(script_dir, "..", "..", "..") |
45 symbolizer_path = os.path.join(src_root, 'third_party', | 47 symbolizer_path = os.path.join(src_root, 'third_party', |
46 'llvm-build', 'Release+Asserts', 'bin', 'llvm-symbolizer') | 48 'llvm-build', 'Release+Asserts', 'bin', 'llvm-symbolizer') |
47 assert(os.path.isfile(symbolizer_path)) | 49 assert(os.path.isfile(symbolizer_path)) |
48 os.environ['LLVM_SYMBOLIZER_PATH'] = os.path.abspath(symbolizer_path) | 50 os.environ['LLVM_SYMBOLIZER_PATH'] = os.path.abspath(symbolizer_path) |
49 | 51 |
50 | 52 |
| 53 def is_hash_name(name): |
| 54 match = re.match('[0-9a-f]+$', name) |
| 55 return bool(match) |
| 56 |
| 57 |
| 58 def split_path(path): |
| 59 ret = [] |
| 60 while True: |
| 61 head, tail = os.path.split(path) |
| 62 if head == path: |
| 63 return [head] + ret |
| 64 ret, path = [tail] + ret, head |
| 65 |
| 66 |
| 67 def chrome_product_dir_path(exe_path): |
| 68 if exe_path is None: |
| 69 return None |
| 70 path_parts = split_path(exe_path) |
| 71 # Make sure the product dir path isn't empty if |exe_path| consists of |
| 72 # a single component. |
| 73 if len(path_parts) == 1: |
| 74 path_parts = ['.'] + path_parts |
| 75 for index, part in enumerate(path_parts): |
| 76 if part.endswith('.app'): |
| 77 return os.path.join(*path_parts[:index]) |
| 78 # If the executable isn't an .app bundle, it's a commandline binary that |
| 79 # resides right in the product dir. |
| 80 return os.path.join(*path_parts[:-1]) |
| 81 |
| 82 |
| 83 inode_path_cache = {} |
| 84 |
| 85 |
| 86 def find_inode_at_path(inode, path): |
| 87 if inode in inode_path_cache: |
| 88 return inode_path_cache[inode] |
| 89 cmd = ['find', path, '-inum', str(inode)] |
| 90 find_line = subprocess.check_output(cmd).rstrip() |
| 91 lines = find_line.split('\n') |
| 92 ret = None |
| 93 if lines: |
| 94 # `find` may give us several paths (e.g. 'Chromium Framework' in the |
| 95 # product dir and 'Chromium Framework' inside 'Chromium.app', |
| 96 # chrome_dsym_hints() will produce correct .dSYM path for any of them. |
| 97 ret = lines[0] |
| 98 inode_path_cache[inode] = ret |
| 99 return ret |
| 100 |
| 101 |
| 102 # Create a binary name filter that works around https://crbug.com/444835. |
| 103 # When running tests on OSX swarming servers, ASan sometimes prints paths to |
| 104 # files in cache (ending with SHA1 filenames) instead of paths to hardlinks to |
| 105 # those files in the product dir. |
| 106 # For a given |binary_path| chrome_osx_binary_name_filter() returns one of the |
| 107 # hardlinks to the same inode in |product_dir_path|. |
| 108 def make_chrome_osx_binary_name_filter(product_dir_path=''): |
| 109 def chrome_osx_binary_name_filter(binary_path): |
| 110 basename = os.path.basename(binary_path) |
| 111 if is_hash_name(basename) and product_dir_path: |
| 112 inode = os.stat(binary_path).st_ino |
| 113 new_binary_path = find_inode_at_path(inode, product_dir_path) |
| 114 if new_binary_path: |
| 115 return new_binary_path |
| 116 return binary_path |
| 117 return chrome_osx_binary_name_filter |
| 118 |
| 119 |
51 # Construct a path to the .dSYM bundle for the given binary. | 120 # Construct a path to the .dSYM bundle for the given binary. |
52 # There are three possible cases for binary location in Chromium: | 121 # There are three possible cases for binary location in Chromium: |
53 # 1. The binary is a standalone executable or dynamic library in the product | 122 # 1. The binary is a standalone executable or dynamic library in the product |
54 # dir, the debug info is in "binary.dSYM" in the product dir. | 123 # dir, the debug info is in "binary.dSYM" in the product dir. |
55 # 2. The binary is a standalone framework or .app bundle, the debug info is in | 124 # 2. The binary is a standalone framework or .app bundle, the debug info is in |
56 # "Framework.framework.dSYM" or "App.app.dSYM" in the product dir. | 125 # "Framework.framework.dSYM" or "App.app.dSYM" in the product dir. |
57 # 3. The binary is a framework or an .app bundle within another .app bundle | 126 # 3. The binary is a framework or an .app bundle within another .app bundle |
58 # (e.g. Outer.app/Contents/Versions/1.2.3.4/Inner.app), and the debug info | 127 # (e.g. Outer.app/Contents/Versions/1.2.3.4/Inner.app), and the debug info |
59 # is in Inner.app.dSYM in the product dir. | 128 # is in Inner.app.dSYM in the product dir. |
60 # The first case is handled by llvm-symbolizer, so we only need to construct | 129 # The first case is handled by llvm-symbolizer, so we only need to construct |
61 # .dSYM paths for .app bundles and frameworks. | 130 # .dSYM paths for .app bundles and frameworks. |
62 # We're assuming that there're no more than two nested bundles in the binary | 131 # We're assuming that there're no more than two nested bundles in the binary |
63 # path. Only one of these bundles may be a framework and frameworks cannot | 132 # path. Only one of these bundles may be a framework and frameworks cannot |
64 # contain other bundles. | 133 # contain other bundles. |
65 def chrome_dsym_hints(binary): | 134 def chrome_dsym_hints(binary): |
66 path_parts = binary.split(os.path.sep) | 135 path_parts = split_path(binary) |
67 app_positions = [] | 136 app_positions = [] |
68 framework_positions = [] | 137 framework_positions = [] |
69 for index, part in enumerate(path_parts): | 138 for index, part in enumerate(path_parts): |
70 if part.endswith('.app'): | 139 if part.endswith('.app'): |
71 app_positions.append(index) | 140 app_positions.append(index) |
72 elif part.endswith('.framework'): | 141 elif part.endswith('.framework'): |
73 framework_positions.append(index) | 142 framework_positions.append(index) |
74 bundle_positions = app_positions + framework_positions | 143 bundle_positions = app_positions + framework_positions |
75 bundle_positions.sort() | 144 bundle_positions.sort() |
76 assert len(bundle_positions) <= 2, \ | 145 assert len(bundle_positions) <= 2, \ |
77 "The path contains more than two nested bundles: %s" % binary | 146 "The path contains more than two nested bundles: %s" % binary |
78 if len(bundle_positions) == 0: | 147 if len(bundle_positions) == 0: |
79 # Case 1: this is a standalone executable or dylib. | 148 # Case 1: this is a standalone executable or dylib. |
80 return [] | 149 return [] |
81 assert (not (len(app_positions) == 1 and | 150 assert (not (len(app_positions) == 1 and |
82 len(framework_positions) == 1 and | 151 len(framework_positions) == 1 and |
83 app_positions[0] > framework_positions[0])), \ | 152 app_positions[0] > framework_positions[0])), \ |
84 "The path contains an app bundle inside a framework: %s" % binary | 153 "The path contains an app bundle inside a framework: %s" % binary |
85 # Cases 2 and 3. The outermost bundle (which is the only bundle in the case 2) | 154 # Cases 2 and 3. The outermost bundle (which is the only bundle in the case 2) |
86 # is located in the product dir. | 155 # is located in the product dir. |
87 outermost_bundle = bundle_positions[0] | 156 outermost_bundle = bundle_positions[0] |
88 product_dir = path_parts[:outermost_bundle] | 157 product_dir = path_parts[:outermost_bundle] |
89 # In case 2 this is the same as |outermost_bundle|. | 158 # In case 2 this is the same as |outermost_bundle|. |
90 innermost_bundle = bundle_positions[-1] | 159 innermost_bundle = bundle_positions[-1] |
91 dsym_path = product_dir + [path_parts[innermost_bundle]] | 160 dsym_path = product_dir + [path_parts[innermost_bundle]] |
92 result = '%s.dSYM' % os.path.sep.join(dsym_path) | 161 result = '%s.dSYM' % os.path.join(*dsym_path) |
93 return [result] | 162 return [result] |
94 | 163 |
95 | 164 |
96 # We want our output to match base::EscapeJSONString(), which produces | 165 # We want our output to match base::EscapeJSONString(), which produces |
97 # doubly-escaped strings. The first escaping pass is handled by this class. The | 166 # doubly-escaped strings. The first escaping pass is handled by this class. The |
98 # second pass happens when JSON data is dumped to file. | 167 # second pass happens when JSON data is dumped to file. |
99 class StringEncoder(json.JSONEncoder): | 168 class StringEncoder(json.JSONEncoder): |
100 def __init__(self): | 169 def __init__(self): |
101 json.JSONEncoder.__init__(self) | 170 json.JSONEncoder.__init__(self) |
102 | 171 |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
162 def main(): | 231 def main(): |
163 parser = argparse.ArgumentParser(description='Symbolize sanitizer reports.') | 232 parser = argparse.ArgumentParser(description='Symbolize sanitizer reports.') |
164 parser.add_argument('--test-summary-json-file', | 233 parser.add_argument('--test-summary-json-file', |
165 help='Path to a JSON file produced by the test launcher. The script will ' | 234 help='Path to a JSON file produced by the test launcher. The script will ' |
166 'ignore stdandard input and instead symbolize the output stnippets ' | 235 'ignore stdandard input and instead symbolize the output stnippets ' |
167 'inside the JSON file. The result will be written back to the JSON ' | 236 'inside the JSON file. The result will be written back to the JSON ' |
168 'file.') | 237 'file.') |
169 parser.add_argument('strip_path_prefix', nargs='*', | 238 parser.add_argument('strip_path_prefix', nargs='*', |
170 help='When printing source file names, the longest prefix ending in one ' | 239 help='When printing source file names, the longest prefix ending in one ' |
171 'of these substrings will be stripped. E.g.: "Release/../../".') | 240 'of these substrings will be stripped. E.g.: "Release/../../".') |
| 241 parser.add_argument('--executable-path', |
| 242 help='Path to program executable. Used on OSX swarming bots to locate ' |
| 243 'dSYM bundles for associated frameworks and bundles.') |
172 args = parser.parse_args() | 244 args = parser.parse_args() |
173 | 245 |
174 disable_buffering() | 246 disable_buffering() |
175 set_symbolizer_path() | 247 set_symbolizer_path() |
176 asan_symbolize.demangle = True | 248 asan_symbolize.demangle = True |
177 asan_symbolize.fix_filename_patterns = args.strip_path_prefix | 249 asan_symbolize.fix_filename_patterns = args.strip_path_prefix |
178 loop = asan_symbolize.SymbolizationLoop(dsym_hint_producer=chrome_dsym_hints) | 250 binary_name_filter = None |
| 251 if os.uname()[0] == 'Darwin': |
| 252 binary_name_filter = make_chrome_osx_binary_name_filter( |
| 253 chrome_product_dir_path(args.executable_path)) |
| 254 loop = asan_symbolize.SymbolizationLoop( |
| 255 binary_name_filter=binary_name_filter, |
| 256 dsym_hint_producer=chrome_dsym_hints) |
179 | 257 |
180 if args.test_summary_json_file: | 258 if args.test_summary_json_file: |
181 symbolize_snippets_in_json(args.test_summary_json_file, loop) | 259 symbolize_snippets_in_json(args.test_summary_json_file, loop) |
182 else: | 260 else: |
183 # Process stdin. | 261 # Process stdin. |
184 asan_symbolize.logfile = sys.stdin | 262 asan_symbolize.logfile = sys.stdin |
185 loop.process_logfile() | 263 loop.process_logfile() |
186 | 264 |
187 if __name__ == '__main__': | 265 if __name__ == '__main__': |
188 main() | 266 main() |
OLD | NEW |