From ba58dcdc5ad6ca021c723d279f33e3f14e1184df Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Fri, 2 Oct 2026 08:36:35 -0400 Subject: debug: add green-eye GSP/uproc RISC-V VM runtime analysis tooling Adds open-iov/debug/risc-v-gsp-rm-vm: a GDB-free instrumentation stack that boots uproc/GSP firmware in qemu-system-riscv64 and provides Frida-like hooks, breakpoints, watchpoints, memory dumps, structure decoding, snapshot diffs, recorded sessions, declarative probes, a plugin API and a Ghidra symbol/hit bridge. Verified: 25 tests green (including a live QEMU smoke test); the demo uproc boots with correct hook/leave/watch/schema/snapshot/fault evidence; the optional TCG trace plugin builds and recorded 15M trace lines in 3 s; the TU104 570.144 fwimage probe reports its first exception (load access fault at firmware vaddr 0x4009014 reading 0x2000000000000A00). Documentation: lean tool docs under debug/risc-v-gsp-rm-vm/docs/00-10, a new docs/05-operations/03-risc-v-gsp-rm-vm.md debugging document, and '# TOOL IN DEVELOPMENT - NOT READY YET!' markers in docs/README.md, the onboarding tooling table and the debugging workflows doc. Vendors qemu-plugin.h from QEMU v8.2.2 (GPL-2.0-or-later), with its license header intact, so the optional TCG plugin builds without a QEMU source tree. --- debug/risc-v-gsp-rm-vm/tests/test_probe.py | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 debug/risc-v-gsp-rm-vm/tests/test_probe.py (limited to 'debug/risc-v-gsp-rm-vm/tests/test_probe.py') diff --git a/debug/risc-v-gsp-rm-vm/tests/test_probe.py b/debug/risc-v-gsp-rm-vm/tests/test_probe.py new file mode 100644 index 0000000..f97a4bf --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/tests/test_probe.py @@ -0,0 +1,65 @@ +"""Unit tests for the ELF trampoline/flat-kernel path and QEMU fault parsing.""" +from __future__ import annotations + +import os +import struct +import tempfile +import unittest + +from greeneye.analyze import describe_fault, parse_qemu_faults +from greeneye.elf import _trampoline, load_elf, write_flat_kernel + +DEMO_ELF = os.path.join(os.path.dirname(__file__), "..", "firmware", "uproc_demo", "build", "uproc_demo.elf") + +SAMPLE_LOG = """ +Invalid read at addr 0x2000000000000a00, size 4, region '(null)', reason: rejected +riscv_cpu_do_interrupt: hart:0, async:0, cause:0000000000000005, epc:0x0000000084009014, tval:0x2000000000000a00, desc=fault_load +Invalid read at addr 0x0, size 2, region '(null)', reason: rejected +riscv_cpu_do_interrupt: hart:0, async:0, cause:0000000000000001, epc:0x0000000000000000, tval:0x0000000000000000, desc=fault_fetch +""" + + +class TrampolineTests(unittest.TestCase): + def test_encoding(self): + stub = _trampoline(0x4009000) + auipc, jalr = struct.unpack("> 7) & 0x1F, 5) # t0 + self.assertEqual((auipc >> 12) & 0xFFFFF, 0x4009) + self.assertEqual(jalr & 0x7F, 0x67) # JALR + self.assertEqual((jalr >> 15) & 0x1F, 5) + self.assertEqual(jalr >> 20, 0) + + def test_flat_demo(self): + if not os.path.exists(DEMO_ELF): + self.skipTest("demo firmware not built") + with tempfile.TemporaryDirectory() as tmp: + out = os.path.join(tmp, "flat.bin") + path, entry = write_flat_kernel(DEMO_ELF, out, load_addr=0x80000000) + self.assertEqual(entry, 0x80000000) + image = load_elf(DEMO_ELF) + self.assertEqual(os.path.getsize(path), image.max_vaddr - image.min_vaddr) + with open(path, "rb") as fh: + # entry at offset 0: the real first instruction must be present + first = fh.read(4) + self.assertNotEqual(first, b"\x00\x00\x00\x00") + + +class FaultParseTests(unittest.TestCase): + def test_parse_and_describe(self): + faults = parse_qemu_faults(SAMPLE_LOG) + self.assertEqual(len(faults), 2) + first = faults[0] + self.assertEqual(first["cause"], 5) + self.assertEqual(first["epc"], 0x84009014) + self.assertEqual(first["access"]["access_addr"], 0x2000000000000A00) + self.assertEqual(first["access"]["size"], 4) + text = describe_fault(first, bias=0x80000000) + self.assertIn("0x4009014", text) + self.assertIn("load access fault", text) + self.assertIn("0x2000000000000a00", text) + self.assertEqual(faults[1]["desc"], "fault_fetch") + + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3