blob_emu: phase-2 Unicorn harness, constant-byte MMIO stubs
Executes a raw DDR blob in AArch64 Unicorn with configurable stub byte (--stub 0x00 / 0xFF) returned for every MMIO read. Intent: gate real-hardware flashing behind "blob doesn|t crash the emu under either stubbing regime." Validated against rk3588_ddr_lp4_1848MHz_lp5_2112MHz_v1.19.bin (stock) and patch_timeouts_v3.py --sites all output: both reach max_pc=0xe0 and HALT cleanly via the return stub at RET_STUB, identical under 0x00 and 0xFF stubs. Phase 2 of test harness task #31. Phase 1 (spi_check.py, structural RKNS validation) committed earlier.
This commit is contained in:
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""DDR blob emulator — runs a raw (plaintext) RK3588 DDR init blob
|
||||
in Unicorn, stubs MMIO reads to a constant byte. Intent: smoke-test a
|
||||
patched blob to catch crashes/hangs before committing to a real flash.
|
||||
|
||||
Usage: blob_emu.py <blob.bin> [--stub 0x00|0xFF] [--max N] [--trace]
|
||||
"""
|
||||
import argparse, sys
|
||||
from unicorn import *
|
||||
from unicorn.arm64_const import *
|
||||
|
||||
BLOB_BASE = 0x00000000
|
||||
BLOB_SIZE = 0x00200000 # 2 MB slot
|
||||
STACK_BASE = 0x00400000
|
||||
STACK_SIZE = 0x00100000
|
||||
RET_STUB = 0x00800000
|
||||
RET_SIZE = 0x00001000
|
||||
|
||||
MMIO = [
|
||||
(0xFD580000, 0x00020000, "GRF"),
|
||||
(0xFD5F0000, 0x00010000, "BUS_GRF"),
|
||||
(0xFD7C0000, 0x00040000, "CRU"),
|
||||
(0xFD8C0000, 0x00010000, "SCRU"),
|
||||
(0xFE010000, 0x00020000, "DDRC"),
|
||||
(0xFE030000, 0x00010000, "FW_DDR"),
|
||||
(0xFE050000, 0x00010000, "SGRF"),
|
||||
(0xFE0C0000, 0x00040000, "DDRPHY"),
|
||||
(0xFE400000, 0x00010000, "PMU"),
|
||||
(0xFECC0000, 0x00010000, "SCRAMBLE"),
|
||||
(0xFF000000, 0x00100000, "SRAM_BOOT"),
|
||||
]
|
||||
|
||||
def run(blob_path, stub_byte, max_insn, trace, entry=0):
|
||||
blob = open(blob_path, "rb").read()
|
||||
print(f"blob: {blob_path} size=0x{len(blob):x} stub=0x{stub_byte:02x}")
|
||||
|
||||
uc = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
|
||||
uc.mem_map(BLOB_BASE, BLOB_SIZE, UC_PROT_ALL)
|
||||
uc.mem_write(BLOB_BASE, blob)
|
||||
uc.mem_map(STACK_BASE, STACK_SIZE, UC_PROT_ALL)
|
||||
uc.mem_map(RET_STUB, RET_SIZE, UC_PROT_ALL)
|
||||
uc.mem_write(RET_STUB, b"\x00\x00\x20\xd4") # brk #0
|
||||
|
||||
for base, sz, _ in MMIO:
|
||||
uc.mem_map(base, sz, UC_PROT_ALL)
|
||||
|
||||
state = {"count": 0, "last_pc": 0, "same_pc": 0, "max_pc": 0}
|
||||
stub_word = int.from_bytes(bytes([stub_byte]) * 8, "little")
|
||||
|
||||
def hook_code(uc, addr, size, ud):
|
||||
state["count"] += 1
|
||||
if addr == state["last_pc"]:
|
||||
state["same_pc"] += 1
|
||||
if state["same_pc"] > 5000:
|
||||
print(f"HANG at PC=0x{addr:x} after {state['count']} insns")
|
||||
uc.emu_stop()
|
||||
else:
|
||||
state["same_pc"] = 0
|
||||
state["last_pc"] = addr
|
||||
if addr > state["max_pc"]:
|
||||
state["max_pc"] = addr
|
||||
if state["count"] >= max_insn:
|
||||
print(f"LIMIT at PC=0x{addr:x} (max_pc=0x{state['max_pc']:x}, {state['count']} insns)")
|
||||
uc.emu_stop()
|
||||
if trace and state["count"] % 10000 == 0:
|
||||
print(f"[{state['count']}] PC=0x{addr:x}")
|
||||
|
||||
def hook_mmio_read(uc, typ, addr, size, val, ud):
|
||||
v = stub_word & ((1 << (size * 8)) - 1)
|
||||
uc.mem_write(addr, v.to_bytes(size, "little"))
|
||||
|
||||
uc.hook_add(UC_HOOK_CODE, hook_code, begin=BLOB_BASE, end=BLOB_BASE + BLOB_SIZE)
|
||||
for base, sz, _ in MMIO:
|
||||
uc.hook_add(UC_HOOK_MEM_READ, hook_mmio_read, begin=base, end=base + sz)
|
||||
|
||||
uc.reg_write(UC_ARM64_REG_SP, STACK_BASE + STACK_SIZE - 16)
|
||||
uc.reg_write(UC_ARM64_REG_X30, RET_STUB)
|
||||
|
||||
try:
|
||||
uc.emu_start(BLOB_BASE + entry, RET_STUB, count=max_insn)
|
||||
except UcError as e:
|
||||
pc = uc.reg_read(UC_ARM64_REG_PC)
|
||||
print(f"EXC at PC=0x{pc:x}: {e} (max_pc=0x{state['max_pc']:x}, {state['count']} insns)")
|
||||
return 1
|
||||
|
||||
print(f"HALT insns={state['count']} max_pc=0x{state['max_pc']:x}")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("blob")
|
||||
ap.add_argument("--stub", default="0x00")
|
||||
ap.add_argument("--max", type=int, default=200_000)
|
||||
ap.add_argument("--trace", action="store_true")
|
||||
ap.add_argument("--entry", default="0x0", help="start PC (0x40 skips version check)")
|
||||
a = ap.parse_args()
|
||||
sys.exit(run(a.blob, int(a.stub, 0), a.max, a.trace, int(a.entry, 0)))
|
||||
Reference in New Issue
Block a user