diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/harness/value_demos/s09_grabbing_a_ghidra_address.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/harness/value_demos/s09_grabbing_a_ghidra_address.py | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/harness/value_demos/s09_grabbing_a_ghidra_address.py b/debug/risc-v-gsp-rm-vm/harness/value_demos/s09_grabbing_a_ghidra_address.py new file mode 100644 index 0000000..1577772 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/harness/value_demos/s09_grabbing_a_ghidra_address.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""s09 - grab a Ghidra address: did the firmware actually execute it? + +Real scenario (docs/05-GHIDRA-BRIDGE.md, docs/13, docs/14 5; and +Handoff-vGPU-IO.md PART 91 ADDENDUM 9 cycle 24, 2026-10-01): a static +hypothesis (the GPFIFO GET encodes entry VA ``0x101000``) was only accepted +after the decoded status-queue fault showed the engine actually fetched +``0x40000`` because the GET field dropped the high VA bits (fix ``6f9cbfa``). +The static/dynamic join is exactly this: Ghidra owns addresses and +decompilation, green-eye answers "did it execute here". + +Analog: export a Ghidra-style ``names.tsv`` from the demo ELF *with a base +offset* (the classic program-address vs runtime-address trap), load it with +the right bias, hook the raw Ghidra address, trigger the path, and write the +resulting ``greeneye_hits.tsv`` annotation back for Ghidra. +""" +from __future__ import annotations + +import os + +try: # package import (tests) or direct script execution (run_all) + from . import _common as C +except ImportError: + import sys as _sys + + _sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import _common as C + +#: Pretend the Ghidra project was based at runtime - 0x1000 (the bias pitfall). +BIAS = 0x1000 + + +def _ghidra_tsv(ge, out_path: str) -> dict: + image = ge.load_elf(C.DEMO_ELF) + rows = [] + for symbol in image.symbols: + if not getattr(symbol, "is_function", False) or not symbol.name: + continue + program = symbol.value - BIAS + rows.append((program, symbol.name, symbol.size)) + rows.sort() + with open(out_path, "w", encoding="utf-8") as fh: + for program, name, size in rows: + fh.write(f"{program:#x}\t{name}\tsize={size}\n") + return {"functions": len(rows), "bias": BIAS} + + +def run() -> dict: + import greeneye as ge + from greeneye.ghidra import export_hits + + backend = C.choose_backend("qemu") + out = C.artifact_dir("s09_grabbing_a_ghidra_address") + names_tsv = os.path.join(out, "ghidra_names.tsv") + facts = _ghidra_tsv(ge, names_tsv) + runtime_rpc_fault = ge.load_elf(C.DEMO_ELF) + addr_rpc_fault = None + for symbol in runtime_rpc_fault.symbols: + if symbol.name == "rpc_fault": + addr_rpc_fault = symbol.value + if addr_rpc_fault is None: + raise C.DemoError("rpc_fault not found in demo ELF") + ghidra_addr = addr_rpc_fault - BIAS + + commands = [ + f'write Ghidra names.tsv from ELF (program = runtime - {BIAS:#x})', + f'SymbolDB.load_names_tsv(names, bias={BIAS:#x})', + f'hook(ghidra_addr {ghidra_addr:#x} + bias) # raw static address', + "trigger mailbox cmd=6; hits recorded; export_hits -> greeneye_hits.tsv", + ] + scenario = C.scenario( + ids=[ + "docs/05-GHIDRA-BRIDGE.md / docs/13: static/dynamic address join", + "Handoff-vGPU-IO.md PART 91 ADDENDUM 9 cycle 24 (2026-10-01): GET fault at 0x40000 vs hypothesis 0x101000", + "docs/14-ADVERSARIAL-INTEROP-GUIDE.md 5: program vs runtime addresses and bias", + ], + baseline=( + "cycle 24's decoded status-queue fault ended a run of eight " + "supervised cycles (18-25, ~1 h 41 m); the static GET-encoding " + "hypothesis was unproven until a live PC/fault address existed" + ), + projection=( + "for the real image the same names.tsv + bias + hook loop runs once " + "H2.2 exposes execution of the .fwimage; the bias arithmetic demoed " + "here is exactly what docs/05 warns about" + ), + blocked=True, + device="H2.2 device model to execute the real .fwimage", + ) + C.render_banner( + "s09_grabbing_a_ghidra_address", + "Ghidra gave an address: does the firmware execute exactly there, and can the hit be annotated back?", + "static/dynamic join for the GPFIFO GET fault (0x40000 vs 0x101000)", + scenario["static_baseline"], + commands, + ) + + session = ge.launch(kernel=C.DEMO_ELF, backend=backend, record=os.path.join(out, "session"), name="vd-s09") + try: + session.symbols.load_names_tsv(names_tsv, bias=BIAS) + ctrl = session.symbols.lookup("gsp_ctrl") + resolved = session.resolve(ghidra_addr + BIAS) + hits = [] + session.hook(ghidra_addr + BIAS, on_enter=lambda hit: hits.append(hit.addr)) + session.run(duration=0.25) + session.memory.write_u64(ctrl + C.CTRL_MAILBOX, 6) + session.run(duration=0.25) + hits_tsv = export_hits(os.path.join(out, "session"), os.path.join(out, "greeneye_hits.tsv"), symbols=session.symbols) + payload = { + "success": len(hits) >= 1 and resolved == addr_rpc_fault, + "backend": backend, + "ghidra_functions": facts["functions"], + "ghidra_names_tsv": os.path.relpath(names_tsv, C.TOOL_ROOT), + "bias": BIAS, + "ghidra_addr": f"{ghidra_addr:#x}", + "runtime_addr": f"{resolved:#x}", + "program_addr_used_without_bias": f"{ghidra_addr:#x}", + "hits": hits, + "hits_tsv": os.path.relpath(hits_tsv, C.TOOL_ROOT), + "question": "join a static Ghidra address to a runtime hit, with the bias handled explicitly", + "answer": ( + f"Ghidra addr {ghidra_addr:#x} + bias {BIAS:#x} = runtime {resolved:#x}; " + f"the hook fired {len(hits)}x at that exact address; export_hits wrote " + "the annotated greeneye_hits.tsv - the static hypothesis is now a " + "recorded dynamic fact (and using the program address raw would miss by 0x1000)" + ), + "success_criteria": { + "pass": "the biased address resolves to the real function and the hook records the hit; TSV annotates it", + "fail": "the hit lands at a different address or the TSV is empty", + }, + "real_case": scenario, + } + with open(hits_tsv, "r", encoding="utf-8") as fh: + payload["greeneye_hits_tsv"] = fh.read() + C.write_json(os.path.join(out, "ghidra-join.json"), payload) + finally: + session.close() + return payload + + +def main() -> int: + return 0 if C.timed_demo(run, C.artifact_dir("s09_grabbing_a_ghidra_address"))["success"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) |
