blob: 255768aebce0341f0bb06d13973e653735aa5154 [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
Kevin O'Connor996a8a92016-08-10 10:52:12 -0400156 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
160 continue
Kevin O'Connor945313c2015-04-17 12:33:58 -0400161 cur.basic_stack_usage = stackusage
162 atstart = 0
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400163
Kevin O'Connor945313c2015-04-17 12:33:58 -0400164 insnaddr = m.group('insnaddr')
165 calladdr = m.group('calladdr')
166 if calladdr is None:
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)
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400175 else:
Kevin O'Connor945313c2015-04-17 12:33:58 -0400176 # misc instruction
177 continue
178 else:
179 # Jump or call insn
180 calladdr = int(calladdr, 16)
181 ref = m.group('ref')
182 if '+' in ref:
183 # Inter-function jump.
184 pass
185 elif insn.startswith('j'):
186 # Tail call
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)
192 else:
193 print("unknown call", ref)
194 cur.noteCall(insnaddr, calladdr, stackusage)
195 # Reset stack usage to preamble usage
196 stackusage = cur.basic_stack_usage
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400197
198 # Calculate maxstackusage
Kevin O'Connor13176172015-03-19 12:43:29 -0400199 for info in funcs.values():
200 calcmaxstack(info, funcs)
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400201
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500202 # Sort functions for output
Kevin O'Connor13176172015-03-19 12:43:29 -0400203 funcinfos = orderfuncs(funcs.keys(), funcs.copy())
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500204
205 # Show all functions
Johannes Krampf064fd062014-01-12 11:14:54 -0500206 print(OUTPUTDESC)
Kevin O'Connor13176172015-03-19 12:43:29 -0400207 for info in funcinfos:
Kevin O'Connord304b592015-03-19 12:10:28 -0400208 if info.max_stack_usage == 0 and info.max_yield_usage < 0:
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400209 continue
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500210 yieldstr = ""
Kevin O'Connord304b592015-03-19 12:10:28 -0400211 if info.max_yield_usage >= 0:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400212 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)
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500217 yieldstr = ""
Kevin O'Connord304b592015-03-19 12:10:28 -0400218 if callinfo.max_yield_usage >= 0:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400219 yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage)
Johannes Krampf064fd062014-01-12 11:14:54 -0500220 print(" %04s:%-40s [%d+%d,%d%s]" % (
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400221 insnaddr, callinfo.funcname, stackusage
222 , callinfo.basic_stack_usage
223 , stackusage+callinfo.max_stack_usage, yieldstr))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400224
225if __name__ == '__main__':
226 main()