"""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()