forked from pytorch/pytorch
-
Notifications
You must be signed in to change notification settings - Fork 1
/
binarysize.py
164 lines (139 loc) · 5.38 KB
/
binarysize.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
"""A tool to inspect the binary size of a built binary file.
This script prints out a tree of symbols and their corresponding sizes, using
Linux's nm functionality.
Usage:
python binary_size.py -- \
--target=/path/to/your/target/binary \
[--nm_command=/path/to/your/custom/nm] \
[--max_depth=10] [--min_size=1024] \
[--color] \
To assist visualization, pass in '--color' to make the symbols color coded to
green, assuming that you have a xterm connection that supports color.
"""
import argparse
import subprocess
import sys
class Trie:
"""A simple class that represents a Trie."""
def __init__(self, name):
"""Initializes a Trie object."""
self.name = name
self.size = 0
self.dictionary = {}
def GetSymbolTrie(target, nm_command, max_depth):
"""Gets a symbol trie with the passed in target.
Args:
target: the target binary to inspect.
nm_command: the command to run nm.
max_depth: the maximum depth to create the trie.
"""
# Run nm to get a dump on the strings.
proc = subprocess.Popen(
[nm_command, '--radix=d', '--size-sort', '--print-size', target],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
nm_out, _ = proc.communicate()
if proc.returncode != 0:
print('NM command failed. Output is as follows:')
print(nm_out)
sys.exit(1)
# Run c++filt to get proper symbols.
proc = subprocess.Popen(['c++filt'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, _ = proc.communicate(input=nm_out)
if proc.returncode != 0:
print('c++filt failed. Output is as follows:')
print(out)
sys.exit(1)
# Splits the output to size and function name.
data = []
for line in out.split('\n'):
if line:
content = line.split(' ')
if len(content) < 4:
# This is a line not representing symbol sizes. skip.
continue
data.append([int(content[1]), ' '.join(content[3:])])
symbol_trie = Trie('')
for size, name in data:
curr = symbol_trie
for c in name:
if c not in curr.dictionary:
curr.dictionary[c] = Trie(curr.name + c)
curr = curr.dictionary[c]
curr.size += size
if len(curr.name) > max_depth:
break
symbol_trie.size = sum(t.size for t in symbol_trie.dictionary.values())
return symbol_trie
def MaybeAddColor(s, color):
"""Wrap the input string to the xterm green color, if color is set.
"""
if color:
return '\033[92m{0}\033[0m'.format(s)
else:
return s
def ReadableSize(num):
"""Get a human-readable size."""
for unit in ['B', 'KB', 'MB', 'GB']:
if abs(num) <= 1024.0:
return '%3.2f%s' % (num, unit)
num /= 1024.0
return '%.1f TB' % (num,)
# Note(jiayq): I know, I know, this is a recursive function, but it is
# convenient to write.
def PrintTrie(trie, prefix, max_depth, min_size, color):
"""Prints the symbol trie in a readable manner.
"""
if len(trie.name) == max_depth or not trie.dictionary.keys():
# If we are reaching a leaf node or the maximum depth, we will print the
# result.
if trie.size > min_size:
print('{0}{1} {2}'.format(
prefix,
MaybeAddColor(trie.name, color),
ReadableSize(trie.size)))
elif len(trie.dictionary.keys()) == 1:
# There is only one child in this dictionary, so we will just delegate
# to the downstream trie to print stuff.
PrintTrie(
trie.dictionary.values()[0], prefix, max_depth, min_size, color)
elif trie.size > min_size:
print('{0}{1} {2}'.format(
prefix,
MaybeAddColor(trie.name, color),
ReadableSize(trie.size)))
keys_with_sizes = [
(k, trie.dictionary[k].size) for k in trie.dictionary.keys()]
keys_with_sizes.sort(key=lambda x: x[1])
for k, _ in keys_with_sizes[::-1]:
PrintTrie(
trie.dictionary[k], prefix + ' |', max_depth, min_size, color)
def main(argv):
if not sys.platform.startswith('linux'):
raise RuntimeError('Currently this tool only supports Linux.')
parser = argparse.ArgumentParser(
description="Tool to inspect binary size.")
parser.add_argument(
'--max_depth', type=int, default=10,
help='The maximum depth to print the symbol tree.')
parser.add_argument(
'--min_size', type=int, default=1024,
help='The mininum symbol size to print.')
parser.add_argument(
'--nm_command', type=str, default='nm',
help='The path to the nm command that the tool needs.')
parser.add_argument(
'--color', action='store_true',
help='If set, use ascii color for output.')
parser.add_argument(
'--target', type=str,
help='The binary target to inspect.')
args = parser.parse_args(argv)
if not args.target:
raise RuntimeError('You must specify a target to inspect.')
symbol_trie = GetSymbolTrie(
args.target, args.nm_command, args.max_depth)
PrintTrie(symbol_trie, '', args.max_depth, args.min_size, args.color)
if __name__ == '__main__':
main(sys.argv[1:])