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 """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 10 matching lines...) Expand all Loading... | |
| 21 import logging | 21 import logging |
| 22 import os | 22 import os |
| 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 models | 32 import models |
| 32 | 33 |
| 33 | 34 |
| 34 # Number of lines before using less for Print(). | 35 # Number of lines before using less for Print(). |
| 35 _THRESHOLD_FOR_PAGER = 30 | 36 _THRESHOLD_FOR_PAGER = 30 |
| 36 | 37 |
| 37 | 38 |
| 38 @contextlib.contextmanager | 39 @contextlib.contextmanager |
| 39 def _LessPipe(): | 40 def _LessPipe(): |
| 40 """Output to `less`. Yields a file object to write to.""" | 41 """Output to `less`. Yields a file object to write to.""" |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 51 | 52 |
| 52 | 53 |
| 53 class _Session(object): | 54 class _Session(object): |
| 54 _readline_initialized = False | 55 _readline_initialized = False |
| 55 | 56 |
| 56 def __init__(self, extra_vars): | 57 def __init__(self, extra_vars): |
| 57 self._variables = { | 58 self._variables = { |
| 58 'Print': self._PrintFunc, | 59 'Print': self._PrintFunc, |
| 59 'Write': self._WriteFunc, | 60 'Write': self._WriteFunc, |
| 60 'Diff': models.Diff, | 61 'Diff': models.Diff, |
| 62 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder, | |
| 61 } | 63 } |
| 62 self._variables.update(extra_vars) | 64 self._variables.update(extra_vars) |
| 63 | 65 |
| 64 def _PrintFunc(self, obj, verbose=False, use_pager=None): | 66 def _PrintFunc(self, obj, verbose=False, use_pager=None): |
| 65 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo. | 67 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo. |
| 66 | 68 |
| 67 Args: | 69 Args: |
| 68 obj: The object to be printed. | 70 obj: The object to be printed. |
| 69 use_pager: Whether to pipe output through `less`. Ignored when |obj| is a | 71 use_pager: Whether to pipe output through `less`. Ignored when |obj| is a |
| 70 Symbol. | 72 Symbol. |
| 71 """ | 73 """ |
| 72 lines = describe.GenerateLines(obj, verbose=verbose) | 74 lines = describe.GenerateLines(obj, verbose=verbose) |
| 73 if use_pager is None and sys.stdout.isatty(): | 75 if use_pager is None and sys.stdout.isatty(): |
| 74 # Does not take into account line-wrapping... Oh well. | 76 # Does not take into account line-wrapping... Oh well. |
| 75 first_lines = list(itertools.islice(lines, _THRESHOLD_FOR_PAGER)) | 77 first_lines = list(itertools.islice(lines, _THRESHOLD_FOR_PAGER)) |
| 76 if len(first_lines) == _THRESHOLD_FOR_PAGER: | 78 if len(first_lines) == _THRESHOLD_FOR_PAGER: |
| 77 use_pager = True | 79 use_pager = True |
| 78 lines = itertools.chain(first_lines, lines) | 80 lines = itertools.chain(first_lines, lines) |
| 79 | 81 |
| 80 if use_pager: | 82 if use_pager: |
| 81 with _LessPipe() as stdin: | 83 with _LessPipe() as stdin: |
| 82 describe.WriteLines(lines, stdin.write) | 84 describe.WriteLines(lines, stdin.write) |
| 83 else: | 85 else: |
| 84 describe.WriteLines(lines, sys.stdout.write) | 86 describe.WriteLines(lines, sys.stdout.write) |
| 85 | 87 |
| 86 | |
| 87 def _WriteFunc(self, obj, path, verbose=False): | 88 def _WriteFunc(self, obj, path, verbose=False): |
| 88 """Same as Print(), but writes to a file. | 89 """Same as Print(), but writes to a file. |
| 89 | 90 |
| 90 Example: Write(Diff(size_info2, size_info1), 'output.txt') | 91 Example: Write(Diff(size_info2, size_info1), 'output.txt') |
| 91 """ | 92 """ |
| 92 parent_dir = os.path.dirname(path) | 93 parent_dir = os.path.dirname(path) |
| 93 if parent_dir and not os.path.exists(parent_dir): | 94 if parent_dir and not os.path.exists(parent_dir): |
| 94 os.makedirs(parent_dir) | 95 os.makedirs(parent_dir) |
| 95 with file_format.OpenMaybeGz(path, 'w') as file_obj: | 96 with file_format.OpenMaybeGz(path, 'w') as file_obj: |
| 96 lines = describe.GenerateLines(obj, verbose=verbose) | 97 lines = describe.GenerateLines(obj, verbose=verbose) |
| 97 describe.WriteLines(lines, file_obj.write) | 98 describe.WriteLines(lines, file_obj.write) |
| 98 | 99 |
| 99 | |
| 100 def _CreateBanner(self): | 100 def _CreateBanner(self): |
| 101 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_') | 101 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_') |
| 102 symbol_group_keys = sorted(m for m in dir(models.SymbolGroup) | 102 symbol_keys = sorted(m for m in dir(models.Symbol) if m[0] != '_') |
| 103 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) | 104 symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff) |
| 105 if m[0] != '_' and m not in symbol_group_keys) | 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) | |
| 106 functions = sorted(k for k in self._variables if k[0].isupper()) | 108 functions = sorted(k for k in self._variables if k[0].isupper()) |
| 107 variables = sorted(k for k in self._variables if k[0].islower()) | 109 variables = sorted(k for k in self._variables if k[0].islower()) |
| 108 return '\n'.join([ | 110 return '\n'.join([ |
| 109 '*' * 80, | 111 '*' * 80, |
| 110 'Entering interactive Python shell. Here is some inspiration:', | 112 'Entering interactive Python shell. Here is some inspiration:', |
| 111 '', | 113 '', |
| 112 '# Show pydoc for main types:', | 114 '# Show pydoc for main types:', |
| 113 'import models', | 115 'import models', |
| 114 'help(models)', | 116 'help(models)', |
| 115 '', | 117 '', |
| 118 '# Show all attributes of all symbols & per-section totals:', | |
| 119 'Print(size_info, verbose=True)', | |
| 120 '', | |
| 116 '# Show two levels of .text, grouped by first two subdirectories', | 121 '# Show two levels of .text, grouped by first two subdirectories', |
| 117 'text_syms = size_info1.symbols.WhereInSection("t")', | 122 'text_syms = symbols.WhereInSection("t")', |
| 118 'by_path = text_syms.GroupBySourcePath(depth=2)', | 123 'by_path = text_syms.GroupBySourcePath(depth=2)', |
| 119 'Print(by_path.WhereBiggerThan(1024))', | 124 'Print(by_path.WhereBiggerThan(1024))', |
| 120 '', | 125 '', |
| 121 '# Show all non-vtable generated symbols', | 126 '# Show all non-vtable generated symbols', |
| 122 'generated_syms = size_info1.symbols.WhereIsGenerated()', | 127 'generated_syms = symbols.WhereIsGenerated()', |
| 123 'Print(generated_syms.WhereNameMatches("vtable").Inverted())', | 128 'Print(generated_syms.WhereNameMatches(r"vtable").Inverted())', |
| 129 '', | |
| 130 '# Show all symbols that have "print" in their name or path, except', | |
| 131 '# those within components/.', | |
| 132 '# Note: could have also used Inverted(), as above.', | |
| 133 'print_syms = symbols.WhereMatches(r"{{_print_}}")', | |
|
estevenson
2017/04/04 19:55:07
Might be worth documenting the regex matching a li
agrieve
2017/04/04 20:11:57
Done.
| |
| 134 'Print(print_syms - print_syms.WherePathMatches(r"^components/"))', | |
| 135 '', | |
| 136 '# Diff two .size files:', | |
| 137 'Print(Diff(size_info1, size_info2))', | |
| 124 '', | 138 '', |
| 125 '*' * 80, | 139 '*' * 80, |
| 126 'Here is some quick reference:', | 140 'Here is some quick reference:', |
| 127 '', | 141 '', |
| 128 'SizeInfo: %s' % ', '.join(symbol_info_keys), | 142 'SizeInfo: %s' % ', '.join(symbol_info_keys), |
| 129 'SymbolGroup: %s' % ', '.join(symbol_group_keys), | 143 'Symbol: %s' % ', '.join(symbol_keys), |
| 144 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys), | |
| 130 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), | 145 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), |
| 131 '', | 146 '', |
| 132 'Functions: %s' % ', '.join('%s()' % f for f in functions), | 147 'Functions: %s' % ', '.join('%s()' % f for f in functions), |
| 133 'Variables: %s' % ', '.join(variables), | 148 'Variables: %s' % ', '.join(variables), |
| 134 '', | 149 '', |
| 135 ]) | 150 ]) |
| 136 | 151 |
| 137 @classmethod | 152 @classmethod |
| 138 def _InitReadline(cls): | 153 def _InitReadline(cls): |
| 139 if cls._readline_initialized: | 154 if cls._readline_initialized: |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 153 self._PrintFunc(eval_result) | 168 self._PrintFunc(eval_result) |
| 154 | 169 |
| 155 def GoInteractive(self): | 170 def GoInteractive(self): |
| 156 _Session._InitReadline() | 171 _Session._InitReadline() |
| 157 code.InteractiveConsole(self._variables).interact(self._CreateBanner()) | 172 code.InteractiveConsole(self._variables).interact(self._CreateBanner()) |
| 158 | 173 |
| 159 | 174 |
| 160 def main(argv): | 175 def main(argv): |
| 161 parser = argparse.ArgumentParser() | 176 parser = argparse.ArgumentParser() |
| 162 parser.add_argument('inputs', nargs='*', | 177 parser.add_argument('inputs', nargs='*', |
| 163 help='Input .size/.map files to load. They will be ' | 178 help='Input .size/.map files to load. For a single file, ' |
| 164 'mapped to variables as: size_info1, size_info2,' | 179 'it will be mapped to variables as: size_info & ' |
| 165 ' etc.') | 180 'symbols (where symbols = size_info.symbols). For ' |
| 181 'multiple inputs, the names will be size_info1, ' | |
| 182 'symbols1, etc.') | |
| 166 parser.add_argument('--query', | 183 parser.add_argument('--query', |
| 167 help='Print the result of the given snippet. Example: ' | 184 help='Print the result of the given snippet. Example: ' |
| 168 'size_info1.symbols.WhereInSection("d").' | 185 'symbols.WhereInSection("d").' |
| 169 'WhereBiggerThan(100)') | 186 'WhereBiggerThan(100)') |
| 170 map2size.AddOptions(parser) | 187 map2size.AddOptions(parser) |
| 171 args = helpers.AddCommonOptionsAndParseArgs(parser, argv) | 188 args = helpers.AddCommonOptionsAndParseArgs(parser, argv) |
| 172 | 189 |
| 173 info_variables = {} | 190 variables = {} |
| 174 for i, path in enumerate(args.inputs): | 191 for i, path in enumerate(args.inputs): |
| 175 size_info = map2size.AnalyzeWithArgs(args, path) | 192 size_info = map2size.AnalyzeWithArgs(args, path) |
| 176 info_variables['size_info%d' % (i + 1)] = size_info | 193 if len(args.inputs) == 1: |
| 194 variables['size_info'] = size_info | |
| 195 variables['symbols'] = size_info.symbols | |
| 196 else: | |
| 197 variables['size_info%d' % (i + 1)] = size_info | |
| 198 variables['symbols%d' % (i + 1)] = size_info.symbols | |
| 177 | 199 |
| 178 session = _Session(info_variables) | 200 session = _Session(variables) |
| 179 | 201 |
| 180 if args.query: | 202 if args.query: |
| 181 logging.info('Running query from command-line.') | 203 logging.info('Running query from command-line.') |
| 182 session.Eval(args.query) | 204 session.Eval(args.query) |
| 183 else: | 205 else: |
| 184 logging.info('Entering interactive console.') | 206 logging.info('Entering interactive console.') |
| 185 session.GoInteractive() | 207 session.GoInteractive() |
| 186 | 208 |
| 187 | 209 |
| 188 if __name__ == '__main__': | 210 if __name__ == '__main__': |
| 189 sys.exit(main(sys.argv)) | 211 sys.exit(main(sys.argv)) |
| OLD | NEW |