Merge branch 'MK3_3.10.1' into PFW-1271_PF-buildv20

This commit is contained in:
DRracer 2021-08-02 08:51:51 +02:00 committed by GitHub
commit fd6dbba06f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 930 additions and 2 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
.project .project
.cproject .cproject
Debug Debug
__pycache__
Firmware/Configuration_prusa.h Firmware/Configuration_prusa.h
Firmware/Doc Firmware/Doc
/Firmware/.vs/Firmware/v14 /Firmware/.vs/Firmware/v14

View File

@ -18,7 +18,7 @@ extern PGM_P sPrinterName;
// Firmware version // Firmware version
#define FW_MAJOR 3 #define FW_MAJOR 3
#define FW_MINOR 10 #define FW_MINOR 10
#define FW_REVISION 0 #define FW_REVISION 1
//#define FW_FLAVOR RC //uncomment if DEBUG, DEVEL, APLHA, BETA or RC //#define FW_FLAVOR RC //uncomment if DEBUG, DEVEL, APLHA, BETA or RC
//#define FW_FLAVERSION 1 //uncomment if FW_FLAVOR is defined and versioning is needed. //#define FW_FLAVERSION 1 //uncomment if FW_FLAVOR is defined and versioning is needed.
#ifndef FW_FLAVOR #ifndef FW_FLAVOR
@ -27,7 +27,7 @@ extern PGM_P sPrinterName;
#define FW_VERSION STR(FW_MAJOR) "." STR(FW_MINOR) "." STR(FW_REVISION) "-" STR(FW_FLAVOR) "" STR(FW_FLAVERSION) #define FW_VERSION STR(FW_MAJOR) "." STR(FW_MINOR) "." STR(FW_REVISION) "-" STR(FW_FLAVOR) "" STR(FW_FLAVERSION)
#endif #endif
#define FW_COMMIT_NR 4481 #define FW_COMMIT_NR 4587
// FW_VERSION_UNKNOWN means this is an unofficial build. // FW_VERSION_UNKNOWN means this is an unofficial build.
// The firmware should only be checked into github with this symbol. // The firmware should only be checked into github with this symbol.

54
tools/README.md Normal file
View File

