Files
lumia-emulator/tools/sbl_patch.py
T
2026-09-14 00:34:52 +00:00

2432 lines
56 KiB
Python

#!/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'),
0x8016334: ('f0f7a4ef', 'f0f7a4ff'), # blx epilogue (Thumb!) -> bl
}
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', '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', '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', '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', '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).
# 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():
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. (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)
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')
# 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')
# b30 = b29 + epilogue blx (0x8016334 blx -> bl, via global BLX2BL):
# 0x8016320 (logger-ish, loads "SBL1" magic) calls the shared
# epilogue 0x8007280 (Thumb: add sp + pop) via blx-imm (even->ARM!),
# faulting on ARM-decoded coprocessor garbage. bl stays Thumb.
# (Patch lives in global BLX2BL, auto-applied to all variants.)
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_b30.elf')
# b31 = b30 + logger-epilogue call NOP (0x8016334 bl -> NOP NOP):
# 0x8016320 (logger-ish) calls the shared epilogue 0x8007280, whose
# pop reads fill/STUBV -> maze -> stale-lr back into logger-mid ->
# blx epilogue again (naked mutual recursion, no stack growth).
# Skip the call (logger continues to its own return; sbl_main
# proceeds past 0x802F6B0 into fresh code). 4B NOP (was blx, b30 bl).
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'),
(0x8016334, 'f0f7a4ff', 'c046c046'),
]:
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_b31.elf')
# b32 = b31 + logger-tail blx->b.w (0x8016342 blx -> b.w epilogue):
# 9th caller of shared epilogue 0x8007280 (missed by static scan).
# blx-imm even->ARM faults; bl-flip lands +2 (skips add sp).
# Exact b.w (4B f0f79dbf) preserves entry. Epilogue pop reads
# logger-caller frames (diagnose next).
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'),
(0x8016334, 'f0f7a4ff', 'c046c046'),
(0x8016342, 'f0f79eef', 'f0f79dbf'),
]:
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_b32.elf')
# b33 = b32 + partition wrap-check (0x801844E blo -> b):
# partition validator (adds r6,r0,r4; blo success) asserts (line
# 0x1FD) because r0+r4 don't wrap (PBL partition tables zeroed;
# r4 stale rodata-addr). Force success path (2B->2B).
# TODO(MainOS): real PBL/SMEM partition tables (r0 base high!).
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'),
(0x8016334, 'f0f7a4ff', 'c046c046'),
(0x8016342, 'f0f79eef', 'f0f79dbf'),
]:
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')
# Partition wrap-check: blo (2B 04d3) -> b (2B 04e0).
o = off(0x801844E)
assert d[o:o + 2].hex() == '04d3', hx(d, 0x801844E, 2)
d[o:o + 2] = bytes.fromhex('04e0')
save(d, 'img0_b33.elf')
# b34 = b33 + hang-site neutralize (0x8018458 b. -> b success):
# the assert-hang is reached directly (maze-stale-lr 0x8018459,
# skipping the patched wrap-check). Route into the success path
# (0x801845A, 2B->2B, no eating).
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'),
(0x8016334, 'f0f7a4ff', 'c046c046'),
(0x8016342, 'f0f79eef', 'f0f79dbf'),
]:
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')
# Partition wrap-check (see b33).
o = off(0x801844E)
assert d[o:o + 2].hex() == '04d3', hx(d, 0x801844E, 2)
d[o:o + 2] = bytes.fromhex('04e0')
# Hang-site neutralize: b. (2B fee7) -> b success (2B ffe7).
o = off(0x8018458)
assert d[o:o + 2].hex() == 'fee7', hx(d, 0x8018458, 2)
d[o:o + 2] = bytes.fromhex('ffe7')
save(d, 'img0_b34.elf')
# b35 = b34 + sbl_mc hangs -> S19 (0x802F9A0-b., 0x802F9B2-b.):
# dead assert-fn (sbl_mc.c:320) reached by accident (pop-garbage);
# both hangs routed to S19 (sbl_main post-logger restart, idempotent
# bounded table work). 4B b.w each (eats dead halves after).
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'),
(0x8016334, 'f0f7a4ff', 'c046c046'),
(0x8016342, 'f0f79eef', 'f0f79dbf'),
(0x802F9A0, 'ffe70648', 'd7f7c4b9'),
(0x802F9B2, 'fee78817', 'd7f7bbb9'),
]:
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')
# Partition wrap-check (see b33).
o = off(0x801844E)
assert d[o:o + 2].hex() == '04d3', hx(d, 0x801844E, 2)
d[o:o + 2] = bytes.fromhex('04e0')
# Hang-site neutralize (see b34).
o = off(0x8018458)
assert d[o:o + 2].hex() == 'fee7', hx(d, 0x8018458, 2)
d[o:o + 2] = bytes.fromhex('ffe7')
save(d, 'img0_b35.elf')
# b36 = b35 + assert-entry -> S19 (0x802F9A2 ldr -> b.w S19):
# the sbl_mc assert-fn is entered mid-way (0x802F9A2, skipping the
# b35 S19-hangs at 0x802F9A0/0x802F9B2!) via pop-garbage, runs its
# blx-wild and faults. Route entry to S19 too (eats ldr + movw
# first half; dead, never fetched after jump).
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'),
(0x8016334, 'f0f7a4ff', 'c046c046'),
(0x8016342, 'f0f79eef', 'f0f79dbf'),
(0x802F9A0, 'ffe70648', 'd7f7c4b9'),
(0x802F9B2, 'fee78817', 'd7f7bbb9'),
(0x802F9A2, 'c4b943f2', 'd7f7c3b9'),
]:
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')
# Partition wrap-check (see b33).
o = off(0x801844E)
assert d[o:o + 2].hex() == '04d3', hx(d, 0x801844E, 2)
d[o:o + 2] = bytes.fromhex('04e0')
# Hang-site neutralize (see b34).
o = off(0x8018458)
assert d[o:o + 2].hex() == 'fee7', hx(d, 0x8018458, 2)
d[o:o + 2] = bytes.fromhex('ffe7')
save(d, 'img0_b36.elf')
# b37 = b36 + S20 clock-dispatcher router (0x800EB96 pop.w -> b.w):
# naked dispatcher (no pushes!) pops 0x30/pass, marching sp to IMEM
# top. S20 (34B @0x08006D38, scaffold): [sp+0x2C] valid-code?
# original pop.w (legit return); else r0=sl + re-run (idempotent).
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'),
(0x8016334, 'f0f7a4ff', 'c046c046'),
(0x8016342, 'f0f79eef', 'f0f79dbf'),
(0x802F9A0, 'ffe70648', 'd7f7c4b9'),
(0x802F9B2, 'fee78817', 'd7f7bbb9'),
(0x802F9A2, 'c4b943f2', 'd7f7c3b9'),
(0x800EB96, 'bde8fe8f', 'f8f7cfb8'),
]:
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')
# Partition wrap-check (see b33).
o = off(0x801844E)
assert d[o:o + 2].hex() == '04d3', hx(d, 0x801844E, 2)
d[o:o + 2] = bytes.fromhex('04e0')
# Hang-site neutralize (see b34).
o = off(0x8018458)
assert d[o:o + 2].hex() == 'fee7', hx(d, 0x8018458, 2)
d[o:o + 2] = bytes.fromhex('ffe7')
save(d, 'img0_b37.elf')
# b38 = b37 + S21 table-walker router (0x8007916 pop.w -> b.w S21):
# naked table-walker (mid-entries, no pushes!) pops 0x28, reading
# saved-FP (0x85FFEB0) as pc -> ARM fill-drift (STM-clobbers!).
# S21 (32B @0x08006D5A, scaffold): [sp+0x24] valid-code? original
# pop.w (legit); else b.w S19 (sbl_main restart, bounded).
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'),
(0x8016334, 'f0f7a4ff', 'c046c046'),
(0x8016342, 'f0f79eef', 'f0f79dbf'),
(0x802F9A0, 'ffe70648', 'd7f7c4b9'),
(0x802F9B2, 'fee78817', 'd7f7bbb9'),
(0x802F9A2, 'c4b943f2', 'd7f7c3b9'),
(0x800EB96, 'bde8fe8f', 'f8f7cfb8'),
(0x8007916, 'bde8f09f', 'fff720ba'),
]:
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')
# Partition wrap-check (see b33).
o = off(0x801844E)
assert d[o:o + 2].hex() == '04d3', hx(d, 0x801844E, 2)
d[o:o + 2] = bytes.fromhex('04e0')
# Hang-site neutralize (see b34).
o = off(0x8018458)
assert d[o:o + 2].hex() == 'fee7', hx(d, 0x8018458, 2)
d[o:o + 2] = bytes.fromhex('ffe7')
save(d, 'img0_b38.elf')
if __name__ == '__main__':
main()