summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/tests/test_injection.py
blob: 5dca9cb05415f3cc6af69b564d82ae9550900d1e (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
"""Integration tests for Frida-like remote calls, run-until and service injection.

Skipped cleanly when QEMU, the demo firmware, clang/lld or the SDK are absent.
"""
from __future__ import annotations

import io
import contextlib
import os
import shutil
import subprocess
import unittest

import greeneye as ge
from greeneye.errors import RspTimeout

HERE = os.path.dirname(os.path.abspath(__file__))
TOOL_ROOT = os.path.join(HERE, "..")
DEMO_ELF = os.path.join(TOOL_ROOT, "firmware", "uproc_demo", "build", "uproc_demo.elf")
SERVICE_ELF = os.path.join(TOOL_ROOT, "sdk", "build", "injected_demo.elf")


def qemu_available() -> bool:
    return bool(os.environ.get("GREENEYE_QEMU") or shutil.which("qemu-system-riscv64"))


def toolchain_available() -> bool:
    return bool(shutil.which("clang")) and (
        shutil.which("ld.lld") or os.path.exists("/usr/lib/llvm-18/bin/ld.lld")
    )


@unittest.skipUnless(qemu_available(), "qemu-system-riscv64 not installed")
@unittest.skipUnless(os.path.exists(DEMO_ELF), "demo firmware not built (make demo)")
class CallTests(unittest.TestCase):
    def test_remote_call(self):
        session = ge.launch(kernel=DEMO_ELF, name="unittest-call")
        try:
            result = session.call("rpc_ping", (0x1111,))
            self.assertEqual(result, 0x1111 ^ 0xBEEF)
            # registers must be restored around the call
            self.assertEqual(session.pc, 0x1000)
        finally:
            session.close()

    def test_run_until(self):
        session = ge.launch(kernel=DEMO_ELF, name="unittest-rununtil")
        try:
            hit = session.run_until("rpc_sum", timeout=5.0)
            self.assertEqual(hit.addr, session.symbols.lookup("rpc_sum"))
            self.assertEqual(hit.kind, "breakpoint")
        finally:
            session.close()

    def test_run_until_timeout(self):
        session = ge.launch(kernel=DEMO_ELF, name="unittest-rununtil-timeout")
        try:
            with self.assertRaises(RspTimeout):
                session.run_until("rpc_fault", timeout=0.2)  # self-test never calls it
        finally:
            session.close()


@unittest.skipUnless(qemu_available(), "qemu-system-riscv64 not installed")
@unittest.skipUnless(os.path.exists(DEMO_ELF), "demo firmware not built (make demo)")
class ReplTests(unittest.TestCase):
    def test_repl_commands(self):
        from greeneye.cli import GreenEyeRepl
        from greeneye.plugins import PluginManager

        session = ge.launch(kernel=DEMO_ELF, record=True, name="unittest-repl")
        output = io.StringIO()
        try:
            ctrl = session.symbols.lookup("gsp_ctrl")
            repl = GreenEyeRepl(session, PluginManager(session))
            with contextlib.redirect_stdout(output):
                repl.onecmd("bp rpc_sum")
                repl.onecmd("watch gsp_ctrl+0x10 8 w")
                repl.onecmd(f"x {ctrl:#x} 16")
                repl.onecmd("sym rpc_ping")
                repl.onecmd("c 0.6")
                repl.onecmd("events 5")
            text = output.getvalue()
            self.assertIn("rpc_sum", text)
            self.assertIn(f"{ctrl:08x}", text)
            self.assertIn(f"watch w@{ctrl + 0x10:#x}", text)
        finally:
            session.close()


@unittest.skipUnless(qemu_available(), "qemu-system-riscv64 not installed")
@unittest.skipUnless(os.path.exists(DEMO_ELF), "demo firmware not built")
@unittest.skipUnless(toolchain_available(), "clang/lld not available")
class InjectionTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        result = subprocess.run(
            ["make", "-C", os.path.join(TOOL_ROOT, "sdk")],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0 or not os.path.exists(SERVICE_ELF):
            raise unittest.SkipTest(f"SDK build failed: {result.stderr[-200:]}")

    def test_direct_and_firmware_invocation(self):
        from greeneye.inject import invoke_remote, invoke_via_firmware, load_service

        session = ge.launch(kernel=DEMO_ELF, record=True, name="unittest-inject")
        try:
            service = load_service(session, SERVICE_ELF, slot=6)
            self.assertEqual(invoke_remote(session, service, 1, 0x41), 0x41 ^ 0xBEEF)
            self.assertEqual(invoke_remote(session, service, 2, 0x10, 0x20), 0x30)
            session.run(duration=0.3)
            self.assertEqual(invoke_via_firmware(session, service, 1, 0x41), 0x41 ^ 0xBEEF)
            self.assertEqual(invoke_via_firmware(session, service, 2, 0x10, 0x20), 0x30)
        finally:
            session.close()


if __name__ == "__main__":
    unittest.main()