Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: tools/binary_size/map2size.py

Issue 2809043003: //tools/binary_size: Group [clone] and ** symbols (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698