| 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 |