mirror of
				https://github.com/Klipper3d/klipper.git
				synced 2025-10-30 18:05:50 +01:00 
			
		
		
		
	Initial commit of source code.
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
		
							
								
								
									
										238
									
								
								scripts/checkstack.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										238
									
								
								scripts/checkstack.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,238 @@ | ||||
| #!/usr/bin/env python | ||||
| # Script that tries to find how much stack space each function in an | ||||
| # object is using. | ||||
| # | ||||
| # Copyright (C) 2015  Kevin O'Connor <kevin@koconnor.net> | ||||
| # | ||||
| # This file may be distributed under the terms of the GNU GPLv3 license. | ||||
|  | ||||
| # Usage: | ||||
| #   avr-objdump -d out/klipper.elf | scripts/checkstack.py | ||||
|  | ||||
| import sys | ||||
| import re | ||||
|  | ||||
| # Functions that change stacks | ||||
| STACKHOP = [] | ||||
| # List of functions we can assume are never called. | ||||
| IGNORE = [] | ||||
|  | ||||
| OUTPUTDESC = """ | ||||
| #funcname1[preamble_stack_usage,max_usage_with_callers]: | ||||
| #    insn_addr:called_function [usage_at_call_point+caller_preamble,total_usage] | ||||
| # | ||||
| #funcname2[p,m,max_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 = -1 | ||||
|         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 < 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 | ||||
| def calcmaxstack(info, funcs): | ||||
|     if info.max_stack_usage is not None: | ||||
|         return | ||||
|     info.max_stack_usage = max_stack_usage = info.basic_stack_usage | ||||
|     info.max_yield_usage = max_yield_usage = info.yield_usage | ||||
|     total_calls = 0 | ||||
|     seenbefore = {} | ||||
|     # Find max of all nested calls. | ||||
|     for insnaddr, calladdr, usage in info.called_funcs: | ||||
|         callinfo = funcs.get(calladdr) | ||||
|         if callinfo is None: | ||||
|             continue | ||||
|         calcmaxstack(callinfo, funcs) | ||||
|         if callinfo.funcname not in seenbefore: | ||||
|             seenbefore[callinfo.funcname] = 1 | ||||
|             total_calls += callinfo.total_calls + 1 | ||||
|         funcnameroot = callinfo.funcname.split('.')[0] | ||||
|         if funcnameroot in IGNORE: | ||||
|             # This called function is ignored - don't contribute it to | ||||
|             # the max stack. | ||||
|             continue | ||||
|         totusage = usage + callinfo.max_stack_usage | ||||
|         totyieldusage = usage + callinfo.max_yield_usage | ||||
|         if funcnameroot in STACKHOP: | ||||
|             # Don't count children of this function | ||||
|             totusage = totyieldusage = usage | ||||
|         if totusage > max_stack_usage: | ||||
|             max_stack_usage = totusage | ||||
|         if callinfo.max_yield_usage >= 0 and totyieldusage > max_yield_usage: | ||||
|             max_yield_usage = totyieldusage | ||||
|     info.max_stack_usage = max_stack_usage | ||||
|     info.max_yield_usage = max_yield_usage | ||||
|     info.total_calls = total_calls | ||||
|  | ||||
| # Try to arrange output so that functions that call each other are | ||||
| # near each other. | ||||
| def orderfuncs(funcaddrs, availfuncs): | ||||
|     l = [(availfuncs[funcaddr].total_calls | ||||
|           , availfuncs[funcaddr].funcname, funcaddr) | ||||
|          for funcaddr in funcaddrs if funcaddr in availfuncs] | ||||
|     l.sort() | ||||
|     l.reverse() | ||||
|     out = [] | ||||
|     while l: | ||||
|         count, name, funcaddr = l.pop(0) | ||||
|         info = availfuncs.get(funcaddr) | ||||
|         if info is None: | ||||
|             continue | ||||
|         calladdrs = [calls[1] for calls in info.called_funcs] | ||||
|         del availfuncs[funcaddr] | ||||
|         out = out + orderfuncs(calladdrs, availfuncs) + [info] | ||||
|     return out | ||||
|  | ||||
| hex_s = r'[0-9a-f]+' | ||||
| re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$') | ||||
| re_asm = re.compile( | ||||
|     r'^[ ]*(?P<insnaddr>' + hex_s | ||||
|     + r'):\t[^\t]*\t(?P<insn>[^\t]+?)(?P<params>\t[^;]*)?' | ||||
|     + r'[ ]*(; (?P<calladdr>0x' + hex_s | ||||
|     + r') <(?P<ref>.*)>)?$') | ||||
|  | ||||
| def main(): | ||||
|     unknownfunc = function(None, "<unknown>") | ||||
|     indirectfunc = function(-1, '<indirect>') | ||||
|     unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0 | ||||
|     unknownfunc.max_yield_usage = indirectfunc.max_yield_usage = -1 | ||||
|     funcs = {-1: indirectfunc} | ||||
|     funcaddr = None | ||||
|     datalines = {} | ||||
|     cur = None | ||||
|     atstart = 0 | ||||
|     stackusage = 0 | ||||
|  | ||||
|     # Parse input lines | ||||
|     for line in sys.stdin.readlines(): | ||||
|         m = re_func.match(line) | ||||
|         if m is not None: | ||||
|             # Found function | ||||
|             funcaddr = int(m.group('funcaddr'), 16) | ||||
|             funcs[funcaddr] = cur = function(funcaddr, m.group('func')) | ||||
|             stackusage = 0 | ||||
|             atstart = 1 | ||||
|             continue | ||||
|         m = re_asm.match(line) | ||||
|         if m is None: | ||||
|             if funcaddr not in datalines: | ||||
|                 datalines[funcaddr] = line.split() | ||||
|             #print("other", repr(line)) | ||||
|             continue | ||||
|         insn = m.group('insn') | ||||
|  | ||||
|         if insn == 'push': | ||||
|             stackusage += 1 | ||||
|             continue | ||||
|         if insn == 'rcall' and m.group('params').strip() == '.+0': | ||||
|             stackusage += 2 | ||||
|             continue | ||||
|  | ||||
|         if atstart: | ||||
|             if insn in ['in', 'eor']: | ||||
|                 continue | ||||
|             cur.basic_stack_usage = stackusage | ||||
|             atstart = 0 | ||||
|  | ||||
|         insnaddr = m.group('insnaddr') | ||||
|         calladdr = m.group('calladdr') | ||||
|         if calladdr is None: | ||||
|             if insn == 'ijmp': | ||||
|                 # Indirect tail call | ||||
|                 cur.noteCall(insnaddr, -1, 0) | ||||
|             elif insn == 'icall': | ||||
|                 cur.noteCall(insnaddr, -1, stackusage + 2) | ||||
|             else: | ||||
|                 # misc instruction | ||||
|                 continue | ||||
|         else: | ||||
|             # Jump or call insn | ||||
|             calladdr = int(calladdr, 16) | ||||
|             ref = m.group('ref') | ||||
|             if '+' in ref: | ||||
|                 # Inter-function jump. | ||||
|                 pass | ||||
|             elif insn in ('rjmp', 'jmp', 'brne', 'brcs'): | ||||
|                 # Tail call | ||||
|                 cur.noteCall(insnaddr, calladdr, 0) | ||||
|             elif insn in ('rcall', 'call'): | ||||
|                 cur.noteCall(insnaddr, calladdr, stackusage + 2) | ||||
|             else: | ||||
|                 print("unknown call", ref) | ||||
|                 cur.noteCall(insnaddr, calladdr, stackusage) | ||||
|         # Reset stack usage to preamble usage | ||||
|         stackusage = cur.basic_stack_usage | ||||
|  | ||||
|     # Update for known indirect functions | ||||
|     funcsbyname = {} | ||||
|     for info in funcs.values(): | ||||
|         funcnameroot = info.funcname.split('.')[0] | ||||
|         funcsbyname[funcnameroot] = info | ||||
|     mainfunc = funcsbyname.get('sched_main') | ||||
|     cmdfunc = funcsbyname.get('command_task') | ||||
|     eventfunc = funcsbyname.get('__vector_13') | ||||
|     for funcnameroot, info in funcsbyname.items(): | ||||
|         if (funcnameroot.startswith('_DECL_taskfuncs_') | ||||
|             or funcnameroot.startswith('_DECL_initfuncs_') | ||||
|             or funcnameroot.startswith('_DECL_shutdownfuncs_')): | ||||
|             funcname = funcnameroot[funcnameroot.index('_', 7)+1:] | ||||
|             f = funcsbyname[funcname] | ||||
|             mainfunc.noteCall(0, f.funcaddr, mainfunc.basic_stack_usage + 2) | ||||
|         if funcnameroot.startswith('parser_'): | ||||
|             f = funcsbyname.get(funcnameroot[7:]) | ||||
|             if f is not None: | ||||
|                 numparams = int(datalines[info.funcaddr][2], 16) | ||||
|                 stackusage = cmdfunc.basic_stack_usage + 2 + numparams * 4 | ||||
|                 cmdfunc.noteCall(0, f.funcaddr, stackusage) | ||||
|         if funcnameroot.endswith('_event'): | ||||
|             eventfunc.noteCall(0, info.funcaddr, eventfunc.basic_stack_usage + 2) | ||||
|  | ||||
|     # Calculate maxstackusage | ||||
|     for info in funcs.values(): | ||||
|         calcmaxstack(info, funcs) | ||||
|  | ||||
|     # Sort functions for output | ||||
|     funcinfos = orderfuncs(funcs.keys(), funcs.copy()) | ||||
|  | ||||
|     # Show all functions | ||||
|     print(OUTPUTDESC) | ||||
|     for info in funcinfos: | ||||
|         if info.max_stack_usage == 0 and info.max_yield_usage < 0: | ||||
|             continue | ||||
|         yieldstr = "" | ||||
|         if info.max_yield_usage >= 0: | ||||
|             yieldstr = ",%d" % info.max_yield_usage | ||||
|         print("\n%s[%d,%d%s]:" % (info.funcname, info.basic_stack_usage | ||||
|                                   , info.max_stack_usage, yieldstr)) | ||||
|         for insnaddr, calladdr, stackusage in info.called_funcs: | ||||
|             callinfo = funcs.get(calladdr, unknownfunc) | ||||
|             yieldstr = "" | ||||
|             if callinfo.max_yield_usage >= 0: | ||||
|                 yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage) | ||||
|             print("    %04s:%-40s [%d+%d,%d%s]" % ( | ||||
|                 insnaddr, callinfo.funcname, stackusage | ||||
|                 , callinfo.basic_stack_usage | ||||
|                 , stackusage+callinfo.max_stack_usage, yieldstr)) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
		Reference in New Issue
	
	Block a user