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 | # |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 5 | # Copyright (C) 2008-2015 Kevin O'Connor <kevin@koconnor.net> |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 6 | # |
| 7 | # This file may be distributed under the terms of the GNU GPLv3 license. |
| 8 | |
| 9 | # Usage: |
Kevin O'Connor | ffc0687 | 2014-06-11 15:40:04 -0400 | [diff] [blame] | 10 | # objdump -m i386 -M i8086 -M suffix -d out/rom16.o | scripts/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 |
Kevin O'Connor | ecdc655 | 2012-05-28 14:25:15 -0400 | [diff] [blame] | 16 | STACKHOP = ['stack_hop', 'stack_hop_back'] |
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 | |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 29 | class 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'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 35 | self.yield_usage = -1 |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 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): |
Kevin O'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 43 | if self.yield_usage < stackusage: |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 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'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 53 | # Find out maximum stack usage for a function |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 54 | def calcmaxstack(info, funcs): |
| 55 | if info.max_stack_usage is not None: |
| 56 | return |
Kevin O'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 57 | 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'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 60 | seenbefore = {} |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 61 | # Find max of all nested calls. |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 62 | for insnaddr, calladdr, usage in info.called_funcs: |
Kevin O'Connor | f59b5ac | 2010-05-01 11:03:10 -0400 | [diff] [blame] | 63 | callinfo = funcs.get(calladdr) |
| 64 | if callinfo is None: |
| 65 | continue |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 66 | calcmaxstack(callinfo, funcs) |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 67 | if callinfo.funcname not in seenbefore: |
| 68 | seenbefore[callinfo.funcname] = 1 |
Kevin O'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 69 | total_calls += callinfo.total_calls + 1 |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 70 | funcnameroot = callinfo.funcname.split('.')[0] |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 71 | if funcnameroot in IGNORE: |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 72 | # This called function is ignored - don't contribute it to |
| 73 | # the max stack. |
| 74 | continue |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 75 | totusage = usage + callinfo.max_stack_usage |
Kevin O'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 76 | 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'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 87 | |
| 88 | # Try to arrange output so that functions that call each other are |
| 89 | # near each other. |
Kevin O'Connor | f59b5ac | 2010-05-01 11:03:10 -0400 | [diff] [blame] | 90 | def orderfuncs(funcaddrs, availfuncs): |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 91 | l = [(availfuncs[funcaddr].total_calls |
| 92 | , availfuncs[funcaddr].funcname, funcaddr) |
Kevin O'Connor | f59b5ac | 2010-05-01 11:03:10 -0400 | [diff] [blame] | 93 | for funcaddr in funcaddrs if funcaddr in availfuncs] |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 94 | l.sort() |
| 95 | l.reverse() |
| 96 | out = [] |
| 97 | while l: |
Kevin O'Connor | f59b5ac | 2010-05-01 11:03:10 -0400 | [diff] [blame] | 98 | count, name, funcaddr = l.pop(0) |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 99 | info = availfuncs.get(funcaddr) |
| 100 | if info is None: |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 101 | continue |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 102 | calladdrs = [calls[1] for calls in info.called_funcs] |
Kevin O'Connor | f59b5ac | 2010-05-01 11:03:10 -0400 | [diff] [blame] | 103 | del availfuncs[funcaddr] |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 104 | out = out + orderfuncs(calladdrs, availfuncs) + [info] |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 105 | return out |
| 106 | |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 107 | hex_s = r'[0-9a-f]+' |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 108 | 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] | 109 | re_asm = re.compile( |
Kevin O'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 110 | r'^[ ]*(?P<insnaddr>' + hex_s |
| 111 | + r'):\t.*\t(addr32 )?(?P<insn>.+?)[ ]*((?P<calladdr>' + hex_s |
| 112 | + r') <(?P<ref>.*)>)?$') |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 113 | re_usestack = re.compile( |
Kevin O'Connor | 0b04b78 | 2009-06-10 21:40:26 -0400 | [diff] [blame] | 114 | 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] | 115 | |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 116 | def main(): |
| 117 | unknownfunc = function(None, "<unknown>") |
| 118 | indirectfunc = function(-1, '<indirect>') |
| 119 | unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0 |
Kevin O'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 120 | unknownfunc.max_yield_usage = indirectfunc.max_yield_usage = -1 |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 121 | funcs = {-1: indirectfunc} |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 122 | 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'Connor | 95cde27 | 2008-07-13 10:59:11 -0400 | [diff] [blame] | 131 | funcaddr = int(m.group('funcaddr'), 16) |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 132 | funcs[funcaddr] = cur = function(funcaddr, m.group('func')) |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 133 | stackusage = 0 |
| 134 | atstart = 1 |
| 135 | continue |
| 136 | m = re_asm.match(line) |
Kevin O'Connor | 945313c | 2015-04-17 12:33:58 -0400 | [diff] [blame] | 137 | if m is None: |
| 138 | #print("other", repr(line)) |
| 139 | continue |
| 140 | insn = m.group('insn') |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 141 | |
Kevin O'Connor | 945313c | 2015-04-17 12:33:58 -0400 | [diff] [blame] | 142 | 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'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 151 | |
Kevin O'Connor | 945313c | 2015-04-17 12:33:58 -0400 | [diff] [blame] | 152 | if atstart: |
| 153 | if '%esp' in insn or insn.startswith('leal'): |
| 154 | # Still part of initial header |
| 155 | continue |
Kevin O'Connor | 996a8a9 | 2016-08-10 10:52:12 -0400 | [diff] [blame] | 156 | 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'Connor | 945313c | 2015-04-17 12:33:58 -0400 | [diff] [blame] | 161 | cur.basic_stack_usage = stackusage |
| 162 | atstart = 0 |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 163 | |
Kevin O'Connor | 945313c | 2015-04-17 12:33:58 -0400 | [diff] [blame] | 164 | 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'Connor | f5cb079 | 2008-06-07 10:11:36 -0400 | [diff] [blame] | 175 | else: |
Kevin O'Connor | 945313c | 2015-04-17 12:33:58 -0400 | [diff] [blame] | 176 | # 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'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 197 | |
| 198 | # Calculate maxstackusage |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 199 | for info in funcs.values(): |
| 200 | calcmaxstack(info, funcs) |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 201 | |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 202 | # Sort functions for output |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 203 | funcinfos = orderfuncs(funcs.keys(), funcs.copy()) |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 204 | |
| 205 | # Show all functions |
Johannes Krampf | 064fd06 | 2014-01-12 11:14:54 -0500 | [diff] [blame] | 206 | print(OUTPUTDESC) |
Kevin O'Connor | 1317617 | 2015-03-19 12:43:29 -0400 | [diff] [blame] | 207 | for info in funcinfos: |
Kevin O'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 208 | if info.max_stack_usage == 0 and info.max_yield_usage < 0: |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 209 | continue |
Kevin O'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 210 | yieldstr = "" |
Kevin O'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 211 | if info.max_yield_usage >= 0: |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 212 | 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'Connor | a979c1c | 2010-03-09 20:01:35 -0500 | [diff] [blame] | 217 | yieldstr = "" |
Kevin O'Connor | d304b59 | 2015-03-19 12:10:28 -0400 | [diff] [blame] | 218 | if callinfo.max_yield_usage >= 0: |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 219 | yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage) |
Johannes Krampf | 064fd06 | 2014-01-12 11:14:54 -0500 | [diff] [blame] | 220 | print(" %04s:%-40s [%d+%d,%d%s]" % ( |
Kevin O'Connor | b5d6c1e | 2015-03-18 23:31:43 -0400 | [diff] [blame] | 221 | insnaddr, callinfo.funcname, stackusage |
| 222 | , callinfo.basic_stack_usage |
| 223 | , stackusage+callinfo.max_stack_usage, yieldstr)) |
Kevin O'Connor | 5c4a8c6 | 2008-05-12 23:50:16 -0400 | [diff] [blame] | 224 | |
| 225 | if __name__ == '__main__': |
| 226 | main() |