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

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

Issue 2801663003: //tools/binary_size: Add Disassemble() to console.py. Tweak metadata. (Closed)
Patch Set: Review comments 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
« no previous file with comments | « tools/binary_size/README.md ('k') | tools/binary_size/create_html_breakdown.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 """Tool for analyzing binary size of executables using nm or linker map files. 6 """Tool for analyzing binary size of executables using nm or linker map files.
7 7
8 Map files can be created by passing "-Map Foo.map" to the linker. If a map file 8 Map files can be created by passing "-Map Foo.map" to the linker. If a map file
9 is unavailable, this tool can also be pointed at an unstripped executable, but 9 is unavailable, this tool can also be pointed at an unstripped executable, but
10 the information does not seem to be as accurate in this case. 10 the information does not seem to be as accurate in this case.
(...skipping 12 matching lines...) Expand all
23 import readline 23 import readline
24 import subprocess 24 import subprocess
25 import sys 25 import sys
26 26
27 import describe 27 import describe
28 import file_format 28 import file_format
29 import helpers 29 import helpers
30 import map2size 30 import map2size
31 import match_util 31 import match_util
32 import models 32 import models
33 import paths
33 34
34 35
35 # Number of lines before using less for Print(). 36 # Number of lines before using less for Print().
36 _THRESHOLD_FOR_PAGER = 30 37 _THRESHOLD_FOR_PAGER = 30
37 38
38 39
39 @contextlib.contextmanager 40 @contextlib.contextmanager
40 def _LessPipe(): 41 def _LessPipe():
41 """Output to `less`. Yields a file object to write to.""" 42 """Output to `less`. Yields a file object to write to."""
42 try: 43 try:
43 proc = subprocess.Popen(['less'], stdin=subprocess.PIPE, stdout=sys.stdout) 44 proc = subprocess.Popen(['less'], stdin=subprocess.PIPE, stdout=sys.stdout)
44 yield proc.stdin 45 yield proc.stdin
45 proc.stdin.close() 46 proc.stdin.close()
46
47 proc.wait() 47 proc.wait()
48 except IOError: 48 except IOError:
49 pass # Happens when less is quit before all data is written. 49 pass # Happens when less is quit before all data is written.
50 except KeyboardInterrupt: 50 except KeyboardInterrupt:
51 pass # Assume used to break out of less. 51 pass # Assume used to break out of less.
52 52
53 53
54 def _WriteToStream(lines, use_pager=None, to_file=None):
55 if to_file:
56 use_pager = False
57 if use_pager is None and sys.stdout.isatty():
58 # Does not take into account line-wrapping... Oh well.
59 first_lines = list(itertools.islice(lines, _THRESHOLD_FOR_PAGER))
60 if len(first_lines) == _THRESHOLD_FOR_PAGER:
61 use_pager = True
62 lines = itertools.chain(first_lines, lines)
63
64 if use_pager:
65 with _LessPipe() as stdin:
66 describe.WriteLines(lines, stdin.write)
67 elif to_file:
68 with open(to_file, 'w') as file_obj:
69 describe.WriteLines(lines, file_obj.write)
70 else:
71 describe.WriteLines(lines, sys.stdout.write)
72
73
54 class _Session(object): 74 class _Session(object):
55 _readline_initialized = False 75 _readline_initialized = False
56 76
57 def __init__(self, extra_vars): 77 def __init__(self, size_infos, lazy_paths):
58 self._variables = { 78 self._variables = {
59 'Print': self._PrintFunc, 79 'Print': self._PrintFunc,
60 'Write': self._WriteFunc,
61 'Diff': models.Diff, 80 'Diff': models.Diff,
81 'Disassemble': self._DisassembleFunc,
62 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder, 82 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder,
83 'ShowExamples': self._ShowExamplesFunc,
63 } 84 }
64 self._variables.update(extra_vars) 85 self._lazy_paths = lazy_paths
86 self._size_infos = size_infos
65 87
66 def _PrintFunc(self, obj, verbose=False, use_pager=None): 88 if len(size_infos) == 1:
89 self._variables['size_info'] = size_infos[0]
90 self._variables['symbols'] = size_infos[0].symbols
91 else:
92 for i, size_info in enumerate(size_infos):
93 self._variables['size_info%d' % (i + 1)] = size_info
94 self._variables['symbols%d' % (i + 1)] = size_info.symbols
95
96 def _PrintFunc(self, obj, verbose=False, use_pager=None, to_file=None):
67 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo. 97 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo.
68 98
69 Args: 99 Args:
70 obj: The object to be printed. 100 obj: The object to be printed.
71 use_pager: Whether to pipe output through `less`. Ignored when |obj| is a 101 verbose: Show more detailed output.
72 Symbol. 102 use_pager: Pipe output through `less`. Ignored when |obj| is a Symbol.
103 default is to automatically pipe when output is long.
104 to_file: Rather than print to stdio, write to the given file.
73 """ 105 """
74 lines = describe.GenerateLines(obj, verbose=verbose) 106 lines = describe.GenerateLines(obj, verbose=verbose)
75 if use_pager is None and sys.stdout.isatty(): 107 _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
76 # Does not take into account line-wrapping... Oh well.
77 first_lines = list(itertools.islice(lines, _THRESHOLD_FOR_PAGER))
78 if len(first_lines) == _THRESHOLD_FOR_PAGER:
79 use_pager = True
80 lines = itertools.chain(first_lines, lines)
81 108
82 if use_pager: 109 def _ElfPathForSymbol(self, symbol):
83 with _LessPipe() as stdin: 110 size_info = None
84 describe.WriteLines(lines, stdin.write) 111 for size_info in self._size_infos:
112 if symbol in size_info.symbols:
113 break
85 else: 114 else:
86 describe.WriteLines(lines, sys.stdout.write) 115 assert False, 'Symbol does not belong to a size_info.'
87 116
88 def _WriteFunc(self, obj, path, verbose=False): 117 filename = size_info.metadata.get(models.METADATA_ELF_FILENAME)
89 """Same as Print(), but writes to a file. 118 output_dir = self._lazy_paths.output_directory or ''
119 path = os.path.normpath(os.path.join(output_dir, filename))
90 120
91 Example: Write(Diff(size_info2, size_info1), 'output.txt') 121 found_build_id = map2size.BuildIdFromElf(
122 path, self._lazy_paths.tool_prefix)
123 expected_build_id = size_info.metadata.get(models.METADATA_ELF_BUILD_ID)
124 assert found_build_id == expected_build_id, (
125 'Build ID does not match for %s' % path)
126 return path
127
128 def _DisassembleFunc(self, symbol, elf_path=None, use_pager=None,
129 to_file=None):
130 """Shows objdump disassembly for the given symbol.
131
132 Args:
133 symbol: Must be a .text symbol and not a SymbolGroup.
134 elf_path: Path to the executable containing the symbol. Required only
135 when auto-detection fails.
92 """ 136 """
93 parent_dir = os.path.dirname(path) 137 assert symbol.address and symbol.section_name == '.text'
94 if parent_dir and not os.path.exists(parent_dir): 138 if not elf_path:
95 os.makedirs(parent_dir) 139 elf_path = self._ElfPathForSymbol(symbol)
96 with file_format.OpenMaybeGz(path, 'w') as file_obj: 140 tool_prefix = self._lazy_paths.tool_prefix
97 lines = describe.GenerateLines(obj, verbose=verbose) 141 args = [tool_prefix + 'objdump', '--disassemble', '--source',
98 describe.WriteLines(lines, file_obj.write) 142 '--line-numbers', '--demangle',
143 '--start-address=0x%x' % symbol.address,
144 '--stop-address=0x%x' % symbol.end_address, elf_path]
145 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
146 lines = itertools.chain(('Showing disassembly for %r' % symbol,
147 'Command: %s' % ' '.join(args)),
148 (l.rstrip() for l in proc.stdout))
149 _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
150 proc.kill()
99 151
100 def _CreateBanner(self): 152 def _ShowExamplesFunc(self):
101 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_') 153 print '\n'.join([
102 symbol_keys = sorted(m for m in dir(models.Symbol) if m[0] != '_')
103 symbol_group_keys = [m for m in dir(models.SymbolGroup) if m[0] != '_']
104 symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff)
105 if m[0] != '_' and m not in symbol_group_keys)
106 symbol_group_keys = sorted(m for m in symbol_group_keys
107 if m not in symbol_keys)
108 functions = sorted(k for k in self._variables if k[0].isupper())
109 variables = sorted(k for k in self._variables if k[0].islower())
110 return '\n'.join([
111 '*' * 80,
112 'Entering interactive Python shell. Here is some inspiration:',
113 '',
114 '# Show pydoc for main types:', 154 '# Show pydoc for main types:',
115 'import models', 155 'import models',
116 'help(models)', 156 'help(models)',
117 '', 157 '',
118 '# Show all attributes of all symbols & per-section totals:', 158 '# Show all attributes of all symbols & per-section totals:',
119 'Print(size_info, verbose=True)', 159 'Print(size_info, verbose=True)',
120 '', 160 '',
121 '# Show two levels of .text, grouped by first two subdirectories', 161 '# Show two levels of .text, grouped by first two subdirectories',
122 'text_syms = symbols.WhereInSection("t")', 162 'text_syms = symbols.WhereInSection("t")',
123 'by_path = text_syms.GroupBySourcePath(depth=2)', 163 'by_path = text_syms.GroupBySourcePath(depth=2)',
124 'Print(by_path.WhereBiggerThan(1024))', 164 'Print(by_path.WhereBiggerThan(1024))',
125 '', 165 '',
126 '# Show all non-vtable generated symbols', 166 '# Show all non-vtable generated symbols',
127 'generated_syms = symbols.WhereIsGenerated()', 167 'generated_syms = symbols.WhereIsGenerated()',
128 'Print(generated_syms.WhereNameMatches(r"vtable").Inverted())', 168 'Print(generated_syms.WhereNameMatches(r"vtable").Inverted())',
129 '', 169 '',
130 '# Show all symbols that have "print" in their name or path, except', 170 '# Show all symbols that have "print" in their name or path, except',
131 '# those within components/.', 171 '# those within components/.',
132 '# Note: Could have also used Inverted(), as above.', 172 '# Note: Could have also used Inverted(), as above.',
133 '# Note: Use "help(ExpandRegex)" for more about what {{_print_}} does.', 173 '# Note: Use "help(ExpandRegex)" for more about what {{_print_}} does.',
134 'print_syms = symbols.WhereMatches(r"{{_print_}}")', 174 'print_syms = symbols.WhereMatches(r"{{_print_}}")',
135 'Print(print_syms - print_syms.WherePathMatches(r"^components/"))', 175 'Print(print_syms - print_syms.WherePathMatches(r"^components/"))',
136 '', 176 '',
137 '# Diff two .size files:', 177 '# Diff two .size files and save result to a file:',
138 'Print(Diff(size_info1, size_info2))', 178 'Print(Diff(size_info1, size_info2), to_file="output.txt")',
139 '', 179 '',
180 ])
181
182 def _CreateBanner(self):
183 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_')
184 symbol_keys = sorted(m for m in dir(models.Symbol) if m[0] != '_')
185 symbol_group_keys = [m for m in dir(models.SymbolGroup) if m[0] != '_']
186 symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff)
187 if m[0] != '_' and m not in symbol_group_keys)
188 symbol_group_keys = sorted(m for m in symbol_group_keys
189 if m not in symbol_keys)
190 functions = sorted(k for k in self._variables if k[0].isupper())
191 variables = sorted(k for k in self._variables if k[0].islower())
192 return '\n'.join([
140 '*' * 80, 193 '*' * 80,
141 'Here is some quick reference:', 194 'Entering interactive Python shell. Quick reference:',
142 '', 195 '',
143 'SizeInfo: %s' % ', '.join(symbol_info_keys), 196 'SizeInfo: %s' % ', '.join(symbol_info_keys),
144 'Symbol: %s' % ', '.join(symbol_keys), 197 'Symbol: %s' % ', '.join(symbol_keys),
145 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys), 198 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys),
146 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), 199 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys),
147 '', 200 '',
148 'Functions: %s' % ', '.join('%s()' % f for f in functions), 201 'Functions: %s' % ', '.join('%s()' % f for f in functions),
149 'Variables: %s' % ', '.join(variables), 202 'Variables: %s' % ', '.join(variables),
150 '', 203 '*' * 80,
151 ]) 204 ])
152 205
153 @classmethod 206 @classmethod
154 def _InitReadline(cls): 207 def _InitReadline(cls):
155 if cls._readline_initialized: 208 if cls._readline_initialized:
156 return 209 return
157 cls._readline_initialized = True 210 cls._readline_initialized = True
158 # Without initializing readline, arrow keys don't even work! 211 # Without initializing readline, arrow keys don't even work!
159 readline.parse_and_bind('tab: complete') 212 readline.parse_and_bind('tab: complete')
160 history_file = os.path.join(os.path.expanduser('~'), 213 history_file = os.path.join(os.path.expanduser('~'),
161 '.binary_size_query_history') 214 '.binary_size_query_history')
162 if os.path.exists(history_file): 215 if os.path.exists(history_file):
163 readline.read_history_file(history_file) 216 readline.read_history_file(history_file)
164 atexit.register(lambda: readline.write_history_file(history_file)) 217 atexit.register(lambda: readline.write_history_file(history_file))
165 218
166 def Eval(self, query): 219 def Eval(self, query):
167 eval_result = eval(query, self._variables) 220 eval_result = eval(query, self._variables)
168 if eval_result: 221 if eval_result:
169 self._PrintFunc(eval_result) 222 self._PrintFunc(eval_result)
170 223
171 def GoInteractive(self): 224 def GoInteractive(self):
172 _Session._InitReadline() 225 _Session._InitReadline()
173 code.InteractiveConsole(self._variables).interact(self._CreateBanner()) 226 code.InteractiveConsole(self._variables).interact(self._CreateBanner())
174 227
175 228
176 def main(argv): 229 def main(argv):
177 parser = argparse.ArgumentParser() 230 parser = argparse.ArgumentParser()
178 parser.add_argument('inputs', nargs='*', 231 parser.add_argument('inputs', nargs='+',
179 help='Input .size/.map files to load. For a single file, ' 232 help='Input .size files to load. For a single file, '
180 'it will be mapped to variables as: size_info & ' 233 'it will be mapped to variables as: size_info & '
181 'symbols (where symbols = size_info.symbols). For ' 234 'symbols (where symbols = size_info.symbols). For '
182 'multiple inputs, the names will be size_info1, ' 235 'multiple inputs, the names will be size_info1, '
183 'symbols1, etc.') 236 'symbols1, etc.')
184 parser.add_argument('--query', 237 parser.add_argument('--query',
185 help='Print the result of the given snippet. Example: ' 238 help='Print the result of the given snippet. Example: '
186 'symbols.WhereInSection("d").' 239 'symbols.WhereInSection("d").'
187 'WhereBiggerThan(100)') 240 'WhereBiggerThan(100)')
188 map2size.AddOptions(parser) 241 paths.AddOptions(parser)
189 args = helpers.AddCommonOptionsAndParseArgs(parser, argv) 242 args = helpers.AddCommonOptionsAndParseArgs(parser, argv)
190 243
191 variables = {} 244 for path in args.inputs:
192 for i, path in enumerate(args.inputs): 245 if not path.endswith('.size'):
193 size_info = map2size.AnalyzeWithArgs(args, path) 246 parser.error('All inputs must end with ".size"')
194 if len(args.inputs) == 1:
195 variables['size_info'] = size_info
196 variables['symbols'] = size_info.symbols
197 else:
198 variables['size_info%d' % (i + 1)] = size_info
199 variables['symbols%d' % (i + 1)] = size_info.symbols
200 247
201 session = _Session(variables) 248 size_infos = [map2size.Analyze(p) for p in args.inputs]
249 lazy_paths = paths.LazyPaths(args=args, input_file=args.inputs[0])
250 session = _Session(size_infos, lazy_paths)
202 251
203 if args.query: 252 if args.query:
204 logging.info('Running query from command-line.') 253 logging.info('Running query from command-line.')
205 session.Eval(args.query) 254 session.Eval(args.query)
206 else: 255 else:
207 logging.info('Entering interactive console.') 256 logging.info('Entering interactive console.')
208 session.GoInteractive() 257 session.GoInteractive()
209 258
210 259
211 if __name__ == '__main__': 260 if __name__ == '__main__':
212 sys.exit(main(sys.argv)) 261 sys.exit(main(sys.argv))
OLDNEW
« no previous file with comments | « tools/binary_size/README.md ('k') | tools/binary_size/create_html_breakdown.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698