| 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 '# Note: Use "help(ExpandRegex)" for more about what {{_print_}} does.', |
| 134 'print_syms = symbols.WhereMatches(r"{{_print_}}")', |
| 135 'Print(print_syms - print_syms.WherePathMatches(r"^components/"))', |
| 136 '', |
| 137 '# Diff two .size files:', |
| 138 'Print(Diff(size_info1, size_info2))', |
| 124 '', | 139 '', |
| 125 '*' * 80, | 140 '*' * 80, |
| 126 'Here is some quick reference:', | 141 'Here is some quick reference:', |
| 127 '', | 142 '', |
| 128 'SizeInfo: %s' % ', '.join(symbol_info_keys), | 143 'SizeInfo: %s' % ', '.join(symbol_info_keys), |
| 129 'SymbolGroup: %s' % ', '.join(symbol_group_keys), | 144 'Symbol: %s' % ', '.join(symbol_keys), |
| 145 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys), |
| 130 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), | 146 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), |
| 131 '', | 147 '', |
| 132 'Functions: %s' % ', '.join('%s()' % f for f in functions), | 148 'Functions: %s' % ', '.join('%s()' % f for f in functions), |
| 133 'Variables: %s' % ', '.join(variables), | 149 'Variables: %s' % ', '.join(variables), |
| 134 '', | 150 '', |
| 135 ]) | 151 ]) |
| 136 | 152 |
| 137 @classmethod | 153 @classmethod |
| 138 def _InitReadline(cls): | 154 def _InitReadline(cls): |
| 139 if cls._readline_initialized: | 155 if cls._readline_initialized: |
| (...skipping 13 matching lines...) Expand all Loading... |
| 153 self._PrintFunc(eval_result) | 169 self._PrintFunc(eval_result) |
| 154 | 170 |
| 155 def GoInteractive(self): | 171 def GoInteractive(self): |
| 156 _Session._InitReadline() | 172 _Session._InitReadline() |
| 157 code.InteractiveConsole(self._variables).interact(self._CreateBanner()) | 173 code.InteractiveConsole(self._variables).interact(self._CreateBanner()) |
| 158 | 174 |
| 159 | 175 |
| 160 def main(argv): | 176 def main(argv): |
| 161 parser = argparse.ArgumentParser() | 177 parser = argparse.ArgumentParser() |
| 162 parser.add_argument('inputs', nargs='*', | 178 parser.add_argument('inputs', nargs='*', |
| 163 help='Input .size/.map files to load. They will be ' | 179 help='Input .size/.map files to load. For a single file, ' |
| 164 'mapped to variables as: size_info1, size_info2,' | 180 'it will be mapped to variables as: size_info & ' |
| 165 ' etc.') | 181 'symbols (where symbols = size_info.symbols). For ' |
| 182 'multiple inputs, the names will be size_info1, ' |
| 183 'symbols1, etc.') |
| 166 parser.add_argument('--query', | 184 parser.add_argument('--query', |
| 167 help='Print the result of the given snippet. Example: ' | 185 help='Print the result of the given snippet. Example: ' |
| 168 'size_info1.symbols.WhereInSection("d").' | 186 'symbols.WhereInSection("d").' |
| 169 'WhereBiggerThan(100)') | 187 'WhereBiggerThan(100)') |
| 170 map2size.AddOptions(parser) | 188 map2size.AddOptions(parser) |
| 171 args = helpers.AddCommonOptionsAndParseArgs(parser, argv) | 189 args = helpers.AddCommonOptionsAndParseArgs(parser, argv) |
| 172 | 190 |
| 173 info_variables = {} | 191 variables = {} |
| 174 for i, path in enumerate(args.inputs): | 192 for i, path in enumerate(args.inputs): |
| 175 size_info = map2size.AnalyzeWithArgs(args, path) | 193 size_info = map2size.AnalyzeWithArgs(args, path) |
| 176 info_variables['size_info%d' % (i + 1)] = size_info | 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 |
| 177 | 200 |
| 178 session = _Session(info_variables) | 201 session = _Session(variables) |
| 179 | 202 |
| 180 if args.query: | 203 if args.query: |
| 181 logging.info('Running query from command-line.') | 204 logging.info('Running query from command-line.') |
| 182 session.Eval(args.query) | 205 session.Eval(args.query) |
| 183 else: | 206 else: |
| 184 logging.info('Entering interactive console.') | 207 logging.info('Entering interactive console.') |
| 185 session.GoInteractive() | 208 session.GoInteractive() |
| 186 | 209 |
| 187 | 210 |
| 188 if __name__ == '__main__': | 211 if __name__ == '__main__': |
| 189 sys.exit(main(sys.argv)) | 212 sys.exit(main(sys.argv)) |
| OLD | NEW |