summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/value_demos/s09_grabbing_a_ghidra_address.py
blob: 5bd1efe16a7af4c9f585b82b2c5d345f62b52953 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/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, risc-v-gsp-rm-debugger 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")
    session_dir = C.session_dir(out)
    names_tsv = os.path.join(session_dir, "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_dir = C.session_dir(out)
    session = ge.launch(kernel=C.DEMO_ELF, backend=backend, record=session_dir, 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(session_dir, os.path.join(session_dir, "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())