Compare commits

..

27 Commits

Author SHA1 Message Date
sasheg 4be2c8dc76 tools: add sled (zero-drift) detector + capstone disassembly history in trap handler 2026-09-13 17:32:57 +00:00
lumia-emu 95e52319f9 IMEM poison drift-trap 2026-09-13 12:25:25 +00:00
lumia-emu 1478602b70 b29: S18 poll-epilogue router 2026-09-13 12:23:15 +00:00
lumia-emu 2f0c82f3f7 b28: poll-epilogue march kill 2026-09-13 12:21:50 +00:00
lumia-emu db3e10e31a S12v5: r7 restore in-stub 2026-09-13 12:20:35 +00:00
lumia-emu a81be9b1c3 S12v4 smart dispatcher baked 2026-09-13 12:16:43 +00:00
lumia-emu 751fb052d4 S16v4: reject STUBV return 2026-09-13 12:15:25 +00:00
lumia-emu 7af645ef45 S16v3: add sp un-leak 2026-09-13 12:13:59 +00:00
lumia-emu 7a77e33eda b27: poll re-init skip + bits pref-set 2026-09-13 12:12:51 +00:00
lumia-emu 42227147d4 b26: S17 safe-logger baked 2026-09-13 12:08:22 +00:00
lumia-emu 4e38215e86 b25: S12v3/S16v2 smart-return baked 2026-09-13 12:05:17 +00:00
lumia-emu 7eef1340b6 b25: S12v2-full + B66-ELF + S16 baked 2026-09-13 12:01:19 +00:00
lumia-emu 48969c60f3 b24 scaffold: S12 + poll-bit2 baked, default b24 2026-09-13 11:37:15 +00:00
lumia-emu ff4c3a54a0 b24: world-switch stub + clock S11 2026-09-13 11:29:17 +00:00
lumia-emu 98c69e2740 b23: parity docs, run-sbl default b23 2026-09-13 10:40:29 +00:00
lumia-emu 830283e12b b23: S10 + table poke + S9 baked 2026-09-13 10:37:18 +00:00
lumia-emu 8388cc0a30 b22: bake 6BE->S9 patch 2026-09-13 10:32:06 +00:00
lumia-emu 063b67318e b22: rollback NOP + S9v3 baked (sp reset) 2026-09-13 10:31:40 +00:00
lumia-emu 285a054d6d b21: logger S8 (pop.w -> bx lr) 2026-09-13 10:25:22 +00:00
lumia-emu 11e358e644 run-sbl default b18 2026-09-13 00:23:42 +00:00
lumia-emu 95352f7818 b18 parity: docs, run-sbl, vec/dstub logging 2026-09-13 00:23:21 +00:00
lumia-emu f5bddadbce b18: single 6B idiom NOP (order fix) 2026-09-13 00:19:10 +00:00
lumia-emu c524cc5c60 b18: NOP full bl idiom (dangling prefix fix) 2026-09-13 00:18:14 +00:00
lumia-emu 18648528c0 saimaa: vec maze+sctlr logging 2026-09-13 00:10:23 +00:00
lumia-emu 41a09121eb saimaa: fix cp15 reg names 2026-09-13 00:08:17 +00:00
lumia-emu 3adf12aa57 saimaa: log DFAR/IFSR/DFSR in vec trap 2026-09-13 00:07:29 +00:00
lumia-emu 27f9e1133e saimaa: vec serves maze to Thumb (null-fetch parity) 2026-09-13 00:04:55 +00:00
5 changed files with 1211 additions and 15 deletions
+80
View File
@@ -64,3 +64,83 @@
Фронтир b18: sbl_main возвращается, новый код `0x801Bxxx`
(`strb [r7,#2]`-паттерн, b17-NOP на 0x801B71E), падеж pop'ом пустого
стека у верха IMEM — следующий фронт.
## b18 parity (QEMU == uni, 2026-09-12)
- QEMU поправлен до паритета: dstub-overlay + vec-maze для Thumb
(null-fetch исполняет maze и возвращается) + убран висячий префикс
bl (bogus odd store -> alignment fault, DFAR=0x8006B8D).
- QEMU b18 доходит до того же фронтира, что uni b18 (38961 инстр.):
`0x801Bxxx`, r1=0x80522A4, r2/r3=STUBV, lr=0x801B73F, sp марширует
к верху IMEM. Смерти чуть различаются по таймингу (uni: read
0x8610000; QEMU: null-jump → maze-hang c lr-top 0x80), регион тот же.
- Следующий фронт (оба): лабиринт `0x801B6xx` (логгер, r7=0/r4=0,
pop пустого стека). Кандидаты: S8-стаблет, таблица логгера.
## b21-b23: логгер, S9, S10, rollback (2026-09-12)
- Логгер `0x8034810` = голый `pop.w 0x28` без push (жрал стек) → S8:
вход → `bx lr` (b21). Возврат игнорируется (callers ставят r0=0).
- Лабиринт `0x801B6A0`: счётчик `[r4]++` + `pop.w` на пустых фреймах
(r4/r7=STUBV из fill) → S9 (b22): `[sp+0x14]` ненулевой → исходный
pop; ноль → restore r0/r4/r5/sl/fp + **reset sp=0x085FFF00** +
прыжок в sbl_main `0x802f69d` через ip (без литералов).
- Rollback `boot_rollback_version.c:580` (query всегда `0x302E`) → NOP NOP
(b22, возврат игнорируется). Query зовёт `*table[0x080528D0]` (нули) →
фабрикация: poke STUBV (maze вернёт 0 = версии OK) (b23).
- Rollback выходил боком в helper-mid (`pop{r4,pc}` без push) → S10 (b23):
`movw/movt ip + bx ip` в sbl_main `0x802f6a3`.
- b23: 2M инстр. без падений (sp здоров), sbl_main идёт дальше
(`0x80164C4`-subtree → прыжок в OCIMEM-нули `0x222F84` — фронт PBL-данных).
QEMU-паритет: R15=0x223000, sp=0x85FFF00, lr=0x80164CB — там же.
## b24: world-switch stub, clock S11 (2026-09-12)
- Диспетчер (`0x80164C4`-subtree) зовёт ARM jump-table `0x8007158`
(`ldr pc`), та — в OCIMEM-трамплин world-switch `0x221EF8`
(ARM: save regs, `cps MON`, `mrc`, `ldr sp/pc` из PBL-таблиц
`[0x221F6C]=0x223620`/`[0x221F70]=0x223930` → вызов TZ; TZ не загружен).
Вход получается в Thumb → b24-стаб (4B `movs r0,#0; bx lr`):
возврат 0 в диспетчер (TZ deferred). Перезаписывает голову ARM'а
(восстановимо из ELF).
- Дальше: clock-fn `0x800E960` (udiv-подсчёты) с голым выходом
`pop{r4,pc}` (без push, tail-chain через NOP-идиому) → S11 (b24):
вход → `bx lr` (lr = брошенный-но-валидный `0x80076CD`).
- Poll `[0x73A100]` (lsls#29+bpl = проверка bit2!): SBL только читает →
фабрикация bit2=1 (b24 scaffold, обе стороны).
- `blx #0x8006964` в файловые нули паддинга (PBL scratch) → S12v2 (b25
scaffold, ARM! т.к. blx-imm на чётный): preset ip=S16 + ACK bit1
`[0x73A100]` (side effect для poll#2) + `bx lr`. Литерал `[0x8006980]`.
- Helper-pop `0x8007B66` читает scratch (счётчики 0-4 вместо pushed lr,
фреймы съедены null-циклами) → S16v2 (b25 smart): B66 (`pop``bx ip`,
2B) → S16 проверяет ip (odd + top 8 = legit pushed-lr → `bx ip`;
иначе default: restore r7, прыжок `0x8007779`). S12v3 грузит ip из
`[sp+4]` (pushed lr) + ACK bit1. B66-патч в ELF (b25), S12/S16 в scaffold.
## b26: S17 safe-logger (2026-09-13)
- S8 (`bx lr`) гасил весь init-лог (UART пуст by design) вслепую.
S17 (50B @0x08006CF0): печать r1-строки в UARTDM-TF, только если r1
в rodata `[0x804B800,0x8058000)` (wild-указатели скипаются), затем
pop-return (без leak'а, возврат игнорируется). Логгер `0x8034810`
(`pop.w` 4B) → `b.w S17`. Видимость прогресса вместо счёта инстр.
## b27: poll re-init skip (2026-09-13)
- Poll-fn `0x8007748`: `cbz r6` (r6=2 константа caller'а, никогда 0) →
вечный re-init (`movs r4,#0`, table loop) вместо poll#2-direct.
Патч 2B→2B в `b`. Биты 0,1,2 `[0x73A100]` pref-set (scaffold).
- S16v3: `add sp,#8` перед `bx ip` (снимает helper-фрейм, пропущенный
B66 — un-leak стека вниз).
- S16v4: отбраковка `ip==STUBV` (fill/maze — никогда legit return) →
default вместо возврата в maze (разрыв maze-B66 петли).
- S12v4 (scaffold, без ELF): smart dispatcher в самом S12 (проверка
pushed-lr + ACK + возврат) — S16 больше не нужен в цепочке (остаётся
как fallback).
- S12v5: +restore r7 (peripheral base, S16 dormant) прямо в S12.
## b28: poll-epilogue march kill (2026-09-13)
- Poll-fn входит mid-way (без prologue-push), выходит через
`add sp,#0x1C + pop.w 0x14` (+0x30/проход, пушей нет) → марш вверх
через fill в IMEM-нули → fault на верху. `add sp` → NOP (2B, b28).
Pop читает helper-фреймы (pushed-lr, валидные возвраты).
## b29: S18 poll-epilogue router (2026-09-13)
- b28-NOP убрал только +0x1C; `pop.w 0x14` без пушей всё равно марширует.
S18 (10B @0x08006D22): слепой прыжок в sbl_main `0x802F6A9`
(post-dispatcher). Эпилог `pop.w` (4B) → `b.w S18`.
+1 -1
View File
@@ -5,7 +5,7 @@
set -e
cd "$(dirname "$0")/.."
QEMU=~/qemu-src/qemu-8.2.2/build/qemu-system-arm
B=${1:-b17}
B=${1:-b29}
IMG=fw/img/emmc-real.img
if [ ! -f "$IMG" ]; then
echo "no $IMG — сначала: python3 tools/ffu_extract.py fw/dl/RM1127_059X5R5_orig.ffu --emmc-user fw/img/user-real.bin"
+194 -6
View File
@@ -104,15 +104,15 @@ 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).
/* DDRLOW stub trap [0x08006B80,0x08006D30): serves the scaffolded bytes
* (S/maze/S6/banner/S7/S9/S10/S16/S17 stubs) on read/fetch, ignores SBL
* heap-clobber writes. Uni parity: deferred-restore guard in sbl_uni.py
* (wider: [0x080068C0,0x08006D30), 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
#define SAIMAA_DSTUB_SIZE 0x000001B0
static uint8_t saimaa_dstub_mem[SAIMAA_DSTUB_SIZE];
static uint64_t saimaa_dstub_read(void *opaque, hwaddr off, unsigned size)
@@ -167,13 +167,45 @@ struct SaimaaUartState {
static int saimaa_vec_n;
static uint64_t saimaa_vec_read(void *opaque, hwaddr off, unsigned size)
{
if (saimaa_vec_n < 400) {
/* Maze blob mirrored at [0,18) for THUMB fetches: null-jump fetches
* (bx r0=0, pop 0, ldr pc,=0) execute the maze and return via lr —
* uni parity (hook_null -> STUB). ARM-mode fetches (real exception
* entry) keep BKPT vectors below. Data probes elsewhere read 0. */
static const uint8_t maze[18] = {
0x00, 0x20, 0x4f, 0xea, 0x1e, 0x6c, 0xbc, 0xf1,
0x08, 0x0f, 0x00, 0xd1, 0x70, 0x47, 0x72, 0xb6,
0xfd, 0xe7,
};
{
ARMCPU *cpu = ARM_CPU(current_cpu);
if (cpu && cpu->env.thumb && off < sizeof(maze)) {
uint64_t v = 0;
unsigned i, n = size;
if (off + n > sizeof(maze)) {
n = sizeof(maze) - off;
}
for (i = 0; i < n; i++) {
v |= (uint64_t)maze[off + i] << (8 * i);
}
return v;
}
} if (saimaa_vec_n < 400) {
ARMCPU *cpu = ARM_CPU(current_cpu);
uint64_t pc = cpu ? cpu->env.regs[15] : 0;
int th = cpu ? cpu->env.thumb : 0;
uint32_t dfar = 0, ifsr = 0, dfsr = 0;
if (cpu) {
/* CP15 DFAR/IFSR/DFSR for abort diagnosis */
dfar = cpu->env.cp15.dfar_s;
ifsr = (uint32_t)cpu->env.cp15.ifsr_s;
dfsr = (uint32_t)cpu->env.cp15.dfsr_s;
fprintf(stderr, " SCTLR=0x%lx", (unsigned long)cpu->env.cp15.sctlr_s);
}
fprintf(stderr, "saimaa-vec: %s off=0x%x pc=0x%lx",
size == 2 ? "fetch?" : "read",
(unsigned)off, (unsigned long)pc - (th ? 4 : 8));
fprintf(stderr, " DFAR=0x%x IFSR=0x%x DFSR=0x%x", dfar, ifsr,
dfsr);
if (cpu) {
int b, r;
fprintf(stderr, " regs=");
@@ -622,6 +654,153 @@ static void saimaa_machine_init(MachineState *machine)
cpu_physical_memory_write(0x08006C10 + k, &word, 4);
}
}
/* S9 stublet at 0x08006C48 (62B, b22): labyrinth-epilogue
* exit router (0x801B6BE pop.w -> b.w S9). Empty frames ->
* restore r0/r4/r5/sl/fp, reset sp to 0x085FFF00, jump to
* sbl_main 0x802f69d via ip. Assembled with keystone. */
{
static const uint8_t s9[] = {
0x05, 0x9b, 0x0b, 0xb1, 0xbd, 0xe8, 0xf0, 0x81,
0x00, 0x20, 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, 0x4f, 0xf6, 0x00, 0x73, 0xc0, 0xf6,
0x5f, 0x03, 0x9d, 0x46, 0x4f, 0xf2, 0x9d, 0x6c,
0xc0, 0xf6, 0x02, 0x0c, 0x60, 0x47,
};
size_t k;
for (k = 0; k < sizeof(s9); k += 4) {
uint32_t word = 0;
size_t n = sizeof(s9) - k < 4 ? sizeof(s9) - k : 4;
__builtin_memcpy(&word, &s9[k], n);
cpu_physical_memory_write(0x08006C48 + k, &word, 4);
}
}
/* S10 stublet at 0x08006C88 (10B, b23): rollback-exit router.
* movw/movt ip + bx ip to sbl_main 0x802f6a3. */
{
static const uint8_t s10[] = {
0x4f, 0xf2, 0xa3, 0x6c, 0xc0, 0xf6, 0x02, 0x0c,
0x60, 0x47,
};
size_t k;
for (k = 0; k < sizeof(s10); k += 4) {
uint32_t word = 0;
size_t n = sizeof(s10) - k < 4 ? sizeof(s10) - k : 4;
__builtin_memcpy(&word, &s10[k], n);
cpu_physical_memory_write(0x08006C88 + k, &word, 4);
}
}
/* S18 stublet at 0x08006D22 (10B, b29): poll-epilogue router.
* movw/movt ip + bx ip to sbl_main 0x802f6a9. */
{
static const uint8_t s18[] = {
0x4f, 0xf2, 0xa9, 0x6c, 0xc0, 0xf6, 0x02, 0x0c,
0x60, 0x47,
};
size_t k;
for (k = 0; k < sizeof(s18); k += 4) {
uint32_t word = 0;
size_t n = sizeof(s18) - k < 4 ? sizeof(s18) - k : 4;
__builtin_memcpy(&word, &s18[k], n);
cpu_physical_memory_write(0x08006D22 + k, &word, 4);
}
}
/* S17 safe-logger (50B @0x08006CF0, b26): print r1-string
* via UARTDM-TF iff r1 in rodata [0x804B800,0x8058000);
* pop-return (no leak). Replaces S8 silence with logs. */
{
static const uint8_t s17[] = {
0x4b, 0xf6, 0x00, 0x03, 0xc0, 0xf6, 0x04, 0x03,
0x99, 0x42, 0x11, 0xd3, 0x48, 0xf2, 0x00, 0x03,
0xc0, 0xf6, 0x05, 0x03, 0x99, 0x42, 0x0b, 0xd2,
0x10, 0xb5, 0x4f, 0xf2, 0x00, 0x03, 0xc0, 0xf2,
0x8a, 0x73, 0x0c, 0x46, 0x14, 0xf8, 0x01, 0x0b,
0x08, 0xb1, 0x18, 0x67, 0xfa, 0xe7, 0x10, 0xbd,
0x70, 0x47,
};
size_t k;
for (k = 0; k < sizeof(s17); k += 4) {
uint32_t word = 0;
size_t n = sizeof(s17) - k < 4 ? sizeof(s17) - k : 4;
__builtin_memcpy(&word, &s17[k], n);
cpu_physical_memory_write(0x08006CF0 + k, &word, 4);
}
}
/* PBL version-table fabrication (b23): [0x080528D0] = STUBV
* so the rollback query's blx calls maze (0 = versions OK). */
{
uint32_t v = 0x08006B8D;
cpu_physical_memory_write(0x080528D0, &v, 4);
}
/* HW-ready bits (b25/b26): [0x073A100] bits 0,1,2 (SBL
* read-only polls: bit2 poll#1, bit1 poll#2, bit0 gate). */
{
uint32_t v = 0x00000007;
cpu_physical_memory_write(0x073A100, &v, 4);
}
/* PBL world-switch Thumb-stub (b24, 4B @0x00221EF8):
* movs r0,#0; bx lr (return 0 to dispatcher, TZ deferred).
* Overwrites ARM trampoline head (recoverable from ELF). */
{
uint32_t v = 0x47702000;
cpu_physical_memory_write(0x00221EF8, &v, 4);
}
/* S12-stub (b27, 92B @0x08006964, ARM): blx-to-padding
* becomes smart dispatcher: ldr ip,[sp,#4] (=pushed lr);
* ip==STUBV/even/non-code => ip=poll resumption 0x8007779;
* else keep pushed-lr; restore r7 (peripheral base);
* ACK peripheral bit1 ([literal 0x073A100]); bx lr.
* PBL would place the real ARM helper here. */
{
static const uint8_t s12[] = {
0x04, 0xc0, 0x9d, 0xe5, 0x8d, 0x3b, 0x06, 0xe3,
0x00, 0x38, 0x40, 0xe3, 0x03, 0x00, 0x5c, 0xe1,
0x07, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x1c, 0xe3,
0x05, 0x00, 0x00, 0x0a, 0x2c, 0x3c, 0xa0, 0xe1,
0x08, 0x00, 0x53, 0xe3, 0x04, 0x00, 0x00, 0x0a,
0x2c, 0x3e, 0xa0, 0xe1, 0x08, 0x00, 0x53, 0xe3,
0x01, 0x00, 0x00, 0x0a, 0x79, 0xc7, 0x07, 0xe3,
0x00, 0xc8, 0x40, 0xe3, 0x00, 0x70, 0x0a, 0xe3,
0x73, 0x70, 0x40, 0xe3, 0x0c, 0x30, 0x9f, 0xe5,
0x00, 0x20, 0x93, 0xe5, 0x02, 0x20, 0x82, 0xe3,
0x00, 0x20, 0x83, 0xe5, 0x1e, 0xff, 0x2f, 0xe1,
0x00, 0xa1, 0x73, 0x00,
};
size_t k;
for (k = 0; k < sizeof(s12); k += 4) {
uint32_t word = 0;
size_t n = sizeof(s12) - k < 4 ? sizeof(s12) - k : 4;
__builtin_memcpy(&word, &s12[k], n);
cpu_physical_memory_write(0x08006964 + k, &word, 4);
}
}
/* S16-stublet (b27, 64B @0x08006CC0): smart return for the
* B66 bx-ip chain. ip==STUBV (fill/maze, never legit) =>
* default; ip odd + top 8 => add sp,#8 (pop the helper
* frame B66 skipped!) + bx ip (legit pushed-lr, un-leaks);
* else default (restore r7, jump poll resumption 0x8007779
* via ip). */
{
static const uint8_t s16[] = {
0xdd, 0xf8, 0x04, 0xc0, 0x46, 0xf6, 0x8d, 0x33,
0xc0, 0xf6, 0x00, 0x03, 0x9c, 0x45, 0x0e, 0xd0,
0x1c, 0xf0, 0x01, 0x0f, 0x0b, 0xd0, 0x5f, 0xea,
0x1c, 0x63, 0x08, 0x2b, 0x01, 0xd1, 0x02, 0xb0,
0x60, 0x47, 0x5f, 0xea, 0x1c, 0x73, 0x08, 0x2b,
0x01, 0xd1, 0x02, 0xb0, 0x60, 0x47, 0x4a, 0xf2,
0x00, 0x07, 0xc0, 0xf2, 0x73, 0x07, 0x47, 0xf2,
0x79, 0x7c, 0xc0, 0xf6, 0x00, 0x0c, 0x60, 0x47,
};
size_t k;
for (k = 0; k < sizeof(s16); k += 4) {
uint32_t word = 0;
size_t n = sizeof(s16) - k < 4 ? sizeof(s16) - k : 4;
__builtin_memcpy(&word, &s16[k], n);
cpu_physical_memory_write(0x08006CC0 + k, &word, 4);
}
}
{
uint32_t fill = 0x08006B8D;
uint32_t a;
@@ -629,6 +808,15 @@ static void saimaa_machine_init(MachineState *machine)
cpu_physical_memory_write(a, &fill, 4);
}
}
/* IMEM poison [0x8600000,0x8600F00): drift-sink trap (UDF in
* both modes). IMEM stack [0x8600F00,0x8610000) intact. */
{
uint32_t p = 0xFFFFFFFF;
uint32_t a;
for (a = 0x08600000; a < 0x08600F00; a += 4) {
cpu_physical_memory_write(a, &p, 4);
}
}
/* pop-site slots -> stublets (sp values from cpu traces) */
v = 0x802f65d; /* S1 pop10 @fef0: resume sbl_main */
cpu_physical_memory_write(0x085FFF14, &v, 4);
+754 -2
View File
@@ -528,6 +528,9 @@ def main():
# 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).
# Also NOPs the FULL bl idiom at 0x8006682 (b17 NOPed only its 2nd
# half, leaving a dangling prefix that fused with the NOP into a
# bogus odd-address store -> alignment fault in QEMU).
d = bytearray(base)
for va, (a, b) in BLX2BL.items():
@@ -550,8 +553,6 @@ def main():
(0x801B76C, '280b0508', '00000708'),
(0x8006684, '01f807bd', '46c046c0'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
@@ -564,6 +565,15 @@ def main():
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs. (b17 NOPed only
# its 2nd half, leaving a dangling prefix that fused with the NOP
# into a bogus odd-address store -> alignment fault in QEMU.)
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
o = off(0x8013AA8)
assert d[o:o + 4].hex() == '07f026be', hx(d, 0x8013AA8)
@@ -651,6 +661,748 @@ def main():
save(d, 'img0_b20.elf')
# b21 = b18 + logger S8 (0x8034810 pop.w -> bx lr):
# the logger is a naked pop.w {r2-r8,sb,sl,pc} (0x28) with no push;
# called via bl from scaffold-frame workers, it eats 0x28 of live
# stack per call (slots->fill->zeros->null-loop->IMEM overflow).
# S8 turns it into a no-op return (bx lr, 2B; tail bytes unreachable):
# return value ignored (callers set r0=0 after), workers unwind via
# their own epilogue pops. Logging already empty (UART stubs).
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Logger S8: pop.w entry (4B bde8fc87) -> bx lr (first 2B).
o = off(0x8034810)
assert d[o:o + 4].hex() == 'bde8fc87', hx(d, 0x8034810)
d[o:o + 2] = bytes.fromhex('7047')
save(d, 'img0_b21.elf')
# b22 = b21 + rollback assert NOP (0x801671C blx + b.n self):
# boot_rollback_version.c:580 (code 0x302E) fires (versions zeroed);
# caller (sbl_main) ignores r0 (movs r0,#0 after), so fall-through
# to the success pop is safe. Same idiom as banner site.
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x801671C, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
(0x801B6BE, 'bde8f081', 'ebf7c3ba'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Logger S8 (see b21).
o = off(0x8034810)
assert d[o:o + 4].hex() == 'bde8fc87', hx(d, 0x8034810)
d[o:o + 2] = bytes.fromhex('7047')
save(d, 'img0_b22.elf')
# b23 = b22 + S10 (rollback sideways-exit fix):
# 0x8016720 pop{r4,pc} has no matching push; it exits sideways to
# helper-mid (0x8005BBA) instead of sbl_main (0x802f6a3), closing a
# ping-pong cycle (S9->rollback->query->helper->assert->labyrinth).
# Route to S10 (0x08006C88, scaffold): movw/movt ip + bx ip to
# 0x802f6a3. Eats the following NOP (movs r0,r0).
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x801671C, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
(0x801B6BE, 'bde8f081', 'ebf7c3ba'),
(0x8016720, '10bd0000', 'f0f7b2ba'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Logger S8 (see b21).
o = off(0x8034810)
assert d[o:o + 4].hex() == 'bde8fc87', hx(d, 0x8034810)
d[o:o + 2] = bytes.fromhex('7047')
save(d, 'img0_b23.elf')
# b24 = b23 + S11 (clock-fn naked-exit fix):
# 0x800E988 pop{r4,pc} has no matching push (naked tail-chain worker:
# bl 0x8006684-idiom tail-jumps to the clock fn, abandoning lr).
# Each dispatcher pass eats 0x8+ ([sp] -> wild 0x902FC506).
# S11 turns the exit into bx lr (lr = abandoned-but-valid resumption
# 0x80076CD/0x80076D3 in the caller). 2B patch, like S8.
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x801671C, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
(0x801B6BE, 'bde8f081', 'ebf7c3ba'),
(0x8016720, '10bd0000', 'f0f7b2ba'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Logger S8 (see b21).
o = off(0x8034810)
assert d[o:o + 4].hex() == 'bde8fc87', hx(d, 0x8034810)
d[o:o + 2] = bytes.fromhex('7047')
# Clock S11: pop{r4,pc} (2B 10bd) -> bx lr (2B 7047).
o = off(0x800E988)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x800E988, 2)
d[o:o + 2] = bytes.fromhex('7047')
save(d, 'img0_b24.elf')
# b25 = b24 + helper-pop S16 chain (0x8007B66 pop{r4,pc} -> bx ip):
# the pop reads scratch ([sp+4] = counters 0..4, frames marched by
# null-cycles) instead of pushed lr. bx ip uses ip preset by S12
# (ARM stub at 0x8006964) to S16 (0x08006CC0, scaffold): restore r7
# (peripheral base) and jump to poll-loop resumption 0x8007779.
# 2B patch, no eating (bx ip = 0x47c0 fits the pop slot).
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x801671C, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
(0x801B6BE, 'bde8f081', 'ebf7c3ba'),
(0x8016720, '10bd0000', 'f0f7b2ba'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Logger S8 (see b21).
o = off(0x8034810)
assert d[o:o + 4].hex() == 'bde8fc87', hx(d, 0x8034810)
d[o:o + 2] = bytes.fromhex('7047')
# Clock S11 (see b24).
o = off(0x800E988)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x800E988, 2)
d[o:o + 2] = bytes.fromhex('7047')
# Helper-pop S16 chain: pop{r4,pc} (2B 10bd) -> bx ip (2B 4760;
# ip preset by S12 to S16). 0x47c0 would be bx lr (wrong reg!).
o = off(0x8007B66)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x8007B66, 2)
d[o:o + 2] = bytes.fromhex('6047')
save(d, 'img0_b25.elf')
# b26 = b25 + S17 safe-logger (0x8034810 pop.w -> b.w S17):
# S8 (bx lr) silenced all init logging (UART empty by design).
# S17 (50B @0x08006CF0, scaffold): print r1-string via UARTDM-TF
# only if r1 in rodata [0x804B800,0x8058000) (wild pointers skip),
# then pop-return (no leak, return value ignored by callers).
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x801671C, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
(0x801B6BE, 'bde8f081', 'ebf7c3ba'),
(0x8016720, '10bd0000', 'f0f7b2ba'),
(0x8034810, 'bde8fc87', 'd2f76eba'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Helper-pop S16 chain (see b25).
o = off(0x8007B66)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x8007B66, 2)
d[o:o + 2] = bytes.fromhex('6047')
# Clock S11 (see b24).
o = off(0x800E988)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x800E988, 2)
d[o:o + 2] = bytes.fromhex('7047')
save(d, 'img0_b26.elf')
# b27 = b26 + poll re-init skip (0x8007748 cbz r6 -> b):
# r6 (table mode, caller-passed constant 2) never 0, so the poll-fn
# re-inits (movs r4,#0) and re-runs its table loop forever instead of
# proceeding to poll#2-direct + epilogue. Force direct (2B->2B).
# Bits 0,1,2 of [0x73A100] pre-set (scaffold), so polls exit.
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x801671C, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
(0x801B6BE, 'bde8f081', 'ebf7c3ba'),
(0x8016720, '10bd0000', 'f0f7b2ba'),
(0x8034810, 'bde8fc87', 'd2f76eba'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Poll re-init skip (b27): cbz r6 (2B 96b1) -> b (2B 14e0).
o = off(0x8007748)
assert d[o:o + 2].hex() == '96b1', hx(d, 0x8007748, 2)
d[o:o + 2] = bytes.fromhex('14e0')
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Helper-pop S16 chain (see b25).
o = off(0x8007B66)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x8007B66, 2)
d[o:o + 2] = bytes.fromhex('6047')
# Clock S11 (see b24).
o = off(0x800E988)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x800E988, 2)
d[o:o + 2] = bytes.fromhex('7047')
save(d, 'img0_b27.elf')
# b28 = b27 + poll-epilogue march kill (0x8007794 add sp,#0x1c NOP):
# the poll-fn is entered mid-way (no prologue push!) but exits via
# add sp,#0x1c + pop.w 0x14 (+0x30/pass, no pushes!) -> marches sp
# UP through fill into IMEM zeros -> top fault. NOP the add (2B);
# the pop then reads helper frames (pushed-lrs, valid returns).
# Slow helper-leak-down (0x8/call, B66 skips pop) remains, harmless.
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x801671C, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
(0x801B6BE, 'bde8f081', 'ebf7c3ba'),
(0x8016720, '10bd0000', 'f0f7b2ba'),
(0x8034810, 'bde8fc87', 'd2f76eba'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Helper-pop S16 chain (see b25).
o = off(0x8007B66)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x8007B66, 2)
d[o:o + 2] = bytes.fromhex('6047')
# Clock S11 (see b24).
o = off(0x800E988)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x800E988, 2)
d[o:o + 2] = bytes.fromhex('7047')
# Poll-epilogue march kill: add sp,#0x1c (2B 07b0) -> NOP (2B c046).
o = off(0x8007794)
assert d[o:o + 2].hex() == '07b0', hx(d, 0x8007794, 2)
d[o:o + 2] = bytes.fromhex('c046')
save(d, 'img0_b28.elf')
# b29 = b28 + S18 (poll-epilogue pop.w -> b.w S18):
# the b28 NOP killed +0x1c but pop.w 0x14 still marches (no pushes,
# mid-entries!). S18 (10B @0x08006D22, scaffold): movw/movt ip +
# bx ip to sbl_main 0x802f6a9 (post-dispatcher, dispatcher work
# done-ish via helpers). Blind jump like S10.
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'),
(0x8017AFC, 'bde8fc87', 'eef74fb8'),
(0x802F690, '9847fee7', 'c046c046'),
(0x801671C, '9847fee7', 'c046c046'),
(0x8013BAC, 'f8bd0a4a', 'f3f730b8'),
(0x801B6BE, 'bde8f081', 'ebf7c3ba'),
(0x8016720, '10bd0000', 'f0f7b2ba'),
(0x8034810, 'bde8fc87', 'd2f76eba'),
(0x8007798, 'bde8f08f', 'fff7c3ba'),
]:
assert hx(d, va) == exp, (hex(va), hx(d, va))
set4(d, va, new)
# Full bl idiom at 0x8006682 (bl + pop, 6B) -> NOPs (see b18).
o = off(0x8006682)
assert d[o:o + 6].hex() == '00f001f807bd', hx(d, 0x8006682, 6)
d[o:o + 6] = bytes.fromhex('c046c046c046')
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')
# Helper-pop S16 chain (see b25).
o = off(0x8007B66)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x8007B66, 2)
d[o:o + 2] = bytes.fromhex('6047')
# Clock S11 (see b24).
o = off(0x800E988)
assert d[o:o + 2].hex() == '10bd', hx(d, 0x800E988, 2)
d[o:o + 2] = bytes.fromhex('7047')
# Poll re-init skip (see b27).
o = off(0x8007748)
assert d[o:o + 2].hex() == '96b1', hx(d, 0x8007748, 2)
d[o:o + 2] = bytes.fromhex('14e0')
# Poll-epilogue add NOP (see b28).
o = off(0x8007794)
assert d[o:o + 2].hex() == '07b0', hx(d, 0x8007794, 2)
d[o:o + 2] = bytes.fromhex('c046')
save(d, 'img0_b29.elf')
+182 -6
View File
@@ -101,9 +101,28 @@ def main():
last = [0]
from collections import deque
hist = deque(maxlen=20000)
# sled detector: sequential runs (pc == prev+2/4) longer than THRESH
# are zero-drifts; log run start (the jump/fall that entered it).
_sled_start = [0]
_sled_n = [0]
_sled_log = []
_SLED_TH = 100
calls = [] if "--calltrace" in sys.argv else None
rets = [] if "--retrace" in sys.argv else None
qlog = [] if "--querylog" in sys.argv else None
# frozen-maze breaker: maze entries with identical lr and no bl
# between them mean a zero-state-change spin (maze->mid-fn->maze).
# After K, redirect once to poll resumption (fresh bls unfreeze lr).
_fmlr = [None]
_fmct = [0]
_FMK = 50
_FMPC = 0x8007779
_traps = []
_trap_n = [0]
if "--trap" in sys.argv:
_traps = [int(x, 0) for x in sys.argv[sys.argv.index("--trap") + 1].split(",")]
if "--trapn" in sys.argv:
_trap_n = [int(sys.argv[sys.argv.index("--trapn") + 1])]
_spmax = [0x085FFF00]
_splog = [] if "--spytrack" in sys.argv else None
elog = [] if "--entrylog" in sys.argv else None
@@ -121,6 +140,35 @@ def main():
def hook_code(mu, addr, size, data):
count[0] += 1
last[0] = addr
_pp = _prev[0]
_prev[0] = addr
if addr == _pp + 2 or addr == _pp + 4:
if _sled_n[0] == 0:
_sled_start[0] = _pp
_sled_n[0] += 1
else:
if _sled_n[0] >= _SLED_TH and len(_sled_log) < 20:
_sled_log.append((count[0], _sled_start[0], _pp, _sled_n[0]))
_sled_n[0] = 0
if 0x08006B8C <= addr < 0x08006B9E:
try:
_lr = mu.reg_read(_LR)
except Exception:
_lr = None
if _lr == _fmlr[0]:
_fmct[0] += 1
if _fmct[0] >= _FMK:
_fmct[0] = 0
_fmlr[0] = None
mu.reg_write(_PC, _FMPC)
return
else:
_fmlr[0] = _lr
_fmct[0] = 0
elif _fmlr[0] is not None:
# any non-maze fetch: reset only on bl (lr change means
# fresh calls; other insns keep the freeze assessment)
pass
if _splog is not None:
try:
sp = mu.reg_read(UC_ARM_REG_SP)
@@ -145,6 +193,38 @@ def main():
except Exception:
thumb = 1
hist.append((addr, thumb))
if _traps and addr in _traps:
try:
vals = [mu.reg_read(_REGS[i]) for i in range(8)]
except Exception:
vals = []
print(f"TRAP {addr:#x} insn#{count[0]} r0-r7={[hex(x) for x in vals]} sp={mu.reg_read(UC_ARM_REG_SP):#x} lr={mu.reg_read(_LR):#x}")
try:
print(f" [73A100]={bytes(mu.mem_read(0x073A100, 4)).hex()}")
except Exception as e:
print(f" [73A100] unreadable: {e}")
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)
for _pc, _th in list(hist)[-60:]:
try:
_b = _pc & ~1
_c = mu.mem_read(_b, 8 if _th else 12)
for _ins in (md_t if _th else md_a).disasm(_c, _b):
print(f" hist {_b:#x}{'t' if _th else 'a'}: {_ins.mnemonic} {_ins.op_str}")
break
except Exception:
print(f" hist {_pc:#x}: <unreadable>")
except ImportError:
pass
_trap_n[0] -= 1
if _trap_n[0] <= 0 and "--trapn" in sys.argv:
mu.emu_stop()
return
if "--trapn" not in sys.argv:
mu.emu_stop()
return
if qlog is not None and addr in (0x8017EA4, 0x8017C60):
try:
vals = [mu.reg_read(_REGS[i]) for i in range(8)]
@@ -158,9 +238,15 @@ def main():
inside = _elo <= addr < _ehi
if inside and not _inrng[_i]:
if len(elog) < 20000:
elog.append((count[0], _prev[0], addr))
try:
_th = (mu.reg_read(_CPSR) >> 5) & 1
except Exception:
_th = -1
elog.append((count[0], _prev[0], addr, _th))
if not inside and _inrng[_i]:
if len(elog) < 20000:
elog.append((count[0], _prev[0], -addr, 2))
_inrng[_i] = inside
_prev[0] = addr
if calls is not None and size in (2, 4):
try:
if size == 2:
@@ -182,6 +268,15 @@ def main():
bxt = -1
if len(calls) < 8000:
calls.append((addr, 100 + bxrm, bxt))
# pop {..,pc} (0xBD00+): log stacked target
if calls is not None and (h & 0xFF00) == 0xBD00:
try:
_sp = mu.reg_read(UC_ARM_REG_SP)
_tgt = struct.unpack("<I", mu.mem_read(_sp, 4))[0]
except Exception:
_tgt = -1
if len(calls) < 12000:
calls.append((addr, 300, _tgt))
if rets is not None and 0x8017E00 <= addr < 0x8018000:
# pop {..,pc} (0xBD00+) or bx lr (0x4770)
if h == 0x4770 or (h & 0xFF00) == 0xBD00:
@@ -236,6 +331,7 @@ def main():
# immune to [0]-writes that clobber a static maze copy
from unicorn.arm_const import UC_ARM_REG_PC as _PC
_nulllog = []
_halt_on_null = "--haltonnull" in sys.argv
def hook_null(mu, addr, size, data):
if len(_nulllog) < 10:
@@ -247,6 +343,8 @@ def main():
r, lr, sp = [], -1, -1
# walk back: find the branch source from hist
_nulllog.append((count[0], r, lr, sp))
if _halt_on_null and len(_nulllog) == 1:
return False
mu.reg_write(_PC, STUB + 1)
mu.hook_add(UC_HOOK_CODE, hook_null, begin=0x0, end=0xFFF)
@@ -313,8 +411,66 @@ def main():
"ff2c13d940f20004c0f2220440f20005c0f2220540f2000a"
"c0f6060a41f2000bc0f6060bdff800f081f60208dff800f0"
"a53b0108"))
# S9 stublet (62B @0x08006C48, b22): labyrinth-epilogue exit router.
# ldr r3,[sp,#0x14]: nonzero -> original pop.w (legit return);
# zero (empty scaffold frames) -> restore r0/r4/r5/sl/fp, reset sp
# to 0x085FFF00 (fresh STUBV-fill stack), jump to sbl_main 0x802f69d
# via ip (no literal loads).
mu.mem_write(0x08006C48, bytes.fromhex(
"059b0bb1bde8f081002040f20004c0f2220440f20005c0f2"
"220540f2000ac0f6060a41f2000bc0f6060b4ff60073c0f6"
"5f039d464ff29d6cc0f6020c6047"))
# S10 stublet (10B @0x08006C88, b23): rollback-exit router.
# movw/movt ip + bx ip to sbl_main 0x802f6a3.
mu.mem_write(0x08006C88, bytes.fromhex("4ff2a36cc0f6020c6047"))
# S18 stublet (10B @0x08006D22, b29): poll-epilogue router.
# movw/movt ip + bx ip to sbl_main 0x802f6a9 (post-dispatcher).
mu.mem_write(0x08006D22, bytes.fromhex("4ff2a96cc0f6020c6047"))
# S17 safe-logger (50B @0x08006CF0, b26): print r1-string via
# UARTDM-TF only if r1 in rodata [0x804B800,0x8058000); pop-return.
mu.mem_write(0x08006CF0, bytes.fromhex(
"4bf60003c0f60403994211d348f20003c0f605039942"
"0bd210b54ff20003c0f28a730c4614f8010b08b11867fae710bd7047"))
# PBL version-table fabrication (b23): [0x080528D0] = STUBV so the
# rollback query's blx calls maze (returns 0 = versions OK).
W32(0x080528D0, STUBV)
# HW-ready bit fabrication (b25): [0x073A100] bits 0,1,2 = peripheral
# status done-bits (poll#1 checks bit2 (lsls#29), poll#2 bit1 (lsls#30),
# post-poll gate bit0 (lsls#31)). SBL only reads it, never writes.
W32(0x073A100, 0x00000007)
# PBL world-switch Thumb-stub (b24): the ARM trampoline at 0x221EF8
# is entered in Thumb (mode confusion at dispatch); real ARM would
# save regs/CPS to MON and call TZ (not loaded yet). Stub returns 0
# to the dispatcher via stale lr (TZ bringup deferred).
# NOTE: overwrites 4B of real ARM trampoline (recoverable from ELF).
mu.mem_write(0x00221EF8, bytes([0x00, 0x20, 0x70, 0x47]))
# S12-stub (b27, 92B @0x08006964, ARM): blx-to-padding becomes
# smart dispatcher: ldr ip,[sp,#4] (=pushed lr); ip==STUBV/even/
# non-code => ip=poll resumption 0x8007779; else keep pushed-lr;
# restore r7 (peripheral base, S16 dormant); ACK peripheral bit1
# ([literal 0x073A100]); bx lr. PBL would place the real helper.
mu.mem_write(0x08006964, bytes.fromhex(
"04c09de58d3b06e3003840e303005ce10700000a0100"
"1ce30500000a2c3ca0e1080053e30400000a2c3ea0e1"
"080053e30100000a79c707e300c840e300700ae37370"
"40e30c309fe5002093e5022082e3002083e51eff2fe1"
"00a17300"))
# S16-stublet (b27, 64B @0x08006CC0): smart return for B66 bx ip.
# ip==STUBV (fill/maze, never a legit return) => default;
# ip odd + top 8 (code) => add sp,#8 (pop the helper frame B66
# skipped!) + bx ip (legit pushed-lr return, un-leaks the stack);
# else default (restore r7, jump poll resumption 0x8007779).
mu.mem_write(0x08006CC0, bytes.fromhex(
"ddf804c046f68d33c0f600039c450ed01cf0010f0bd0"
"5fea1c63082b01d102b060475fea1c73082b01d102b0"
"60474af20007c0f2730747f2797cc0f6000c6047"))
# stack zone fill (STUBV odd)
mu.mem_write(0x085F0000, struct.pack("<I", STUBV) * (0x10000 // 4))
# IMEM poison [0x8600000,0x8600F00): drift-sink trap. Fill/IMEM-zero
# sleds (fall-through ldr/movs/andeq) drift silently to IMEM-top
# fault; poison faults LOUDLY at drift-entry (UDF in both modes).
# IMEM stack [0x8600F00,0x8610000) left intact.
mu.mem_write(0x08600000, b"\xff\xff\xff\xff" * (0xF00 // 4))
# pop-site slots (sp values from traces)
for a, v in [(0x085FFF14, 0x802f65d), (0x085FFF1C, 0x802f665),
(0x085FFF44, 0x8013ba5), (0x085FFF5C, 0x802f681),
@@ -349,7 +505,7 @@ def main():
# --- end parity ---
# stub range is readonly on HW/QEMU: ignore SBL heap-clobber writes,
# keep maze/stublets intact (restore-on-write with reentrancy guard)
STUB_LO, STUB_HI = 0x080068C0, 0x08006C50 # +S7 + banner tail
STUB_LO, STUB_HI = 0x080068C0, 0x08006D30 # +S7/S9/S10/S16/S17
stub_snap = bytes(mu.mem_read(STUB_LO, STUB_HI - STUB_LO))
_inww = [False]
_swn = [0]
@@ -417,7 +573,7 @@ def main():
wlolog.append((pc, addr, size, value))
mu.hook_add(UC_HOOK_MEM_WRITE, hook_wlo,
begin=0x08000000, end=0x085FFFFF)
begin=0x00200000, end=0x085FFFFF)
import atexit as _ax2
def _dwlo():
@@ -459,10 +615,24 @@ def main():
print(f" {pc:#x}: <unreadable>")
except ImportError:
print("hist:", [hex(pc) for pc, _ in list(hist)[-12:]])
if _sled_log or _sled_n[0] >= _SLED_TH:
if _sled_n[0] >= _SLED_TH:
_sled_log.append((count[0], _sled_start[0], last[0], _sled_n[0]))
print(f"sleds: {len(_sled_log)}")
for n, s, e, c in _sled_log:
print(f" insn#{n} [{s:#x}..{e:#x}] len={c}")
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}")
for n, prev, cur, th in elog[:10]:
if cur < 0:
print(f" FIRST EXIT insn#{n} {prev:#x} -> {(-cur):#x}")
else:
print(f" FIRST insn#{n} {prev:#x} -> {cur:#x}{'t' if th == 1 else ('a' if th == 0 else '?')}")
for n, prev, cur, th in elog[-10:]:
if cur < 0:
print(f" LAST EXIT insn#{n} {prev:#x} -> {(-cur):#x}")
else:
print(f" LAST insn#{n} {prev:#x} -> {cur:#x}{'t' if th == 1 else ('a' if th == 0 else '?')}")
if _splog is not None:
print(f"sp-highs: {len(_splog)}")
for n, pc, sp in _splog:
@@ -485,6 +655,12 @@ def main():
print(f"bl-immed: {len(bls)}")
for pc, tgt in bls[-30:]:
print(f" {pc:#x}: bl #{tgt:#x}")
pops = [(pc, tgt) for pc, rm, tgt in calls if rm == 300]
print(f"pop-pc: {len(pops)}")
for pc, tgt in pops[:15]:
print(f" FIRST {pc:#x}: pop-> {tgt:#x}" if tgt >= 0 else f" FIRST {pc:#x}: pop-> ?")
for pc, tgt in pops[-15:]:
print(f" LAST {pc:#x}: pop-> {tgt:#x}" if tgt >= 0 else f" LAST {pc:#x}: pop-> ?")
for pc, rm, tgt in calls[-12:]:
if rm == 200:
continue