diff --git a/docs/sbl-bringup.md b/docs/sbl-bringup.md index 2af41a1..e2d3934 100644 --- a/docs/sbl-bringup.md +++ b/docs/sbl-bringup.md @@ -41,3 +41,26 @@ 1. Добить баннер (см. свежий in_asm-хвост). 2. Заменить стаблеты настоящим PBL (нужны дампы с живого тела!). 3. UEFI (img3, EDK2) — отдельный фронт после SBL1. + +## b18: S7 и цикл delay-helper (2026-09-12) +Механизм зацикливания (b17, доказан Unicorn-harness `tools/sbl_uni.py`): +- sbl_main → delay-функция `0x8013B78` (256-итераций table-walk, `bl 0x8017AF4` + каждую итерацию) → b14 (`0x8017AFC: b.w 0x8005B9E`) → helper `0x8005BB0` + → выход `ldr pc,[sp],#0x14` (0x8005BCA) → S3-слот (0x8013BA5, рестарт). +- Эпилог delay (`0x8013BAC: pop {r3-r7,pc}`) снимает 0x18 без push; каждый + S3-рестарт съедает 0x18 живого стека. Маршрут: слоты → fill (STUBV → + pop pc = maze) → pristine IMEM (нули → null-jump → hook_null → maze → + stale lr) → переполнение IMEM (0x8610008). +- Heap SBL (stm в 0x8006684) затирает maze/S6/banner (0, 0x7a, STUBV). + QEMU: dstub-overlay `[0x08006B80,0x08006C50)` (read = снапшот ELF+стабы, + write = игнор). Uni: deferred-restore guard (MEM_WRITE-хук срабатывает + ДО записи, restore надо делать в hook_code!). +- Maze-blob: `movs r0,#0; lsr ip,lr,#24; cmp #8; bne hang; bx lr` + (возврат 0 только для lr из DDRLOW 0x08..., иначе hang). +Фикс b18: патч BAC (`f8bd0a4a` → `f3f730b8`, `b.w S7`) + S7-стаблет +(52B @ 0x08006C10): `cmp r4,#0xff` → iterate (`0x8013BA5`) или done +(restore r4/r5/sl/fp, возврат в sbl_main `0x802f681`). BAE-путь +(`[r7]==2`) принесён в жертву (r7=0, недостижим). +Фронтир b18: sbl_main возвращается, новый код `0x801Bxxx` +(`strb [r7,#2]`-паттерн, b17-NOP на 0x801B71E), падеж pop'ом пустого +стека у верха IMEM — следующий фронт. diff --git a/qemu/maze.S b/qemu/maze.S index 52c7cd8..6d6c864 100644 --- a/qemu/maze.S +++ b/qemu/maze.S @@ -1,22 +1,16 @@ .syntax unified .cpu cortex-a7 .thumb - @ maze entry logger: ring buffer of 8 (lr,sp) at MAILBOX, return 0. - @ Preserves all regs except r0 (return value) and r12 (IP, clobber-ok). + @ maze entry: return 0. Guard: bad lr (high byte != 0x08) spins + @ with IRQs off instead of jumping into the void. .text .global maze_stub maze_stub: - push {r0-r3} - movw r12, #:lower16:0x08006890 - movt r12, #:upper16:0x08006890 - ldr r0, [r12, #32] - add r0, #1 - and r0, #7 - str r0, [r12, #32] - lsl r0, #3 - add r0, r12 - str lr, [r0] - str sp, [r0, #4] - pop {r0-r3} movs r0, #0 + lsr r12, lr, #24 + cmp r12, #8 + bne maze_hang bx lr +maze_hang: + cpsid i + b maze_hang diff --git a/qemu/pbl.S b/qemu/pbl.S new file mode 100644 index 0000000..1e9cf63 --- /dev/null +++ b/qemu/pbl.S @@ -0,0 +1,22 @@ + .syntax unified + .cpu cortex-a7 + .thumb + @ Minimal PBL stub (position-independent): BLX to SBL1 entry (seg1), + @ then canonical regs + jump to sbl_main. Mirrors PBL call-then-jump. + .text + .global pbl_stub +pbl_stub: + push {r4-r7, lr} + movw r12, #0x6b81 + movt r12, #0x800 + blx r12 @ SBL1 entry (odd: stay Thumb) + movw r0, #0x0000 + movt r0, #0x0022 @ r0 = pbl_shared 0x00220000 + mov r5, r0 + movw sl, #0x0000 + movt sl, #0x0806 @ sl = table base 0x08060000 + movw fp, #0x1000 + movt fp, #0x0806 @ fp = 0x08061000 + movw lr, #0xf63f + movt lr, #0x0802 @ lr = sbl_main odd (noreturn marker) + bx lr diff --git a/qemu/saimaa.c b/qemu/saimaa.c index 61c4a45..9e61dac 100644 --- a/qemu/saimaa.c +++ b/qemu/saimaa.c @@ -68,10 +68,13 @@ struct SaimaaTmrState { static uint64_t saimaa_tmr_read(void *opaque, hwaddr off, unsigned size) { + /* Deterministic tick (+1/read): advances delay loops but keeps runs + * reproducible (qemu_clock would diverge run to run). */ + static uint64_t tick; (void)opaque; (void)off; (void)size; - return (uint64_t)(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >> 12); + return ++tick; } static void saimaa_tmr_write(void *opaque, hwaddr off, uint64_t val, @@ -101,6 +104,54 @@ static void saimaa_tmr_init(Object *obj) /* Stubs region handle for set_readonly after scaffold implant. */ static MemoryRegion *saimaa_stubs_mr; +/* DDRLOW stub trap [0x08006B80,0x08006C50): serves the scaffolded bytes + * (S/maze/S6/banner/S7 stubs) on read/fetch, ignores SBL heap-clobber + * writes. Uni parity: deferred-restore guard in sbl_uni.py (wider: + * [0x080068C0,0x08006C50), also covers PBL stub). + * Snapshot is taken AFTER ELF load + all stub writes (readback). + * PBL stub [0x080068C0,0x080068F4) and the ELF-text gap below stay plain + * RAM: PBL runs once (if at all), gap holds real hot SBL code. */ +#define SAIMAA_DSTUB_BASE 0x08006B80 +#define SAIMAA_DSTUB_SIZE 0x000000D0 +static uint8_t saimaa_dstub_mem[SAIMAA_DSTUB_SIZE]; + +static uint64_t saimaa_dstub_read(void *opaque, hwaddr off, unsigned size) +{ + uint64_t v = 0; + unsigned i; + (void)opaque; + if (off >= SAIMAA_DSTUB_SIZE) { + return 0; + } + if (off + size > SAIMAA_DSTUB_SIZE) { + size = SAIMAA_DSTUB_SIZE - off; + } + for (i = 0; i < size; i++) { + v |= (uint64_t)saimaa_dstub_mem[off + i] << (8 * i); + } + return v; +} + +static void saimaa_dstub_write(void *opaque, hwaddr off, uint64_t val, + unsigned size) +{ + static int n = 0; + (void)opaque; + (void)val; + (void)size; + if (n < 8) { + fprintf(stderr, "saimaa-dstub: ignored heap write @0x%x\n", + SAIMAA_DSTUB_BASE + (unsigned)off); + n++; + } +} + +static const MemoryRegionOps saimaa_dstub_ops = { + .read = saimaa_dstub_read, + .write = saimaa_dstub_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + /* ---- minimal MSM UARTDM stub: TX prints, status always ready ---- */ #define TYPE_SAIMAA_VEC "saimaa-vec" #define TYPE_SAIMAA_UART "saimaa-uart" @@ -120,17 +171,9 @@ static uint64_t saimaa_vec_read(void *opaque, hwaddr off, unsigned size) ARMCPU *cpu = ARM_CPU(current_cpu); uint64_t pc = cpu ? cpu->env.regs[15] : 0; int th = cpu ? cpu->env.thumb : 0; - uint32_t mb0 = 0, mb1 = 0; - if (saimaa_vec_n < 3) { - cpu_physical_memory_read(0x08006890, &mb0, 4); - cpu_physical_memory_read(0x08006894, &mb1, 4); - } fprintf(stderr, "saimaa-vec: %s off=0x%x pc=0x%lx", size == 2 ? "fetch?" : "read", (unsigned)off, (unsigned long)pc - (th ? 4 : 8)); - if (saimaa_vec_n < 3) { - fprintf(stderr, " maze-mailbox: lr=0x%x sp=0x%x", mb0, mb1); - } if (cpu) { int b, r; fprintf(stderr, " regs="); @@ -402,6 +445,12 @@ static void saimaa_machine_init(MachineState *machine) ARM_CPU(first_cpu)->env.banked_r13[b] = 0x085FFF00; } ARM_CPU(first_cpu)->env.regs[13] = 0x085FFF00; + /* Stale lr (never bl'd on synthetic paths) returns into maze + * (returns 0 = SBL "empty" convention) instead of jumping to 0. */ + ARM_CPU(first_cpu)->env.regs[14] = 0x08006B8D; + for (int b = 0; b < 8; b++) { + ARM_CPU(first_cpu)->env.banked_r14[b] = 0x08006B8D; + } ARM_CPU(first_cpu)->env.regs[0] = 0x00220000; /* fake pbl_shared */ /* sbl_main expects r5 = mem-table pointer (PBL trampoline does * mov r5, r0 before jumping here); point it at pbl_shared too. */ @@ -447,7 +496,10 @@ static void saimaa_machine_init(MachineState *machine) tgt & ~1u, (tgt & 1) ? " thumb" : " arm", (unsigned long)entry); } else { - cpu_set_pc(first_cpu, entry); + /* Default: start at PBL stub (faithful call-then-jump flow) */ + cpu_set_pc(first_cpu, 0x080068C1); + printf("saimaa: PBL-stub start -> entry 0x%lx then sbl_main\n", + (unsigned long)entry); } /* Boot scaffolding for SBL1 BringUp (WIP stub-driven PBL): * static continuation slots for shared-epilogue pops. @@ -455,14 +507,14 @@ static void saimaa_machine_init(MachineState *machine) * (sp values from cpu traces, b10 image) */ { uint32_t magic = 0x00000000; /* IMEM poll spins WHILE equal */ - uint32_t S = 0x08006820, STUB = 0x08006830; /* in-code zeros, safe from heap */ - uint32_t STUBV = 0x08006831; /* odd: blx stays Thumb */ + uint32_t S = 0x08006B80, STUB = 0x08006B8C; /* dead PBL-entry code */ + uint32_t STUBV = 0x08006B8D; /* odd: blx stays Thumb */ uint32_t w; uint16_t h0 = 0x2000, h1 = 0x4770; /* movs r0,#0; bx lr */ uint32_t v; /* pointer maze in owned RAM for SBL1 struct chains. - * STUB = mailbox logger + return-0: records lr/sp at entry - * to 0x08006964 for post-mortem, then movs r0,#0; bx lr. */ + * STUB = minimal logger + return-0 (movs r0,#0; bx lr); + * mailbox at 0x08006BD0 records last (lr,sp). */ w = S + 8; cpu_physical_memory_write(S, &w, 4); /* [struct+4] is another fn ptr (e.g. 0x801b72e ldr r3,[r0,#4] @@ -471,16 +523,13 @@ static void saimaa_machine_init(MachineState *machine) cpu_physical_memory_write(S + 4, &STUBV, 4); cpu_physical_memory_write(S + 8, &STUBV, 4); { - /* from qemu/maze.S: mailbox logger + lr-range guard. - * Bad lr (not 0x08xxxxxx) spins with IRQs off instead of - * jumping into the void. 36 bytes. */ + /* Minimal maze: movs r0,#0; bx lr (4B). No mailbox, no + * guard — vec trap already reports fault state. Less + * surface for SBL heap clobbers. */ static const uint8_t mb[] = { - 0x0f, 0xb4, 0x46, 0xf6, 0x90, 0x0c, 0xc0, 0xf6, - 0x00, 0x0c, 0xdc, 0xf8, 0x20, 0x00, 0x00, 0xf1, - 0x01, 0x00, 0x00, 0xf0, 0x07, 0x00, 0xcc, 0xf8, - 0x20, 0x00, 0x4f, 0xea, 0xc0, 0x00, 0x60, 0x44, - 0xc0, 0xf8, 0x00, 0xe0, 0xc0, 0xf8, 0x04, 0xd0, - 0x0f, 0xbc, 0x00, 0x20, 0x70, 0x47, + 0x00, 0x20, 0x4f, 0xea, 0x1e, 0x6c, 0xbc, 0xf1, + 0x08, 0x0f, 0x00, 0xd1, 0x70, 0x47, 0x72, 0xb6, + 0xfd, 0xe7, }; size_t k; for (k = 0; k < sizeof(mb); k += 4) { @@ -493,7 +542,7 @@ static void saimaa_machine_init(MachineState *machine) cpu_physical_memory_write(0x087c29d18, &S, 4); cpu_physical_memory_write(0x08050b28, &S, 4); cpu_physical_memory_write(0x08070000, &S, 4); - /* S6 stublet at 0x0800685e: rebuild popped frame regs, + /* S6 stublet at 0x08006B9E: rebuild popped frame regs, * return to 0x8013ba5 (b14 redirects 0x8017afc pop here) */ { static const uint8_t s6[] = { @@ -507,10 +556,10 @@ static void saimaa_machine_init(MachineState *machine) uint32_t word = 0; size_t n = sizeof(s6) - k < 4 ? sizeof(s6) - k : 4; __builtin_memcpy(&word, &s6[k], n); - cpu_physical_memory_write(0x0800685e + k, &word, 4); + cpu_physical_memory_write(0x08006B9E + k, &word, 4); } } - /* Banner stub at 0x0800686c: print r0 string via UARTDM + /* Banner stub at 0x08006BAC: print r0 string via UARTDM * (b16 redirects 0x802f690 blx here). Built from qemu/banner.S: * push frame, print loop, ldr pc,=0x802f694 (callee noreturn). * 28 bytes total. */ @@ -527,13 +576,54 @@ static void saimaa_machine_init(MachineState *machine) uint32_t word = 0; size_t n = sizeof(bn) - k < 4 ? sizeof(bn) - k : 4; __builtin_memcpy(&word, &bn[k], n); - cpu_physical_memory_write(0x0800686c + k, &word, 4); + cpu_physical_memory_write(0x08006BAC + k, &word, 4); } } - /* stack zone defaults to plain-stub addr: every register - * popped from untouched stack becomes callable (returns 0). */ + /* PBL stub at 0x080068C0 (52B, from qemu/pbl.S): BLX to SBL1 + * entry, then canonical regs + jump to sbl_main. Used when + * sblmain prop is unset (default): faithful PBL call flow. */ { - uint32_t fill = 0x08006831; + static const uint8_t pbl[] = { + 0xf0, 0xb5, 0x46, 0xf6, 0x81, 0x3c, 0xc0, 0xf6, + 0x00, 0x0c, 0xe0, 0x47, 0x40, 0xf2, 0x00, 0x00, + 0xc0, 0xf2, 0x22, 0x00, 0x05, 0x46, 0x40, 0xf2, + 0x00, 0x0a, 0xc0, 0xf6, 0x06, 0x0a, 0x41, 0xf2, + 0x00, 0x0b, 0xc0, 0xf6, 0x06, 0x0b, 0x4f, 0xf2, + 0x3f, 0x6e, 0xc0, 0xf6, 0x02, 0x0e, 0x70, 0x47, + 0x00, 0x00, 0x00, 0x00, + }; + size_t k; + for (k = 0; k < sizeof(pbl); k += 4) { + uint32_t word = 0; + size_t n = sizeof(pbl) - k < 4 ? sizeof(pbl) - k : 4; + __builtin_memcpy(&word, &pbl[k], n); + cpu_physical_memory_write(0x080068C0 + k, &word, 4); + } + } + /* S7 stublet at 0x08006C10 (52B, b18): delay-epilogue exit + * router. cmp r4,#0xff: iterate (r4<=0xff -> 0x8013ba5) or + * done (restore r4/r5/sl/fp, return to sbl_main 0x802f681). + * Assembled with keystone (see docs/sbl-bringup.md). */ + { + static const uint8_t s7[] = { + 0xff, 0x2c, 0x13, 0xd9, 0x40, 0xf2, 0x00, 0x04, + 0xc0, 0xf2, 0x22, 0x04, 0x40, 0xf2, 0x00, 0x05, + 0xc0, 0xf2, 0x22, 0x05, 0x40, 0xf2, 0x00, 0x0a, + 0xc0, 0xf6, 0x06, 0x0a, 0x41, 0xf2, 0x00, 0x0b, + 0xc0, 0xf6, 0x06, 0x0b, 0xdf, 0xf8, 0x00, 0xf0, + 0x81, 0xf6, 0x02, 0x08, 0xdf, 0xf8, 0x00, 0xf0, + 0xa5, 0x3b, 0x01, 0x08, + }; + size_t k; + for (k = 0; k < sizeof(s7); k += 4) { + uint32_t word = 0; + size_t n = sizeof(s7) - k < 4 ? sizeof(s7) - k : 4; + __builtin_memcpy(&word, &s7[k], n); + cpu_physical_memory_write(0x08006C10 + k, &word, 4); + } + } + { + uint32_t fill = 0x08006B8D; uint32_t a; for (a = 0x085F0000; a < 0x08600000; a += 4) { cpu_physical_memory_write(a, &fill, 4); @@ -612,6 +702,20 @@ static void saimaa_machine_init(MachineState *machine) printf("saimaa: uefi tables live\n"); } printf("saimaa: slots live\n"); + /* DDRLOW stub trap overlay: snapshot ELF+stubs, serve on + * read/fetch, ignore heap writes (uni parity). Must come + * AFTER all stub writes above. Priority 2 > RAMs' 1. */ + cpu_physical_memory_read(SAIMAA_DSTUB_BASE, saimaa_dstub_mem, + SAIMAA_DSTUB_SIZE); + { + MemoryRegion *dstub = g_new(MemoryRegion, 1); + memory_region_init_io(dstub, NULL, &saimaa_dstub_ops, + NULL, "saimaa-dstub", + SAIMAA_DSTUB_SIZE); + memory_region_add_subregion_overlap(sysmem, + SAIMAA_DSTUB_BASE, + dstub, 2); + } /* Freeze stubs: any SBL write here now faults loudly * (data abort with DFAR) instead of silent corruption. */ memory_region_set_readonly(saimaa_stubs_mr, true); @@ -624,7 +728,7 @@ static void saimaa_machine_init(MachineState *machine) static void saimaa_post_reset(void *opaque) { - uint32_t S = 0x08006820; + uint32_t S = 0x08006B80; (void)opaque; /* Re-apply pokes that live inside ROM-covered ranges (rom_reset * restores file bytes over them). */ diff --git a/tools/sbl_patch.py b/tools/sbl_patch.py index 6aeadb5..01e67d5 100644 --- a/tools/sbl_patch.py +++ b/tools/sbl_patch.py @@ -1,263 +1,661 @@ #!/usr/bin/env python3 + """sbl_patch.py — reproduce all WIP file patches for SBL1 bring-up. + Usage: ./tools/sbl_patch.py fw/sbl/img0.elf out-dir/ + Produces img0_bl.elf, img0_b2..b8.elf (cumulative variants, see STEPS). + Each step is documented with WHY (all WIP, to be replaced by real PBL). + """ + import os + import sys + + SEG_OFF, SEG_VA = 0x3000, 0x8005800 + + BLX2BL = { # blx-imm -> bl (stay Thumb): set bit12 of 2nd halfword + 0x802F658: ('d8f7c2ed', 'd8f7c2fd'), + 0x802F660: ('d8f702ec', 'd8f702fc'), + } + NOPS = { # call -> NOP NOP (skip unmodellable init/assert; r0/r5 ignored) + 0x802F664: 'e4f77efc', # bl walker (result ignored by caller) + 0x802F672: 'e6f7bbfa', # bl 0x8015bec (assert-hang) + 0x802F678: None, # bl 0x8018a78, see REDIRECT (installer runs!) + 0x801B71E: '9847fee7', # blx r3 + b.n self (terminal assert, 4B) + 0x8016184: 'fdf790fc', # bl 0x8013aa8 (subtree nopped, see b8) + } + REDIRECT = { + # bl target off-by-N fixes (verified by disasm + QEMU traces): + 0x8013AA8: ('07f026be', '07f027be'), # jump-table case0: +2 + 0x802F678: ('e9f7fef9', 'e9f700fa'), # bl 0x8018a78 -> 0x8018a7c (+4) + } + POOLS = { + # literal pool words redirected to owned scratch (orig struct garbage): + 0x801B76C: ('280b0508', '00000708'), # -> 0x08070000 + } + + + def off(va): + return SEG_OFF + (va - SEG_VA) + + + def hx(d, va, n=4): + return d[off(va):off(va) + n].hex() + + + def set4(d, va, hexbytes): + d[off(va):off(va) + 4] = bytes.fromhex(hexbytes) + + + def main(): + src, outdir = sys.argv[1], sys.argv[2] + os.makedirs(outdir, exist_ok=True) + base = bytearray(open(src, 'rb').read()) + + def save(d, name): + p = os.path.join(outdir, name) + open(p, 'wb').write(bytes(d)) + print('wrote', p, len(d)) + + d = bytearray(base) + for va, (a, b) in BLX2BL.items(): + assert hx(d, va) == a, (hex(va), hx(d, va)) + set4(d, va, b) + save(d, 'img0_bl.elf') + + # b2 = bl + walker NOP + o = off(0x802F664) + assert d[o:o + 4].hex() == 'e4f77efc' + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b2.elf') + # b3 = b2 + 0x8015bec NOP + o = off(0x802F672) + assert d[o:o + 4].hex() == 'e6f7bbfa', hx(d, 0x802F672) + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b3.elf') + # b4 = b3 + 0x8018a78 NOP + o = off(0x802F678) + assert d[o:o + 4].hex() == 'e9f7fef9', hx(d, 0x802F678) + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b4.elf') + # b5 = b4 + jump-table +2 + o = off(0x8013AA8) + assert d[o:o + 4].hex() == '07f026be' + d[o:o + 4] = bytes.fromhex('07f027be') + save(d, 'img0_b5.elf') + # b6 = b5 + terminal assert NOP (4B) + o = off(0x801B71E) + assert d[o:o + 4].hex() == '9847fee7' + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b6.elf') + # b7 = b6 + redirect bl to installer entry (undoes b4 NOP) + o = off(0x802F678) + assert d[o:o + 4].hex() == 'c046c046' + d[o:o + 4] = bytes.fromhex('e9f700fa') + save(d, 'img0_b7.elf') + # b8 = b7 + subtree NOP + pool redirect + o = off(0x8016184) + assert d[o:o + 4].hex() == 'fdf790fc', hx(d, 0x8016184) + d[o:o + 4] = bytes.fromhex('c046c046') + o = off(0x801B76C) + assert d[o:o + 4].hex() == '280b0508' + d[o:o + 4] = bytes.fromhex('00000708') + save(d, 'img0_b8.elf') + # b9 = b8 + hole-call NOP (EDL-dead padding slide at 0x80068FC) + o = off(0x8008396) + assert d[o:o + 4].hex() == 'fef780f9', hx(d, 0x8008396) + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b9.elf') + # b10 = b9 with installer-bl reverted to NOP (S2 lands mid-NOPs safely, + # falls into the 0x8013b7c call; installer skipped, structs via maze) + o = off(0x802F678) + assert d[o:o + 4].hex() == 'e9f700fa', hx(d, 0x802F678) + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b10.elf') + # b11 = b10 with installer-bl restored (redirect, installer runs again) + o = off(0x802F678) + assert d[o:o + 4].hex() == 'c046c046' + d[o:o + 4] = bytes.fromhex('e9f700fa') + save(d, 'img0_b11.elf') + # b12: b10-variant with jump-table entry0 NOP (fall into entry1/case-1); + # rebuilt from b10 base (installer stays skipped) + d = bytearray(base) + for va, (a, b) in BLX2BL.items(): + set4(d, va, b) + for va, exp, new in [ + (0x802F664, 'e4f77efc', 'c046c046'), + (0x802F672, 'e6f7bbfa', 'c046c046'), + (0x802F678, 'e9f7fef9', 'c046c046'), + (0x801B71E, '9847fee7', 'c046c046'), + (0x8008396, 'fef780f9', 'c046c046'), + (0x8016184, 'fdf790fc', 'c046c046'), + (0x801B76C, '280b0508', '00000708'), + ]: + assert hx(d, va) == exp, (hex(va), hx(d, va)) + set4(d, va, new) + o = off(0x8013AA8) + assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8) + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b12.elf') + # b15 = b14 + force table-loop count (0x8013ba6 ldrh r0,[r7,#4] -> + # movs r0,#0xff): exit pop reads clobbered slot (->0); looping builds + # tables via helpers instead. 2-byte patch. + d = bytearray(base) + for va, (a, b) in BLX2BL.items(): + set4(d, va, b) + for va, exp, new in [ + (0x802F664, 'e4f77efc', 'c046c046'), + (0x802F672, 'e6f7bbfa', 'c046c046'), + (0x802F678, 'e9f7fef9', 'c046c046'), + (0x801B71E, '9847fee7', 'c046c046'), + (0x8008396, 'fef780f9', 'c046c046'), + (0x8016184, 'fdf790fc', 'c046c046'), + (0x801B76C, '280b0508', '00000708'), + (0x8006684, '01f807bd', '46c046c0'), - (0x8017AFC, 'bde8fc87', 'eef7afbe'), + + (0x8017AFC, 'bde8fc87', 'eef74fb8'), + ]: + assert hx(d, va) == exp, (hex(va), hx(d, va)) + set4(d, va, new) + o = off(0x8013AA8) + assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8) + d[o:o + 4] = bytes.fromhex('c046c046') + o = off(0x8013BA6) + assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2)) + d[o:o + 2] = bytes.fromhex('ff20') + save(d, 'img0_b15.elf') + # b16 = b15 + banner call redirect (0x802f690 blx r3 -> bl banner stub + # at 0x08062150; table[0] is garbage 0x690ce0ee). Stub prints r0 string + # via UARTDM, returns 0 -> falls into cbz path (banner done). + d = bytearray(base) + for va, (a, b) in BLX2BL.items(): + set4(d, va, b) + for va, exp, new in [ + (0x802F664, 'e4f77efc', 'c046c046'), + (0x802F672, 'e6f7bbfa', 'c046c046'), + (0x802F678, 'e9f7fef9', 'c046c046'), + (0x801B71E, '9847fee7', 'c046c046'), + (0x8008396, 'fef780f9', 'c046c046'), + (0x8016184, 'fdf790fc', 'c046c046'), + (0x801B76C, '280b0508', '00000708'), + (0x8006684, '01f807bd', '46c046c0'), - (0x8017AFC, 'bde8fc87', 'eef7afbe'), - (0x802F690, '9847fee7', 'd7f7ecb8'), + + (0x8017AFC, 'bde8fc87', 'eef74fb8'), + + (0x802F690, '9847fee7', 'd7f7ba8c'), + ]: + assert hx(d, va) == exp, (hex(va), hx(d, va)) + set4(d, va, new) + o = off(0x8013AA8) + assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8) + d[o:o + 4] = bytes.fromhex('c046c046') + o = off(0x8013BA6) + assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2)) + d[o:o + 2] = bytes.fromhex('ff20') + save(d, 'img0_b16.elf') + # b14 = b13 + redirect S6 pop (0x8017afc ldmia.w sp!,{r2-r9,sl,pc}) + # to S6 stublet at 0x08062140 (pc slot clobbered to 0 at runtime) + d = bytearray(base) + for va, (a, b) in BLX2BL.items(): + set4(d, va, b) + for va, exp, new in [ + (0x802F664, 'e4f77efc', 'c046c046'), + (0x802F672, 'e6f7bbfa', 'c046c046'), + (0x802F678, 'e9f7fef9', 'c046c046'), + (0x801B71E, '9847fee7', 'c046c046'), + (0x8008396, 'fef780f9', 'c046c046'), + (0x8016184, 'fdf790fc', 'c046c046'), + (0x801B76C, '280b0508', '00000708'), + (0x8006684, '01f807bd', '46c046c0'), - (0x8017AFC, 'bde8fc87', 'eef7afbe'), + + (0x8017AFC, 'bde8fc87', 'eef74fb8'), + ]: + assert hx(d, va) == exp, (hex(va), hx(d, va)) + set4(d, va, new) + o = off(0x8013AA8) + assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8) + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b14.elf') + # b13 = b12 + NOP strb.w fp,[r1,#-7]! at 0x8006684 (helper stores + # fp into struct r1, but r1 arrives NULL from 0x8007e82 chain; + # store to [0-7]=0xfffffff9 faults; struct unrecoverable here) + d = bytearray(base) + for va, (a, b) in BLX2BL.items(): + set4(d, va, b) + for va, exp, new in [ + (0x802F664, 'e4f77efc', 'c046c046'), + (0x802F672, 'e6f7bbfa', 'c046c046'), + (0x802F678, 'e9f7fef9', 'c046c046'), + (0x801B71E, '9847fee7', 'c046c046'), + (0x8008396, 'fef780f9', 'c046c046'), + (0x8016184, 'fdf790fc', 'c046c046'), + (0x801B76C, '280b0508', '00000708'), + (0x8006684, '01f807bd', '46c046c0'), + ]: + assert hx(d, va) == exp, (hex(va), hx(d, va)) + set4(d, va, new) + o = off(0x8013AA8) + assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8) + d[o:o + 4] = bytes.fromhex('c046c046') + save(d, 'img0_b13.elf') + # b17 = b16 with banner call NOPed (blx r3 + b.n self -> NOP NOP): + # logger is non-essential; banner stub tail gets heap-clobbered and + # the 4B bl return lands mid-insn. Fall through to 0x802f694. + d = bytearray(base) + for va, (a, b) in BLX2BL.items(): + set4(d, va, b) + for va, exp, new in [ + (0x802F664, 'e4f77efc', 'c046c046'), + (0x802F672, 'e6f7bbfa', 'c046c046'), + (0x802F678, 'e9f7fef9', 'c046c046'), + (0x801B71E, '9847fee7', 'c046c046'), + (0x8008396, 'fef780f9', 'c046c046'), + (0x8016184, 'fdf790fc', 'c046c046'), + (0x801B76C, '280b0508', '00000708'), + (0x8006684, '01f807bd', '46c046c0'), - (0x8017AFC, 'bde8fc87', 'eef7afbe'), + + (0x8017AFC, 'bde8fc87', 'eef74fb8'), + (0x802F690, '9847fee7', 'c046c046'), + ]: + assert hx(d, va) == exp, (hex(va), hx(d, va)) + set4(d, va, new) + o = off(0x8013AA8) + assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8) + d[o:o + 4] = bytes.fromhex('c046c046') + o = off(0x8013BA6) + assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2)) + d[o:o + 2] = bytes.fromhex('ff20') + save(d, 'img0_b17.elf') + + + + # b18 = b17 + S7 exit router (delay-epilogue leak fix): + # 0x8013BAC pop{r3-r7,pc} pops 0x18 with no matching push; S3-slot + # restarts (0x8013BA5) multiply it into a stack-eating cycle + # (slots->fill->zeros->null-loop->IMEM overflow). Route the epilogue + # to S7 (0x08006C10, scaffold stublet): r4<=0xff -> restart loop, + # else restore r4/r5/sl/fp and return to sbl_main (0x802f681). + # NOTE: overwrites BAE-path head (ldr r2,[pc,#0x28], only used when + # [r7]==2 — never in our flow, r7=0). + d = bytearray(base) + + for va, (a, b) in BLX2BL.items(): + + set4(d, va, b) + + for va, exp, new in [ + + (0x802F664, 'e4f77efc', 'c046c046'), + + (0x802F672, 'e6f7bbfa', 'c046c046'), + + (0x802F678, 'e9f7fef9', 'c046c046'), + + (0x801B71E, '9847fee7', 'c046c046'), + + (0x8008396, 'fef780f9', 'c046c046'), + + (0x8016184, 'fdf790fc', 'c046c046'), + + (0x801B76C, '280b0508', '00000708'), + + (0x8006684, '01f807bd', '46c046c0'), + + (0x8017AFC, 'bde8fc87', 'eef74fb8'), + + (0x802F690, '9847fee7', 'c046c046'), + + (0x8013BAC, 'f8bd0a4a', 'f3f730b8'), + + ]: + + assert hx(d, va) == exp, (hex(va), hx(d, va)) + + set4(d, va, new) + + o = off(0x8013AA8) + + assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8) + + d[o:o + 4] = bytes.fromhex('c046c046') + + o = off(0x8013BA6) + + assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2)) + + d[o:o + 2] = bytes.fromhex('ff20') + + save(d, 'img0_b18.elf') + + + + + + # b19 = b17 + 0x80065d6 ldrb r0,[r1] -> movs r0,#2 (NULL table entry: + + # type 2 takes exit-ish path instead of faulting on bx lr=0) + + d = bytearray(base) + + for va, (a, b) in BLX2BL.items(): + + set4(d, va, b) + + for va, exp, new in [ + + (0x802F664, 'e4f77efc', 'c046c046'), + + (0x802F672, 'e6f7bbfa', 'c046c046'), + + (0x802F678, 'e9f7fef9', 'c046c046'), + + (0x801B71E, '9847fee7', 'c046c046'), + + (0x8008396, 'fef780f9', 'c046c046'), + + (0x8016184, 'fdf790fc', 'c046c046'), + + (0x801B76C, '280b0508', '00000708'), + + (0x8006684, '01f807bd', '46c046c0'), + + (0x8017AFC, 'bde8fc87', 'eef74fb8'), + + (0x802F690, '9847fee7', 'c046c046'), + + ]: + + assert hx(d, va) == exp, (hex(va), hx(d, va)) + + set4(d, va, new) + + o = off(0x8013AA8) + + assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8) + + d[o:o + 4] = bytes.fromhex('c046c046') + + o = off(0x8013BA6) + + assert d[o:o + 2].hex() == 'b888', (hex(0x8013BA6), hx(d, 0x8013BA6, 2)) + + d[o:o + 2] = bytes.fromhex('ff20') + + o = off(0x80065D6) + + assert d[o:o + 2].hex() == '0878', (hex(0x80065D6), d[o:o+2].hex()) + + d[o:o + 2] = bytes.fromhex('0220') + + save(d, 'img0_b19.elf') + + # b20 = b19 with 0x80065d6 movs#2 -> movs#1 (helper returns 1: + # caller cmp/beq falls through to cmp #0x25 path instead of skip) + + o = off(0x80065D6) + + assert d[o:o + 2].hex() == '0220' + + d[o:o + 2] = bytes.fromhex('0120') + + save(d, 'img0_b20.elf') + + + + + if __name__ == '__main__': + main() + diff --git a/tools/sbl_uni.py b/tools/sbl_uni.py new file mode 100644 index 0000000..88a8b6b --- /dev/null +++ b/tools/sbl_uni.py @@ -0,0 +1,506 @@ +#!/usr/bin/env python3 +"""sbl_uni.py — быстрый harness SBL1 на Unicorn (без пересборки QEMU). +Карта RAM как в saimaa; ELF-сегменты; UARTDM/TF в stdout; MMIO->0. +Использование: + ./tools/sbl_uni.py fw/img/patch/img0_b17.elf --thumb 0x802f63f [--regs r0=0x220000,...] + [--fill 0x085F0000:0x10000:0x08006831] [--poke 0xADDR=0xVAL] [--limit 1000000] +Печатает: выполнено инстр., последний PC, fault-адрес, UART. +""" +import struct +import sys + +from unicorn import (Uc, UC_ARCH_ARM, UC_MODE_THUMB, UC_HOOK_MEM_UNMAPPED, + UC_HOOK_MEM_WRITE, UC_HOOK_CODE) +from unicorn.arm_const import (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, + UC_ARM_REG_R3, UC_ARM_REG_R4, UC_ARM_REG_R5, + UC_ARM_REG_R6, UC_ARM_REG_R7, UC_ARM_REG_SP, + UC_ARM_REG_LR, UC_ARM_REG_PC, + UC_ARM_REG_CPSR) +_REGS = (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, + UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R6, UC_ARM_REG_R7) + +RAMS = [ + (0x00000000, 0x00001000), # zero page (QEMU-compat: [0] writes succeed) + (0x00200000, 0x00100000), # OCIMEM + (0x00400000, 0x07400000), # RPM range (heap-ish) + (0x08000000, 0x00600000), # DDRLOW + (0x08600000, 0x00010000), # IMEM + (0x80000000, 0x07C00000), # DDRHIGH_A + (0x87C00000, 0x00800000), # SMEM(+SBL BSS) + (0x078AF000, 0x00001000), # UARTDM page + (0x13000000, 0x00002000), # UEFI tables (already above, keep) +] +UART_TF = 0x078AF070 +UART_SR = 0x078AF008 + + +def load_elf(mu, path): + d = open(path, "rb").read() + assert d[:4] == b"\x7fELF" + entry = struct.unpack(" maze + mu.reg_write(UC_ARM_REG_CPSR, 0x13) + if "--fill" in sys.argv: + for spec in sys.argv[sys.argv.index("--fill") + 1].split(","): + a, sz, v = spec.split(":") + a, sz, v = int(a, 0), int(sz, 0), int(v, 0) + mu.mem_write(a, struct.pack(" _spmax[0] and len(_splog) < 40: + _spmax[0] = sp + _splog.append((count[0], addr, sp)) + if _dirty[0]: + # deferred restore: MEM_WRITE hooks fire BEFORE the write lands, + # so in-hook restore gets overwritten by the instruction itself + _inww[0] = True + try: + for a, b in _dirty[1]: + mu.mem_write(a, stub_snap[a - STUB_LO:b - STUB_LO]) + finally: + _inww[0] = False + _dirty[0] = False + del _dirty[1][:] + try: + thumb = (mu.reg_read(_CPSR) >> 5) & 1 + except Exception: + thumb = 1 + hist.append((addr, thumb)) + if qlog is not None and addr in (0x8017EA4, 0x8017C60): + try: + vals = [mu.reg_read(_REGS[i]) for i in range(8)] + except Exception: + vals = [] + qlog.append((addr, vals, + mu.reg_read(UC_ARM_REG_SP), + mu.reg_read(UC_ARM_REG_LR))) + if elog is not None: + for _i, (_elo, _ehi) in enumerate(_eranges): + inside = _elo <= addr < _ehi + if inside and not _inrng[_i]: + if len(elog) < 20000: + elog.append((count[0], _prev[0], addr)) + _inrng[_i] = inside + _prev[0] = addr + if calls is not None and size in (2, 4): + try: + if size == 2: + h = struct.unpack("> 3) & 7 if is_blx else -1 + # bx Rm (jump, no link): 010001110 Rm:000 + is_bx = (h & 0xFF80) == 0x4700 + if is_bx: + bxrm = (h >> 3) & 15 + try: + if bxrm < 8: + bxt = mu.reg_read(_REGS[bxrm]) + elif bxrm == 14: + bxt = mu.reg_read(_LR) + else: + bxt = -1 + except Exception: + bxt = -1 + if len(calls) < 8000: + calls.append((addr, 100 + bxrm, bxt)) + if rets is not None and 0x8017E00 <= addr < 0x8018000: + # pop {..,pc} (0xBD00+) or bx lr (0x4770) + if h == 0x4770 or (h & 0xFF00) == 0xBD00: + try: + rv = mu.reg_read(_REGS[0]) + except Exception: + rv = -1 + if len(rets) < 200: + rets.append((addr, rv)) + else: + w = struct.unpack("> 16) & 0xFFFF + if (h0 & 0xF800) == 0xF000 and (h1 & 0xE000) == 0xE000: + S = (h0 >> 10) & 1 + J1 = (h1 >> 13) & 1 + J2 = (h1 >> 11) & 1 + I1 = 1 - (J1 ^ S) + I2 = 1 - (J2 ^ S) + off = (I1 << 23) | (I2 << 22) | ((h0 & 0x3FF) << 12) | ((h1 & 0x7FF) << 1) + if S: + off -= 0x1000000 + if len(calls) < 8000: + calls.append((addr, 200, (addr + 4 + off) & 0xFFFFFFFF)) + if rets is not None and 0x8017E00 <= addr < 0x8018000: + # pop.w {...,pc}: 0xE8BDxxxx with bit15 set + if (w & 0xFFFF8000) == 0xE8BD8000: + try: + rv = mu.reg_read(_REGS[0]) + except Exception: + rv = -1 + if len(rets) < 200: + rets.append((addr, rv)) + if is_blx: + try: + tgt = mu.reg_read(_REGS[rm]) + except Exception: + tgt = -1 + if len(calls) < 4000: + calls.append((addr, rm, tgt)) + except Exception: + return + + mu.hook_add(UC_HOOK_CODE, hook_code) + # null-fetch redirect: branch-to-0 lands in maze (movs r0,#0; bx lr), + # immune to [0]-writes that clobber a static maze copy + from unicorn.arm_const import UC_ARM_REG_PC as _PC + _nulllog = [] + + def hook_null(mu, addr, size, data): + if len(_nulllog) < 10: + try: + r = [mu.reg_read(_REGS[i]) for i in range(4)] + lr = mu.reg_read(_LR) + sp = mu.reg_read(UC_ARM_REG_SP) + except Exception: + r, lr, sp = [], -1, -1 + # walk back: find the branch source from hist + _nulllog.append((count[0], r, lr, sp)) + mu.reg_write(_PC, STUB + 1) + + mu.hook_add(UC_HOOK_CODE, hook_null, begin=0x0, end=0xFFF) + if "--watchcode" in sys.argv: + from unicorn.arm_const import UC_ARM_REG_PC as _PC2 + wlog = [] + + def hook_ww(mu, access, addr, size, value, data): + if len(wlog) < 30: + try: + pc = mu.reg_read(_PC2) + except Exception: + pc = -1 + wlog.append((pc, addr, size, value)) + + mu.hook_add(UC_HOOK_MEM_WRITE, hook_ww, + begin=0x8005800, end=0x8039254) + import atexit + + def _dump(): + print(f"code-writes: {len(wlog)}") + for pc, a, sz, v in wlog[:30]: + print(f" pc={pc:#x} [{a:#x}] sz={sz} val={v:#x}") + atexit.register(_dump) + # --- machine scaffold parity (saimaa.c values, verbatim) --- + def W32(a, v): + mu.mem_write(a, struct.pack(" restart 0x8013ba5) or done + # (restore r4/r5/sl/fp, return to sbl_main 0x802f681). + mu.mem_write(0x08006C10, bytes.fromhex( + "ff2c13d940f20004c0f2220440f20005c0f2220540f2000a" + "c0f6060a41f2000bc0f6060bdff800f081f60208dff800f0" + "a53b0108")) + # stack zone fill (STUBV odd) + mu.mem_write(0x085F0000, struct.pack("= STUB_HI: + return + _swn[0] += 1 + if len(_swlog) < 8: + try: + _pc = mu.reg_read(_PC) + except Exception: + _pc = -1 + _swlog.append((_pc, addr, size)) + # record for deferred restore (see hook_code); in-hook restore + # would be overwritten by the faulting instruction itself + a = max(addr, STUB_LO) + b = min(addr + size, STUB_HI) + if b > a and len(_dirty[1]) < 16: + _dirty[1].append((a, b)) + _dirty[0] = True + + #hook all DDRLOW, filter in python (catches writes starting below LO) + mu.hook_add(UC_HOOK_MEM_WRITE, hook_stubw, + begin=0x08000000, end=0x085FFFFF) + if "--watchmaze" in sys.argv: + from unicorn.arm_const import UC_ARM_REG_PC as _PC3 + mzlog = [] + + def hook_mz(mu, access, addr, size, value, data): + if len(mzlog) < 40: + try: + pc = mu.reg_read(_PC3) + except Exception: + pc = -1 + mzlog.append((pc, addr, size, value)) + + mu.hook_add(UC_HOOK_MEM_WRITE, hook_mz, + begin=0x08006B8C, end=0x08006B9D) + import atexit as _ax + + def _dmz(): + print(f"maze-writes: {len(mzlog)}") + for pc, a, sz, v in mzlog[:40]: + print(f" pc={pc:#x} [{a:#x}] sz={sz} val={v:#x}") + _ax.register(_dmz) + if "--watchlow" in sys.argv: + _spec = sys.argv[sys.argv.index("--watchlow") + 1].split(":") + _wlo, _whi = int(_spec[0], 0), int(_spec[0], 0) + int(_spec[1], 0) + from unicorn.arm_const import UC_ARM_REG_PC as _PC4 + wlolog = [] + + def hook_wlo(mu, access, addr, size, value, data): + if len(wlolog) < 60: + try: + pc = mu.reg_read(_PC4) + except Exception: + pc = -1 + wlolog.append((pc, addr, size, value)) + + mu.hook_add(UC_HOOK_MEM_WRITE, hook_wlo, + begin=0x08000000, end=0x085FFFFF) + import atexit as _ax2 + + def _dwlo(): + inn = [(pc, a, sz, v) for pc, a, sz, v in wlolog + if not (a + sz <= _wlo or a >= _whi)] + print(f"low-writes-in-range: {len(inn)}") + for pc, a, sz, v in inn[:60]: + print(f" pc={pc:#x} [{a:#x}] sz={sz} val={v:#x}") + _ax2.register(_dwlo) + limit = 2000000 + if "--limit" in sys.argv: + limit = int(sys.argv[sys.argv.index("--limit") + 1]) + try: + mu.emu_start(thumb | 1, 0xFFFFFFFF, 0, limit) + except Exception as e: + print("EMU-EXC:", str(e)[:120]) + print("insns:", count[0], "last-pc:", hex(last[0])) + print("unmapped:", unmapped["n"], unmapped["first"][:4]) + print("regs:", + " ".join(f"r{i}={mu.reg_read(_REGS[i]):#x}" for i in range(8)), + f"sp={mu.reg_read(UC_ARM_REG_SP):#x}", + f"lr={mu.reg_read(UC_ARM_REG_LR):#x}", + f"pc={mu.reg_read(UC_ARM_REG_PC):#x}") + try: + from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB, CS_MODE_ARM + md_t = Cs(CS_ARCH_ARM, CS_MODE_THUMB) + md_a = Cs(CS_ARCH_ARM, CS_MODE_ARM) + real = [(pc, th) for pc, th in hist + if not (0 <= (pc & ~1) < 0x1000)][-48:] + for pc, thumb in real: + try: + base = pc & ~1 + code = mu.mem_read(base, 8 if thumb else 12) + md = md_t if thumb else md_a + for ins in md.disasm(code, base): + print(f" {base:#x}{'t' if thumb else 'a'}: {ins.mnemonic} {ins.op_str}") + break + except Exception: + print(f" {pc:#x}: ") + except ImportError: + print("hist:", [hex(pc) for pc, _ in list(hist)[-12:]]) + if elog is not None: + print(f"entries: {len(elog)}") + for n, prev, cur in elog[-30:]: + print(f" insn#{n} {prev:#x} -> {cur:#x}") + if _splog is not None: + print(f"sp-highs: {len(_splog)}") + for n, pc, sp in _splog: + print(f" insn#{n} {pc:#x} sp={sp:#x}") + if _nulllog: + print(f"null-entries: {len(_nulllog)}") + for n, r, lr, sp in _nulllog: + print(f" insn#{n} r0-r3={[hex(x) for x in r]} lr={lr:#x} sp={sp:#x}") + if qlog is not None: + print(f"querylog: {len(qlog)}") + for pc, r, sp, lr in qlog[:20]: + print(f" {pc:#x} r0-r7={[hex(x) for x in r]} sp={sp:#x} lr={lr:#x}") + if rets is not None: + print(f"rets-in-range: {len(rets)}") + for pc, rv in rets[-20:]: + print(f" ret {pc:#x} r0={rv:#x}" if rv >= 0 else f" ret {pc:#x} r0=?") + if calls is not None: + print(f"blx-calls: {len(calls)}") + bls = [(pc, tgt) for pc, rm, tgt in calls if rm == 200] + print(f"bl-immed: {len(bls)}") + for pc, tgt in bls[-30:]: + print(f" {pc:#x}: bl #{tgt:#x}") + for pc, rm, tgt in calls[-12:]: + if rm == 200: + continue + tag = f"bx r{rm - 100}" if rm >= 100 else f"blx r{rm}" + print(f" {pc:#x}: {tag} -> {tgt:#x}" if tgt >= 0 else f" {pc:#x}: {tag} -> ?") + if "--dumpmem" in sys.argv: + for spec in sys.argv[sys.argv.index("--dumpmem") + 1].split(","): + a, sz = spec.split(":") + a, sz = int(a, 0), int(sz, 0) + print(f"mem[{a:#x}+{sz:#x}]:") + print(bytes(mu.mem_read(a, sz)).hex()) + print("UART:", "".join(uart_out)[:160]) + print(f"stubguard-hits: {_swn[0]}") + for _pc, _a, _s in _swlog: + print(f" guarded pc={_pc:#x} [{_a:#x}] sz={_s}") + + +if __name__ == "__main__": + main()