2
# Script that tries to find how much stack space each function in an
5
# Copyright (C) 2008-2015 Kevin O'Connor <kevin@koconnor.net>
7
# This file may be distributed under the terms of the GNU GPLv3 license.
10
# objdump -m i386 -M i8086 -M suffix -d out/rom16.o | scripts/checkstack.py
15
# Functions that change stacks
16
STACKHOP = ['stack_hop', 'stack_hop_back']
17
# List of functions we can assume are never called.
18
#IGNORE = ['panic', '__dprintf']
22
#funcname1[preamble_stack_usage,max_usage_with_callers]:
23
# insn_addr:called_function [usage_at_call_point+caller_preamble,total_usage]
25
#funcname2[p,m,max_usage_to_yield_point]:
26
# insn_addr:called_function [u+c,t,usage_to_yield_point]
30
def __init__(self, funcaddr, funcname):
31
self.funcaddr = funcaddr
32
self.funcname = funcname
33
self.basic_stack_usage = 0
34
self.max_stack_usage = None
36
self.max_yield_usage = None
38
# called_funcs = [(insnaddr, calladdr, stackusage), ...]
39
self.called_funcs = []
41
# Update function info with a found "yield" point.
42
def noteYield(self, stackusage):
43
if self.yield_usage < stackusage:
44
self.yield_usage = stackusage
45
# Update function info with a found "call" point.
46
def noteCall(self, insnaddr, calladdr, stackusage):
47
if (calladdr, stackusage) in self.subfuncs:
48
# Already noted a nearly identical call - ignore this one.
50
self.called_funcs.append((insnaddr, calladdr, stackusage))
51
self.subfuncs[(calladdr, stackusage)] = 1
53
# Find out maximum stack usage for a function
54
def calcmaxstack(info, funcs):
55
if info.max_stack_usage is not None:
57
info.max_stack_usage = max_stack_usage = info.basic_stack_usage
58
info.max_yield_usage = max_yield_usage = info.yield_usage
61
# Find max of all nested calls.
62
for insnaddr, calladdr, usage in info.called_funcs:
63
callinfo = funcs.get(calladdr)
66
calcmaxstack(callinfo, funcs)
67
if callinfo.funcname not in seenbefore:
68
seenbefore[callinfo.funcname] = 1
69
total_calls += callinfo.total_calls + 1
70
funcnameroot = callinfo.funcname.split('.')[0]
71
if funcnameroot in IGNORE:
72
# This called function is ignored - don't contribute it to
75
totusage = usage + callinfo.max_stack_usage
76
totyieldusage = usage + callinfo.max_yield_usage
77
if funcnameroot in STACKHOP:
78
# Don't count children of this function
79
totusage = totyieldusage = usage
80
if totusage > max_stack_usage:
81
max_stack_usage = totusage
82
if callinfo.max_yield_usage >= 0 and totyieldusage > max_yield_usage:
83
max_yield_usage = totyieldusage
84
info.max_stack_usage = max_stack_usage
85
info.max_yield_usage = max_yield_usage
86
info.total_calls = total_calls
88
# Try to arrange output so that functions that call each other are
90
def orderfuncs(funcaddrs, availfuncs):
91
l = [(availfuncs[funcaddr].total_calls
92
, availfuncs[funcaddr].funcname, funcaddr)
93
for funcaddr in funcaddrs if funcaddr in availfuncs]
98
count, name, funcaddr = l.pop(0)
99
info = availfuncs.get(funcaddr)
102
calladdrs = [calls[1] for calls in info.called_funcs]
103
del availfuncs[funcaddr]
104
out = out + orderfuncs(calladdrs, availfuncs) + [info]
108
re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
110
r'^[ ]*(?P<insnaddr>' + hex_s
111
+ r'):\t.*\t(addr32 )?(?P<insn>.+?)[ ]*((?P<calladdr>' + hex_s
112
+ r') <(?P<ref>.*)>)?$')
113
re_usestack = re.compile(
114
r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$')
117
unknownfunc = function(None, "<unknown>")
118
indirectfunc = function(-1, '<indirect>')
119
unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0
120
unknownfunc.max_yield_usage = indirectfunc.max_yield_usage = -1
121
funcs = {-1: indirectfunc}
127
for line in sys.stdin.readlines():
128
m = re_func.match(line)
131
funcaddr = int(m.group('funcaddr'), 16)
132
funcs[funcaddr] = cur = function(funcaddr, m.group('func'))
136
m = re_asm.match(line)
138
#print("other", repr(line))
140
insn = m.group('insn')
142
im = re_usestack.match(insn)
144
if insn.startswith('pushl') or insn.startswith('pushfl'):
147
elif insn.startswith('pushw') or insn.startswith('pushfw'):
150
stackusage += int(im.group('num'), 16)
153
if '%esp' in insn or insn.startswith('leal'):
154
# Still part of initial header
156
if not stackusage and (
157
insn.startswith('test') or insn.startswith('cmp')
158
or insn.startswith('j')):
159
# There may be conditional checks prior to stack frame
161
cur.basic_stack_usage = stackusage
164
insnaddr = m.group('insnaddr')
165
calladdr = m.group('calladdr')
167
if insn.startswith('lcallw'):
168
cur.noteCall(insnaddr, -1, stackusage + 4)
169
cur.noteYield(stackusage + 4)
170
elif insn.startswith('int'):
171
cur.noteCall(insnaddr, -1, stackusage + 6)
172
cur.noteYield(stackusage + 6)
173
elif insn.startswith('sti'):
174
cur.noteYield(stackusage)
180
calladdr = int(calladdr, 16)
183
# Inter-function jump.
185
elif insn.startswith('j'):
187
cur.noteCall(insnaddr, calladdr, 0)
188
elif insn.startswith('calll'):
189
cur.noteCall(insnaddr, calladdr, stackusage + 4)
190
elif insn.startswith('callw'):
191
cur.noteCall(insnaddr, calladdr, stackusage + 2)
193
print("unknown call", ref)
194
cur.noteCall(insnaddr, calladdr, stackusage)
195
# Reset stack usage to preamble usage
196
stackusage = cur.basic_stack_usage
198
# Calculate maxstackusage
199
for info in funcs.values():
200
calcmaxstack(info, funcs)
202
# Sort functions for output
203
funcinfos = orderfuncs(funcs.keys(), funcs.copy())
207
for info in funcinfos:
208
if info.max_stack_usage == 0 and info.max_yield_usage < 0:
211
if info.max_yield_usage >= 0:
212
yieldstr = ",%d" % info.max_yield_usage
213
print("\n%s[%d,%d%s]:" % (info.funcname, info.basic_stack_usage
214
, info.max_stack_usage, yieldstr))
215
for insnaddr, calladdr, stackusage in info.called_funcs:
216
callinfo = funcs.get(calladdr, unknownfunc)
218
if callinfo.max_yield_usage >= 0:
219
yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage)
220
print(" %04s:%-40s [%d+%d,%d%s]" % (
221
insnaddr, callinfo.funcname, stackusage
222
, callinfo.basic_stack_usage
223
, stackusage+callinfo.max_stack_usage, yieldstr))
225
if __name__ == '__main__':