checkstack: Replace function information tuple with class

Replace the six-tuple storing information on each parsed function with
a class.  This makes the code more readable.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2015-03-18 23:31:43 -04:00
parent d79855e772
commit b5d6c1e6e6
1 changed files with 77 additions and 69 deletions

View File

@ -2,7 +2,7 @@
# Script that tries to find how much stack space each function in an # Script that tries to find how much stack space each function in an
# object is using. # object is using.
# #
# Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2008-2015 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
@ -26,29 +26,53 @@ OUTPUTDESC = """
# insn_addr:called_function [u+c,t,usage_to_yield_point] # insn_addr:called_function [u+c,t,usage_to_yield_point]
""" """
class function:
def __init__(self, funcaddr, funcname):
self.funcaddr = funcaddr
self.funcname = funcname
self.basic_stack_usage = 0
self.max_stack_usage = None
self.yield_usage = None
self.max_yield_usage = None
self.total_calls = 0
# called_funcs = [(insnaddr, calladdr, stackusage), ...]
self.called_funcs = []
self.subfuncs = {}
# Update function info with a found "yield" point.
def noteYield(self, stackusage):
if self.yield_usage is None or self.yield_usage < stackusage:
self.yield_usage = stackusage
# Update function info with a found "call" point.
def noteCall(self, insnaddr, calladdr, stackusage):
if (calladdr, stackusage) in self.subfuncs:
# Already noted a nearly identical call - ignore this one.
return
self.called_funcs.append((insnaddr, calladdr, stackusage))
self.subfuncs[(calladdr, stackusage)] = 1
# Find out maximum stack usage for a function # Find out maximum stack usage for a function
def calcmaxstack(funcs, funcaddr): def calcmaxstack(funcs, funcaddr):
info = funcs[funcaddr] info = funcs[funcaddr]
# Find max of all nested calls. # Find max of all nested calls.
maxusage = info[1] maxusage = info.basic_stack_usage
maxyieldusage = doesyield = 0 maxyieldusage = doesyield = 0
if info[3] is not None: if info.yield_usage is not None:
maxyieldusage = info[3] maxyieldusage = info.yield_usage
doesyield = 1 doesyield = 1
info[2] = maxusage info.max_stack_usage = maxusage
info[4] = info[3] info.max_yield_usage = info.yield_usage
seenbefore = {} seenbefore = {}
totcalls = 0 totcalls = 0
for insnaddr, calladdr, usage in info[6]: for insnaddr, calladdr, usage in info.called_funcs:
callinfo = funcs.get(calladdr) callinfo = funcs.get(calladdr)
if callinfo is None: if callinfo is None:
continue continue
if callinfo[2] is None: if callinfo.max_stack_usage is None:
calcmaxstack(funcs, calladdr) calcmaxstack(funcs, calladdr)
if callinfo[0] not in seenbefore: if callinfo.funcname not in seenbefore:
seenbefore[callinfo[0]] = 1 seenbefore[callinfo.funcname] = 1
totcalls += 1 + callinfo[5] totcalls += 1 + callinfo.total_calls
funcnameroot = callinfo[0].split('.')[0] funcnameroot = callinfo.funcname.split('.')[0]
if funcnameroot in IGNORE: if funcnameroot in IGNORE:
# This called function is ignored - don't contribute it to # This called function is ignored - don't contribute it to
# the max stack. # the max stack.
@ -56,28 +80,29 @@ def calcmaxstack(funcs, funcaddr):
if funcnameroot in STACKHOP: if funcnameroot in STACKHOP:
if usage > maxusage: if usage > maxusage:
maxusage = usage maxusage = usage
if callinfo[4] is not None: if callinfo.max_yield_usage is not None:
doesyield = 1 doesyield = 1
if usage > maxyieldusage: if usage > maxyieldusage:
maxyieldusage = usage maxyieldusage = usage
continue continue
totusage = usage + callinfo[2] totusage = usage + callinfo.max_stack_usage
if totusage > maxusage: if totusage > maxusage:
maxusage = totusage maxusage = totusage
if callinfo[4] is not None: if callinfo.max_yield_usage is not None:
doesyield = 1 doesyield = 1
totyieldusage = usage + callinfo[4] totyieldusage = usage + callinfo.max_yield_usage
if totyieldusage > maxyieldusage: if totyieldusage > maxyieldusage:
maxyieldusage = totyieldusage maxyieldusage = totyieldusage
info[2] = maxusage info.max_stack_usage = maxusage
if doesyield: if doesyield:
info[4] = maxyieldusage info.max_yield_usage = maxyieldusage
info[5] = totcalls info.total_calls = totcalls
# Try to arrange output so that functions that call each other are # Try to arrange output so that functions that call each other are
# near each other. # near each other.
def orderfuncs(funcaddrs, availfuncs): def orderfuncs(funcaddrs, availfuncs):
l = [(availfuncs[funcaddr][5], availfuncs[funcaddr][0], funcaddr) l = [(availfuncs[funcaddr].total_calls
, availfuncs[funcaddr].funcname, funcaddr)
for funcaddr in funcaddrs if funcaddr in availfuncs] for funcaddr in funcaddrs if funcaddr in availfuncs]
l.sort() l.sort()
l.reverse() l.reverse()
@ -86,25 +111,11 @@ def orderfuncs(funcaddrs, availfuncs):
count, name, funcaddr = l.pop(0) count, name, funcaddr = l.pop(0)
if funcaddr not in availfuncs: if funcaddr not in availfuncs:
continue continue
calladdrs = [calls[1] for calls in availfuncs[funcaddr][6]] calladdrs = [calls[1] for calls in availfuncs[funcaddr].called_funcs]
del availfuncs[funcaddr] del availfuncs[funcaddr]
out = out + orderfuncs(calladdrs, availfuncs) + [funcaddr] out = out + orderfuncs(calladdrs, availfuncs) + [funcaddr]
return out return out
# Update function info with a found "yield" point.
def noteYield(info, stackusage):
prevyield = info[3]
if prevyield is None or prevyield < stackusage:
info[3] = stackusage
# Update function info with a found "call" point.
def noteCall(info, subfuncs, insnaddr, calladdr, stackusage):
if (calladdr, stackusage) in subfuncs:
# Already noted a nearly identical call - ignore this one.
return
info[6].append((insnaddr, calladdr, stackusage))
subfuncs[(calladdr, stackusage)] = 1
hex_s = r'[0-9a-f]+' hex_s = r'[0-9a-f]+'
re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$') re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
re_asm = re.compile( re_asm = re.compile(
@ -114,11 +125,11 @@ re_asm = re.compile(
re_usestack = re.compile( re_usestack = re.compile(
r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$') r'^(push[f]?[lw])|(sub.* [$](?P<num>0x' + hex_s + r'),%esp)$')
def calc(): def main():
# funcs[funcaddr] = [funcname, basicstackusage, maxstackusage unknownfunc = function(None, "<unknown>")
# , yieldusage, maxyieldusage, totalcalls indirectfunc = function(-1, '<indirect>')
# , [(insnaddr, calladdr, stackusage), ...]] unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0
funcs = {-1: ['<indirect>', 0, 0, None, None, 0, []]} funcs = {-1: indirectfunc}
cur = None cur = None
atstart = 0 atstart = 0
stackusage = 0 stackusage = 0
@ -129,10 +140,9 @@ def calc():
if m is not None: if m is not None:
# Found function # Found function
funcaddr = int(m.group('funcaddr'), 16) funcaddr = int(m.group('funcaddr'), 16)
funcs[funcaddr] = cur = [m.group('func'), 0, None, None, None, 0, []] funcs[funcaddr] = cur = function(funcaddr, m.group('func'))
stackusage = 0 stackusage = 0
atstart = 1 atstart = 1
subfuncs = {}
continue continue
m = re_asm.match(line) m = re_asm.match(line)
if m is not None: if m is not None:
@ -152,20 +162,20 @@ def calc():
if '%esp' in insn or insn.startswith('leal'): if '%esp' in insn or insn.startswith('leal'):
# Still part of initial header # Still part of initial header
continue continue
cur[1] = stackusage cur.basic_stack_usage = stackusage
atstart = 0 atstart = 0
insnaddr = m.group('insnaddr') insnaddr = m.group('insnaddr')
calladdr = m.group('calladdr') calladdr = m.group('calladdr')
if calladdr is None: if calladdr is None:
if insn.startswith('lcallw'): if insn.startswith('lcallw'):
noteCall(cur, subfuncs, insnaddr, -1, stackusage + 4) cur.noteCall(insnaddr, -1, stackusage + 4)
noteYield(cur, stackusage + 4) cur.noteYield(stackusage + 4)
elif insn.startswith('int'): elif insn.startswith('int'):
noteCall(cur, subfuncs, insnaddr, -1, stackusage + 6) cur.noteCall(insnaddr, -1, stackusage + 6)
noteYield(cur, stackusage + 6) cur.noteYield(stackusage + 6)
elif insn.startswith('sti'): elif insn.startswith('sti'):
noteYield(cur, stackusage) cur.noteYield(stackusage)
else: else:
# misc instruction # misc instruction
continue continue
@ -178,22 +188,22 @@ def calc():
pass pass
elif insn.startswith('j'): elif insn.startswith('j'):
# Tail call # Tail call
noteCall(cur, subfuncs, insnaddr, calladdr, 0) cur.noteCall(insnaddr, calladdr, 0)
elif insn.startswith('calll'): elif insn.startswith('calll'):
noteCall(cur, subfuncs, insnaddr, calladdr, stackusage + 4) cur.noteCall(insnaddr, calladdr, stackusage + 4)
elif insn.startswith('callw'): elif insn.startswith('callw'):
noteCall(cur, subfuncs, insnaddr, calladdr, stackusage + 2) cur.noteCall(insnaddr, calladdr, stackusage + 2)
else: else:
print("unknown call", ref) print("unknown call", ref)
noteCall(cur, subfuncs, insnaddr, calladdr, stackusage) cur.noteCall(insnaddr, calladdr, stackusage)
# Reset stack usage to preamble usage # Reset stack usage to preamble usage
stackusage = cur[1] stackusage = cur.basic_stack_usage
#print("other", repr(line)) #print("other", repr(line))
# Calculate maxstackusage # Calculate maxstackusage
for funcaddr, info in funcs.items(): for funcaddr, info in funcs.items():
if info[2] is not None: if info.max_stack_usage is not None:
continue continue
calcmaxstack(funcs, funcaddr) calcmaxstack(funcs, funcaddr)
@ -203,25 +213,23 @@ def calc():
# Show all functions # Show all functions
print(OUTPUTDESC) print(OUTPUTDESC)
for funcaddr in funcaddrs: for funcaddr in funcaddrs:
name, basicusage, maxusage, yieldusage, maxyieldusage, count, calls = \ info = funcs[funcaddr]
funcs[funcaddr] if info.max_stack_usage == 0 and info.max_yield_usage is None:
if maxusage == 0 and maxyieldusage is None:
continue continue
yieldstr = "" yieldstr = ""
if maxyieldusage is not None: if info.max_yield_usage is not None:
yieldstr = ",%d" % maxyieldusage yieldstr = ",%d" % info.max_yield_usage
print("\n%s[%d,%d%s]:" % (name, basicusage, maxusage, yieldstr)) print("\n%s[%d,%d%s]:" % (info.funcname, info.basic_stack_usage
for insnaddr, calladdr, stackusage in calls: , info.max_stack_usage, yieldstr))
callinfo = funcs.get(calladdr, ("<unknown>", 0, 0, 0, None)) for insnaddr, calladdr, stackusage in info.called_funcs:
callinfo = funcs.get(calladdr, unknownfunc)
yieldstr = "" yieldstr = ""
if callinfo[4] is not None: if callinfo.max_yield_usage is not None:
yieldstr = ",%d" % (stackusage + callinfo[4]) yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage)
print(" %04s:%-40s [%d+%d,%d%s]" % ( print(" %04s:%-40s [%d+%d,%d%s]" % (
insnaddr, callinfo[0], stackusage, callinfo[1] insnaddr, callinfo.funcname, stackusage
, stackusage+callinfo[2], yieldstr)) , callinfo.basic_stack_usage
, stackusage+callinfo.max_stack_usage, yieldstr))
def main():
calc()
if __name__ == '__main__': if __name__ == '__main__':
main() main()