blob: 5d9b0bfaf304d7d4b2a6d6c8128ad7f61ac807ec [file] [log] [blame]
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -04001#!/usr/bin/env python
2# Script that tries to find how much stack space each function in an
3# object is using.
4#
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -04005# Copyright (C) 2008-2015 Kevin O'Connor <kevin@koconnor.net>
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -04006#
7# This file may be distributed under the terms of the GNU GPLv3 license.
8
9# Usage:
Kevin O'Connorffc06872014-06-11 15:40:04 -040010# objdump -m i386 -M i8086 -M suffix -d out/rom16.o | scripts/checkstack.py
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -040011
12import sys
13import re
14
Kevin O'Connora979c1c2010-03-09 20:01:35 -050015# Functions that change stacks
Kevin O'Connorecdc6552012-05-28 14:25:15 -040016STACKHOP = ['stack_hop', 'stack_hop_back']
Kevin O'Connorf5cb0792008-06-07 10:11:36 -040017# List of functions we can assume are never called.
Kevin O'Connora979c1c2010-03-09 20:01:35 -050018#IGNORE = ['panic', '__dprintf']
19IGNORE = ['panic']
20
21OUTPUTDESC = """
22#funcname1[preamble_stack_usage,max_usage_with_callers]:
23# insn_addr:called_function [usage_at_call_point+caller_preamble,total_usage]
24#
25#funcname2[p,m,max_usage_to_yield_point]:
26# insn_addr:called_function [u+c,t,usage_to_yield_point]
27"""
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -040028
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040029class function:
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
Kevin O'Connord304b592015-03-19 12:10:28 -040035 self.yield_usage = -1
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040036 self.max_yield_usage = None
37 self.total_calls = 0
38 # called_funcs = [(insnaddr, calladdr, stackusage), ...]
39 self.called_funcs = []
40 self.subfuncs = {}
41 # Update function info with a found "yield" point.
42 def noteYield(self, stackusage):
Kevin O'Connord304b592015-03-19 12:10:28 -040043 if self.yield_usage < stackusage:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040044 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.
49 return
50 self.called_funcs.append((insnaddr, calladdr, stackusage))
51 self.subfuncs[(calladdr, stackusage)] = 1
52
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -040053# Find out maximum stack usage for a function
Kevin O'Connor13176172015-03-19 12:43:29 -040054def calcmaxstack(info, funcs):
55 if info.max_stack_usage is not None:
56 return
Kevin O'Connord304b592015-03-19 12:10:28 -040057 info.max_stack_usage = max_stack_usage = info.basic_stack_usage
58 info.max_yield_usage = max_yield_usage = info.yield_usage
59 total_calls = 0
Kevin O'Connora979c1c2010-03-09 20:01:35 -050060 seenbefore = {}
Kevin O'Connor13176172015-03-19 12:43:29 -040061 # Find max of all nested calls.
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040062 for insnaddr, calladdr, usage in info.called_funcs:
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -040063 callinfo = funcs.get(calladdr)
64 if callinfo is None:
65 continue
Kevin O'Connor13176172015-03-19 12:43:29 -040066 calcmaxstack(callinfo, funcs)
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040067 if callinfo.funcname not in seenbefore:
68 seenbefore[callinfo.funcname] = 1
Kevin O'Connord304b592015-03-19 12:10:28 -040069 total_calls += callinfo.total_calls + 1
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040070 funcnameroot = callinfo.funcname.split('.')[0]
Kevin O'Connora979c1c2010-03-09 20:01:35 -050071 if funcnameroot in IGNORE:
Kevin O'Connor95cde272008-07-13 10:59:11 -040072 # This called function is ignored - don't contribute it to
73 # the max stack.
74 continue
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040075 totusage = usage + callinfo.max_stack_usage
Kevin O'Connord304b592015-03-19 12:10:28 -040076 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
Kevin O'Connora979c1c2010-03-09 20:01:35 -050087
88# Try to arrange output so that functions that call each other are
89# near each other.
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -040090def orderfuncs(funcaddrs, availfuncs):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040091 l = [(availfuncs[funcaddr].total_calls
92 , availfuncs[funcaddr].funcname, funcaddr)
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -040093 for funcaddr in funcaddrs if funcaddr in availfuncs]
Kevin O'Connora979c1c2010-03-09 20:01:35 -050094 l.sort()
95 l.reverse()
96 out = []
97 while l:
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -040098 count, name, funcaddr = l.pop(0)
Kevin O'Connor13176172015-03-19 12:43:29 -040099 info = availfuncs.get(funcaddr)
100 if info is None:
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500101 continue
Kevin O'Connor13176172015-03-19 12:43:29 -0400102 calladdrs = [calls[1] for calls in info.called_funcs]
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400103 del availfuncs[funcaddr]
Kevin O'Connor13176172015-03-19 12:43:29 -0400104 out = out + orderfuncs(calladdrs, availfuncs) + [info]
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500105 return out
106
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400107hex_s = r'[0-9a-f]+'
Kevin O'Connor95cde272008-07-13 10:59:11 -0400108re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400109re_asm = re.compile(
Kevin O'Connor95cde272008-07-13 10:59:11 -0400110 r'^[ ]*(?P<insnaddr>' + hex_s
111 + r'):\t.*\t(addr32 )?(?P<insn>.+?)[ ]*((?P<calladdr>' + hex_s
112 + r') <(?P<ref>.*)>)?$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400113re_usestack = re.compile(
Kevin O'Connor0b04b782009-06-10 21:40:26 -0400114 r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400115
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400116def main():
117 unknownfunc = function(None, "<unknown>")
118 indirectfunc = function(-1, '<indirect>')
119 unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0
Kevin O'Connord304b592015-03-19 12:10:28 -0400120 unknownfunc.max_yield_usage = indirectfunc.max_yield_usage = -1
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400121 funcs = {-1: indirectfunc}
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400122 cur = None
123 atstart = 0
124 stackusage = 0
125
126 # Parse input lines
127 for line in sys.stdin.readlines():
128 m = re_func.match(line)
129 if m is not None:
130 # Found function
Kevin O'Connor95cde272008-07-13 10:59:11 -0400131 funcaddr = int(m.group('funcaddr'), 16)
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400132 funcs[funcaddr] = cur = function(funcaddr, m.group('func'))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400133 stackusage = 0
134 atstart = 1
135 continue
136 m = re_asm.match(line)
Kevin O'Connor945313c2015-04-17 12:33:58 -0400137 if m is None:
138 #print("other", repr(line))
139 continue
140 insn = m.group('insn')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400141
Kevin O'Connor945313c2015-04-17 12:33:58 -0400142 im = re_usestack.match(insn)
143 if im is not None:
144 if insn.startswith('pushl') or insn.startswith('pushfl'):
145 stackusage += 4
146 continue
147 elif insn.startswith('pushw') or insn.startswith('pushfw'):
148 stackusage += 2
149 continue
150 stackusage += int(im.group('num'), 16)
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400151
Kevin O'Connor945313c2015-04-17 12:33:58 -0400152 if atstart:
153 if '%esp' in insn or insn.startswith('leal'):
154 # Still part of initial header
155 continue
156 cur.basic_stack_usage = stackusage
157 atstart = 0
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400158
Kevin O'Connor945313c2015-04-17 12:33:58 -0400159 insnaddr = m.group('insnaddr')
160 calladdr = m.group('calladdr')
161 if calladdr is None:
162 if insn.startswith('lcallw'):
163 cur.noteCall(insnaddr, -1, stackusage + 4)
164 cur.noteYield(stackusage + 4)
165 elif insn.startswith('int'):
166 cur.noteCall(insnaddr, -1, stackusage + 6)
167 cur.noteYield(stackusage + 6)
168 elif insn.startswith('sti'):
169 cur.noteYield(stackusage)
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400170 else:
Kevin O'Connor945313c2015-04-17 12:33:58 -0400171 # misc instruction
172 continue
173 else:
174 # Jump or call insn
175 calladdr = int(calladdr, 16)
176 ref = m.group('ref')
177 if '+' in ref:
178 # Inter-function jump.
179 pass
180 elif insn.startswith('j'):
181 # Tail call
182 cur.noteCall(insnaddr, calladdr, 0)
183 elif insn.startswith('calll'):
184 cur.noteCall(insnaddr, calladdr, stackusage + 4)
185 elif insn.startswith('callw'):
186 cur.noteCall(insnaddr, calladdr, stackusage + 2)
187 else:
188 print("unknown call", ref)
189 cur.noteCall(insnaddr, calladdr, stackusage)
190 # Reset stack usage to preamble usage
191 stackusage = cur.basic_stack_usage
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400192
193 # Calculate maxstackusage
Kevin O'Connor13176172015-03-19 12:43:29 -0400194 for info in funcs.values():
195 calcmaxstack(info, funcs)
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400196
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500197 # Sort functions for output
Kevin O'Connor13176172015-03-19 12:43:29 -0400198 funcinfos = orderfuncs(funcs.keys(), funcs.copy())
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500199
200 # Show all functions
Johannes Krampf064fd062014-01-12 11:14:54 -0500201 print(OUTPUTDESC)
Kevin O'Connor13176172015-03-19 12:43:29 -0400202 for info in funcinfos:
Kevin O'Connord304b592015-03-19 12:10:28 -0400203 if info.max_stack_usage == 0 and info.max_yield_usage < 0:
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400204 continue
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500205 yieldstr = ""
Kevin O'Connord304b592015-03-19 12:10:28 -0400206 if info.max_yield_usage >= 0:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400207 yieldstr = ",%d" % info.max_yield_usage
208 print("\n%s[%d,%d%s]:" % (info.funcname, info.basic_stack_usage
209 , info.max_stack_usage, yieldstr))
210 for insnaddr, calladdr, stackusage in info.called_funcs:
211 callinfo = funcs.get(calladdr, unknownfunc)
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500212 yieldstr = ""
Kevin O'Connord304b592015-03-19 12:10:28 -0400213 if callinfo.max_yield_usage >= 0:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400214 yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage)
Johannes Krampf064fd062014-01-12 11:14:54 -0500215 print(" %04s:%-40s [%d+%d,%d%s]" % (
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400216 insnaddr, callinfo.funcname, stackusage
217 , callinfo.basic_stack_usage
218 , stackusage+callinfo.max_stack_usage, yieldstr))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400219
220if __name__ == '__main__':
221 main()