blob: f13a7bc06d11eb375c989fe7ea5663ff3b944dd6 [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
35 self.yield_usage = None
36 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):
43 if self.yield_usage is None or 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.
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'Connorb5d6c1e2015-03-18 23:31:43 -040057 maxusage = info.basic_stack_usage
Kevin O'Connora979c1c2010-03-09 20:01:35 -050058 maxyieldusage = doesyield = 0
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040059 if info.yield_usage is not None:
60 maxyieldusage = info.yield_usage
Kevin O'Connora979c1c2010-03-09 20:01:35 -050061 doesyield = 1
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040062 info.max_stack_usage = maxusage
63 info.max_yield_usage = info.yield_usage
Kevin O'Connora979c1c2010-03-09 20:01:35 -050064 seenbefore = {}
65 totcalls = 0
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040066 for insnaddr, calladdr, usage in info.called_funcs:
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -040067 callinfo = funcs.get(calladdr)
68 if callinfo is None:
69 continue
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040070 if callinfo.max_stack_usage is None:
Kevin O'Connor95cde272008-07-13 10:59:11 -040071 calcmaxstack(funcs, calladdr)
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040072 if callinfo.funcname not in seenbefore:
73 seenbefore[callinfo.funcname] = 1
74 totcalls += 1 + callinfo.total_calls
75 funcnameroot = callinfo.funcname.split('.')[0]
Kevin O'Connora979c1c2010-03-09 20:01:35 -050076 if funcnameroot in IGNORE:
Kevin O'Connor95cde272008-07-13 10:59:11 -040077 # This called function is ignored - don't contribute it to
78 # the max stack.
79 continue
Kevin O'Connora979c1c2010-03-09 20:01:35 -050080 if funcnameroot in STACKHOP:
81 if usage > maxusage:
82 maxusage = usage
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040083 if callinfo.max_yield_usage is not None:
Kevin O'Connora979c1c2010-03-09 20:01:35 -050084 doesyield = 1
85 if usage > maxyieldusage:
86 maxyieldusage = usage
87 continue
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040088 totusage = usage + callinfo.max_stack_usage
Kevin O'Connora979c1c2010-03-09 20:01:35 -050089 if totusage > maxusage:
90 maxusage = totusage
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040091 if callinfo.max_yield_usage is not None:
Kevin O'Connora979c1c2010-03-09 20:01:35 -050092 doesyield = 1
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040093 totyieldusage = usage + callinfo.max_yield_usage
Kevin O'Connora979c1c2010-03-09 20:01:35 -050094 if totyieldusage > maxyieldusage:
95 maxyieldusage = totyieldusage
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040096 info.max_stack_usage = maxusage
Kevin O'Connora979c1c2010-03-09 20:01:35 -050097 if doesyield:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -040098 info.max_yield_usage = maxyieldusage
99 info.total_calls = totcalls
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500100
101# Try to arrange output so that functions that call each other are
102# near each other.
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400103def orderfuncs(funcaddrs, availfuncs):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400104 l = [(availfuncs[funcaddr].total_calls
105 , availfuncs[funcaddr].funcname, funcaddr)
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400106 for funcaddr in funcaddrs if funcaddr in availfuncs]
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500107 l.sort()
108 l.reverse()
109 out = []
110 while l:
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400111 count, name, funcaddr = l.pop(0)
112 if funcaddr not in availfuncs:
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500113 continue
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400114 calladdrs = [calls[1] for calls in availfuncs[funcaddr].called_funcs]
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400115 del availfuncs[funcaddr]
116 out = out + orderfuncs(calladdrs, availfuncs) + [funcaddr]
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500117 return out
118
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400119hex_s = r'[0-9a-f]+'
Kevin O'Connor95cde272008-07-13 10:59:11 -0400120re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400121re_asm = re.compile(
Kevin O'Connor95cde272008-07-13 10:59:11 -0400122 r'^[ ]*(?P<insnaddr>' + hex_s
123 + r'):\t.*\t(addr32 )?(?P<insn>.+?)[ ]*((?P<calladdr>' + hex_s
124 + r') <(?P<ref>.*)>)?$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400125re_usestack = re.compile(
Kevin O'Connor0b04b782009-06-10 21:40:26 -0400126 r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$')
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400127
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400128def main():
129 unknownfunc = function(None, "<unknown>")
130 indirectfunc = function(-1, '<indirect>')
131 unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0
132 funcs = {-1: indirectfunc}
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400133 cur = None
134 atstart = 0
135 stackusage = 0
136
137 # Parse input lines
138 for line in sys.stdin.readlines():
139 m = re_func.match(line)
140 if m is not None:
141 # Found function
Kevin O'Connor95cde272008-07-13 10:59:11 -0400142 funcaddr = int(m.group('funcaddr'), 16)
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400143 funcs[funcaddr] = cur = function(funcaddr, m.group('func'))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400144 stackusage = 0
145 atstart = 1
146 continue
147 m = re_asm.match(line)
148 if m is not None:
149 insn = m.group('insn')
150
151 im = re_usestack.match(insn)
152 if im is not None:
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400153 if insn.startswith('pushl') or insn.startswith('pushfl'):
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400154 stackusage += 4
155 continue
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400156 elif insn.startswith('pushw') or insn.startswith('pushfw'):
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400157 stackusage += 2
158 continue
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400159 stackusage += int(im.group('num'), 16)
160
161 if atstart:
Kevin O'Connor6ee837b2012-02-13 20:09:02 -0500162 if '%esp' in insn or insn.startswith('leal'):
Kevin O'Connordbbb7cf2009-08-09 13:10:47 -0400163 # Still part of initial header
164 continue
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400165 cur.basic_stack_usage = stackusage
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400166 atstart = 0
167
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500168 insnaddr = m.group('insnaddr')
Kevin O'Connor95cde272008-07-13 10:59:11 -0400169 calladdr = m.group('calladdr')
170 if calladdr is None:
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400171 if insn.startswith('lcallw'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400172 cur.noteCall(insnaddr, -1, stackusage + 4)
173 cur.noteYield(stackusage + 4)
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400174 elif insn.startswith('int'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400175 cur.noteCall(insnaddr, -1, stackusage + 6)
176 cur.noteYield(stackusage + 6)
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400177 elif insn.startswith('sti'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400178 cur.noteYield(stackusage)
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400179 else:
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500180 # misc instruction
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400181 continue
182 else:
183 # Jump or call insn
Kevin O'Connor95cde272008-07-13 10:59:11 -0400184 calladdr = int(calladdr, 16)
185 ref = m.group('ref')
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400186 if '+' in ref:
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500187 # Inter-function jump.
188 pass
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400189 elif insn.startswith('j'):
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400190 # Tail call
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400191 cur.noteCall(insnaddr, calladdr, 0)
Kevin O'Connor6c2e7812010-09-13 18:04:02 -0400192 elif insn.startswith('calll'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400193 cur.noteCall(insnaddr, calladdr, stackusage + 4)
Kevin O'Connorc9d97d52015-01-19 12:41:33 -0500194 elif insn.startswith('callw'):
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400195 cur.noteCall(insnaddr, calladdr, stackusage + 2)
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400196 else:
Johannes Krampf064fd062014-01-12 11:14:54 -0500197 print("unknown call", ref)
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400198 cur.noteCall(insnaddr, calladdr, stackusage)
Kevin O'Connorf5cb0792008-06-07 10:11:36 -0400199 # Reset stack usage to preamble usage
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400200 stackusage = cur.basic_stack_usage
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400201
Johannes Krampf064fd062014-01-12 11:14:54 -0500202 #print("other", repr(line))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400203
204 # Calculate maxstackusage
Kevin O'Connor95cde272008-07-13 10:59:11 -0400205 for funcaddr, info in funcs.items():
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400206 if info.max_stack_usage is not None:
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400207 continue
Kevin O'Connor95cde272008-07-13 10:59:11 -0400208 calcmaxstack(funcs, funcaddr)
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400209
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500210 # Sort functions for output
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400211 funcaddrs = orderfuncs(funcs.keys(), funcs.copy())
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500212
213 # Show all functions
Johannes Krampf064fd062014-01-12 11:14:54 -0500214 print(OUTPUTDESC)
Kevin O'Connorf59b5ac2010-05-01 11:03:10 -0400215 for funcaddr in funcaddrs:
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400216 info = funcs[funcaddr]
217 if info.max_stack_usage == 0 and info.max_yield_usage is None:
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400218 continue
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500219 yieldstr = ""
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400220 if info.max_yield_usage is not None:
221 yieldstr = ",%d" % info.max_yield_usage
222 print("\n%s[%d,%d%s]:" % (info.funcname, info.basic_stack_usage
223 , info.max_stack_usage, yieldstr))
224 for insnaddr, calladdr, stackusage in info.called_funcs:
225 callinfo = funcs.get(calladdr, unknownfunc)
Kevin O'Connora979c1c2010-03-09 20:01:35 -0500226 yieldstr = ""
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400227 if callinfo.max_yield_usage is not None:
228 yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage)
Johannes Krampf064fd062014-01-12 11:14:54 -0500229 print(" %04s:%-40s [%d+%d,%d%s]" % (
Kevin O'Connorb5d6c1e2015-03-18 23:31:43 -0400230 insnaddr, callinfo.funcname, stackusage
231 , callinfo.basic_stack_usage
232 , stackusage+callinfo.max_stack_usage, yieldstr))
Kevin O'Connor5c4a8c62008-05-12 23:50:16 -0400233
234if __name__ == '__main__':
235 main()