| 1 |
|
| 2 |
|
| 3 |
from executils import captureStdout |
| 4 |
|
| 5 |
from collections import defaultdict, namedtuple |
| 6 |
from os.path import normpath |
| 7 |
import re, sys |
| 8 |
|
| 9 |
|
| 10 |
Symbol = namedtuple('Symbol', ('name', 'typ', 'size', 'source', 'lineNo')) |
| 11 |
|
| 12 |
_reSymbolInfo = re.compile( |
| 13 |
r'^([0-9a-f]+\s)?([0-9a-f]+\s)?\s*([A-Za-z])\s([^\t]+)(\t[^\t]+)?$' |
| 14 |
) |
| 15 |
def parseSymbolSize(objectFile): |
| 16 |
text = captureStdout(sys.stderr, 'nm -CSl "%s"' % objectFile) |
| 17 |
if text is not None: |
| 18 |
for line in text.split('\n'): |
| 19 |
if line: |
| 20 |
match = _reSymbolInfo.match(line) |
| 21 |
assert match is not None, line |
| 22 |
addr_, sizeStr, typ, name, originStr = match.groups() |
| 23 |
if sizeStr is None: |
| 24 |
continue |
| 25 |
if typ in 'Bb': |
| 26 |
|
| 27 |
|
| 28 |
continue |
| 29 |
if originStr is None: |
| 30 |
source = lineNo = None |
| 31 |
else: |
| 32 |
source, lineNo = originStr.lstrip().rsplit(':', 1) |
| 33 |
source = normpath(source) |
| 34 |
lineNo = int(lineNo) |
| 35 |
yield Symbol(name, typ, int(sizeStr, 16), source, lineNo) |
| 36 |
|
| 37 |
if __name__ == '__main__': |
| 38 |
if len(sys.argv) == 2: |
| 39 |
executable = sys.argv[1] |
| 40 |
|
| 41 |
|
| 42 |
symbolsBySource = defaultdict(list) |
| 43 |
for symbol in parseSymbolSize(executable): |
| 44 |
symbolsBySource[symbol.source].append(symbol) |
| 45 |
|
| 46 |
|
| 47 |
def newDict(): |
| 48 |
return defaultdict(newDict) |
| 49 |
dirTree = newDict() |
| 50 |
for source, symbols in symbolsBySource.items(): |
| 51 |
if source is None: |
| 52 |
parts = [ '(no source)' ] |
| 53 |
else: |
| 54 |
assert source[0] == '/' |
| 55 |
parts = source[1 : ].split('/') |
| 56 |
parts[0] = '/' + parts[0] |
| 57 |
node = dirTree |
| 58 |
for part in parts[ : -1]: |
| 59 |
node = node[part + '/'] |
| 60 |
node[parts[-1]] = symbols |
| 61 |
|
| 62 |
|
| 63 |
def compactTree(node): |
| 64 |
names = set(node.keys()) |
| 65 |
while names: |
| 66 |
name = names.pop() |
| 67 |
content = node[name] |
| 68 |
if isinstance(content, dict) and len(content) == 1: |
| 69 |
subName, subContent = next(iter(content.items())) |
| 70 |
if isinstance(subContent, dict): |
| 71 |
|
| 72 |
del node[name] |
| 73 |
node[name + subName] = subContent |
| 74 |
names.add(name + subName) |
| 75 |
for content in node.values(): |
| 76 |
if isinstance(content, dict): |
| 77 |
compactTree(content) |
| 78 |
compactTree(dirTree) |
| 79 |
|
| 80 |
|
| 81 |
def buildSizeTree(node): |
| 82 |
if isinstance(node, dict): |
| 83 |
newNode = {} |
| 84 |
for name, content in node.items(): |
| 85 |
newNode[name] = buildSizeTree(content) |
| 86 |
nodeSize = sum(size for size, subNode in newNode.values()) |
| 87 |
return nodeSize, newNode |
| 88 |
else: |
| 89 |
nodeSize = sum(symbol.size for symbol in node) |
| 90 |
return nodeSize, node |
| 91 |
totalSize, sizeTree = buildSizeTree(dirTree) |
| 92 |
|
| 93 |
|
| 94 |
def printTree(size, node, indent): |
| 95 |
if isinstance(node, dict): |
| 96 |
for name, (contentSize, content) in sorted( |
| 97 |
node.items(), |
| 98 |
key=lambda item: (-item[1][0], item[0]) |
| 99 |
): |
| 100 |
print('%s%8d %s' % (indent, contentSize, name)) |
| 101 |
printTree(contentSize, content, indent + ' ') |
| 102 |
else: |
| 103 |
for symbol in sorted( |
| 104 |
node, |
| 105 |
key=lambda symbol: (-symbol.size, symbol.name) |
| 106 |
): |
| 107 |
lineNo = symbol.lineNo |
| 108 |
print('%s%8d %s %s %s' % ( |
| 109 |
indent, symbol.size, symbol.typ, symbol.name, |
| 110 |
'' if lineNo is None else '(line %d)' % lineNo |
| 111 |
)) |
| 112 |
printTree(totalSize, sizeTree, '') |
| 113 |
else: |
| 114 |
print('Usage: python3 sizestats.py executable', file=sys.stderr) |
| 115 |
sys.exit(2) |