#!/usr/bin/python # stack_size - a program to parse ARM assembly files and produce # a listing of stack sizes for the functions encountered. # # To use: compile your kernel as normal, then run this program on vmlinux: # $ scripts/stac_size vmlinux # # If you have cross-compiled, then your CROSS_COMPILE environment variable # should be set to the cross prefix for your toolchain # # Author: Tim Bird # Copyright 2011 Sony Network Entertainment, Inc # # GPL version 2.0 applies. # import os, sys version = "1.0" debug = 0 def dprint(msg): global debug if debug: print msg def usage(): print """Usage: %s [options] Show stack size for functions in a program or object file. Generate and parse the ARM assembly for the indicated filename, and print out the size of the stack for each function. If you used a cross-compiler, please set the cross-compiler prefix in the environment variable 'CROSS_COMPILE', before using this tool. Options: -h show this usage help -t show the top n functions with the largest stacks (default is to show top 10 functions with largest stacks) -f show full function list -g show histogram of stack depths -V or --version show version information and exit Notes: This leaves the assembly file in the same directory as the source filename, called '.S' This program currently only supports ARM EABI. """ % os.path.basename(sys.argv[0]) sys.exit(0) class function_class(): def __init__(self, name, addr): self.name = name self.addr = addr self.depth = 0 def __repr__(self): return "function: %s with depth %d" % (self.name, self.depth) def open_assembly(filename): base = filename if base.endswith(".o"): base = base[:-2] assembly_filename = "%s.S" % base try: fd = open(assembly_filename) except: print "Could not find %s - trying to generate from %s\n" % \ (assembly_filename, filename) print "This might take a few minutes..." try: cross = os.environ["CROSS_COMPILE"] except: cross = "" cmd = "%sobjdump -d %s >%s" % \ (cross, filename, assembly_filename) print "Using command: '%s'" % cmd os.system(cmd) try: fd = open(assembly_filename) except: print "Could not find %s, and conversion didn't work\n" % \ (assembly_filename) sys.exit(1) return fd; def do_show_histogram(functions): depth_count = {} max_depth_bucket = 0 for funcname in functions.keys(): depth = functions[funcname].depth depth_bucket = depth/10 if depth_bucket > max_depth_bucket: max_depth_bucket = depth_bucket try: depth_count[depth_bucket] += 1 except: depth_count[depth_bucket] = 1 print "=========== HISTOGRAM ==============" for i in range(0,max_depth_bucket+1): try: count = depth_count[i] except: count = 0 # use numbers for bars that are too long if count<72: bar = count*"*" else: bar = (72-8)*"*" + "(%d)*" % count print "%3d: %s" % (i*10,bar) def cmp_depth(f1, f2): return cmp(f1.depth, f2.depth) def main(): global debug full_list = 0 show_histogram = 0 debug = 0 top_count = 10 if "-h" in sys.argv: usage() if "-f" in sys.argv: full_list = 1 sys.argv.remove("-f") if "-g" in sys.argv: show_histogram = 1 sys.argv.remove("-g") if "-t" in sys.argv: top_count = sys.argv[sys.argv.index("-t")+1] sys.argv.remove(top_count) sys.argv.remove("-t") top_count = int(top_count) if "--debug" in sys.argv: debug = 1 sys.argv.remove("--debug") if "--version" in sys.argv or "-V" in sys.argv: print "%s version %s" % (os.path.basename(sys.argv[0]),version) sys.exit(0) try: filename = sys.argv[1] except: print "Error: missing filename to scan" usage() fd = open_assembly(filename) functions = {} func = None max_cur_depth = 0 depth = 0 func_line_count = 0 PROGRAM_ENTRY_MAXLINES = 4 for line in fd.xreadlines(): # parse lines # find function starts try: if line[8:10]==" <": func_line = line (func_addr_s,rest) = func_line.split(" ",1) funcname = rest[1:-3] #print line, func_addr = int(func_addr_s, 16) #print "func_addr=%x, func=%s" % (func_addr, func) # record depth of last function if func: func.depth = max_cur_depth dprint("stack depth: %d" % max_cur_depth) # start new function dprint("function: %s" % funcname) func = function_class(funcname, func_addr) functions[funcname] = func depth = 0 max_cur_depth = 0 func_line_count = 0 except: pass func_line_count += 1 # calculate stack depth # pattern is: initial register push (with "push {...} # reservation of local stack space if line.find("push\t") != -1 and \ func_line_countmax_cur_depth: max_cur_depth = depth dprint("depth=%d" % depth) if line.find("sub\tsp, sp, #") != -1 and \ func_line_countmax_cur_depth: max_cur_depth = depth dprint("depth=%d" % depth) # disable check for pops (used for debugging) if None and line.find("pop\t") != -1: # parse pop (before, args) = line.split("pop",1) dprint("pop args=%s" % args) regs = line.split(",") depth -= 4 * len(regs) if depth<0: print "Parser Error: function: %s, depth=%d" % \ (funcname, depth) dprint("depth=%d" % depth) # record depth of last function if func: func.depth = max_cur_depth dprint("max stack depth: %d" % max_cur_depth) fd.close() if not functions: print "No functions found. Done." return # calculate results max_depth = 0 maxfunc = func for func in functions.values(): if func.depth > max_depth: max_depth = func.depth maxfunc = func print "============ RESULTS ===============" print "number of functions = %d" % len(functions) print "max function stack depth= %d" % maxfunc.depth print "function with max depth = %s\n" % maxfunc.name if full_list or top_count: funclist = functions.values() funclist.sort(cmp_depth) print "%-32s %s" % ("Function Name ","Stack Depth") print "%-32s %s" % ("=====================","===========") if full_list: top_count = len(funclist) start = len(funclist)-top_count for i in range(start, start+top_count): func = funclist[i] print "%-32s %4d" % (func.name, func.depth) if show_histogram: do_show_histogram(functions) if __name__=="__main__": main()