diff --git a/docs/findings.md b/docs/findings.md index 7dcba66..58a8dcf 100644 --- a/docs/findings.md +++ b/docs/findings.md @@ -149,6 +149,22 @@ decompiler shows `unaff_gp + imm`) — dispatch found structurally instead: Next: same scan on WM; handler-by-handler decompile + naming pass. +## F8 — mt7981_wm survey (2026-08-20) + +Ghidra project `wm`: forced disassembly to **555,385 instructions / 4,422 +functions** (import-time analysis alone: 226K/4,355). + +- Code model differs from patch/WA: only **27 GP-relative references** in + the whole image → WM uses (near-)absolute global addressing, so GP-base + inference is not the unlock it would otherwise be. +- Structural dispatch scan (scan_tables.py) across every region pairing + found **no absolute function-pointer table** (unlike WA's 65-entry + table, F7). WM command dispatch must use another mechanism — + FP/register-relative tables, switch jump tables, or runtime handler + registration (unknown U4). +- Regions: code 0xe003b000 (398KB) + 0xe009c400 (473KB mixed); + data 0x0231dc00 (205KB), 0x0041xxxx pair; 0xf0xxxxxx block (feat 0x80) + still uncharacterized (U1). ## Unknowns registry - **U1 — `feature_set` bit 7 (0x80):** observed only on WM regions at diff --git a/tools/ghidra_scripts/FindGPInit.py b/tools/ghidra_scripts/FindGPInit.py new file mode 100644 index 0000000..ffe6855 --- /dev/null +++ b/tools/ghidra_scripts/FindGPInit.py @@ -0,0 +1,28 @@ +# Headless post-script: find instructions that write the GP register and +# report candidate GP base values. Once GP is known, set it as register +# context and re-run analysis so GP-relative data references resolve. +# Run via pyghidra ghidra_launch ... -postScript FindGPInit.py +#@category Analysis + +listing = currentProgram.getListing() +reg = currentProgram.getLanguage().getRegisters() +gp_regs = [r for r in reg if r.getName().lower() in ('gp', 'r26', 'r11')] +print('GP candidates: %s' % [r.getName() for r in gp_regs]) + +it = listing.getInstructions(True) +hits = 0 +while it.hasNext() and hits < 80: + ins = it.next() + n = ins.getNumOperands() + for i in range(n): + try: + ops = ins.getOpObjects(i) + except Exception: # noqa: BLE001 + continue + for o in ops: + if hasattr(o, 'getName') and o in gp_regs: + # operand 0 = destination on NDS32 ALU forms + if i == 0: + print('%s %s' % (ins.getAddress(), ins)) + hits += 1 +print('total gp-writes shown: %d' % hits) diff --git a/tools/ghidra_scripts/GPHistogram.py b/tools/ghidra_scripts/GPHistogram.py new file mode 100644 index 0000000..5c76eba --- /dev/null +++ b/tools/ghidra_scripts/GPHistogram.py @@ -0,0 +1,37 @@ +# Headless post-script: collect all GP-relative references (addr, imm). +# Dump as CSV lines "GPREF,," for offline base inference. +#@category Analysis + +listing = currentProgram.getListing() +lang = currentProgram.getLanguage() +gp = None +for r in lang.getRegisters(): + if r.getName().lower() == 'gp': + gp = r + break +print('gp register: %s' % (gp.getName() if gp else None)) + +from ghidra.program.model.scalar import Scalar # noqa: E402 + +it = listing.getInstructions(True) +n = 0 +while it.hasNext(): + ins = it.next() + uses_gp = False + for i in range(ins.getNumOperands()): + for o in ins.getOpObjects(i): + if o == gp: + uses_gp = True + if not uses_gp: + continue + imm = None + for i in range(ins.getNumOperands()): + for o in ins.getOpObjects(i): + if isinstance(o, Scalar): + imm = o.getSignedValue() + break + if imm is not None: + break + print('GPREF,%s,%s' % (ins.getAddress(), imm if imm is not None else '')) + n += 1 +print('total gp-refs: %d' % n)