Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2017 The Chromium Authors. All rights reserved. | 2 # Copyright 2017 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Main Python API for analyzing binary size.""" | 6 """Main Python API for analyzing binary size.""" |
| 7 | 7 |
| 8 import argparse | 8 import argparse |
| 9 import calendar | 9 import calendar |
| 10 import collections | |
| 10 import datetime | 11 import datetime |
| 11 import gzip | 12 import gzip |
| 12 import logging | 13 import logging |
| 13 import os | 14 import os |
| 14 import re | 15 import re |
| 15 import subprocess | 16 import subprocess |
| 16 import sys | 17 import sys |
| 17 | 18 |
| 18 import describe | 19 import describe |
| 19 import file_format | 20 import file_format |
| 20 import function_signature | 21 import function_signature |
| 21 import helpers | 22 import helpers |
| 22 import linker_map_parser | 23 import linker_map_parser |
| 23 import models | 24 import models |
| 24 import ninja_parser | 25 import ninja_parser |
| 25 import paths | 26 import paths |
| 26 | 27 |
| 27 | 28 |
| 28 def _OpenMaybeGz(path, mode=None): | 29 def _OpenMaybeGz(path, mode=None): |
| 29 """Calls `gzip.open()` if |path| ends in ".gz", otherwise calls `open()`.""" | 30 """Calls `gzip.open()` if |path| ends in ".gz", otherwise calls `open()`.""" |
| 30 if path.endswith('.gz'): | 31 if path.endswith('.gz'): |
| 31 if mode and 'w' in mode: | 32 if mode and 'w' in mode: |
| 32 return gzip.GzipFile(path, mode, 1) | 33 return gzip.GzipFile(path, mode, 1) |
| 33 return gzip.open(path, mode) | 34 return gzip.open(path, mode) |
| 34 return open(path, mode or 'r') | 35 return open(path, mode or 'r') |
| 35 | 36 |
| 36 | 37 |
| 37 def _UnmangleRemainingSymbols(symbol_group, tool_prefix): | 38 def _UnmangleRemainingSymbols(symbols, tool_prefix): |
| 38 """Uses c++filt to unmangle any symbols that need it.""" | 39 """Uses c++filt to unmangle any symbols that need it.""" |
| 39 to_process = [s for s in symbol_group if s.name.startswith('_Z')] | 40 to_process = [s for s in symbols if s.name.startswith('_Z')] |
| 40 if not to_process: | 41 if not to_process: |
| 41 return | 42 return |
| 42 | 43 |
| 43 logging.info('Unmangling %d names', len(to_process)) | 44 logging.info('Unmangling %d names', len(to_process)) |
| 44 proc = subprocess.Popen([tool_prefix + 'c++filt'], stdin=subprocess.PIPE, | 45 proc = subprocess.Popen([tool_prefix + 'c++filt'], stdin=subprocess.PIPE, |
| 45 stdout=subprocess.PIPE) | 46 stdout=subprocess.PIPE) |
| 46 stdout = proc.communicate('\n'.join(s.name for s in to_process))[0] | 47 stdout = proc.communicate('\n'.join(s.name for s in to_process))[0] |
| 47 assert proc.returncode == 0 | 48 assert proc.returncode == 0 |
| 48 | 49 |
| 49 for i, line in enumerate(stdout.splitlines()): | 50 for i, line in enumerate(stdout.splitlines()): |
| 50 to_process[i].name = line | 51 to_process[i].name = line |
| 51 | 52 |
| 52 | 53 |
| 53 def _NormalizeNames(symbol_group): | 54 def _NormalizeNames(symbols): |
| 54 """Ensures that all names are formatted in a useful way. | 55 """Ensures that all names are formatted in a useful way. |
| 55 | 56 |
| 56 This includes: | 57 This includes: |
| 57 - Assigning of |full_name|. | 58 - Assigning of |full_name|. |
| 58 - Stripping of return types in |full_name| and |name| (for functions). | 59 - Stripping of return types in |full_name| and |name| (for functions). |
| 59 - Stripping parameters from |name|. | 60 - Stripping parameters from |name|. |
| 60 - Moving "vtable for" and the like to be suffixes rather than prefixes. | 61 - Moving "vtable for" and the like to be suffixes rather than prefixes. |
| 61 """ | 62 """ |
| 62 found_prefixes = set() | 63 found_prefixes = set() |
| 63 for symbol in symbol_group: | 64 for symbol in symbols: |
| 64 if symbol.name.startswith('*'): | 65 if symbol.name.startswith('*'): |
| 65 # See comment in _RemoveDuplicatesAndCalculatePadding() about when this | 66 # See comment in _CalculatePadding() about when this |
| 66 # can happen. | 67 # can happen. |
| 67 continue | 68 continue |
| 68 | 69 |
| 69 # E.g.: vtable for FOO | 70 # E.g.: vtable for FOO |
| 70 idx = symbol.name.find(' for ', 0, 30) | 71 idx = symbol.name.find(' for ', 0, 30) |
| 71 if idx != -1: | 72 if idx != -1: |
| 72 found_prefixes.add(symbol.name[:idx + 4]) | 73 found_prefixes.add(symbol.name[:idx + 4]) |
| 73 symbol.name = symbol.name[idx + 5:] + ' [' + symbol.name[:idx] + ']' | 74 symbol.name = symbol.name[idx + 5:] + ' [' + symbol.name[:idx] + ']' |
| 74 | 75 |
| 75 # E.g.: virtual thunk to FOO | 76 # E.g.: virtual thunk to FOO |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 96 symbol.full_name = symbol.name | 97 symbol.full_name = symbol.name |
| 97 symbol.name = re.sub(r'\(.*\)', '', symbol.full_name) | 98 symbol.name = re.sub(r'\(.*\)', '', symbol.full_name) |
| 98 | 99 |
| 99 # Don't bother storing both if they are the same. | 100 # Don't bother storing both if they are the same. |
| 100 if symbol.full_name == symbol.name: | 101 if symbol.full_name == symbol.name: |
| 101 symbol.full_name = '' | 102 symbol.full_name = '' |
| 102 | 103 |
| 103 logging.debug('Found name prefixes of: %r', found_prefixes) | 104 logging.debug('Found name prefixes of: %r', found_prefixes) |
| 104 | 105 |
| 105 | 106 |
| 106 def _NormalizeObjectPaths(symbol_group): | 107 def _NormalizeObjectPaths(symbols): |
| 107 """Ensures that all paths are formatted in a useful way.""" | 108 """Ensures that all paths are formatted in a useful way.""" |
| 108 for symbol in symbol_group: | 109 for symbol in symbols: |
| 109 path = symbol.object_path | 110 path = symbol.object_path |
| 110 if path.startswith('obj/'): | 111 if path.startswith('obj/'): |
| 111 # Convert obj/third_party/... -> third_party/... | 112 # Convert obj/third_party/... -> third_party/... |
| 112 path = path[4:] | 113 path = path[4:] |
| 113 elif path.startswith('../../'): | 114 elif path.startswith('../../'): |
| 114 # Convert ../../third_party/... -> third_party/... | 115 # Convert ../../third_party/... -> third_party/... |
| 115 path = path[6:] | 116 path = path[6:] |
| 116 if path.endswith(')'): | 117 if path.endswith(')'): |
| 117 # Convert foo/bar.a(baz.o) -> foo/bar.a/baz.o | 118 # Convert foo/bar.a(baz.o) -> foo/bar.a/baz.o |
| 118 start_idx = path.index('(') | 119 start_idx = path.index('(') |
| 119 path = os.path.join(path[:start_idx], path[start_idx + 1:-1]) | 120 path = os.path.join(path[:start_idx], path[start_idx + 1:-1]) |
| 120 symbol.object_path = path | 121 symbol.object_path = path |
| 121 | 122 |
| 122 | 123 |
| 123 def _NormalizeSourcePath(path): | 124 def _NormalizeSourcePath(path): |
| 124 if path.startswith('gen/'): | 125 if path.startswith('gen/'): |
| 125 # Convert gen/third_party/... -> third_party/... | 126 # Convert gen/third_party/... -> third_party/... |
| 126 return path[4:] | 127 return path[4:] |
| 127 if path.startswith('../../'): | 128 if path.startswith('../../'): |
| 128 # Convert ../../third_party/... -> third_party/... | 129 # Convert ../../third_party/... -> third_party/... |
| 129 return path[6:] | 130 return path[6:] |
| 130 return path | 131 return path |
| 131 | 132 |
| 132 | 133 |
| 133 def _ExtractSourcePaths(symbol_group, output_directory): | 134 def _ExtractSourcePaths(symbols, output_directory): |
| 134 """Fills in the .source_path attribute of all symbols. | 135 """Fills in the .source_path attribute of all symbols. |
| 135 | 136 |
| 136 Returns True if source paths were found. | 137 Returns True if source paths were found. |
| 137 """ | 138 """ |
| 138 all_found = True | 139 all_found = True |
| 139 mapper = ninja_parser.SourceFileMapper(output_directory) | 140 mapper = ninja_parser.SourceFileMapper(output_directory) |
| 140 | 141 |
| 141 for symbol in symbol_group: | 142 for symbol in symbols: |
| 142 object_path = symbol.object_path | 143 object_path = symbol.object_path |
| 143 if symbol.source_path or not object_path: | 144 if symbol.source_path or not object_path: |
| 144 continue | 145 continue |
| 145 # We don't have source info for prebuilt .a files. | 146 # We don't have source info for prebuilt .a files. |
| 146 if not object_path.startswith('..'): | 147 if not object_path.startswith('..'): |
| 147 source_path = mapper.FindSourceForPath(object_path) | 148 source_path = mapper.FindSourceForPath(object_path) |
| 148 if source_path: | 149 if source_path: |
| 149 symbol.source_path = _NormalizeSourcePath(source_path) | 150 symbol.source_path = _NormalizeSourcePath(source_path) |
| 150 else: | 151 else: |
| 151 all_found = False | 152 all_found = False |
| 152 logging.warning('Could not find source path for %s', object_path) | 153 logging.warning('Could not find source path for %s', object_path) |
| 153 logging.debug('Parsed %d .ninja files.', mapper.GetParsedFileCount()) | 154 logging.debug('Parsed %d .ninja files.', mapper.GetParsedFileCount()) |
| 154 return all_found | 155 return all_found |
| 155 | 156 |
| 156 | 157 |
| 157 def _RemoveDuplicatesAndCalculatePadding(symbol_group): | 158 def _CalculatePadding(symbols): |
| 158 """Removes symbols at the same address and calculates the |padding| field. | 159 """Populates the |padding| field based on symbol addresses. |
| 159 | 160 |
| 160 Symbols must already be sorted by |address|. | 161 Symbols must already be sorted by |address|. |
| 161 """ | 162 """ |
| 162 to_remove = [] | |
| 163 seen_sections = [] | 163 seen_sections = [] |
| 164 for i, symbol in enumerate(symbol_group[1:]): | 164 for i, symbol in enumerate(symbols[1:]): |
| 165 prev_symbol = symbol_group[i] | 165 prev_symbol = symbols[i] |
| 166 if prev_symbol.section_name != symbol.section_name: | 166 if prev_symbol.section_name != symbol.section_name: |
| 167 assert symbol.section_name not in seen_sections, ( | 167 assert symbol.section_name not in seen_sections, ( |
| 168 'Input symbols must be sorted by section, then address.') | 168 'Input symbols must be sorted by section, then address.') |
| 169 seen_sections.append(symbol.section_name) | 169 seen_sections.append(symbol.section_name) |
| 170 continue | 170 continue |
| 171 if symbol.address <= 0 or prev_symbol.address <= 0: | 171 if symbol.address <= 0 or prev_symbol.address <= 0: |
| 172 continue | 172 continue |
| 173 # Fold symbols that are at the same address (happens in nm output). | 173 # Padding-only symbols happen for ** symbol gaps. |
| 174 prev_is_padding_only = prev_symbol.size_without_padding == 0 | 174 prev_is_padding_only = prev_symbol.size_without_padding == 0 |
| 175 if symbol.address == prev_symbol.address and not prev_is_padding_only: | 175 if symbol.address == prev_symbol.address and not prev_is_padding_only: |
| 176 symbol.size = max(prev_symbol.size, symbol.size) | 176 assert False, 'Found duplicate symbols:\n%r\n%r' % (prev_symbol, symbol) |
| 177 to_remove.add(symbol) | |
| 178 continue | |
| 179 # Even with symbols at the same address removed, overlaps can still | 177 # Even with symbols at the same address removed, overlaps can still |
| 180 # happen. In this case, padding will be negative (and this is fine). | 178 # happen. In this case, padding will be negative (and this is fine). |
| 181 padding = symbol.address - prev_symbol.end_address | 179 padding = symbol.address - prev_symbol.end_address |
| 182 # These thresholds were found by manually auditing arm32 Chrome. | 180 # These thresholds were found by manually auditing arm32 Chrome. |
| 183 # E.g.: Set them to 0 and see what warnings get logged. | 181 # E.g.: Set them to 0 and see what warnings get logged. |
| 184 # TODO(agrieve): See if these thresholds make sense for architectures | 182 # TODO(agrieve): See if these thresholds make sense for architectures |
| 185 # other than arm32. | 183 # other than arm32. |
| 186 if not symbol.name.startswith('*') and ( | 184 if not symbol.name.startswith('*') and ( |
| 187 symbol.section in 'rd' and padding >= 256 or | 185 symbol.section in 'rd' and padding >= 256 or |
| 188 symbol.section in 't' and padding >= 64): | 186 symbol.section in 't' and padding >= 64): |
| 189 # For nm data, this is caused by data that has no associated symbol. | 187 # For nm data, this is caused by data that has no associated symbol. |
| 190 # The linker map file lists them with no name, but with a file. | 188 # The linker map file lists them with no name, but with a file. |
| 191 # Example: | 189 # Example: |
| 192 # .data 0x02d42764 0x120 .../V8SharedWorkerGlobalScope.o | 190 # .data 0x02d42764 0x120 .../V8SharedWorkerGlobalScope.o |
| 193 # Where as most look like: | 191 # Where as most look like: |
| 194 # .data.MANGLED_NAME... | 192 # .data.MANGLED_NAME... |
| 195 logging.debug('Large padding of %d between:\n A) %r\n B) %r' % ( | 193 logging.debug('Large padding of %d between:\n A) %r\n B) %r' % ( |
| 196 padding, prev_symbol, symbol)) | 194 padding, prev_symbol, symbol)) |
| 197 continue | 195 continue |
| 198 symbol.padding = padding | 196 symbol.padding = padding |
| 199 symbol.size += padding | 197 symbol.size += padding |
| 200 assert symbol.size >= 0, ( | 198 assert symbol.size >= 0, ( |
| 201 'Symbol has negative size (likely not sorted propertly): ' | 199 'Symbol has negative size (likely not sorted propertly): ' |
| 202 '%r\nprev symbol: %r' % (symbol, prev_symbol)) | 200 '%r\nprev symbol: %r' % (symbol, prev_symbol)) |
| 203 # Map files have no overlaps, so worth special-casing the no-op case. | |
| 204 if to_remove: | |
| 205 logging.info('Removing %d overlapping symbols', len(to_remove)) | |
| 206 symbol_group -= models.SymbolGroup(to_remove) | |
| 207 | 201 |
| 208 | 202 |
| 209 def Analyze(path, lazy_paths=None): | 203 def _ClusterSymbols(symbols): |
| 210 """Returns a SizeInfo for the given |path|. | 204 """Returns a new list of symbols with some symbols moved into groups. |
| 211 | 205 |
| 212 Args: | 206 Groups include: |
| 213 path: Can be a .size file, or a .map(.gz). If the latter, then lazy_paths | 207 * Symbols that have [clone] in their name (created by compiler optimization). |
| 214 must be provided as well. | 208 * Star symbols (such as "** merge strings", and "** symbol gap") |
| 215 """ | 209 """ |
| 216 if path.endswith('.size'): | 210 # http://unix.stackexchange.com/questions/223013/function-symbol-gets-part-suf fix-after-compilation |
| 217 logging.debug('Loading results from: %s', path) | 211 # Example name suffixes: |
| 218 size_info = file_format.LoadSizeInfo(path) | 212 # [clone .part.322] |
| 219 # Recompute derived values (padding and function names). | 213 # [clone .isra.322] |
| 220 logging.info('Calculating padding') | 214 # [clone .constprop.1064] |
| 221 _RemoveDuplicatesAndCalculatePadding(size_info.symbols) | 215 |
| 222 logging.info('Deriving signatures') | 216 # Step 1: Create name map, find clones, collect star syms into replacements. |
| 223 # Re-parse out function parameters. | 217 logging.debug('Creating name -> symbol map') |
| 224 _NormalizeNames(size_info.symbols) | 218 clone_indices = [] |
| 225 return size_info | 219 indices_by_full_name = {} |
| 226 elif not path.endswith('.map') and not path.endswith('.map.gz'): | 220 # (name, full_name) -> [(index, sym),...] |
| 227 raise Exception('Expected input to be a .map or a .size') | 221 replacements_by_name = collections.defaultdict(list) |
| 228 else: | 222 for i, symbol in enumerate(symbols): |
| 223 if symbol.name.startswith('**'): | |
| 224 # "symbol gap 3" -> "symbol gaps" | |
| 225 name = re.sub(r'\s+\d+$', 's', symbol.name) | |
| 226 replacements_by_name[(name, None)].append((i, symbol)) | |
| 227 elif symbol.full_name: | |
| 228 if symbol.full_name.endswith(']') and ' [clone ' in symbol.full_name: | |
| 229 clone_indices.append(i) | |
| 230 else: | |
| 231 indices_by_full_name[symbol.full_name] = i | |
| 232 | |
| 233 # Step 2: Collect same-named clone symbols. | |
| 234 logging.debug('Grouping all clones') | |
| 235 group_names_by_index = {} | |
| 236 for i in clone_indices: | |
| 237 symbol = symbols[i] | |
| 238 # Multiple attributes could exist, so search from left-to-right. | |
| 239 stripped_name = symbol.name[:symbol.name.index(' [clone ')] | |
| 240 stripped_full_name = symbol.full_name[:symbol.full_name.index(' [clone ')] | |
| 241 name_tup = (stripped_name, stripped_full_name) | |
| 242 replacement_list = replacements_by_name[name_tup] | |
| 243 | |
| 244 if not replacement_list: | |
| 245 # First occurance, check for non-clone symbol. | |
| 246 non_clone_idx = indices_by_full_name.get(stripped_name) | |
| 247 if non_clone_idx is not None: | |
| 248 non_clone_symbol = symbols[non_clone_idx] | |
| 249 replacement_list.append((non_clone_idx, non_clone_symbol)) | |
| 250 group_names_by_index[non_clone_idx] = stripped_name | |
| 251 | |
| 252 replacement_list.append((i, symbol)) | |
| 253 group_names_by_index[i] = stripped_name | |
| 254 | |
| 255 # Step 3: Undo clustering when length=1. | |
| 256 # Removing these groups means Diff() logic must know about [clone] suffix. | |
| 257 to_clear = [] | |
| 258 for name_tup, replacement_list in replacements_by_name.iteritems(): | |
| 259 if len(replacement_list) == 1: | |
| 260 to_clear.append(name_tup) | |
| 261 for name_tup in to_clear: | |
| 262 del replacements_by_name[name_tup] | |
| 263 | |
| 264 # Step 4: Replace first symbol from each cluster with a SymbolGroup. | |
| 265 before_symbol_count = sum(len(x) for x in replacements_by_name.itervalues()) | |
| 266 logging.debug('Creating %d symbol groups from %d symbols. %d clones had only ' | |
| 267 'one symbol.', len(replacements_by_name), before_symbol_count, | |
| 268 len(to_clear)) | |
| 269 | |
| 270 len_delta = len(replacements_by_name) - before_symbol_count | |
| 271 grouped_symbols = [None] * (len(symbols) + len_delta) | |
| 272 dest_index = 0 | |
| 273 src_index = 0 | |
| 274 seen_names = set() | |
| 275 replacement_names_by_index = {} | |
| 276 for name_tup, replacement_list in replacements_by_name.iteritems(): | |
| 277 for tup in replacement_list: | |
| 278 replacement_names_by_index[tup[0]] = name_tup | |
| 279 | |
| 280 sorted_items = replacement_names_by_index.items() | |
| 281 sorted_items.sort(key=lambda tup: tup[0]) | |
| 282 for index, name_tup in sorted_items: | |
| 283 count = index - src_index | |
| 284 grouped_symbols[dest_index:dest_index + count] = ( | |
| 285 symbols[src_index:src_index + count]) | |
| 286 src_index = index + 1 | |
| 287 dest_index += count | |
| 288 if name_tup not in seen_names: | |
| 289 seen_names.add(name_tup) | |
| 290 group_symbols = [tup[1] for tup in replacements_by_name[name_tup]] | |
| 291 grouped_symbols[dest_index] = models.SymbolGroup( | |
| 292 group_symbols, name=name_tup[0], full_name=name_tup[1], | |
| 293 section_name=group_symbols[0].section_name) | |
| 294 dest_index += 1 | |
| 295 | |
| 296 assert len(grouped_symbols[dest_index:None]) == len(symbols[src_index:None]) | |
| 297 grouped_symbols[dest_index:None] = symbols[src_index:None] | |
| 298 logging.debug('Finished making groups.') | |
| 299 return grouped_symbols | |
| 300 | |
| 301 | |
| 302 def LoadAndPostProcessSizeInfo(path): | |
| 303 """Returns a SizeInfo for the given |path|.""" | |
| 304 logging.debug('Loading results from: %s', path) | |
| 305 size_info = file_format.LoadSizeInfo(path) | |
| 306 _PostProcessSizeInfo(size_info) | |
| 307 return size_info | |
| 308 | |
| 309 | |
| 310 def _PostProcessSizeInfo(size_info): | |
| 311 logging.info('Normalizing symbol names') | |
| 312 _NormalizeNames(size_info.raw_symbols) | |
| 313 logging.info('Calculating padding') | |
| 314 _CalculatePadding(size_info.raw_symbols) | |
| 315 logging.info('Grouping decomposed functions') | |
| 316 size_info.symbols = models.SymbolGroup( | |
| 317 _ClusterSymbols(size_info.raw_symbols)) | |
| 318 logging.info('Processed %d symbols', len(size_info.raw_symbols)) | |
| 319 | |
| 320 | |
| 321 def CreateSizeInfo(map_path, lazy_paths=None, no_source_paths=False, | |
| 322 raw_only=False): | |
| 323 """Creates a SizeInfo from the given map file.""" | |
| 324 if not no_source_paths: | |
| 229 # output_directory needed for source file information. | 325 # output_directory needed for source file information. |
| 230 lazy_paths.VerifyOutputDirectory() | 326 lazy_paths.VerifyOutputDirectory() |
| 231 # tool_prefix needed for c++filt. | 327 # tool_prefix needed for c++filt. |
| 232 lazy_paths.VerifyToolPrefix() | 328 lazy_paths.VerifyToolPrefix() |
| 233 | 329 |
| 234 with _OpenMaybeGz(path) as map_file: | 330 with _OpenMaybeGz(map_path) as map_file: |
| 235 section_sizes, symbols = linker_map_parser.MapFileParser().Parse(map_file) | 331 section_sizes, raw_symbols = ( |
| 236 size_info = models.SizeInfo(section_sizes, models.SymbolGroup(symbols)) | 332 linker_map_parser.MapFileParser().Parse(map_file)) |
| 237 | 333 |
| 238 # Map file for some reason doesn't unmangle all names. | 334 if not no_source_paths: |
| 239 logging.info('Calculating padding') | |
| 240 _RemoveDuplicatesAndCalculatePadding(size_info.symbols) | |
| 241 # Unmangle prints its own log statement. | |
| 242 _UnmangleRemainingSymbols(size_info.symbols, lazy_paths.tool_prefix) | |
| 243 logging.info('Extracting source paths from .ninja files') | 335 logging.info('Extracting source paths from .ninja files') |
| 244 all_found = _ExtractSourcePaths(size_info.symbols, | 336 all_found = _ExtractSourcePaths(raw_symbols, lazy_paths.output_directory) |
| 245 lazy_paths.output_directory) | |
| 246 assert all_found, ( | 337 assert all_found, ( |
| 247 'One or more source file paths could not be found. Likely caused by ' | 338 'One or more source file paths could not be found. Likely caused by ' |
| 248 '.ninja files being generated at a different time than the .map file.') | 339 '.ninja files being generated at a different time than the .map file.') |
| 249 # Resolve paths prints its own log statement. | 340 # Map file for some reason doesn't unmangle all names. |
| 250 logging.info('Normalizing names') | 341 # Unmangle prints its own log statement. |
| 251 _NormalizeNames(size_info.symbols) | 342 _UnmangleRemainingSymbols(raw_symbols, lazy_paths.tool_prefix) |
| 252 logging.info('Normalizing paths') | 343 logging.info('Normalizing object paths') |
| 253 _NormalizeObjectPaths(size_info.symbols) | 344 _NormalizeObjectPaths(raw_symbols) |
| 345 size_info = models.SizeInfo(section_sizes, raw_symbols) | |
| 254 | 346 |
| 255 if logging.getLogger().isEnabledFor(logging.INFO): | 347 # Name normalization not strictly required, but makes for smaller files. |
| 348 if raw_only: | |
| 349 logging.info('Normalizing symbol names') | |
| 350 _NormalizeNames(size_info.raw_symbols) | |
| 351 else: | |
| 352 _PostProcessSizeInfo(size_info) | |
| 353 | |
| 354 if logging.getLogger().isEnabledFor(logging.DEBUG): | |
| 256 for line in describe.DescribeSizeInfoCoverage(size_info): | 355 for line in describe.DescribeSizeInfoCoverage(size_info): |
| 257 logging.info(line) | 356 logging.info(line) |
| 258 logging.info('Finished analyzing %d symbols', len(size_info.symbols)) | 357 logging.info('Recorded info for %d symbols', len(size_info.raw_symbols)) |
| 259 return size_info | 358 return size_info |
| 260 | 359 |
| 261 | 360 |
| 262 def _DetectGitRevision(directory): | 361 def _DetectGitRevision(directory): |
| 263 try: | 362 try: |
| 264 git_rev = subprocess.check_output( | 363 git_rev = subprocess.check_output( |
| 265 ['git', '-C', directory, 'rev-parse', 'HEAD']) | 364 ['git', '-C', directory, 'rev-parse', 'HEAD']) |
| 266 return git_rev.rstrip() | 365 return git_rev.rstrip() |
| 267 except Exception: | 366 except Exception: |
| 268 logging.warning('Failed to detect git revision for file metadata.') | 367 logging.warning('Failed to detect git revision for file metadata.') |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 296 # Strips #s even if within string literal. Not a problem in practice. | 395 # Strips #s even if within string literal. Not a problem in practice. |
| 297 parts = l.split('#')[0].split('=') | 396 parts = l.split('#')[0].split('=') |
| 298 if len(parts) != 2: | 397 if len(parts) != 2: |
| 299 continue | 398 continue |
| 300 args[parts[0].strip()] = parts[1].strip() | 399 args[parts[0].strip()] = parts[1].strip() |
| 301 return ["%s=%s" % x for x in sorted(args.iteritems())] | 400 return ["%s=%s" % x for x in sorted(args.iteritems())] |
| 302 | 401 |
| 303 | 402 |
| 304 def main(argv): | 403 def main(argv): |
| 305 parser = argparse.ArgumentParser(argv) | 404 parser = argparse.ArgumentParser(argv) |
| 306 parser.add_argument('elf_file', help='Path to input ELF file.') | 405 parser.add_argument('--elf-file', required=True, |
|
estevenson
2017/04/11 17:11:42
nit: won't the --map-file always be --elf-file + '
agrieve
2017/04/11 17:32:49
That is the default for the arg, so normally you w
| |
| 307 parser.add_argument('output_file', help='Path to output .size(.gz) file.') | 406 help='Path to input ELF file. Currently used for ' |
| 407 'capturing metadata. Pass "" to skip metadata ' | |
| 408 'collection.') | |
| 308 parser.add_argument('--map-file', | 409 parser.add_argument('--map-file', |
| 309 help='Path to input .map(.gz) file. Defaults to ' | 410 help='Path to input .map(.gz) file. Defaults to ' |
| 310 '{{elf_file}}.map(.gz)?') | 411 '{{elf_file}}.map(.gz)?') |
| 412 parser.add_argument('--output-file', required=True, | |
| 413 help='Path to output .size file.') | |
| 414 parser.add_argument('--no-source-paths', action='store_true', | |
| 415 help='Do not use .ninja files to map ' | |
| 416 'object_path -> source_path') | |
| 311 paths.AddOptions(parser) | 417 paths.AddOptions(parser) |
| 312 args = helpers.AddCommonOptionsAndParseArgs(parser, argv) | 418 args = helpers.AddCommonOptionsAndParseArgs(parser, argv) |
| 313 if not args.output_file.endswith('.size'): | 419 if not args.output_file.endswith('.size'): |
| 314 parser.error('output_file must end with .size') | 420 parser.error('output_file must end with .size') |
| 315 | 421 |
| 316 if args.map_file: | 422 if args.map_file: |
| 423 if (not args.map_file.endswith('.map') | |
| 424 and not args.map_file.endswith('.map.gz')): | |
| 425 parser.error('Expected --map-file to end with .map or .map.gz') | |
| 317 map_file_path = args.map_file | 426 map_file_path = args.map_file |
| 318 elif args.elf_file.endswith('.size'): | |
| 319 # Allow a .size file to be passed as input as well. Useful for measuring | |
| 320 # serialization speed. | |
| 321 pass | |
| 322 else: | 427 else: |
| 323 map_file_path = args.elf_file + '.map' | 428 map_file_path = args.elf_file + '.map' |
| 324 if not os.path.exists(map_file_path): | 429 if not os.path.exists(map_file_path): |
| 325 map_file_path += '.gz' | 430 map_file_path += '.gz' |
| 326 if not os.path.exists(map_file_path): | 431 if not os.path.exists(map_file_path): |
| 327 parser.error('Could not find .map(.gz)? file. Use --map-file.') | 432 parser.error('Could not find .map(.gz)? file. Use --map-file.') |
| 328 | 433 |
| 329 lazy_paths = paths.LazyPaths(args=args, input_file=args.elf_file) | 434 lazy_paths = paths.LazyPaths(args=args, input_file=args.elf_file) |
| 330 metadata = None | 435 metadata = None |
| 331 if args.elf_file and not args.elf_file.endswith('.size'): | 436 if args.elf_file: |
| 332 logging.debug('Constructing metadata') | 437 logging.debug('Constructing metadata') |
| 333 git_rev = _DetectGitRevision(os.path.dirname(args.elf_file)) | 438 git_rev = _DetectGitRevision(os.path.dirname(args.elf_file)) |
| 334 build_id = BuildIdFromElf(args.elf_file, lazy_paths.tool_prefix) | 439 build_id = BuildIdFromElf(args.elf_file, lazy_paths.tool_prefix) |
| 335 timestamp_obj = datetime.datetime.utcfromtimestamp(os.path.getmtime( | 440 timestamp_obj = datetime.datetime.utcfromtimestamp(os.path.getmtime( |
| 336 args.elf_file)) | 441 args.elf_file)) |
| 337 timestamp = calendar.timegm(timestamp_obj.timetuple()) | 442 timestamp = calendar.timegm(timestamp_obj.timetuple()) |
| 338 gn_args = _ParseGnArgs(os.path.join(lazy_paths.output_directory, 'args.gn')) | 443 gn_args = _ParseGnArgs(os.path.join(lazy_paths.output_directory, 'args.gn')) |
| 339 | 444 |
| 340 def relative_to_out(path): | 445 def relative_to_out(path): |
| 341 return os.path.relpath(path, lazy_paths.VerifyOutputDirectory()) | 446 return os.path.relpath(path, lazy_paths.VerifyOutputDirectory()) |
| 342 | 447 |
| 343 metadata = { | 448 metadata = { |
| 344 models.METADATA_GIT_REVISION: git_rev, | 449 models.METADATA_GIT_REVISION: git_rev, |
| 345 models.METADATA_MAP_FILENAME: relative_to_out(map_file_path), | 450 models.METADATA_MAP_FILENAME: relative_to_out(map_file_path), |
| 346 models.METADATA_ELF_FILENAME: relative_to_out(args.elf_file), | 451 models.METADATA_ELF_FILENAME: relative_to_out(args.elf_file), |
| 347 models.METADATA_ELF_MTIME: timestamp, | 452 models.METADATA_ELF_MTIME: timestamp, |
| 348 models.METADATA_ELF_BUILD_ID: build_id, | 453 models.METADATA_ELF_BUILD_ID: build_id, |
| 349 models.METADATA_GN_ARGS: gn_args, | 454 models.METADATA_GN_ARGS: gn_args, |
| 350 } | 455 } |
| 351 | 456 |
| 352 size_info = Analyze(map_file_path, lazy_paths) | 457 size_info = CreateSizeInfo(map_file_path, lazy_paths, |
| 458 no_source_paths=args.no_source_paths, | |
| 459 raw_only=True) | |
| 353 | 460 |
| 354 if metadata: | 461 if metadata: |
| 462 size_info.metadata = metadata | |
| 355 logging.debug('Validating section sizes') | 463 logging.debug('Validating section sizes') |
| 356 elf_section_sizes = _SectionSizesFromElf(args.elf_file, | 464 elf_section_sizes = _SectionSizesFromElf(args.elf_file, |
| 357 lazy_paths.tool_prefix) | 465 lazy_paths.tool_prefix) |
| 358 for k, v in elf_section_sizes.iteritems(): | 466 for k, v in elf_section_sizes.iteritems(): |
| 359 assert v == size_info.section_sizes.get(k), ( | 467 assert v == size_info.section_sizes.get(k), ( |
| 360 'ELF file and .map file do not match.') | 468 'ELF file and .map file do not match.') |
| 361 | 469 |
| 362 size_info.metadata = metadata | |
| 363 | |
| 364 logging.info('Recording metadata: \n %s', | 470 logging.info('Recording metadata: \n %s', |
| 365 '\n '.join(describe.DescribeMetadata(size_info.metadata))) | 471 '\n '.join(describe.DescribeMetadata(size_info.metadata))) |
| 366 logging.info('Saving result to %s', args.output_file) | 472 logging.info('Saving result to %s', args.output_file) |
| 367 file_format.SaveSizeInfo(size_info, args.output_file) | 473 file_format.SaveSizeInfo(size_info, args.output_file) |
| 368 logging.info('Done') | 474 logging.info('Done') |
| 369 | 475 |
| 370 | 476 |
| 371 if __name__ == '__main__': | 477 if __name__ == '__main__': |
| 372 sys.exit(main(sys.argv)) | 478 sys.exit(main(sys.argv)) |
| OLD | NEW |