Add hack to prevent checkstack.py from infinitely recursing (with bad input).
[seabios.git] / tools / checkstack.py
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:
10 #   objdump -m i386 -M i8086 -M suffix -d out/rom16.o | tools/checkstack.py
11
12 import sys
13 import re
14
15 # List of functions we can assume are never called.
16 #IGNORE = ['screenc', 'BX_PANIC', '__dprintf']
17 IGNORE = ['screenc', 'BX_PANIC']
18
19 # Find out maximum stack usage for a function
20 def calcmaxstack(funcs, func):
21     info = funcs[func]
22     if func.split('.')[0] in IGNORE:
23         # Function is hardcoded to report 0 stack usage
24         info[1] = 0
25         return
26     # Find max of all nested calls.
27     max = info[0]
28     info[1] = max
29     for addr, callfname, usage in info[2]:
30         callinfo = funcs[callfname]
31         if callinfo[1] is None:
32             calcmaxstack(funcs, callfname)
33         totusage = usage + callinfo[1]
34         if totusage > max:
35             max = totusage
36     info[1] = max
37
38 hex_s = r'[0-9a-f]+'
39 re_func = re.compile(r'^' + hex_s + r' <(?P<func>.*)>:$')
40 re_asm = re.compile(
41     r'^[ ]*(?P<addr>' + hex_s + r'):\t.*\t'
42     r'(addr32 )?(?P<insn>[a-z0-9]+ [^<]*)( <(?P<ref>.*)>)?$')
43 re_usestack = re.compile(
44     r'^(push.*)|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$')
45
46 def calc():
47     # funcs = {funcname: [basicstackusage, maxstackusage
48     #                     , [(addr, callfname, stackusage), ...]] }
49     funcs = {'<indirect>': [0, 0, []]}
50     cur = None
51     atstart = 0
52     stackusage = 0
53
54     # Parse input lines
55     for line in sys.stdin.readlines():
56         m = re_func.match(line)
57         if m is not None:
58             # Found function
59             cur = [0, None, []]
60             funcs[m.group('func')] = cur
61             stackusage = 0
62             atstart = 1
63             subfuncs = {}
64             continue
65         m = re_asm.match(line)
66         if m is not None:
67             insn = m.group('insn')
68
69             im = re_usestack.match(insn)
70             if im is not None:
71                 if insn[:5] == 'pushl' or insn[:6] == 'pushfl':
72                     stackusage += 4
73                     continue
74                 elif insn[:5] == 'pushw' or insn[:6] == 'pushfw':
75                     stackusage += 2
76                     continue
77                 stackusage += int(im.group('num'), 16)
78
79             if atstart:
80                 cur[0] = stackusage
81                 atstart = 0
82
83             ref = m.group('ref')
84             if ref is None:
85                 if insn[:6] == 'lcallw':
86                     stackusage += 4
87                     ref = '<indirect>'
88                 else:
89                     # misc instruction - just ignore
90                     continue
91             else:
92                 # Jump or call insn
93                 if '+' in ref:
94                     # Inter-function jump - reset stack usage to
95                     # preamble usage
96                     stackusage = cur[0]
97                     continue
98                 if ref.split('.')[0] in IGNORE:
99                     # Call ignored - list only for informational purposes
100                     stackusage = 0
101                 elif insn[:1] == 'j':
102                     # Tail call
103                     stackusage = 0
104                 elif insn[:5] == 'calll':
105                     stackusage += 4
106                 else:
107                     print "unknown call", ref
108             if (ref, stackusage) not in subfuncs:
109                 cur[2].append((m.group('addr'), ref, stackusage))
110                 subfuncs[(ref, stackusage)] = 1
111             # Reset stack usage to preamble usage
112             stackusage = cur[0]
113
114             continue
115
116         #print "other", repr(line)
117
118     # Calculate maxstackusage
119     for func, info in funcs.items():
120         if info[1] is not None:
121             continue
122         calcmaxstack(funcs, func)
123
124     # Show all functions
125     funcnames = funcs.keys()
126     funcnames.sort()
127     for func in funcnames:
128         basicusage, maxusage, calls = funcs[func]
129         if maxusage == 0:
130             continue
131         print "\n%s[%d,%d]:" % (func, basicusage, maxusage)
132         for addr, callfname, stackusage in calls:
133             callinfo = funcs[callfname]
134             print "    %04s:%-40s [%d+%d,%d]" % (
135                 addr, callfname, stackusage, callinfo[0], stackusage+callinfo[1])
136
137 def main():
138     calc()
139
140 if __name__ == '__main__':
141     main()