summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/value_demos/s09_grabbing_a_ghidra_address.py
diff options
context:
space:
mode:
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.py146
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())