[PATCH 2/3] ARM: Add static kernel function stack size analyzer, for ARM

Tim Bird tim.bird at am.sony.com
Tue Oct 18 19:31:50 EDT 2011


This tool can be used to analyze the function stack size for ARM kernels,
statically, based on a disassembly of vmlinux (or an individual .o file

Signed-off-by: Tim Bird <tim.bird at am.sony.com>
---
 scripts/stack_size |  289 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 289 insertions(+), 0 deletions(-)
 create mode 100755 scripts/stack_size

diff --git a/scripts/stack_size b/scripts/stack_size
new file mode 100755
index 0000000..af9fdac
--- /dev/null
+++ b/scripts/stack_size
@@ -0,0 +1,289 @@
+#!/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 <tim dot bird ~(at)~ am dot sony dot com>
+# 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] <filename>
+
+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 <n>   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 '<filename>.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_count<PROGRAM_ENTRY_MAXLINES:
+			# parse push
+			(before, args) = line.split("push",1)
+			dprint("push args=%s" % args)
+			regs = line.split(",")
+			depth += 4 * len(regs)
+			if depth>max_cur_depth:
+				max_cur_depth = depth
+
+			dprint("depth=%d" % depth)
+
+		if line.find("sub\tsp, sp, #") != -1 and \
+		    func_line_count<PROGRAM_ENTRY_MAXLINES:
+			# parse sub
+			(before, args) = line.split("sub",1)
+			if debug:
+				print "sub args=%s" % args
+			(sp1,sp2,imm) = line.split(",",2)
+			# next line splits: ' #84\t ; 0x54\n'
+			# into ['#84',';','0x54']
+			# take [0]th element, and strip leading '#'
+			#print "imm='%s'" % imm
+			depth += int(imm.split()[0][1:])
+			if depth>max_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()
-- 
1.6.6




More information about the linux-arm-kernel mailing list