import sys import re import enum import struct from . import avr FILL_BYTE = b'\0' # used to fill memory gaps in the dump 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('warning: incomplete D23 dump', file=sys.stderr) else: 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('