2 # Script that tries to find how much stack space each function in an
5 # Copyright (C) 2008 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.reloc.o | tools/checkstack.py
15 # Functions that change stacks
16 STACKHOP = ['__send_disk_op']
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]
29 # Find out maximum stack usage for a function
30 def calcmaxstack(funcs, funcaddr):
31 info = funcs[funcaddr]
32 # Find max of all nested calls.
34 maxyieldusage = doesyield = 0
35 if info[3] is not None:
36 maxyieldusage = info[3]
42 for insnaddr, calladdr, usage in info[6]:
43 callinfo = funcs[calladdr]
44 if callinfo[2] is None:
45 calcmaxstack(funcs, calladdr)
46 if callinfo[0] not in seenbefore:
47 seenbefore[callinfo[0]] = 1
48 totcalls += 1 + callinfo[5]
49 funcnameroot = callinfo[0].split('.')[0]
50 if funcnameroot in IGNORE:
51 # This called function is ignored - don't contribute it to
54 if funcnameroot in STACKHOP:
57 if callinfo[4] is not None:
59 if usage > maxyieldusage:
62 totusage = usage + callinfo[2]
63 if totusage > maxusage:
65 if callinfo[4] is not None:
67 totyieldusage = usage + callinfo[4]
68 if totyieldusage > maxyieldusage:
69 maxyieldusage = totyieldusage
72 info[4] = maxyieldusage
75 # Try to arrange output so that functions that call each other are
77 def orderfuncs(funcnames, availnames, funcs):
78 l = [(availnames[name][5], name)
79 for name in funcnames if name in availnames]
84 count, name = l.pop(0)
85 if name not in availnames:
87 callnames = [funcs[calls[1]][0] for calls in availnames[name][6]]
89 out = out + orderfuncs(callnames, availnames, funcs) + [name]
92 # Update function info with a found "yield" point.
93 def noteYield(info, stackusage):
95 if prevyield is None or prevyield < stackusage:
98 # Update function info with a found "call" point.
99 def noteCall(info, subfuncs, insnaddr, calladdr, stackusage):
100 if (calladdr, stackusage) in subfuncs:
101 # Already noted a nearly identical call - ignore this one.
103 info[6].append((insnaddr, calladdr, stackusage))
104 subfuncs[(calladdr, stackusage)] = 1
107 re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
109 r'^[ ]*(?P<insnaddr>' + hex_s
110 + r'):\t.*\t(addr32 )?(?P<insn>.+?)[ ]*((?P<calladdr>' + hex_s
111 + r') <(?P<ref>.*)>)?$')
112 re_usestack = re.compile(
113 r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$')
116 # funcs[funcaddr] = [funcname, basicstackusage, maxstackusage
117 # , yieldusage, maxyieldusage, totalcalls
118 # , [(insnaddr, calladdr, stackusage), ...]]
119 funcs = {-1: ['<indirect>', 0, 0, None, None, 0, []]}
125 for line in sys.stdin.readlines():
126 m = re_func.match(line)
129 funcaddr = int(m.group('funcaddr'), 16)
130 funcs[funcaddr] = cur = [m.group('func'), 0, None, None, None, 0, []]
135 m = re_asm.match(line)
137 insn = m.group('insn')
139 im = re_usestack.match(insn)
141 if insn[:5] == 'pushl' or insn[:6] == 'pushfl':
144 elif insn[:5] == 'pushw' or insn[:6] == 'pushfw':
147 stackusage += int(im.group('num'), 16)
150 if insn == 'movl %esp,%ebp':
151 # Still part of initial header
156 insnaddr = m.group('insnaddr')
157 calladdr = m.group('calladdr')
159 if insn[:6] == 'lcallw':
160 noteCall(cur, subfuncs, insnaddr, -1, stackusage + 4)
161 noteYield(cur, stackusage + 4)
162 elif insn[:3] == 'int':
163 noteCall(cur, subfuncs, insnaddr, -1, stackusage + 6)
164 noteYield(cur, stackusage + 6)
165 elif insn[:3] == 'sti':
166 noteYield(cur, stackusage)
172 calladdr = int(calladdr, 16)
175 # Inter-function jump.
177 elif insn[:1] == 'j':
179 noteCall(cur, subfuncs, insnaddr, calladdr, 0)
180 elif insn[:5] == 'calll':
181 noteCall(cur, subfuncs, insnaddr, calladdr, stackusage + 4)
183 print "unknown call", ref
184 noteCall(cur, subfuncs, insnaddr, calladdr, stackusage)
185 # Reset stack usage to preamble usage
188 #print "other", repr(line)
190 # Calculate maxstackusage
192 for funcaddr, info in funcs.items():
193 bynames[info[0]] = info
194 if info[2] is not None:
196 calcmaxstack(funcs, funcaddr)
198 # Sort functions for output
199 funcnames = bynames.keys()
200 funcnames = orderfuncs(funcnames, bynames.copy(), funcs)
204 for funcname in funcnames:
205 name, basicusage, maxusage, yieldusage, maxyieldusage, count, calls = \
207 if maxusage == 0 and maxyieldusage is None:
210 if maxyieldusage is not None:
211 yieldstr = ",%d" % maxyieldusage
212 print "\n%s[%d,%d%s]:" % (funcname, basicusage, maxusage, yieldstr)
213 for insnaddr, calladdr, stackusage in calls:
214 callinfo = funcs[calladdr]
216 if callinfo[4] is not None:
217 yieldstr = ",%d" % (stackusage + callinfo[4])
218 print " %04s:%-40s [%d+%d,%d%s]" % (
219 insnaddr, callinfo[0], stackusage, callinfo[1]
220 , stackusage+callinfo[2], yieldstr)
225 if __name__ == '__main__':