Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Script that tries to find how much stack space each function in an |
| 3 | # object is using. |
| 4 | # |
| 5 | # Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net> |
| 6 | # |
| 7 | # This file may be distributed under the terms of the GNU GPLv3 license. |
| 8 | |
| 9 | # Usage: |
Kevin O'Connor | 7173f6f | 2009-05-27 22:23:33 -0400 | [diff] [blame] | 10 | # objdump -m i386 -M i8086 -M suffix -d out/rom16.reloc.o | tools/checkstack.py |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 11 | |
| 12 | import sys |
| 13 | import re |
| 14 | |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 15 | # Functions that change stacks |
| 16 | STACKHOP = ['__send_disk_op'] |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 17 | # List of functions we can assume are never called. |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 18 | #IGNORE = ['panic', '__dprintf'] |
| 19 | IGNORE = ['panic'] |
| 20 | |
| 21 | OUTPUTDESC = """ |
| 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'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 28 | |
| 29 | # Find out maximum stack usage for a function |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 30 | def calcmaxstack(funcs, funcaddr): |
| 31 | info = funcs[funcaddr] |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 32 | # Find max of all nested calls. |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 33 | maxusage = info[1] |
| 34 | maxyieldusage = doesyield = 0 |
| 35 | if info[3] is not None: |
| 36 | maxyieldusage = info[3] |
| 37 | doesyield = 1 |
| 38 | info[2] = maxusage |
| 39 | info[4] = info[3] |
| 40 | seenbefore = {} |
| 41 | totcalls = 0 |
| 42 | for insnaddr, calladdr, usage in info[6]: |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 43 | callinfo = funcs[calladdr] |
| 44 | if callinfo[2] is None: |
| 45 | calcmaxstack(funcs, calladdr) |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 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: |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 51 | # This called function is ignored - don't contribute it to |
| 52 | # the max stack. |
| 53 | continue |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 54 | if funcnameroot in STACKHOP: |
| 55 | if usage > maxusage: |
| 56 | maxusage = usage |
| 57 | if callinfo[4] is not None: |
| 58 | doesyield = 1 |
| 59 | if usage > maxyieldusage: |
| 60 | maxyieldusage = usage |
| 61 | continue |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 62 | totusage = usage + callinfo[2] |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 63 | if totusage > maxusage: |
| 64 | maxusage = totusage |
| 65 | if callinfo[4] is not None: |
| 66 | doesyield = 1 |
| 67 | totyieldusage = usage + callinfo[4] |
| 68 | if totyieldusage > maxyieldusage: |
| 69 | maxyieldusage = totyieldusage |
| 70 | info[2] = maxusage |
| 71 | if doesyield: |
| 72 | info[4] = maxyieldusage |
| 73 | info[5] = totcalls |
| 74 | |
| 75 | # Try to arrange output so that functions that call each other are |
| 76 | # near each other. |
| 77 | def orderfuncs(funcnames, availnames, funcs): |
| 78 | l = [(availnames[name][5], name) |
| 79 | for name in funcnames if name in availnames] |
| 80 | l.sort() |
| 81 | l.reverse() |
| 82 | out = [] |
| 83 | while l: |
| 84 | count, name = l.pop(0) |
| 85 | if name not in availnames: |
| 86 | continue |
| 87 | callnames = [funcs[calls[1]][0] for calls in availnames[name][6]] |
| 88 | del availnames[name] |
| 89 | out = out + orderfuncs(callnames, availnames, funcs) + [name] |
| 90 | return out |
| 91 | |
| 92 | # Update function info with a found "yield" point. |
| 93 | def noteYield(info, stackusage): |
| 94 | prevyield = info[3] |
| 95 | if prevyield is None or prevyield < stackusage: |
| 96 | info[3] = stackusage |
| 97 | |
| 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. |
| 102 | return |
| 103 | info[6].append((insnaddr, calladdr, stackusage)) |
| 104 | subfuncs[(calladdr, stackusage)] = 1 |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 105 | |
| 106 | hex_s = r'[0-9a-f]+' |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 107 | re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$') |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 108 | re_asm = re.compile( |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 109 | r'^[ ]*(?P<insnaddr>' + hex_s |
| 110 | + r'):\t.*\t(addr32 )?(?P<insn>.+?)[ ]*((?P<calladdr>' + hex_s |
| 111 | + r') <(?P<ref>.*)>)?$') |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 112 | re_usestack = re.compile( |
Kevin O'Connor | 0b04b78 | 2009-06-10 21:40:26 -0400 | [diff] [blame] | 113 | r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$') |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 114 | |
| 115 | def calc(): |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 116 | # funcs[funcaddr] = [funcname, basicstackusage, maxstackusage |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 117 | # , yieldusage, maxyieldusage, totalcalls |
| 118 | # , [(insnaddr, calladdr, stackusage), ...]] |
| 119 | funcs = {-1: ['<indirect>', 0, 0, None, None, 0, []]} |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 120 | cur = None |
| 121 | atstart = 0 |
| 122 | stackusage = 0 |
| 123 | |
| 124 | # Parse input lines |
| 125 | for line in sys.stdin.readlines(): |
| 126 | m = re_func.match(line) |
| 127 | if m is not None: |
| 128 | # Found function |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 129 | funcaddr = int(m.group('funcaddr'), 16) |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 130 | funcs[funcaddr] = cur = [m.group('func'), 0, None, None, None, 0, []] |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 131 | stackusage = 0 |
| 132 | atstart = 1 |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 133 | subfuncs = {} |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 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'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 141 | if insn[:5] == 'pushl' or insn[:6] == 'pushfl': |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 142 | stackusage += 4 |
| 143 | continue |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 144 | elif insn[:5] == 'pushw' or insn[:6] == 'pushfw': |
| 145 | stackusage += 2 |
| 146 | continue |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 147 | stackusage += int(im.group('num'), 16) |
| 148 | |
| 149 | if atstart: |
Kevin O'Connor | dbbb7cf | 2009-08-09 13:10:47 -0400 | [diff] [blame] | 150 | if insn == 'movl %esp,%ebp': |
| 151 | # Still part of initial header |
| 152 | continue |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 153 | cur[1] = stackusage |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 154 | atstart = 0 |
| 155 | |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 156 | insnaddr = m.group('insnaddr') |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 157 | calladdr = m.group('calladdr') |
| 158 | if calladdr is None: |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 159 | if insn[:6] == 'lcallw': |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 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) |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 167 | else: |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 168 | # misc instruction |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 169 | continue |
| 170 | else: |
| 171 | # Jump or call insn |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 172 | calladdr = int(calladdr, 16) |
| 173 | ref = m.group('ref') |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 174 | if '+' in ref: |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 175 | # Inter-function jump. |
| 176 | pass |
| 177 | elif insn[:1] == 'j': |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 178 | # Tail call |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 179 | noteCall(cur, subfuncs, insnaddr, calladdr, 0) |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 180 | elif insn[:5] == 'calll': |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 181 | noteCall(cur, subfuncs, insnaddr, calladdr, stackusage + 4) |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 182 | else: |
| 183 | print "unknown call", ref |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 184 | noteCall(cur, subfuncs, insnaddr, calladdr, stackusage) |
Kevin O'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 185 | # Reset stack usage to preamble usage |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 186 | stackusage = cur[1] |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 187 | |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 188 | #print "other", repr(line) |
| 189 | |
| 190 | # Calculate maxstackusage |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 191 | bynames = {} |
| 192 | for funcaddr, info in funcs.items(): |
| 193 | bynames[info[0]] = info |
| 194 | if info[2] is not None: |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 195 | continue |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 196 | calcmaxstack(funcs, funcaddr) |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 197 | |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 198 | # Sort functions for output |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 199 | funcnames = bynames.keys() |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 200 | funcnames = orderfuncs(funcnames, bynames.copy(), funcs) |
| 201 | |
| 202 | # Show all functions |
| 203 | print OUTPUTDESC |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 204 | for funcname in funcnames: |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 205 | name, basicusage, maxusage, yieldusage, maxyieldusage, count, calls = \ |
| 206 | bynames[funcname] |
| 207 | if maxusage == 0 and maxyieldusage is None: |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 208 | continue |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 209 | yieldstr = "" |
| 210 | if maxyieldusage is not None: |
| 211 | yieldstr = ",%d" % maxyieldusage |
| 212 | print "\n%s[%d,%d%s]:" % (funcname, basicusage, maxusage, yieldstr) |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 213 | for insnaddr, calladdr, stackusage in calls: |
| 214 | callinfo = funcs[calladdr] |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 215 | yieldstr = "" |
| 216 | if callinfo[4] is not None: |
| 217 | yieldstr = ",%d" % (stackusage + callinfo[4]) |
| 218 | print " %04s:%-40s [%d+%d,%d%s]" % ( |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 219 | insnaddr, callinfo[0], stackusage, callinfo[1] |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 220 | , stackusage+callinfo[2], yieldstr) |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 221 | |
| 222 | def main(): |
| 223 | calc() |
| 224 | |
| 225 | if __name__ == '__main__': |
| 226 | main() |