OXYGENHUB
◆ main openmsx / build / sizestats.py
3.3 KiB · Python · executable · last changed chore(): initial commit 1 month ago source blame history raw
1
#!/usr/bin/env python3
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
					# Symbols in BSS (uninitialized data section) do not
27
					# contribute to executable size, so ignore them.
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
		# Get symbol information.
42
		symbolsBySource = defaultdict(list)
43
		for symbol in parseSymbolSize(executable):
44
			symbolsBySource[symbol.source].append(symbol)
45
46
		# Build directory tree.
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
		# Combine branches without forks.
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
						# A directory containing a single directory.
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
		# Compute size of all nodes in the tree.
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
		# Output.
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)