OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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() |
OLD | NEW |