Chromium Code Reviews| 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 |