| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 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 | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Tool for analyzing binary size of executables using nm or linker map files. | |
| 7 | |
| 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 | |
| 10 the information does not seem to be as accurate in this case. | |
| 11 | |
| 12 Inspired by SymbolSort for Windows: | |
| 13 https://github.com/adrianstone55/SymbolSort | |
| 14 """ | |
| 15 | |
| 16 import argparse | |
| 17 import atexit | |
| 18 import code | |
| 19 import contextlib | |
| 20 import itertools | |
| 21 import logging | |
| 22 import os | |
| 23 import readline | |
| 24 import subprocess | |
| 25 import sys | |
| 26 | |
| 27 import describe | |
| 28 import file_format | |
| 29 import helpers | |
| 30 import map2size | |
| 31 import models | |
| 32 | |
| 33 | |
| 34 # Number of lines before using less for Print(). | |
| 35 _THRESHOLD_FOR_PAGER = 30 | |
| 36 | |
| 37 | |
| 38 @contextlib.contextmanager | |
| 39 def _LessPipe(): | |
| 40 """Output to `less`. Yields a file object to write to.""" | |
| 41 try: | |
| 42 proc = subprocess.Popen(['less'], stdin=subprocess.PIPE, stdout=sys.stdout) | |
| 43 yield proc.stdin | |
| 44 proc.stdin.close() | |
| 45 | |
| 46 proc.wait() | |
| 47 except IOError: | |
| 48 pass # Happens when less is quit before all data is written. | |
| 49 except KeyboardInterrupt: | |
| 50 pass # Assume used to break out of less. | |
| 51 | |
| 52 | |
| 53 class _Session(object): | |
| 54 _readline_initialized = False | |
| 55 | |
| 56 def __init__(self, extra_vars): | |
| 57 self._variables = { | |
| 58 'Print': self._PrintFunc, | |
| 59 'Write': self._WriteFunc, | |
| 60 'Diff': models.Diff, | |
| 61 } | |
| 62 self._variables.update(extra_vars) | |
| 63 | |
| 64 def _PrintFunc(self, obj, verbose=False, use_pager=None): | |
| 65 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo. | |
| 66 | |
| 67 Args: | |
| 68 obj: The object to be printed. | |
| 69 use_pager: Whether to pipe output through `less`. Ignored when |obj| is a | |
| 70 Symbol. | |
| 71 """ | |
| 72 lines = describe.GenerateLines(obj, verbose=verbose) | |
| 73 if use_pager is None and sys.stdout.isatty(): | |
| 74 # Does not take into account line-wrapping... Oh well. | |
| 75 first_lines = list(itertools.islice(lines, _THRESHOLD_FOR_PAGER)) | |
| 76 if len(first_lines) == _THRESHOLD_FOR_PAGER: | |
| 77 use_pager = True | |
| 78 lines = itertools.chain(first_lines, lines) | |
| 79 | |
| 80 if use_pager: | |
| 81 with _LessPipe() as stdin: | |
| 82 describe.WriteLines(lines, stdin.write) | |
| 83 else: | |
| 84 describe.WriteLines(lines, sys.stdout.write) | |
| 85 | |
| 86 | |
| 87 def _WriteFunc(self, obj, path, verbose=False): | |
| 88 """Same as Print(), but writes to a file. | |
| 89 | |
| 90 Example: Write(Diff(size_info2, size_info1), 'output.txt') | |
| 91 """ | |
| 92 parent_dir = os.path.dirname(path) | |
| 93 if parent_dir and not os.path.exists(parent_dir): | |
| 94 os.makedirs(parent_dir) | |
| 95 with file_format.OpenMaybeGz(path, 'w') as file_obj: | |
| 96 lines = describe.GenerateLines(obj, verbose=verbose) | |
| 97 describe.WriteLines(lines, file_obj.write) | |
| 98 | |
| 99 | |
| 100 def _CreateBanner(self): | |
| 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) | |
| 103 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 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()) | |
| 108 return '\n'.join([ | |
| 109 '*' * 80, | |
| 110 'Entering interactive Python shell. Here is some inspiration:', | |
| 111 '', | |
| 112 '# Show pydoc for main types:', | |
| 113 'import models', | |
| 114 'help(models)', | |
| 115 '', | |
| 116 '# Show two levels of .text, grouped by first two subdirectories', | |
| 117 'text_syms = size_info1.symbols.WhereInSection("t")', | |
| 118 'by_path = text_syms.GroupByPath(depth=2)', | |
| 119 'Print(by_path.WhereBiggerThan(1024))', | |
| 120 '', | |
| 121 '# Show all non-vtable generated symbols', | |
| 122 'generated_syms = size_info1.symbols.WhereIsGenerated()', | |
| 123 'Print(generated_syms.WhereNameMatches("vtable").Inverted())', | |
| 124 '', | |
| 125 '*' * 80, | |
| 126 'Here is some quick reference:', | |
| 127 '', | |
| 128 'SizeInfo: %s' % ', '.join(symbol_info_keys), | |
| 129 'SymbolGroup: %s' % ', '.join(symbol_group_keys), | |
| 130 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), | |
| 131 '', | |
| 132 'Functions: %s' % ', '.join('%s()' % f for f in functions), | |
| 133 'Variables: %s' % ', '.join(variables), | |
| 134 '', | |
| 135 ]) | |
| 136 | |
| 137 @classmethod | |
| 138 def _InitReadline(cls): | |
| 139 if cls._readline_initialized: | |
| 140 return | |
| 141 cls._readline_initialized = True | |
| 142 # Without initializing readline, arrow keys don't even work! | |
| 143 readline.parse_and_bind('tab: complete') | |
| 144 history_file = os.path.join(os.path.expanduser('~'), | |
| 145 '.binary_size_query_history') | |
| 146 if os.path.exists(history_file): | |
| 147 readline.read_history_file(history_file) | |
| 148 atexit.register(lambda: readline.write_history_file(history_file)) | |
| 149 | |
| 150 def Eval(self, query): | |
| 151 eval_result = eval(query, self._variables) | |
| 152 if eval_result: | |
| 153 self._PrintFunc(eval_result) | |
| 154 | |
| 155 def GoInteractive(self): | |
| 156 _Session._InitReadline() | |
| 157 code.InteractiveConsole(self._variables).interact(self._CreateBanner()) | |
| 158 | |
| 159 | |
| 160 def main(argv): | |
| 161 parser = argparse.ArgumentParser() | |
| 162 parser.add_argument('inputs', nargs='*', | |
| 163 help='Input .size/.map files to load. They will be ' | |
| 164 'mapped to variables as: size_info1, size_info2,' | |
| 165 ' etc.') | |
| 166 parser.add_argument('--query', | |
| 167 help='Print the result of the given snippet. Example: ' | |
| 168 'size_info1.symbols.WhereInSection("d").' | |
| 169 'WhereBiggerThan(100)') | |
| 170 map2size.AddOptions(parser) | |
| 171 args = helpers.AddCommonOptionsAndParseArgs(parser, argv) | |
| 172 | |
| 173 info_variables = {} | |
| 174 for i, path in enumerate(args.inputs): | |
| 175 size_info = map2size.AnalyzeWithArgs(args, path) | |
| 176 info_variables['size_info%d' % (i + 1)] = size_info | |
| 177 | |
| 178 session = _Session(info_variables) | |
| 179 | |
| 180 if args.query: | |
| 181 logging.info('Running query from command-line.') | |
| 182 session.Eval(args.query) | |
| 183 else: | |
| 184 logging.info('Entering interactive console.') | |
| 185 session.GoInteractive() | |
| 186 | |
| 187 | |
| 188 if __name__ == '__main__': | |
| 189 sys.exit(main(sys.argv)) | |
| OLD | NEW |