diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 08:36:35 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 08:36:35 -0400 |
| commit | 31e27d455969d33190d28917ef98f034888d217b (patch) | |
| tree | c0c4dc18e78e6a1702fcda43bc2806e7cfb7c931 /debug/risc-v-gsp-rm-vm/tests/test_core.py | |
| parent | c155ee8de5b642cbd407bf2231041f17ebe43734 (diff) | |
| download | open-iov-31e27d455969d33190d28917ef98f034888d217b.tar.gz open-iov-31e27d455969d33190d28917ef98f034888d217b.zip | |
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.
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_core.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/tests/test_core.py | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_core.py b/debug/risc-v-gsp-rm-vm/tests/test_core.py new file mode 100644 index 0000000..ab27451 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/tests/test_core.py @@ -0,0 +1,131 @@ +"""Unit tests for ELF parsing, symbol resolution and structure decoding.""" +from __future__ import annotations + +import json +import os +import tempfile +import unittest + +from greeneye.elf import EM_RISCV, parse_elf, load_elf +from greeneye.structs import Field, Schema, decode, render, schema_from_json +from greeneye.symbols import Symbol, SymbolDB + +DEMO_ELF = os.path.join(os.path.dirname(__file__), "..", "firmware", "uproc_demo", "build", "uproc_demo.elf") + + +class ElfTests(unittest.TestCase): + def test_parse_demo_elf(self): + if not os.path.exists(DEMO_ELF): + self.skipTest("demo firmware not built") + image = load_elf(DEMO_ELF) + self.assertTrue(image.is_riscv) + self.assertEqual(image.e_machine, EM_RISCV) + self.assertEqual(image.entry, 0x80000000) + self.assertGreaterEqual(len(image.load_segments), 1) + names = {s.name for s in image.symbols} + self.assertIn("main", names) + self.assertIn("rpc_ping", names) + self.assertIn("gsp_ctrl", names) + ctrl = next(s for s in image.symbols if s.name == "gsp_ctrl") + self.assertEqual(ctrl.value, 0x80000840) + self.assertEqual(ctrl.size, 0x188) + plan = image.flat_load_plan() + self.assertTrue(plan and plan[0][0] == 0x80000000) + + def test_not_elf(self): + with self.assertRaises(Exception): + parse_elf(b"not an elf") + + +class SymbolTests(unittest.TestCase): + def setUp(self): + self.db = SymbolDB() + self.db.add(Symbol(addr=0x1000, name="alpha", size=0x100, source="test")) + self.db.add(Symbol(addr=0x1200, name="beta", size=0x20, source="test")) + + def test_resolve(self): + self.assertEqual(self.db.resolve(0x1010).name, "alpha") + self.assertEqual(self.db.resolve(0x1210).name, "beta") + self.assertIsNone(self.db.resolve(0x5000)) + self.assertEqual(self.db.annotate(0x1010), "0x1010 alpha+0x10") + self.assertEqual(self.db.lookup("beta"), 0x1200) + + def test_names_tsv(self): + with tempfile.NamedTemporaryFile("w", suffix=".tsv", delete=False) as fh: + fh.write("# comment\n0x00279000\t_DT_INIT\tghidra\n0x00279020\tFUN_x\n") + path = fh.name + try: + count = self.db.load_names_tsv(path, bias=0x100000) + self.assertEqual(count, 2) + self.assertEqual(self.db.lookup("_DT_INIT"), 0x279000 + 0x100000) + finally: + os.unlink(path) + + def test_index_tsv(self): + with tempfile.NamedTemporaryFile("w", suffix=".tsv", delete=False) as fh: + fh.write("addr\tfile_addr\tsize\tname\n") + fh.write("0x00279000\t0x00179000\t24\t_DT_INIT\n") + path = fh.name + try: + count = self.db.load_index_tsv(path, bias=0) + self.assertEqual(count, 1) + sym = self.db.get("_DT_INIT") + self.assertEqual(sym.addr, 0x179000) + self.assertEqual(sym.size, 24) + self.assertEqual(sym.extras["program_addr"], 0x279000) + finally: + os.unlink(path) + + def test_json_roundtrip(self): + with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fh: + path = fh.name + try: + self.db.save_json(path, meta={"unit": "test"}) + other = SymbolDB() + self.assertEqual(other.load_json(path), 2) + self.assertEqual(other.lookup("alpha"), 0x1000) + payload = json.load(open(path)) + self.assertEqual(payload["format"], "greeneye-symbols/1") + finally: + os.unlink(path) + + +class StructTests(unittest.TestCase): + SCHEMA = { + "name": "demo", + "fields": [ + {"name": "magic", "offset": 0, "fmt": "u32"}, + {"name": "state", "offset": 4, "fmt": "u32", "enum": {"0": "IDLE", "1": "RUN"}}, + {"name": "count", "offset": 8, "fmt": "u64"}, + {"name": "name", "offset": 16, "fmt": "cstr", "size": 16}, + {"name": "buf", "offset": 32, "fmt": "bytes", "size": 8}, + ], + } + + def test_decode_render(self): + schema = schema_from_json(self.SCHEMA) + self.assertEqual(schema.size, 40) + data = bytearray(40) + data[0:4] = (0x47535031).to_bytes(4, "little") + data[4:8] = (1).to_bytes(4, "little") + data[8:16] = (42).to_bytes(8, "little") + data[16:22] = b"green\x00" + data[32:40] = bytes(range(8)) + values = decode(schema, bytes(data), base=0x80000000) + self.assertEqual(values["magic"], 0x47535031) + self.assertEqual(values["state"], 1) + self.assertEqual(values["count"], 42) + self.assertEqual(values["name"], "green") + self.assertEqual(values["buf"], bytes(range(8))) + text = render(schema, values) + self.assertIn("magic", text) + self.assertIn("0x47535031", text) + self.assertIn("green", text) + + def test_invalid_schema(self): + with self.assertRaises(Exception): + Field(name="x", offset=0, fmt="nope").nbytes + + +if __name__ == "__main__": + unittest.main() |
