aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/avrsim.py212
-rw-r--r--scripts/buildcommands.py313
-rwxr-xr-xscripts/checkstack.py238
3 files changed, 763 insertions, 0 deletions
diff --git a/scripts/avrsim.py b/scripts/avrsim.py
new file mode 100755
index 00000000..bbcc20b7
--- /dev/null
+++ b/scripts/avrsim.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python
+# Script to interact with simulavr by simulating a serial port.
+#
+# Copyright (C) 2015 Kevin O'Connor <kevin@koconnor.net>
+#
+# This file may be distributed under the terms of the GNU GPLv3 license.
+
+import sys, optparse, os, pty, select, fcntl, termios, traceback, errno
+import pysimulavr
+
+SERIALBITS = 10 # 8N1 = 1 start, 8 data, 1 stop
+
+# Class to read serial data from AVR serial transmit pin.
+class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
+ def __init__(self, baud):
+ pysimulavr.Pin.__init__(self)
+ pysimulavr.PySimulationMember.__init__(self)
+ self.sc = pysimulavr.SystemClock.Instance()
+ self.delay = 10**9 / baud
+ self.current = 0
+ self.pos = -1
+ self.queue = ""
+ def SetInState(self, pin):
+ pysimulavr.Pin.SetInState(self, pin)
+ self.state = pin.outState
+ if self.pos < 0 and pin.outState == pin.LOW:
+ self.pos = 0
+ self.sc.Add(self)
+ def DoStep(self, trueHwStep):
+ ishigh = self.state == self.HIGH
+ self.current |= ishigh << self.pos
+ self.pos += 1
+ if self.pos == 1:
+ return int(self.delay * 1.5)
+ if self.pos >= SERIALBITS:
+ self.queue += chr((self.current >> 1) & 0xff)
+ self.pos = -1
+ self.current = 0
+ return -1
+ return self.delay
+ def popChars(self):
+ d = self.queue
+ self.queue = ""
+ return d
+
+# Class to send serial data to AVR serial receive pin.
+class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
+ MAX_QUEUE = 64
+ def __init__(self, baud):
+ pysimulavr.Pin.__init__(self)
+ pysimulavr.PySimulationMember.__init__(self)
+ self.SetPin('H')
+ self.sc = pysimulavr.SystemClock.Instance()
+ self.delay = 10**9 / baud
+ self.current = 0
+ self.pos = 0
+ self.queue = ""
+ def DoStep(self, trueHwStep):
+ if not self.pos:
+ if not self.queue:
+ return -1
+ self.current = (ord(self.queue[0]) << 1) | 0x200
+ self.queue = self.queue[1:]
+ newstate = 'L'
+ if self.current & (1 << self.pos):
+ newstate = 'H'
+ self.SetPin(newstate)
+ self.pos += 1
+ if self.pos >= SERIALBITS:
+ self.pos = 0
+ return self.delay
+ def needChars(self):
+ if len(self.queue) > self.MAX_QUEUE / 2:
+ return 0
+ return self.MAX_QUEUE - len(self.queue)
+ def pushChars(self, c):
+ queueEmpty = not self.queue
+ self.queue += c
+ if queueEmpty:
+ self.sc.Add(self)
+
+# Support for creating VCD trace files
+class Tracing:
+ def __init__(self, filename, signals):
+ self.filename = filename
+ self.signals = signals
+ if not signals:
+ self.dman = None
+ return
+ self.dman = pysimulavr.DumpManager.Instance()
+ self.dman.SetSingleDeviceApp()
+ def show_help(self):
+ ostr = pysimulavr.ostringstream()
+ self.dman.save(ostr)
+ sys.stdout.write(ostr.str())
+ sys.exit(1)
+ def load_options(self):
+ if self.dman is None:
+ return
+ if self.signals.strip() == '?':
+ self.show_help()
+ sigs = "\n".join(["+ " + s for s in self.signals.split(',')])
+ self.dman.addDumpVCD(self.filename, sigs, "ns", False, False)
+ def start(self):
+ if self.dman is not None:
+ self.dman.start()
+ def finish(self):
+ if self.dman is not None:
+ self.dman.stopApplication()
+
+# Support for creating a pseudo-tty for emulating a serial port
+def create_pty(ptyname):
+ mfd, sfd = pty.openpty()
+ try:
+ os.unlink(ptyname)
+ except os.error:
+ pass
+ os.symlink(os.ttyname(sfd), ptyname)
+ fcntl.fcntl(mfd, fcntl.F_SETFL
+ , fcntl.fcntl(mfd, fcntl.F_GETFL) | os.O_NONBLOCK)
+ old = termios.tcgetattr(mfd)
+ old[3] = old[3] & ~termios.ECHO
+ termios.tcsetattr(mfd, termios.TCSADRAIN, old)
+ return mfd
+
+def main():
+ usage = "%prog [options] <program.elf>"
+ opts = optparse.OptionParser(usage)
+ opts.add_option("-m", "--machine", type="string", dest="machine",
+ default="atmega644", help="type of AVR machine to simulate")
+ opts.add_option("-s", "--speed", type="int", dest="speed", default=8000000,
+ help="machine speed")
+ opts.add_option("-b", "--baud", type="int", dest="baud", default=38400,
+ help="baud rate of the emulated serial port")
+ opts.add_option("-t", "--trace", type="string", dest="trace",
+ help="signals to trace (? for help)")
+ opts.add_option("-p", "--port", type="string", dest="port",
+ default="/tmp/pseudoserial",
+ help="pseudo-tty device to create for serial port")
+ deffile = os.path.splitext(os.path.basename(sys.argv[0]))[0] + ".vcd"
+ opts.add_option("-f", "--tracefile", type="string", dest="tracefile",
+ default=deffile, help="filename to write signal trace to")
+ options, args = opts.parse_args()
+ if len(args) != 1:
+ opts.error("Incorrect number of arguments")
+ elffile = args[0]
+ proc = options.machine
+ ptyname = options.port
+ speed = options.speed
+ baud = options.baud
+
+ # launch simulator
+ sc = pysimulavr.SystemClock.Instance()
+ trace = Tracing(options.tracefile, options.trace)
+ dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
+ dev.Load(elffile)
+ dev.SetClockFreq(10**9 / speed)
+ sc.Add(dev)
+ trace.load_options()
+
+ # Setup rx pin
+ rxpin = SerialRxPin(baud)
+ net = pysimulavr.Net()
+ net.Add(rxpin)
+ net.Add(dev.GetPin("D1"))
+
+ # Setup tx pin
+ txpin = SerialTxPin(baud)
+ net2 = pysimulavr.Net()
+ net2.Add(dev.GetPin("D0"))
+ net2.Add(txpin)
+
+ # Display start banner
+ msg = "Starting AVR simulation: machine=%s speed=%d\n" % (proc, speed)
+ msg += "Serial: port=%s baud=%d\n" % (ptyname, baud)
+ if options.trace:
+ msg += "Trace file: %s\n" % (options.tracefile,)
+ sys.stdout.write(msg)
+ sys.stdout.flush()
+
+ # Create terminal device
+ fd = create_pty(ptyname)
+
+ # Run loop
+ try:
+ trace.start()
+ while 1:
+ starttime = sc.GetCurrentTime()
+ r = sc.RunTimeRange(speed/1000)
+ endtime = sc.GetCurrentTime()
+ if starttime == endtime:
+ break
+ d = rxpin.popChars()
+ if d:
+ os.write(fd, d)
+ txsize = txpin.needChars()
+ if txsize:
+ res = select.select([fd], [], [], 0)
+ if res[0]:
+ try:
+ d = os.read(fd, txsize)
+ except os.error, e:
+ if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
+ continue
+ break
+ txpin.pushChars(d)
+ trace.finish()
+ finally:
+ os.unlink(ptyname)
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py
new file mode 100644
index 00000000..8b9cc9d8
--- /dev/null
+++ b/scripts/buildcommands.py
@@ -0,0 +1,313 @@
+#!/usr/bin/env python
+# Script to handle build time requests embedded in C code.
+#
+# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+#
+# This file may be distributed under the terms of the GNU GPLv3 license.
+
+import sys, os, subprocess, optparse, logging, shlex, socket, time
+import json, zlib
+sys.path.append('./klippy')
+import msgproto
+
+FILEHEADER = """
+/* DO NOT EDIT! This is an autogenerated file. See scripts/buildcommands.py. */
+
+#include "board/pgm.h"
+#include "command.h"
+"""
+
+def error(msg):
+ sys.stderr.write(msg + "\n")
+ sys.exit(-1)
+
+# Parser for constants in simple C header files.
+def scan_config(file):
+ f = open(file, 'r')
+ opts = {}
+ for l in f.readlines():
+ parts = l.split()
+ if len(parts) != 3:
+ continue
+ if parts[0] != '#define':
+ continue
+ value = parts[2]
+ if value.isdigit() or (value.startswith('0x') and value[2:].isdigit()):
+ value = int(value, 0)
+ elif value.startswith('"'):
+ value = value[1:-1]
+ opts[parts[1]] = value
+ f.close()
+ return opts
+
+
+######################################################################
+# Command and output parser generation
+######################################################################
+
+def build_parser(parser, iscmd, all_param_types):
+ if parser.name == "#empty":
+ return "\n // Empty message\n .max_size=0,"
+ if parser.name == "#output":
+ comment = "Output: " + parser.msgformat
+ else:
+ comment = parser.msgformat
+ params = '0'
+ types = tuple([t.__class__.__name__ for t in parser.param_types])
+ if types:
+ paramid = all_param_types.get(types)
+ if paramid is None:
+ paramid = len(all_param_types)
+ all_param_types[types] = paramid
+ params = 'command_parameters%d' % (paramid,)
+ out = """
+ // %s
+ .msg_id=%d,
+ .num_params=%d,
+ .param_types = %s,
+""" % (comment, parser.msgid, len(types), params)
+ if iscmd:
+ num_args = (len(types) + types.count('PT_progmem_buffer')
+ + types.count('PT_buffer'))
+ out += " .num_args=%d," % (num_args,)
+ else:
+ max_size = min(msgproto.MESSAGE_MAX
+ , 1 + sum([t.max_length for t in parser.param_types]))
+ out += " .max_size=%d," % (max_size,)
+ return out
+
+def build_parsers(parsers, msg_to_id, all_param_types):
+ pcode = []
+ for msgname, msg in parsers:
+ msgid = msg_to_id[msg]
+ if msgname is None:
+ parser = msgproto.OutputFormat(msgid, msg)
+ else:
+ parser = msgproto.MessageFormat(msgid, msg)
+ parsercode = build_parser(parser, 0, all_param_types)
+ pcode.append("{%s\n}, " % (parsercode,))
+ fmt = """
+const struct command_encoder command_encoders[] PROGMEM = {
+%s
+};
+"""
+ return fmt % ("".join(pcode).strip(),)
+
+def build_param_types(all_param_types):
+ sorted_param_types = sorted([(i, a) for a, i in all_param_types.items()])
+ params = ['']
+ for paramid, argtypes in sorted_param_types:
+ params.append(
+ 'static const uint8_t command_parameters%d[] PROGMEM = {\n'
+ ' %s };' % (
+ paramid, ', '.join(argtypes),))
+ params.append('')
+ return "\n".join(params)
+
+def build_commands(cmd_by_id, messages_by_name, all_param_types):
+ max_cmd_msgid = max(cmd_by_id.keys())
+ index = []
+ parsers = []
+ externs = {}
+ for msgid in range(max_cmd_msgid+1):
+ if msgid not in cmd_by_id:
+ index.append(" 0,")
+ continue
+ funcname, flags, msgname = cmd_by_id[msgid]
+ msg = messages_by_name[msgname]
+ externs[funcname] = 1
+ parsername = 'parser_%s' % (funcname,)
+ index.append(" &%s," % (parsername,))
+ parser = msgproto.MessageFormat(msgid, msg)
+ parsercode = build_parser(parser, 1, all_param_types)
+ parsers.append("const struct command_parser %s PROGMEM = {"
+ " %s\n .flags=%s,\n .func=%s\n};" % (
+ parsername, parsercode, flags, funcname))
+ index = "\n".join(index)
+ externs = "\n".join(["extern void "+funcname+"(uint32_t*);"
+ for funcname in sorted(externs)])
+ fmt = """
+%s
+
+%s
+
+const struct command_parser * const command_index[] PROGMEM = {
+%s
+};
+
+const uint8_t command_index_size PROGMEM = ARRAY_SIZE(command_index);
+"""
+ return fmt % (externs, '\n'.join(parsers), index)
+
+
+######################################################################
+# Identify data dictionary generation
+######################################################################
+
+def build_identify(cmd_by_id, msg_to_id, responses, static_strings
+ , config, version):
+ #commands, messages, static_strings
+ messages = dict((msgid, msg) for msg, msgid in msg_to_id.items())
+ data = {}
+ data['messages'] = messages
+ data['commands'] = sorted(cmd_by_id.keys())
+ data['responses'] = sorted(responses)
+ data['static_strings'] = static_strings
+ configlist = ['MCU', 'CLOCK_FREQ']
+ data['config'] = dict((i, config['CONFIG_'+i]) for i in configlist
+ if 'CONFIG_'+i in config)
+ data['version'] = version
+
+ # Format compressed info into C code
+ data = json.dumps(data)
+ zdata = zlib.compress(data, 9)
+ out = []
+ for i in range(len(zdata)):
+ if i % 8 == 0:
+ out.append('\n ')
+ out.append(" 0x%02x," % (ord(zdata[i]),))
+ fmt = """
+const uint8_t command_identify_data[] PROGMEM = {%s
+};
+
+// Identify size = %d (%d uncompressed)
+const uint32_t command_identify_size PROGMEM = ARRAY_SIZE(command_identify_data);
+"""
+ return fmt % (''.join(out), len(zdata), len(data))
+
+
+######################################################################
+# Version generation
+######################################################################
+
+# Run program and return the specified output
+def check_output(prog):
+ logging.debug("Running %s" % (repr(prog),))
+ try:
+ process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE)
+ output = process.communicate()[0]
+ retcode = process.poll()
+ except OSError:
+ logging.debug("Exception on run: %s" % (traceback.format_exc(),))
+ return ""
+ logging.debug("Got (code=%s): %s" % (retcode, repr(output)))
+ if retcode:
+ return ""
+ try:
+ return output.decode()
+ except UnicodeError:
+ logging.debug("Exception on decode: %s" % (traceback.format_exc(),))
+ return ""
+
+# Obtain version info from "git" program
+def git_version():
+ if not os.path.exists('.git'):
+ logging.debug("No '.git' file/directory found")
+ return ""
+ ver = check_output("git describe --tags --long --dirty").strip()
+ logging.debug("Got git version: %s" % (repr(ver),))
+ return ver
+
+def build_version(extra):
+ version = git_version()
+ if not version:
+ version = "?"
+ btime = time.strftime("%Y%m%d_%H%M%S")
+ hostname = socket.gethostname()
+ version = "%s-%s-%s%s" % (version, btime, hostname, extra)
+ return version
+
+
+######################################################################
+# Main code
+######################################################################
+
+def main():
+ usage = "%prog [options] <cmd section file> <autoconf.h> <output.c>"
+ opts = optparse.OptionParser(usage)
+ opts.add_option("-e", "--extra", dest="extra", default="",
+ help="extra version string to append to version")
+ opts.add_option("-v", action="store_true", dest="verbose",
+ help="enable debug messages")
+
+ options, args = opts.parse_args()
+ if len(args) != 3:
+ opts.error("Incorrect arguments")
+ incmdfile, inheader, outcfile = args
+ if options.verbose:
+ logging.basicConfig(level=logging.DEBUG)
+
+ # Setup
+ commands = {}
+ messages_by_name = dict((m.split()[0], m)
+ for m in msgproto.DefaultMessages.values())
+ parsers = []
+ static_strings = []
+ # Parse request file
+ f = open(incmdfile, 'rb')
+ data = f.read()
+ f.close()
+ for req in data.split('\0'):
+ req = req.lstrip()
+ parts = req.split()
+ if not parts:
+ continue
+ cmd = parts[0]
+ msg = req[len(cmd)+1:]
+ if cmd == '_DECL_COMMAND':
+ funcname, flags, msgname = parts[1:4]
+ if msgname in commands:
+ error("Multiple definitions for command '%s'" % msgname)
+ commands[msgname] = (funcname, flags, msgname)
+ msg = req.split(None, 3)[3]
+ m = messages_by_name.get(msgname)
+ if m is not None and m != msg:
+ error("Conflicting definition for command '%s'" % msgname)
+ messages_by_name[msgname] = msg
+ elif cmd == '_DECL_PARSER':
+ if len(parts) == 1:
+ msgname = msg = "#empty"
+ else:
+ msgname = parts[1]
+ m = messages_by_name.get(msgname)
+ if m is not None and m != msg:
+ error("Conflicting definition for message '%s'" % msgname)
+ messages_by_name[msgname] = msg
+ parsers.append((msgname, msg))
+ elif cmd == '_DECL_OUTPUT':
+ parsers.append((None, msg))
+ elif cmd == '_DECL_STATIC_STR':
+ static_strings.append(req[17:])
+ else:
+ error("Unknown build time command '%s'" % cmd)
+ # Create unique ids for each message type
+ msgid = max(msgproto.DefaultMessages.keys())
+ msg_to_id = dict((m, i) for i, m in msgproto.DefaultMessages.items())
+ for msgname in commands.keys() + [m for n, m in parsers]:
+ msg = messages_by_name.get(msgname, msgname)
+ if msg not in msg_to_id:
+ msgid += 1
+ msg_to_id[msg] = msgid
+ # Create message definitions
+ all_param_types = {}
+ parsercode = build_parsers(parsers, msg_to_id, all_param_types)
+ # Create command definitions
+ cmd_by_id = dict((msg_to_id[messages_by_name.get(msgname, msgname)], cmd)
+ for msgname, cmd in commands.items())
+ cmdcode = build_commands(cmd_by_id, messages_by_name, all_param_types)
+ paramcode = build_param_types(all_param_types)
+ # Create identify information
+ config = scan_config(inheader)
+ version = build_version(options.extra)
+ sys.stdout.write("Version: %s\n" % (version,))
+ responses = [msg_to_id[msg] for msgname, msg in messages_by_name.items()
+ if msgname not in commands]
+ icode = build_identify(cmd_by_id, msg_to_id, responses, static_strings
+ , config, version)
+ # Write output
+ f = open(outcfile, 'wb')
+ f.write(FILEHEADER + paramcode + parsercode + cmdcode + icode)
+ f.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/checkstack.py b/scripts/checkstack.py
new file mode 100755
index 00000000..d4f58cf3
--- /dev/null
+++ b/scripts/checkstack.py
@@ -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()