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': | |
Timur Iskhodzhanov
2015/02/11 21:36:16
this asserts on Windows, again.
| |
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 |