|
|
1.1 root 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 <[email protected]>
6: #
7: # This file may be distributed under the terms of the GNU GPLv3 license.
8:
9: # Usage:
1.1.1.2 ! root 10: # objdump -m i386 -M i8086 -M suffix -d out/rom16.o | tools/checkstack.py
1.1 root 11:
12: import sys
13: import re
14:
1.1.1.2 ! root 15: # Functions that change stacks
! 16: STACKHOP = ['__send_disk_op']
1.1 root 17: # List of functions we can assume are never called.
1.1.1.2 ! root 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: """
1.1 root 28:
29: # Find out maximum stack usage for a function
30: def calcmaxstack(funcs, funcaddr):
31: info = funcs[funcaddr]
32: # Find max of all nested calls.
1.1.1.2 ! root 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]:
! 43: callinfo = funcs.get(calladdr)
! 44: if callinfo is None:
! 45: continue
1.1 root 46: if callinfo[2] is None:
47: calcmaxstack(funcs, calladdr)
1.1.1.2 ! root 48: if callinfo[0] not in seenbefore:
! 49: seenbefore[callinfo[0]] = 1
! 50: totcalls += 1 + callinfo[5]
! 51: funcnameroot = callinfo[0].split('.')[0]
! 52: if funcnameroot in IGNORE:
1.1 root 53: # This called function is ignored - don't contribute it to
54: # the max stack.
55: continue
1.1.1.2 ! root 56: if funcnameroot in STACKHOP:
! 57: if usage > maxusage:
! 58: maxusage = usage
! 59: if callinfo[4] is not None:
! 60: doesyield = 1
! 61: if usage > maxyieldusage:
! 62: maxyieldusage = usage
! 63: continue
1.1 root 64: totusage = usage + callinfo[2]
1.1.1.2 ! root 65: if totusage > maxusage:
! 66: maxusage = totusage
! 67: if callinfo[4] is not None:
! 68: doesyield = 1
! 69: totyieldusage = usage + callinfo[4]
! 70: if totyieldusage > maxyieldusage:
! 71: maxyieldusage = totyieldusage
! 72: info[2] = maxusage
! 73: if doesyield:
! 74: info[4] = maxyieldusage
! 75: info[5] = totcalls
! 76:
! 77: # Try to arrange output so that functions that call each other are
! 78: # near each other.
! 79: def orderfuncs(funcaddrs, availfuncs):
! 80: l = [(availfuncs[funcaddr][5], availfuncs[funcaddr][0], funcaddr)
! 81: for funcaddr in funcaddrs if funcaddr in availfuncs]
! 82: l.sort()
! 83: l.reverse()
! 84: out = []
! 85: while l:
! 86: count, name, funcaddr = l.pop(0)
! 87: if funcaddr not in availfuncs:
! 88: continue
! 89: calladdrs = [calls[1] for calls in availfuncs[funcaddr][6]]
! 90: del availfuncs[funcaddr]
! 91: out = out + orderfuncs(calladdrs, availfuncs) + [funcaddr]
! 92: return out
! 93:
! 94: # Update function info with a found "yield" point.
! 95: def noteYield(info, stackusage):
! 96: prevyield = info[3]
! 97: if prevyield is None or prevyield < stackusage:
! 98: info[3] = stackusage
! 99:
! 100: # Update function info with a found "call" point.
! 101: def noteCall(info, subfuncs, insnaddr, calladdr, stackusage):
! 102: if (calladdr, stackusage) in subfuncs:
! 103: # Already noted a nearly identical call - ignore this one.
! 104: return
! 105: info[6].append((insnaddr, calladdr, stackusage))
! 106: subfuncs[(calladdr, stackusage)] = 1
1.1 root 107:
108: hex_s = r'[0-9a-f]+'
109: re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
110: re_asm = re.compile(
111: r'^[ ]*(?P<insnaddr>' + hex_s
112: + r'):\t.*\t(addr32 )?(?P<insn>.+?)[ ]*((?P<calladdr>' + hex_s
113: + r') <(?P<ref>.*)>)?$')
114: re_usestack = re.compile(
115: r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$')
116:
117: def calc():
118: # funcs[funcaddr] = [funcname, basicstackusage, maxstackusage
1.1.1.2 ! root 119: # , yieldusage, maxyieldusage, totalcalls
! 120: # , [(insnaddr, calladdr, stackusage), ...]]
! 121: funcs = {-1: ['<indirect>', 0, 0, None, None, 0, []]}
1.1 root 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
131: funcaddr = int(m.group('funcaddr'), 16)
1.1.1.2 ! root 132: funcs[funcaddr] = cur = [m.group('func'), 0, None, None, None, 0, []]
1.1 root 133: stackusage = 0
134: atstart = 1
135: subfuncs = {}
136: continue
137: m = re_asm.match(line)
138: if m is not None:
139: insn = m.group('insn')
140:
141: im = re_usestack.match(insn)
142: if im is not None:
143: if insn[:5] == 'pushl' or insn[:6] == 'pushfl':
144: stackusage += 4
145: continue
146: elif insn[:5] == 'pushw' or insn[:6] == 'pushfw':
147: stackusage += 2
148: continue
149: stackusage += int(im.group('num'), 16)
150:
151: if atstart:
152: if insn == 'movl %esp,%ebp':
153: # Still part of initial header
154: continue
155: cur[1] = stackusage
156: atstart = 0
157:
1.1.1.2 ! root 158: insnaddr = m.group('insnaddr')
1.1 root 159: calladdr = m.group('calladdr')
160: if calladdr is None:
161: if insn[:6] == 'lcallw':
1.1.1.2 ! root 162: noteCall(cur, subfuncs, insnaddr, -1, stackusage + 4)
! 163: noteYield(cur, stackusage + 4)
! 164: elif insn[:3] == 'int':
! 165: noteCall(cur, subfuncs, insnaddr, -1, stackusage + 6)
! 166: noteYield(cur, stackusage + 6)
! 167: elif insn[:3] == 'sti':
! 168: noteYield(cur, stackusage)
1.1 root 169: else:
1.1.1.2 ! root 170: # misc instruction
1.1 root 171: continue
172: else:
173: # Jump or call insn
174: calladdr = int(calladdr, 16)
175: ref = m.group('ref')
176: if '+' in ref:
1.1.1.2 ! root 177: # Inter-function jump.
! 178: pass
! 179: elif insn[:1] == 'j':
1.1 root 180: # Tail call
1.1.1.2 ! root 181: noteCall(cur, subfuncs, insnaddr, calladdr, 0)
1.1 root 182: elif insn[:5] == 'calll':
1.1.1.2 ! root 183: noteCall(cur, subfuncs, insnaddr, calladdr, stackusage + 4)
1.1 root 184: else:
185: print "unknown call", ref
1.1.1.2 ! root 186: noteCall(cur, subfuncs, insnaddr, calladdr, stackusage)
1.1 root 187: # Reset stack usage to preamble usage
188: stackusage = cur[1]
189:
190: #print "other", repr(line)
191:
192: # Calculate maxstackusage
193: for funcaddr, info in funcs.items():
194: if info[2] is not None:
195: continue
196: calcmaxstack(funcs, funcaddr)
197:
1.1.1.2 ! root 198: # Sort functions for output
! 199: funcaddrs = orderfuncs(funcs.keys(), funcs.copy())
! 200:
1.1 root 201: # Show all functions
1.1.1.2 ! root 202: print OUTPUTDESC
! 203: for funcaddr in funcaddrs:
! 204: name, basicusage, maxusage, yieldusage, maxyieldusage, count, calls = \
! 205: funcs[funcaddr]
! 206: if maxusage == 0 and maxyieldusage is None:
1.1 root 207: continue
1.1.1.2 ! root 208: yieldstr = ""
! 209: if maxyieldusage is not None:
! 210: yieldstr = ",%d" % maxyieldusage
! 211: print "\n%s[%d,%d%s]:" % (name, basicusage, maxusage, yieldstr)
1.1 root 212: for insnaddr, calladdr, stackusage in calls:
1.1.1.2 ! root 213: callinfo = funcs.get(calladdr, ("<unknown>", 0, 0, 0, None))
! 214: yieldstr = ""
! 215: if callinfo[4] is not None:
! 216: yieldstr = ",%d" % (stackusage + callinfo[4])
! 217: print " %04s:%-40s [%d+%d,%d%s]" % (
1.1 root 218: insnaddr, callinfo[0], stackusage, callinfo[1]
1.1.1.2 ! root 219: , stackusage+callinfo[2], yieldstr)
1.1 root 220:
221: def main():
222: calc()
223:
224: if __name__ == '__main__':
225: main()
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.