diff --git a/blob_emu.py b/blob_emu.py new file mode 100755 index 0000000..d870819 --- /dev/null +++ b/blob_emu.py @@ -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 [--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)))