@ -0,0 +1,54 @@
# Host debugging tools for Prusa MK3 firmware
## Tools
### ``dump_eeprom``
Dump the content of the entire EEPROM using the D3 command.
Requires ``printcore`` from [Pronterface].
### ``dump_sram``
Dump the content of the entire SRAM using the D2 command.
Requires ``printcore`` from [Pronterface].
### ``dump_crash``
Dump the content of the last crash dump on MK3+ printers using D21.
Requires ``printcore`` from [Pronterface].
### ``elf_mem_map``
Generate a symbol table map with decoded information starting directly from an ELF firmware with DWARF debugging information (which is the default using the stock board definition).
When used along with a memory dump obtained from the D2 g-code, show the value of each symbol which is within the address range of the dump.
When used with ``--map`` and a single elf file, generate a map consisting of memory location and source location for each statically-addressed variable.
With ``--qdirstat`` and a single elf file, generate a [qdirstat](https://github.com/shundhammer/qdirstat) compatible cache file which can be loaded to inspect memory utilization interactively in a treemap.
This assumes the running firmware generating the dump and the elf file are the same.
Requires Python3 and the [pyelftools](https://github.com/eliben/pyelftools) module.
### ``dump2bin``
Parse and decode a memory dump obtained from the D2/D21/D23 g-code into readable metadata and binary. The output binary is padded and extended to fit the original address range.
### ``xfimg2dump``
Extract a crash dump from an external flash image and output the same format produced by the D21 g-code.
### ``update_eeprom``
Given one EEPROM dump, convert the dump to update instructions that can be sent to a printer.
Given two EEPROM dumps, produces only the required instructions needed to update the contents from the first to the second. This is currently quite crude and assumes dumps are aligned (starting from the same address or same stride).
Optionally writes the instructions to the specified port (requires ``printcore`` from [Pronterface]).
### ``noreset``
Set the required TTY flags on the specified port to avoid reset-on-connect for *subsequent* requests (issuing this command might still cause the printer to reset).
[Pronterface]: https://github.com/kliment/Printrun

59
tools/dump2bin Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
import argparse
import os, sys
from lib.dump import decode_dump
def main():
# parse the arguments
ap = argparse.ArgumentParser(description="""
Parse and decode a memory dump obtained from the D2/D21/D23 g-code
into readable metadata and binary. The output binary is padded and
extended to fit the original address range.
""")
ap.add_argument('-i', dest='info', action='store_true',
help='display crash info only')
ap.add_argument('dump')
ap.add_argument('output', nargs='?')
args = ap.parse_args()
# decode the dump data
dump = decode_dump(args.dump)
if dump is None:
return os.EX_DATAERR
# output descriptors
if args.info:
o_fd = None
o_md = sys.stdout
elif args.output is None:
o_fd = sys.stdout.buffer
o_md = sys.stderr
else:
o_fd = open(args.output, 'wb')
o_md = sys.stdout
# output binary
if o_fd:
o_fd.write(dump.data)
o_fd.close()
# metadata
print(' dump type: {typ}\n'
'crash reason: {reason}\n'
' registers: {regs}\n'
' PC: {pc}\n'
' SP: {sp}\n'
' ranges: {ranges}'.format(
typ=dump.typ,
reason=dump.reason.name if dump.reason is not None else 'N/A',
regs=dump.regs,
pc='{:#x}'.format(dump.pc) if dump.pc is not None else 'N/A',
sp='{:#x}'.format(dump.sp) if dump.sp is not None else 'N/A',
ranges=str(dump.ranges)),
file=o_md)
if __name__ == '__main__':
exit(main())

17
tools/dump_crash Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
prg=$(basename "$0")
port="$1"
if [ -z "$port" -o "$port" = "-h" ]
then
echo "usage: $0 <port>" >&2
echo "Connect to <port> and dump the content of last crash using D21 to stdout" >&2
exit 1
fi
set -e
tmp=$(mktemp)
trap "rm -f \"$tmp\"" EXIT
echo D21 > "$tmp"
printcore -v "$port" "$tmp" 2>&1 | \
sed -ne '/^RECV: D21 /,/RECV: ok$/s/^RECV: //p'

17
tools/dump_eeprom Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
prg=$(basename "$0")
port="$1"
if [ -z "$port" -o "$port" = "-h" ]
then
echo "usage: $0 <port>" >&2
echo "Connect to <port> and dump the content of the EEPROM using D3 to stdout" >&2
exit 1
fi
set -e
tmp=$(mktemp)
trap "rm -f \"$tmp\"" EXIT
echo D3 > "$tmp"
printcore -v "$port" "$tmp" 2>&1 | \
sed -ne '/^RECV: D3 /,/RECV: ok$/s/^RECV: //p'

17
tools/dump_sram Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
prg=$(basename "$0")
port="$1"
if [ -z "$port" -o "$port" = "-h" ]
then
echo "usage: $0 <port>" >&2
echo "Connect to <port> and dump the content of the SRAM using D2 to stdout" >&2
exit 1
fi
set -e
tmp=$(mktemp)
trap "rm -f \"$tmp\"" EXIT
echo D2 > "$tmp"
printcore -v "$port" "$tmp" 2>&1 | \
sed -ne '/^RECV: D2 /,/RECV: ok$/s/^RECV: //p'

389
tools/elf_mem_map Executable file
View File

@ -0,0 +1,389 @@
#!/usr/bin/env python3
import argparse
import elftools.elf.elffile
import elftools.dwarf.descriptions
from collections import namedtuple
from struct import unpack
import os
from lib.dump import decode_dump
from lib.avr import *
Entry = namedtuple('Entry', ['name', 'loc', 'size', 'declpos'])
Member = namedtuple('Member', ['name', 'off', 'size'])
def array_inc(loc, dim, idx=0):
if idx == len(dim):
return True
loc[idx] += 1
if loc[idx] == dim[idx]:
loc[idx] = 0
return array_inc(loc, dim, idx+1)
return False
def get_type_size(type_DIE):
while True:
if 'DW_AT_byte_size' in type_DIE.attributes:
return type_DIE, type_DIE.attributes.get('DW_AT_byte_size').value
if 'DW_AT_type' not in type_DIE.attributes:
return None
type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
def get_type_arrsize(type_DIE):
size = get_type_size(type_DIE)
if size is None:
return None
byte_size = size[1]
if size[0].tag != 'DW_TAG_pointer_type':
array_DIE = get_type_def(type_DIE, 'DW_TAG_array_type')
if array_DIE is not None:
for range_DIE in array_DIE.iter_children():
if range_DIE.tag == 'DW_TAG_subrange_type' and \
'DW_AT_upper_bound' in range_DIE.attributes:
dim = range_DIE.attributes['DW_AT_upper_bound'].value + 1
byte_size *= dim
return byte_size
def get_type_def(type_DIE, type_tag):
while True:
if type_DIE.tag == type_tag:
return type_DIE
if 'DW_AT_type' not in type_DIE.attributes:
return None
type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
def get_FORM_block1(attr):
if attr.form != 'DW_FORM_block1':
return None
if attr.value[0] == 3: # OP_addr
return int.from_bytes(attr.value[1:], 'little')
if attr.value[0] == 35: # OP_plus_uconst (ULEB128)
v = 0
s = 0
for b in attr.value[1:]:
v |= (b & 0x7f) << s
if b & 0x80 == 0:
break
s += 7
return v
return None
def get_array_dims(DIE):
array_DIE = get_type_def(DIE, 'DW_TAG_array_type')
if array_DIE is None:
return []
array_dim = []
for range_DIE in array_DIE.iter_children():
if range_DIE.tag == 'DW_TAG_subrange_type' and \
'DW_AT_upper_bound' in range_DIE.attributes:
array_dim.append(range_DIE.attributes['DW_AT_upper_bound'].value + 1)
return array_dim
def get_struct_members(DIE, entry, expand_structs, struct_gaps):
struct_DIE = get_type_def(DIE, 'DW_TAG_structure_type')
if struct_DIE is None:
return []
members = []
for member_DIE in struct_DIE.iter_children():
if member_DIE.tag == 'DW_TAG_member' and 'DW_AT_name' in member_DIE.attributes:
m_name = member_DIE.attributes['DW_AT_name'].value.decode('ascii')
m_off = get_FORM_block1(member_DIE.attributes['DW_AT_data_member_location'])
m_byte_size = get_type_size(member_DIE)[1]
# still expand member arrays
m_array_dim = get_array_dims(member_DIE)
if m_byte_size == 1 and len(m_array_dim) > 1:
# likely string, remove one dimension
m_byte_size *= m_array_dim.pop()
if len(m_array_dim) == 0 or (len(m_array_dim) == 1 and m_array_dim[0] == 1):
# plain entry
members.append(Member(m_name, m_off, m_byte_size))
elif len(m_array_dim) == 1 and m_byte_size == 1:
# likely string, avoid expansion
members.append(Member(m_name + '[]', m_off, m_array_dim[0]))
else:
# expand array entries
m_array_pos = m_off
m_array_loc = [0] * len(m_array_dim)
while True:
# location index
sfx = ''
for d in range(len(m_array_dim)):
sfx += '[{}]'.format(str(m_array_loc[d]).rjust(len(str(m_array_dim[d]-1)), '0'))
members.append(Member(m_name + sfx, m_array_pos, m_byte_size))
# advance
if array_inc(m_array_loc, m_array_dim):
break
m_array_pos += m_byte_size
if struct_gaps and len(members):
# fill gaps in the middle
members = list(sorted(members, key=lambda x: x.off))
last_end = 0
for n in range(len(members)):
member = members[n]
if member.off > last_end:
members.append(Member('*UNKNOWN*', last_end, member.off - last_end))
last_end = member.off + member.size
if struct_gaps and len(members):
# fill gap at the end
members = list(sorted(members, key=lambda x: x.off))
last = members[-1]
last_end = last.off + last.size
if entry.size > last_end:
members.append(Member('*UNKNOWN*', last_end, entry.size - last_end))
return members
def get_elf_globals(path, expand_structs, struct_gaps=True):
fd = open(path, "rb")
if fd is None:
return
elffile = elftools.elf.elffile.ELFFile(fd)
if elffile is None or not elffile.has_dwarf_info():
return
# probably not needed, since we're decoding expressions manually
elftools.dwarf.descriptions.set_global_machine_arch(elffile.get_machine_arch())
dwarfinfo = elffile.get_dwarf_info()
grefs = []
for CU in dwarfinfo.iter_CUs():
file_entries = dwarfinfo.line_program_for_CU(CU).header["file_entry"]
for DIE in CU.iter_DIEs():
# handle only variable types
if DIE.tag != 'DW_TAG_variable':
continue
if 'DW_AT_location' not in DIE.attributes:
continue
if 'DW_AT_name' not in DIE.attributes and \
'DW_AT_abstract_origin' not in DIE.attributes:
continue
# handle locations encoded directly as DW_OP_addr (leaf globals)
loc = get_FORM_block1(DIE.attributes['DW_AT_location'])
if loc is None or loc < SRAM_OFFSET or loc >= EEPROM_OFFSET:
continue
loc -= SRAM_OFFSET
# variable name/type
if 'DW_AT_name' not in DIE.attributes and \
'DW_AT_abstract_origin' in DIE.attributes:
DIE = DIE.get_DIE_from_attribute('DW_AT_abstract_origin')
if 'DW_AT_location' in DIE.attributes:
# duplicate reference (handled directly), skip
continue
if 'DW_AT_name' not in DIE.attributes:
continue
if 'DW_AT_type' not in DIE.attributes:
continue
name = DIE.attributes['DW_AT_name'].value.decode('ascii')
# get final storage size
size = get_type_size(DIE)
if size is None:
continue
byte_size = size[1]
# location of main definition
declpos = ''
if 'DW_AT_decl_file' in DIE.attributes and \
'DW_AT_decl_line' in DIE.attributes:
line = DIE.attributes['DW_AT_decl_line'].value
fname = DIE.attributes['DW_AT_decl_file'].value
if fname and fname - 1 < len(file_entries):
fname = file_entries[fname-1].name.decode('ascii')
declpos = '{}:{}'.format(fname, line)
# fetch array dimensions (if known)
array_dim = get_array_dims(DIE)
# fetch structure members (one level only)
entry = Entry(name, loc, byte_size, declpos)
if not expand_structs or size[0].tag == 'DW_TAG_pointer_type':
members = []
else:
members = get_struct_members(DIE, entry, expand_structs, struct_gaps)
def expand_members(entry, members):
if len(members) == 0:
grefs.append(entry)
else:
for member in members:
grefs.append(Entry(entry.name + '.' + member.name,
entry.loc + member.off, member.size,
entry.declpos))
if byte_size == 1 and len(array_dim) > 1:
# likely string, remove one dimension
byte_size *= array_dim.pop()
if len(array_dim) == 0 or (len(array_dim) == 1 and array_dim[0] == 1):
# plain entry
expand_members(entry, members)
elif len(array_dim) == 1 and byte_size == 1:
# likely string, avoid expansion
grefs.append(Entry(entry.name + '[]', entry.loc,
array_dim[0], entry.declpos))
else:
# expand array entries
array_pos = loc
array_loc = [0] * len(array_dim)
while True:
# location index
sfx = ''
for d in range(len(array_dim)):
sfx += '[{}]'.format(str(array_loc[d]).rjust(len(str(array_dim[d]-1)), '0'))
expand_members(Entry(entry.name + sfx, array_pos,
byte_size, entry.declpos), members)
# advance
if array_inc(array_loc, array_dim):
break
array_pos += byte_size
return grefs
def annotate_refs(grefs, addr, data, width, gaps=True, overlaps=True):
last_end = None
for entry in grefs:
if entry.loc < addr:
continue
if entry.loc + entry.size > addr + len(data):
continue
pos = entry.loc-addr
end_pos = pos + entry.size
buf = data[pos:end_pos]
buf_repr = ''
if len(buf) in [1, 2, 4]:
# attempt to decode as integers
buf_repr += ' I:' + str(int.from_bytes(buf, 'little')).rjust(10)
if len(buf) in [4, 8]:
# attempt to decode as floats
typ = 'f' if len(buf) == 4 else 'd'
buf_repr += ' F:' + '{:10.3f}'.format(unpack(typ, buf)[0])
if last_end is not None:
if gaps and last_end < pos:
# decode gaps
gap_size = pos - last_end
gap_buf = data[last_end:pos]
print('{:04x} {} {:4} R:{}'.format(addr+last_end, "*UNKNOWN*".ljust(width),
gap_size, gap_buf.hex()))
if overlaps and last_end > pos + 1:
gap_size = pos - last_end
print('{:04x} {} {:4}'.format(addr+last_end, "*OVERLAP*".ljust(width), gap_size))
print('{:04x} {} {:4}{} R:{}'.format(entry.loc, entry.name.ljust(width),
entry.size, buf_repr, buf.hex()))
last_end = end_pos
def print_map(grefs):
print('OFFSET\tSIZE\tNAME\tDECLPOS')
for entry in grefs:
print('{:x}\t{}\t{}\t{}'.format(entry.loc, entry.size, entry.name, entry.declpos))
def print_qdirstat(grefs):
print('[qdirstat 1.0 cache file]')
entries = {}
for entry in grefs:
# do not output registers when looking at space usage
if entry.loc < SRAM_START:
continue
paths = list(filter(None, re.split(r'[\[\].]', entry.name)))
base = entries
for i in range(len(paths) - 1):
name = paths[i]
if name not in base:
base[name] = {}
base = base[name]
name = paths[-1]
if name in base:
name = '{}_{:x}'.format(entry.name, entry.loc)
base[name] = entry.size
def walker(root, prefix):
files = []
dirs = []
for name, entries in root.items():
if type(entries) == int:
files.append([name, entries])
else:
dirs.append([name, entries])
# print files
print('D\t{}\t{}\t0x0'.format(prefix, 0))
for name, size in files:
print('F\t{}\t{}\t0x0'.format(name, size))
# recurse directories
for name, entries in dirs:
walker(entries, prefix + '/' + name)
walker(entries, '/')
def main():
ap = argparse.ArgumentParser(description="""
Generate a symbol table map starting directly from an ELF
firmware with DWARF3 debugging information.
When used along with a memory dump obtained from the D2/D21/D23 g-code,
show the value of each symbol which is within the address range.
""")
ap.add_argument('elf', help='ELF file containing DWARF debugging information')
ap.add_argument('--no-gaps', action='store_true',
help='do not dump memory inbetween known symbols')
ap.add_argument('--no-expand-structs', action='store_true',
help='do not decode structure data')
ap.add_argument('--overlaps', action='store_true',
help='annotate overlaps greater than 1 byte')
ap.add_argument('--name-width', type=int, default=50,
help='set name column width')
g = ap.add_mutually_exclusive_group(required=True)
g.add_argument('dump', nargs='?', help='RAM dump obtained from D2 g-code')
g.add_argument('--map', action='store_true', help='dump global memory map')
g.add_argument('--qdirstat', action='store_true',
help='dump qdirstat-compatible size usage map')
args = ap.parse_args()
grefs = get_elf_globals(args.elf, expand_structs=not args.no_expand_structs)
grefs = list(sorted(grefs, key=lambda x: x.loc))
if args.map:
print_map(grefs)
elif args.qdirstat:
print_qdirstat(grefs)
else:
# fetch the memory data
dump = decode_dump(args.dump)
if dump is None:
return os.EX_DATAERR
# strip padding, if present
addr_start = dump.ranges[0][0]
addr_end = dump.ranges[-1][0]+dump.ranges[-1][1]
data = dump.data[addr_start:addr_end]
annotate_refs(grefs, addr_start, data,
width=args.name_width,
gaps=not args.no_gaps,
overlaps=args.overlaps)
if __name__ == '__main__':
exit(main())

4
tools/lib/avr.py Normal file
View File

@ -0,0 +1,4 @@
SRAM_START = 0x200
SRAM_SIZE = 0x2000
SRAM_OFFSET = 0x800000
EEPROM_OFFSET = 0x810000

169
tools/lib/dump.py Normal file
View File

@ -0,0 +1,169 @@
import sys
import re
import enum
import struct
from . import avr
FILL_BYTE = b'\0' # used to fill memory gaps in the dump
DUMP_MAGIC = 0x55525547 # XFLASH dump magic
DUMP_OFFSET = 0x3d000 # XFLASH dump offset
DUMP_SIZE = 0x2300 # XFLASH dump size
class CrashReason(enum.IntEnum):
MANUAL = 0
STACK_ERROR = 1
WATCHDOG = 2
BAD_ISR = 3
class Dump():
def __init__(self, typ, reason, regs, pc, sp, data, ranges):
self.typ = typ
self.reason = reason
self.regs = regs
self.pc = pc
self.sp = sp
self.data = data
self.ranges = ranges
# expand the buffer identified by addr+data to fill the region start+size
def region_expand(addr, data, start, size):
if start < addr:
data = FILL_BYTE * (addr - start) + data
addr = start
end = start + size
data_end = addr + len(data)
if end > data_end:
data += FILL_BYTE * (data_end - end)
return addr, data
def merge_ranges(ranges):
ranges = list(sorted(ranges, key=lambda x: x[0]))
if len(ranges) < 2:
return ranges
ret = [ranges[0]]
for cur in ranges[1:]:
last = ret[-1]
last_end = last[0] + last[1]
if last_end < cur[0]:
ret.append(cur)
else:
cur_end = cur[0] + cur[1]
last = (last[0], max(last_end, cur_end) - last[0])
ret[-1] = last
return ret
def decode_dump(path):
fd = open(path, 'r')
if fd is None:
return None
buf_addr = None # starting address
buf_data = None # data
typ = None # dump type
reason = None # crash reason
regs = None # registers present
pc = None # PC address
sp = None # SP address
ranges = [] # dumped ranges
in_dump = False
for line in enumerate(fd):
line = (line[0], line[1].rstrip())
tokens = line[1].split(maxsplit=1)
def line_error():
print('malformed line {}: {}'.format(*line), file=sys.stderr)
# handle metadata
if not in_dump:
if len(tokens) > 0 and tokens[0] in ['D2', 'D21', 'D23']:
in_dump = True
typ = tokens[0]
continue
else:
if len(tokens) == 0:
line_error()
continue
elif tokens[0] == 'ok':
break
elif tokens[0] == 'error:' and len(tokens) == 2:
values = tokens[1].split(' ')
if typ == 'D23' and len(values) >= 3:
reason = CrashReason(int(values[0], 0))
pc = int(values[1], 0)
sp = int(values[2], 0)
else:
line_error()
continue
elif len(tokens) != 2 or not re.match(r'^[0-9a-fA-F]+$', tokens[0]):
line_error()
continue
# decode hex data
addr = int.from_bytes(bytes.fromhex(tokens[0]), 'big')
data = bytes.fromhex(tokens[1])
ranges.append((addr, len(data)))
if buf_addr is None:
buf_addr = addr
buf_data = data
else:
# grow buffer as needed
buf_addr, buf_data = region_expand(buf_addr, buf_data,
addr, len(data))
# replace new part
rep_start = addr - buf_addr
rep_end = rep_start + len(data)
buf_data = buf_data[:rep_start] + data + buf_data[rep_end:]
# merge continuous ranges
ranges = merge_ranges(ranges)
if typ == 'D2':
# D2 doesn't guarantee registers to be present
regs = len(ranges) > 0 and \
ranges[0][0] == 0 and \
ranges[0][1] >= avr.SRAM_START
# fill to fit for easy loading
buf_addr, buf_data = region_expand(
buf_addr, buf_data, 0, avr.SRAM_START + avr.SRAM_SIZE)
elif typ == 'D23':
# check if the dump is complete
if len(ranges) != 1 or ranges[0][0] != 0 or \
ranges[0][1] != avr.SRAM_START + avr.SRAM_SIZE:
print('error: incomplete D23 dump', file=sys.stderr)
return None
regs = True
if reason is None:
print('warning: no error line in D23', file=sys.stderr)
elif typ == 'D21':
if len(ranges) != 1 or len(buf_data) != (avr.SRAM_START + avr.SRAM_SIZE + 256):
print('error: incomplete D21 dump', file=sys.stderr)
return None
# decode the header structure
magic, regs_present, crash_reason, pc, sp = struct.unpack('<LBBLH', buf_data[0:12])
if magic != DUMP_MAGIC:
print('error: invalid dump header in D21', file=sys.stderr)
return None
regs = bool(regs_present)
reason = CrashReason(crash_reason)
# extract the data section
buf_addr = 0
buf_data = buf_data[256:]
ranges[0] = (0, len(buf_data))
return Dump(typ, reason, regs, pc, sp, buf_data, ranges)

12
tools/noreset Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
prg=$(basename "$0")
port="$1"
if [ -z "$port" -o "$port" = "-h" ]
then
echo "usage: $0 <port>" >&2
echo "Set TTY flags on <port> to avoid reset-on-connect" >&2
exit 1
fi
set -e
stty -F "$port" -hup

54
tools/update_eeprom Executable file
View File

@ -0,0 +1,54 @@
#!/bin/sh
prg=$(basename "$0")
# parse arguments
while getopts f:h optname
do
case $optname in
f) port="$OPTARG" ;;
*) help=1 ;;
esac
done
shift `expr $OPTIND - 1`
old="$1"
new="$2"
if [ -z "$old" -o "$help" = "-h" -o "$#" -gt 2 ]
then
echo "usage: $0 [-f <port>] <old dump> [<new dump>]" >&2
echo "Convert <old dump> to instructions to update instructions." >&2
echo "With <new dump>, generate instructions to update EEPROM changes only." >&2
echo "Optionally write such changes directly if <port> if given." >&2
exit 1
fi
set -e
instr=$(mktemp)
trap "rm -f \"$instr\"" EXIT
convert()
{
sed -ne 's/^\([0-9a-f]\{4\}\) \([0-9a-f ]*\)$/D3 Ax\1 C16 X\2/p' "$@"
}
if [ -z "$new" ]; then
# convert the instructions to updates
convert "$old" > "$instr"
else
tmp1=$(mktemp)
tmp2=$(mktemp)
trap "rm -f \"$tmp1\" \"$tmp2\"" EXIT
convert "$old" > "$tmp1"
convert "$new" > "$tmp2"
comm -13 "$tmp1" "$tmp2" > "$instr"
fi
# write the instructions if requested
if [ -z "$port" ]; then
cat "$instr"
else
printcore -v "$port" "$instr"
fi

