mirror of
https://github.com/Klipper3d/klipper.git
synced 2025-07-15 02:37:52 -06:00
Initial commit of source code.
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
37a91e9c10
commit
f582a36e4d
71 changed files with 9950 additions and 0 deletions
212
scripts/avrsim.py
Executable file
212
scripts/avrsim.py
Executable file
|
@ -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()
|
313
scripts/buildcommands.py
Normal file
313
scripts/buildcommands.py
Normal file
|
@ -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()
|
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()
|
Loading…
Add table
Add a link
Reference in a new issue