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

Side by Side Diff: tools/binary_size/libsupersize/console.py

Issue 2881563003: supersize: Make Disassemble() work for downloaded .size files (Closed)
Patch Set: self-review Created 3 years, 7 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 # Copyright 2017 The Chromium Authors. All rights reserved. 1 # Copyright 2017 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """An interactive console for looking analyzing .size files.""" 5 """An interactive console for looking analyzing .size files."""
6 6
7 import argparse 7 import argparse
8 import atexit 8 import atexit
9 import code 9 import code
10 import contextlib 10 import contextlib
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
59 elif to_file: 59 elif to_file:
60 with open(to_file, 'w') as file_obj: 60 with open(to_file, 'w') as file_obj:
61 describe.WriteLines(lines, file_obj.write) 61 describe.WriteLines(lines, file_obj.write)
62 else: 62 else:
63 describe.WriteLines(lines, sys.stdout.write) 63 describe.WriteLines(lines, sys.stdout.write)
64 64
65 65
66 class _Session(object): 66 class _Session(object):
67 _readline_initialized = False 67 _readline_initialized = False
68 68
69 def __init__(self, size_infos, lazy_paths): 69 def __init__(self, size_infos, size_paths, lazy_paths):
70 self._variables = { 70 self._variables = {
71 'Print': self._PrintFunc, 71 'Print': self._PrintFunc,
72 'Diff': self._DiffFunc, 72 'Diff': self._DiffFunc,
73 'Disassemble': self._DisassembleFunc, 73 'Disassemble': self._DisassembleFunc,
74 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder, 74 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder,
75 'ShowExamples': self._ShowExamplesFunc, 75 'ShowExamples': self._ShowExamplesFunc,
76 'canned_queries': canned_queries.CannedQueries(size_infos), 76 'canned_queries': canned_queries.CannedQueries(size_infos),
77 } 77 }
78 self._lazy_paths = lazy_paths 78 self._lazy_paths = lazy_paths
79 self._size_infos = size_infos 79 self._size_infos = size_infos
80 self._size_paths = size_paths
81 self._disassemble_prefix_len = None
80 82
81 if len(size_infos) == 1: 83 if len(size_infos) == 1:
82 self._variables['size_info'] = size_infos[0] 84 self._variables['size_info'] = size_infos[0]
83 else: 85 else:
84 for i, size_info in enumerate(size_infos): 86 for i, size_info in enumerate(size_infos):
85 self._variables['size_info%d' % (i + 1)] = size_info 87 self._variables['size_info%d' % (i + 1)] = size_info
86 88
87 def _DiffFunc(self, before=None, after=None, cluster=True, sort=True): 89 def _DiffFunc(self, before=None, after=None, cluster=True, sort=True):
88 """Diffs two SizeInfo objects. Returns a SizeInfoDiff. 90 """Diffs two SizeInfo objects. Returns a SizeInfoDiff.
89 91
(...skipping 22 matching lines...) Expand all
112 verbose: Show more detailed output. 114 verbose: Show more detailed output.
113 recursive: Print children of nested SymbolGroups. 115 recursive: Print children of nested SymbolGroups.
114 use_pager: Pipe output through `less`. Ignored when |obj| is a Symbol. 116 use_pager: Pipe output through `less`. Ignored when |obj| is a Symbol.
115 default is to automatically pipe when output is long. 117 default is to automatically pipe when output is long.
116 to_file: Rather than print to stdio, write to the given file. 118 to_file: Rather than print to stdio, write to the given file.
117 """ 119 """
118 obj = obj if obj is not None else self._size_infos[-1] 120 obj = obj if obj is not None else self._size_infos[-1]
119 lines = describe.GenerateLines(obj, verbose=verbose, recursive=recursive) 121 lines = describe.GenerateLines(obj, verbose=verbose, recursive=recursive)
120 _WriteToStream(lines, use_pager=use_pager, to_file=to_file) 122 _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
121 123
122 def _ElfPathForSymbol(self, symbol): 124 def _ElfPathAndToolPrefixForSymbol(self, symbol, elf_path, tool_prefix):
123 size_info = None 125 size_info = None
124 for size_info in self._size_infos: 126 size_path = None
127 for size_info, size_path in zip(self._size_infos, self._size_paths):
125 if symbol in size_info.symbols: 128 if symbol in size_info.symbols:
126 break 129 break
127 else: 130 else:
128 assert False, 'Symbol does not belong to a size_info.' 131 # If symbols is from a diff(), use its address+name to find it.
132 for size_info, size_path in zip(self._size_infos, self._size_paths):
133 matched = size_info.symbols.WhereAddressInRange(symbol.address)
134 # Use last matched symbol to skip over padding-only symbols.
135 if len(matched) > 0 and matched[-1].full_name == symbol.full_name:
136 symbol = matched[-1]
137 break
138 else:
139 assert False, 'Symbol does not belong to a size_info.'
129 140
130 filename = size_info.metadata.get(models.METADATA_ELF_FILENAME) 141 orig_tool_prefix = size_info.metadata.get(models.METADATA_TOOL_PREFIX)
131 output_dir = self._lazy_paths.output_directory or '' 142 if orig_tool_prefix:
132 path = os.path.normpath(os.path.join(output_dir, filename)) 143 orig_tool_prefix = paths.FromSrcRootRelative(orig_tool_prefix)
144 if os.path.exists(orig_tool_prefix + 'objdump'):
145 tool_prefix = orig_tool_prefix
133 146
134 found_build_id = archive.BuildIdFromElf(path, self._lazy_paths.tool_prefix) 147 # TODO(agrieve): Would be even better to use objdump --info to check that
148 # the toolchain is for the correct architecture.
149 assert tool_prefix is not None, (
150 'Could not determine --tool-prefix. Possible fixes include setting '
151 '--tool-prefix, or setting --output-directory')
152
153 if elf_path is None:
154 filename = size_info.metadata.get(models.METADATA_ELF_FILENAME)
155 output_dir = self._lazy_paths.output_directory
156 size_path = self._size_paths[self._size_infos.index(size_info)]
157 if output_dir:
158 # Local build: File is located in output directory.
159 path = os.path.normpath(os.path.join(output_dir, filename))
160 if not output_dir or not os.path.exists(path):
161 # Downloaded build: File is located beside .size file.
162 path = os.path.normpath(os.path.join(
163 os.path.dirname(size_path), os.path.basename(filename)))
164
165 assert os.path.exists(path), (
166 'Could locate ELF file. If binary was built locally, ensure '
167 '--output-directory is set. If output directory is unavailable, '
168 'ensure {} is located beside {}, or pass its path explicitly using '
169 'elf_path=').format(os.path.basename(filename), size_path)
170
171 found_build_id = archive.BuildIdFromElf(path, tool_prefix)
135 expected_build_id = size_info.metadata.get(models.METADATA_ELF_BUILD_ID) 172 expected_build_id = size_info.metadata.get(models.METADATA_ELF_BUILD_ID)
136 assert found_build_id == expected_build_id, ( 173 assert found_build_id == expected_build_id, (
137 'Build ID does not match for %s' % path) 174 'Build ID does not match for %s' % path)
138 return path 175 return path, tool_prefix
176
177 def _DetectDisassemblePrefixLen(self, args):
178 # Look for a line that looks like:
179 # /usr/{snip}/src/out/Release/../../net/quic/core/quic_time.h:100
180 output = subprocess.check_output(args)
181 for line in output.splitlines():
182 if line and line[0] == os.path.sep and line[-1].isdigit():
183 release_idx = line.find('Release')
184 if release_idx == -1:
185 break
186 return line.count(os.path.sep, 0, release_idx)
187 logging.warning('Found no source paths in objdump output.')
188 return None
139 189
140 def _DisassembleFunc(self, symbol, elf_path=None, use_pager=None, 190 def _DisassembleFunc(self, symbol, elf_path=None, use_pager=None,
141 to_file=None): 191 to_file=None):
142 """Shows objdump disassembly for the given symbol. 192 """Shows objdump disassembly for the given symbol.
143 193
144 Args: 194 Args:
145 symbol: Must be a .text symbol and not a SymbolGroup. 195 symbol: Must be a .text symbol and not a SymbolGroup.
146 elf_path: Path to the executable containing the symbol. Required only 196 elf_path: Path to the executable containing the symbol. Required only
147 when auto-detection fails. 197 when auto-detection fails.
148 """ 198 """
149 assert symbol.address and symbol.section_name == '.text' 199 assert symbol.address and symbol.section_name == '.text'
200
201 tool_prefix = self._lazy_paths.tool_prefix
150 if not elf_path: 202 if not elf_path:
151 elf_path = self._ElfPathForSymbol(symbol) 203 elf_path, tool_prefix = self._ElfPathAndToolPrefixForSymbol(
152 tool_prefix = self._lazy_paths.tool_prefix 204 symbol, elf_path, tool_prefix)
205
153 args = [tool_prefix + 'objdump', '--disassemble', '--source', 206 args = [tool_prefix + 'objdump', '--disassemble', '--source',
154 '--line-numbers', '--demangle', 207 '--line-numbers', '--demangle',
155 '--start-address=0x%x' % symbol.address, 208 '--start-address=0x%x' % symbol.address,
156 '--stop-address=0x%x' % symbol.end_address, elf_path] 209 '--stop-address=0x%x' % symbol.end_address, elf_path]
210 if self._disassemble_prefix_len is None:
211 prefix_len = self._DetectDisassemblePrefixLen(args)
212 if prefix_len is not None:
213 self._disassemble_prefix_len = prefix_len
214
215 if self._disassemble_prefix_len is not None:
216 output_directory = self._lazy_paths.output_directory
217 # Only matters for non-generated paths, so be lenient here.
218 if output_directory is None:
219 output_directory = os.path.join(paths.SRC_ROOT, 'out', 'Release')
220 if not os.path.exists(output_directory):
221 os.makedirs(output_directory)
222
223 args += [
224 '--prefix-strip', str(self._disassemble_prefix_len),
225 '--prefix', os.path.normpath(os.path.relpath(output_directory))
226 ]
227
157 proc = subprocess.Popen(args, stdout=subprocess.PIPE) 228 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
158 lines = itertools.chain(('Showing disassembly for %r' % symbol, 229 lines = itertools.chain(('Showing disassembly for %r' % symbol,
159 'Command: %s' % ' '.join(args)), 230 'Command: %s' % ' '.join(args)),
160 (l.rstrip() for l in proc.stdout)) 231 (l.rstrip() for l in proc.stdout))
161 _WriteToStream(lines, use_pager=use_pager, to_file=to_file) 232 _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
162 proc.kill() 233 proc.kill()
163 234
164 def _ShowExamplesFunc(self): 235 def _ShowExamplesFunc(self):
165 print self._CreateBanner() 236 print self._CreateBanner()
166 print '\n'.join([ 237 print '\n'.join([
167 '# Show pydoc for main types:', 238 '# Show pydoc for main types:',
168 'import models', 239 'import models',
169 'help(models)', 240 'help(models)',
170 '', 241 '',
171 '# Show all attributes of all symbols & per-section totals:', 242 '# Show all attributes of all symbols & per-section totals:',
172 'Print(size_info, verbose=True)', 243 'Print(size_info, verbose=True)',
173 '', 244 '',
174 '# Show two levels of .text, grouped by first two subdirectories', 245 '# Show two levels of .text, grouped by first two subdirectories',
175 'text_syms = size_info.symbols.WhereInSection("t")', 246 'text_syms = size_info.symbols.WhereInSection("t")',
176 'by_path = text_syms.GroupedByPath(depth=2)', 247 'by_path = text_syms.GroupedByPath(depth=2)',
177 'Print(by_path.WhereBiggerThan(1024))', 248 'Print(by_path.WherePssBiggerThan(1024))',
178 '', 249 '',
179 '# Show all non-vtable generated symbols', 250 '# Show all non-vtable generated symbols',
180 'generated_syms = size_info.symbols.WhereGeneratedByToolchain()', 251 'generated_syms = size_info.symbols.WhereGeneratedByToolchain()',
181 'Print(generated_syms.WhereNameMatches(r"vtable").Inverted().Sorted())', 252 'Print(generated_syms.WhereNameMatches(r"vtable").Inverted().Sorted())',
182 '', 253 '',
183 '# Show all symbols that have "print" in their name or path, except', 254 '# Show all symbols that have "print" in their name or path, except',
184 '# those within components/.', 255 '# those within components/.',
185 '# Note: Could have also used Inverted(), as above.', 256 '# Note: Could have also used Inverted(), as above.',
186 '# Note: Use "help(ExpandRegex)" for more about what {{_print_}} does.', 257 '# Note: Use "help(ExpandRegex)" for more about what {{_print_}} does.',
187 'print_syms = size_info.symbols.WhereMatches(r"{{_print_}}")', 258 'print_syms = size_info.symbols.WhereMatches(r"{{_print_}}")',
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
269 340
270 def Run(args, parser): 341 def Run(args, parser):
271 for path in args.inputs: 342 for path in args.inputs:
272 if not path.endswith('.size'): 343 if not path.endswith('.size'):
273 parser.error('All inputs must end with ".size"') 344 parser.error('All inputs must end with ".size"')
274 345
275 size_infos = [archive.LoadAndPostProcessSizeInfo(p) for p in args.inputs] 346 size_infos = [archive.LoadAndPostProcessSizeInfo(p) for p in args.inputs]
276 lazy_paths = paths.LazyPaths(tool_prefix=args.tool_prefix, 347 lazy_paths = paths.LazyPaths(tool_prefix=args.tool_prefix,
277 output_directory=args.output_directory, 348 output_directory=args.output_directory,
278 any_path_within_output_directory=args.inputs[0]) 349 any_path_within_output_directory=args.inputs[0])
279 session = _Session(size_infos, lazy_paths) 350 session = _Session(size_infos, args.inputs, lazy_paths)
280 351
281 if args.query: 352 if args.query:
282 logging.info('Running query from command-line.') 353 logging.info('Running query from command-line.')
283 session.Eval(args.query) 354 session.Eval(args.query)
284 else: 355 else:
285 logging.info('Entering interactive console.') 356 logging.info('Entering interactive console.')
286 session.GoInteractive() 357 session.GoInteractive()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698