blob: 6b3c80a18db4aeddd24caaee46966d07b494f2f7 [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'Connor95cde272008-07-13 10:59:11 -040054def calcmaxstack(funcs, funcaddr):
55 info = funcs[funcaddr]
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -040056 # Find max of all nested calls.
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'Connorb5d6c1e2015-03-18 23:31:43 -040061 for insnaddr, calladdr, usage in info.called_funcs:
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -040062 callinfo = funcs.get(calladdr)
63 if callinfo is None:
64 continue
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040065 if callinfo.max_stack_usage is None:
Kevin O'Connor95cde272008-07-13 10:59:11 -040066 calcmaxstack(funcs, calladdr)
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)
99 if funcaddr not in availfuncs:
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500100 continue
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400101 calladdrs = [calls[1] for calls in availfuncs[funcaddr].called_funcs]
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400102 del availfuncs[funcaddr]
103 out = out + orderfuncs(calladdrs, availfuncs) + [funcaddr]
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500104 return out
105
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400106hex_s = r'[0-9a-f]+'
Kevin O'Connor95cde272008-07-13 10:59:11 -0400107re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400108re_asm = re.compile(
Kevin O'Connor95cde272008-07-13 10:59:11 -0400109 r'^[ ]*(?P<insnaddr>' + hex_s
110 + r'):\t.*\t(addr32 )?(?P<insn>.+?)[ ]*((?P<calladdr>' + hex_s
111 + r') <(?P<ref>.*)>)?$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400112re_usestack = re.compile(
Kevin O'Connor0b04b782009-06-10 21:40:26 -0400113 r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400114
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400115def main():
116 unknownfunc = function(None, "<unknown>")
117 indirectfunc = function(-1, '<indirect>')
118 unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0
Kevin O'Connord304b592015-03-19 12:10:28 -0400119 unknownfunc.max_yield_usage = indirectfunc.max_yield_usage = -1
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400120 funcs = {-1: indirectfunc}
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400121 cur = None
122 atstart = 0
123 stackusage = 0
124
125 # Parse input lines
126 for line in sys.stdin.readlines():
127 m = re_func.match(line)
128 if m is not None:
129 # Found function
Kevin O'Connor95cde272008-07-13 10:59:11 -0400130 funcaddr = int(m.group('funcaddr'), 16)
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400131 funcs[funcaddr] = cur = function(funcaddr, m.group('func'))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400132 stackusage = 0
133 atstart = 1
134 continue
135 m = re_asm.match(line)
136 if m is not None:
137 insn = m.group('insn')
138
139 im = re_usestack.match(insn)
140 if im is not None:
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400141 if insn.startswith('pushl') or insn.startswith('pushfl'):
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400142 stackusage += 4
143 continue
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400144 elif insn.startswith('pushw') or insn.startswith('pushfw'):
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400145 stackusage += 2
146 continue
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400147 stackusage += int(im.group('num'), 16)
148
149 if atstart:
Kevin O'Connor6ee837b2012-02-13 20:09:02 -0500150 if '%esp' in insn or insn.startswith('leal'):
Kevin O'Connordbbb7cf2009-08-09 13:10:47 -0400151 # Still part of initial header
152 continue
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400153 cur.basic_stack_usage = stackusage
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400154 atstart = 0
155
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500156 insnaddr = m.group('insnaddr')
Kevin O'Connor95cde272008-07-13 10:59:11 -0400157 calladdr = m.group('calladdr')
158 if calladdr is None:
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400159 if insn.startswith('lcallw'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400160 cur.noteCall(insnaddr, -1, stackusage + 4)
161 cur.noteYield(stackusage + 4)
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400162 elif insn.startswith('int'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400163 cur.noteCall(insnaddr, -1, stackusage + 6)
164 cur.noteYield(stackusage + 6)
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400165 elif insn.startswith('sti'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400166 cur.noteYield(stackusage)
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400167 else:
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500168 # misc instruction
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400169 continue
170 else:
171 # Jump or call insn
Kevin O'Connor95cde272008-07-13 10:59:11 -0400172 calladdr = int(calladdr, 16)
173 ref = m.group('ref')
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400174 if '+' in ref:
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500175 # Inter-function jump.
176 pass
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400177 elif insn.startswith('j'):
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400178 # Tail call
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400179 cur.noteCall(insnaddr, calladdr, 0)
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400180 elif insn.startswith('calll'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400181 cur.noteCall(insnaddr, calladdr, stackusage + 4)
Kevin O'Connorc9d97d52015-01-19 12:41:33 -0500182 elif insn.startswith('callw'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400183 cur.noteCall(insnaddr, calladdr, stackusage + 2)
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400184 else:
Johannes Krampf064fd062014-01-12 11:14:54 -0500185 print("unknown call", ref)
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400186 cur.noteCall(insnaddr, calladdr, stackusage)
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400187 # Reset stack usage to preamble usage
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400188 stackusage = cur.basic_stack_usage
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400189
Johannes Krampf064fd062014-01-12 11:14:54 -0500190 #print("other", repr(line))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400191
192 # Calculate maxstackusage
Kevin O'Connor95cde272008-07-13 10:59:11 -0400193 for funcaddr, info in funcs.items():
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400194 if info.max_stack_usage is not None:
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400195 continue
Kevin O'Connor95cde272008-07-13 10:59:11 -0400196 calcmaxstack(funcs, funcaddr)
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400197
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500198 # Sort functions for output
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400199 funcaddrs = orderfuncs(funcs.keys(), funcs.copy())
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500200
201 # Show all functions
Johannes Krampf064fd062014-01-12 11:14:54 -0500202 print(OUTPUTDESC)
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400203 for funcaddr in funcaddrs:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400204 info = funcs[funcaddr]
Kevin O'Connord304b592015-03-19 12:10:28 -0400205 if info.max_stack_usage == 0 and info.max_yield_usage < 0:
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400206 continue
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500207 yieldstr = ""
Kevin O'Connord304b592015-03-19 12:10:28 -0400208 if info.max_yield_usage >= 0:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400209 yieldstr = ",%d" % info.max_yield_usage
210 print("\n%s[%d,%d%s]:" % (info.funcname, info.basic_stack_usage
211 , info.max_stack_usage, yieldstr))
212 for insnaddr, calladdr, stackusage in info.called_funcs:
213 callinfo = funcs.get(calladdr, unknownfunc)
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500214 yieldstr = ""
Kevin O'Connord304b592015-03-19 12:10:28 -0400215 if callinfo.max_yield_usage >= 0:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400216 yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage)
Johannes Krampf064fd062014-01-12 11:14:54 -0500217 print(" %04s:%-40s [%d+%d,%d%s]" % (
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400218 insnaddr, callinfo.funcname, stackusage
219 , callinfo.basic_stack_usage
220 , stackusage+callinfo.max_stack_usage, yieldstr))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400221
222if __name__ == '__main__':
223 main()