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
|
#!/usr/bin/env python3
from executils import captureStdout
from collections import defaultdict, namedtuple
from os.path import normpath
import re, sys
Symbol = namedtuple('Symbol', ('name', 'typ', 'size', 'source', 'lineNo'))
_reSymbolInfo = re.compile(
r'^([0-9a-f]+\s)?([0-9a-f]+\s)?\s*([A-Za-z])\s([^\t]+)(\t[^\t]+)?$'
)
def parseSymbolSize(objectFile):
text = captureStdout(sys.stderr, 'nm -CSl "%s"' % objectFile)
if text is not None:
for line in text.split('\n'):
if line:
match = _reSymbolInfo.match(line)
assert match is not None, line
addr_, sizeStr, typ, name, originStr = match.groups()
if sizeStr is None:
continue
if typ in 'Bb':
# Symbols in BSS (uninitialized data section) do not
# contribute to executable size, so ignore them.
continue
if originStr is None:
source = lineNo = None
else:
source, lineNo = originStr.lstrip().rsplit(':', 1)
source = normpath(source)
lineNo = int(lineNo)
yield Symbol(name, typ, int(sizeStr, 16), source, lineNo)
if __name__ == '__main__':
if len(sys.argv) == 2:
executable = sys.argv[1]
# Get symbol information.
symbolsBySource = defaultdict(list)
for symbol in parseSymbolSize(executable):
symbolsBySource[symbol.source].append(symbol)
# Build directory tree.
def newDict():
return defaultdict(newDict)
dirTree = newDict()
for source, symbols in symbolsBySource.items():
if source is None:
parts = [ '(no source)' ]
else:
assert source[0] == '/'
parts = source[1 : ].split('/')
parts[0] = '/' + parts[0]
node = dirTree
for part in parts[ : -1]:
node = node[part + '/']
node[parts[-1]] = symbols
# Combine branches without forks.
def compactTree(node):
names = set(node.keys())
while names:
name = names.pop()
content = node[name]
if isinstance(content, dict) and len(content) == 1:
subName, subContent = next(iter(content.items()))
if isinstance(subContent, dict):
# A directory containing a single directory.
del node[name]
node[name + subName] = subContent
names.add(name + subName)
for content in node.values():
if isinstance(content, dict):
compactTree(content)
compactTree(dirTree)
# Compute size of all nodes in the tree.
def buildSizeTree(node):
if isinstance(node, dict):
newNode = {}
for name, content in node.items():
newNode[name] = buildSizeTree(content)
nodeSize = sum(size for size, subNode in newNode.values())
return nodeSize, newNode
else:
nodeSize = sum(symbol.size for symbol in node)
return nodeSize, node
totalSize, sizeTree = buildSizeTree(dirTree)
# Output.
def printTree(size, node, indent):
if isinstance(node, dict):
for name, (contentSize, content) in sorted(
node.items(),
key=lambda item: (-item[1][0], item[0])
):
print('%s%8d %s' % (indent, contentSize, name))
printTree(contentSize, content, indent + ' ')
else:
for symbol in sorted(
node,
key=lambda symbol: (-symbol.size, symbol.name)
):
lineNo = symbol.lineNo
print('%s%8d %s %s %s' % (
indent, symbol.size, symbol.typ, symbol.name,
'' if lineNo is None else '(line %d)' % lineNo
))
printTree(totalSize, sizeTree, '')
else:
print('Usage: python3 sizestats.py executable', file=sys.stderr)
sys.exit(2)
|