89
tools/utils.gdb Normal file
View File

@ -0,0 +1,89 @@
# -*- gdb-script -*-
define load_dump
restore $arg0 binary 0x800000
set $pc = (((unsigned long)$arg1) - 2) << 1
set $sp = $arg2
where
end
document load_dump
Load a crash dump, setup PC/SP and show the current backtrace
Usage: load_dump <file> <PC-addr> <SP-addr>
end
define sp_skip
if $argc == 0
set $shift = 3
else
set $shift = $arg0
end
set $new_pc = ((((unsigned long)*(uint8_t*)($sp+$shift+1)) << 16) + \
(((unsigned long)*(uint8_t*)($sp+$shift+2)) << 8) + \
(((unsigned long)*(uint8_t*)($sp+$shift+3)) << 0)) << 1
set $new_sp = $sp+$shift+3
select-frame 0
set $saved_pc = $pc
set $saved_sp = $sp
set $pc = $new_pc
set $sp = $new_sp
where
end
document sp_skip
Decode the PC address at SP+offset, then show the resulting stack.
The default (and minimum) offset is 3.
Usage: sp_skip [off]
end
define sp_restore
select-frame 0
set $pc = $saved_pc
set $sp = $saved_sp
where
end
document sp_restore
Undo an sp_skip move (restore existing PC/SP positions)
Usage: sp_restore
end
define sp_test
sp_skip $arg0
set $pc = $saved_pc
set $sp = $saved_sp
end
document sp_test
Attempt to decode the PC address at SP+offset, then show the resulting stack.
The default (and minimum) offset is 3.
Usage: sp_test [off]
end
define sp_scan
dont-repeat
if $argc == 0
set $sp_end = 0x802200
else
set $sp_end = $arg0
end
set $sp_pos = $sp
while $sp_pos < ($sp_end-4)
set $sp_off = $sp_pos - $sp
printf "**** scanning %#x (+%u) ****\n", $sp_pos, $sp_off
sp_test $sp_off
set $sp_pos += 1
end
end
document sp_scan
Attempt to decode PC at any location starting from the SP+3 and up to SP-end
(by default the end of the SRAM) and show the resulting stack at all locations.
Usage: sp_scan [SP-end]
end

46
tools/xfimg2dump Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python3
import argparse
import struct
import os, sys
from lib.dump import DUMP_MAGIC, DUMP_OFFSET, DUMP_SIZE
def error(msg):
print(msg, file=sys.stderr)
def main():
# parse the arguments
ap = argparse.ArgumentParser(description="""
Extract a crash dump from an external flash image and output
the same format produced by the D21 g-code.
""")
ap.add_argument('image')
args = ap.parse_args()
# read the image
off = DUMP_OFFSET
with open(args.image, 'rb') as fd:
fd.seek(off)
data = fd.read(DUMP_SIZE)
if len(data) != DUMP_SIZE:
error('incorrect image size')
return os.EX_DATAERR
# check for magic header
magic, = struct.unpack('<L', data[:4])
if magic != DUMP_MAGIC:
error('invalid dump magic or no dump')
return os.EX_DATAERR
# output D21 dump
print('D21 - read crash dump', end='')
for i in range(len(data)):
if i % 16 == 0:
print('\n{:06x} '.format(off + i), end='')
print(' {:02x}'.format(data[i]), end='')
print('\nok')
if __name__ == '__main__':
exit(main())