From bcb993f20431612f2f5d4eedd0f0495d02a1918d Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Fri, 3 Apr 2026 01:12:20 +0100 Subject: [PATCH] Patch sketches witch -c dryboot as with -c urclock -x nometadata --- src/dryrun.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 220 insertions(+), 3 deletions(-) diff --git a/src/dryrun.c b/src/dryrun.c index 2d004428..20072e6f 100644 --- a/src/dryrun.c +++ b/src/dryrun.c @@ -62,9 +62,8 @@ typedef struct { int bootstart, bootsize; // Start and size of boot section (if any) int initialised; // 1 once the part memories are initialised struct { - int vblvectornum, // Vector bootloader vector number for jump to application op code - vbllevel, // 0=n/a, 1=patch externally, 2=bl patches, 3=bl patches & verifies - blurversion; // Octal byte 076 means v7.6 (minor version number is lowest 3 bit) + int vectornum; // Vector bootloader vector number for jump to application op code + int urversion; // Octal byte 076 means v7.6 (minor version number is lowest 3 bit) int32_t blstart, blend; // Bootloader address range [blstart, blend] for write protection int32_t pfstart, pfend; // Programmable flash address range [pfstart, pfend] } urdesc; @@ -80,6 +79,215 @@ typedef struct { static int dryrun_readonly(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, unsigned int addr); +// Initialise urboot descriptor once an urboot bootloader is detected +static int dryrun_init_ur(const PROGRAMMER *pgm, const AVRPART *p) { + // The first urboot bootloader detection freezes the parameters + if(ur.urversion) + return 0; + + uint8_t top[6]; + const AVRMEM *flm = avr_locate_flash(p); + if(!flm) + Return("cannot locate flash memory for %s\n", p->desc); + + // No urboot bootloaders on AVR32 parts, neither on really small devices + if(is_awire(p) || flm->size < 512) + return 0; + + if(!is_updi(p)) { + // Check top 6 bytes from flash to obtain intell about bootloader and type + for(int i = sizeof top - 1; i >= 0; i--) { + if(pgm->read_byte(pgm, p, flm, flm->size - sizeof top + i, top + i) < 0) + return -1; + // Abort if last byte in flash does not indicate urboot v7.5 ... v12.7 == 0147 + if(i == sizeof top - 1 && (top[i] < 075 || top[i] > 0147)) + return 0; + } + + uint8_t numpags = top[0] & 0x7f; // Number of bootloader pages from v7.5 + uint8_t vectnum = top[1] & 0x7f; // Vector number for application start from v7.5 + uint16_t rjmpwp = buf2uint16(top+2); // rjmp to bootloader pgm_write_page() or ret + // uint8_t cap = top[4]; // Capability byte not needed + uint8_t urver = top[5]; // Urboot version; low 3 bits = minor version: 076 = v7.6 + + // Could be urboot bootloader v7.5 .. v12.7: check further properties + if(isop(rjmpwp, rjmp) || isop(rjmpwp, ret)) { // OK, valid rjmpwp opcode + int blsize = numpags*flm->page_size; + // Size of urboot bootloader should be in [64, 2048] (in v7.6 these are 224-512 bytes) + if(blsize >= 64 && blsize <= 2048 && vectnum <= p->n_interrupts) { // Within range + int dfromend = dist_rjmp(rjmpwp, flm->size) - 4; + // Further check whether writepage() rjmp opcode jumps backwards into bootloader + if(isop(rjmpwp, ret) || (dfromend >= -blsize && dfromend < -6)) { // urboot! + ur.blstart = flm->size - blsize; + ur.blend = flm->size - 1; + ur.pfend = ur.blstart - 1; + ur.vectornum = vectnum; + ur.urversion = urver; + } + } + } + } else { // @@@ Fixme: todo when UPDI urboot bootloaders available + } + + if(ur.urversion) { + char buf[20]; + urbootPutVersion(buf, (uint16_t *) top); + pmsg_info("detected urboot bootloader %s in [0x%04x, 0x%04x] with vector=%d\n", + buf, ur.blstart, ur.blend, ur.vectornum); + } + + return 0; +} + +// At the beginning of every terminal command +static int dryrun_cmdhook(const PROGRAMMER *pgm, const AVRPART *p, int argc_uu, const char *argv_uu[]) { + return dryrun_init_ur(pgm, p); +} + +// At the beginning of every -U command and during execution of terminal backup, restore and verify +static int dryrun_updatehook(const PROGRAMMER *pgm, const AVRPART *p, const UPDATE *upd_uu, int flags_uu) { + return dryrun_init_ur(pgm, p); +} + +// Called after the input file has been read for writing or verifying flash +static int dryrun_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *flm, + const char *fname_uu, int size) { + + int maxsize = ur.pfend+1, firstbeg, firstlen; + const int vecsz = flm->size <= 8192? 2: 4; // Small parts use rjmp, large a 4-byte jmp + + // Sanity: no patching if bootloader location is unknown + if(ur.blend <= ur.blstart) + goto nopatch; + + // Compute begin and length of first contiguous block in input + for(firstbeg=0; firstbeg < size; firstbeg++) + if(flm->tags[firstbeg] & TAG_ALLOCATED) + break; + for(firstlen=0; firstbeg+firstlen < size; firstlen++) + if(!(flm->tags[firstbeg+firstlen] & TAG_ALLOCATED)) + break; + + // Sanity check the bootloader position + if(ur.blstart < 0 || ur.blstart >= flm->size || ur.blend < 0 || ur.blend >= flm->size) + Return("bootloader [0x%04x, 0x%04x] outside flash [0, 0x%04x]", + ur.blstart, ur.blend, flm->size-1); + + // Check size of uploded application and protect bootloader from being overwritten + if((!is_updi(p) && size > maxsize) || (is_updi(p) && firstbeg <= ur.blend)) + Return("input [0x%04x, 0x%04x] overlaps b/loader [0x%04x, 0x%04x]", + firstbeg, size-1, ur.blstart, ur.blend); + + if(size > maxsize) + Return("input [0x%04x, 0x%04x] extends programmable area [0x%04x, 0x%04x]", + firstbeg, size-1, ur.pfstart, ur.pfend); + + if(is_updi(p)) + goto nopatch; + + bool llcode = firstbeg == 0 && firstlen > p->n_interrupts*vecsz; // Looks like code + bool llvectors = firstbeg == 0 && firstlen >= p->n_interrupts*vecsz; // Looks like vector table + for(int i = 0; llvectors && i < p->n_interrupts*vecsz; i += vecsz) { + uint16_t op16 = buf2uint16(flm->buf+i); + if(!isop(op16, rjmp) && !(vecsz == 4 && isop(op16, jmp))) + llvectors = 0; + } + + if(llcode && !llvectors && ur.vectornum > 0) + pmsg_warning("not patching jmp to application as input does not start with a vector table\n"); + + // Patch vectors if input looks like code and it's a vector bootloader with known vector number + if(llcode && llvectors && ur.vectornum > 0) { + uint16_t reset16; + int reset32, appstart, appvecloc; + + appvecloc = ur.vectornum*vecsz; // Location of jump-to-application in vector table + reset16 = buf2uint16(flm->buf); // First reset word of to-be-written application + reset32 = vecsz == 2? reset16: buf2uint32(flm->buf); + + /* + * Compute where the application starts from the reset vector. The assumptions are that the + * - Vector table, and therefore the reset vector, resides at address zero + * - Compiler puts either a jmp or an rjmp at address zero + * - Compiler does not shorten the vector table if no or few interrupts are used + * - Compiler does not utilise unused interrupt vectors to place code there + */ + + if(reset2addr(flm->buf, vecsz, flm->size, &appstart) < 0) { + pmsg_warning("not patching input as opcode word %04x at reset is not r/jmp\n", reset16); + goto nopatch; + } + + // Only patch if appstart does not already point to the bootloader + if(appstart != ur.blstart) { + int vectorsend = vecsz*ur.vectornum; + if(appstart < vectorsend || appstart >= size) { // appstart should be in [vectorsend, size) + if(appstart != ur.blstart) { + pmsg_warning("not patching as reset opcode %0*x jumps to 0x%04x,\n", + vecsz*2, reset32, appstart); + imsg_warning("ie, outside code area [0x%04x, 0x%04x)\n", + vectorsend, size); + } + goto nopatch; + } + + // OK, now have bootloader start and application start: patch + set_resetvector(ur.blstart, flm->size, flm->buf+0, vecsz, 1); + if(vecsz == 4) + uint32tobuf(flm->buf+appvecloc, jmp_opcode(appstart)); + else + uint16tobuf(flm->buf+appvecloc, rjmp_opcode(appstart - appvecloc, flm->size)); + } + } + +nopatch: + + // Ensure that vector bootloaders have correct r/jmp at address 0 + if(!is_updi(p) && ur.blstart && ur.vectornum > 0) { + int resetdest, set = 0; + for(int i = 0; i < vecsz; i++) + if(flm->tags[i] & TAG_ALLOCATED) + set++; + + // Reset vector not programmed? Or -F? Ensure a jmp to bootloader + if(ovsigck || set != vecsz) { + unsigned char jmptoboot[4]; + int resetsize = set_resetvector(ur.blstart, flm->size, jmptoboot, vecsz, 1); + + if(set != vecsz) { + unsigned char device[4]; + // Read reset vector from device flash + for(int i = 0; i < vecsz; i++) + if(pgm->read_byte(pgm, p, flm, i, device+i) < 0) + return -1; + + // Mix with already set bytes + for(int i = 0; i < vecsz; i++) + if(!(flm->tags[i] & TAG_ALLOCATED)) + flm->buf[i] = device[i]; + } + + if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0 || resetdest != ur.blstart) { + for(int i=0; i < resetsize; i++) { + flm->buf[i] = jmptoboot[i]; + flm->tags[i] |= TAG_ALLOCATED; + } + } + } else if(firstbeg < vecsz) { // Double-check reset vector jumps to bootloader + if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0) + Return("input would overwrite the reset vector bricking the bootloader\n" + " using -F will try to patch the input but this may not be what is needed"); + if(resetdest != ur.blstart) + Return("input points reset to 0x%04x, not to bootloader at 0x%04x\n" + " using -F will try to patch the input but this may not be what is needed", + resetdest, ur.blstart); + } + } + + return size; +} + + // Read expected signature bytes from part description static int dryrun_read_sig_bytes(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *sigmem) { pmsg_debug("%s()", __func__); @@ -523,6 +731,10 @@ static void dryrun_enable(PROGRAMMER *pgm, const AVRPART *p) { AVRMEM *m, *fusesm = NULL, *prodsigm = NULL, *calm; AVRPART *q = dry.dp = avr_dup_part(p); // Allocate dryrun part and abbreviate with q + // Initialise urboot descriptor so that all flash is programmable and there is no bootloader + if((m = avr_locate_flash(p))) + ur.pfend = m->size-1; + memset(inifuses, 0xff, sizeof inifuses); srandom(dry.seed? dry.seed: time(NULL)); @@ -1082,4 +1294,9 @@ void dryrun_initpgm(PROGRAMMER *pgm) { pgm->term_keep_alive = dryrun_term_keep_alive; pgm->readonly = dryrun_readonly; pgm->parseextparams = dryrun_parseextparams; + if(is_spm(pgm)) { + pgm->flash_readhook = dryrun_flash_readhook; + pgm->updatehook = dryrun_updatehook; + pgm->cmdhook = dryrun_cmdhook; + } }