summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/tests/test_rsp.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_rsp.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/tests/test_rsp.py175
1 files changed, 175 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_rsp.py b/debug/risc-v-gsp-rm-vm/tests/test_rsp.py
new file mode 100644
index 0000000..70b9f45
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/tests/test_rsp.py
@@ -0,0 +1,175 @@
+"""Unit tests for the RSP client using an in-process fake stub."""
+from __future__ import annotations
+
+import socket
+import threading
+import unittest
+
+from greeneye.rsp import (
+ RV64_FALLBACK_REGS,
+ RspClient,
+ checksum,
+ encode_packet,
+ parse_stop_reply,
+ _parse_target_xml,
+)
+
+TARGET_XML = """<?xml version="1.0"?>
+<target><architecture>riscv:rv64</architecture>
+<feature name="org.gnu.gdb.riscv.cpu">
+<reg name="x0" bitsize="64" regnum="0"/>
+<reg name="ra" bitsize="64" regnum="1"/>
+<reg name="pc" bitsize="64" regnum="32"/>
+</feature></target>"""
+
+
+class FakeStub(threading.Thread):
+ """Minimal RSP server for unit tests."""
+
+ def __init__(self, sock: socket.socket):
+ super().__init__(daemon=True)
+ self.sock = sock
+ self.no_ack = False
+ self.stopped = True
+ self.regs = bytearray(33 * 8)
+ self.regs[8:16] = (0xAA).to_bytes(8, "little") # ra (x1)
+ self.regs[16:24] = (0x80000194).to_bytes(8, "little") # pc (3-reg test layout)
+ self.memory = bytearray(0x10000)
+ for i in range(len(self.memory)):
+ self.memory[i] = i & 0xFF
+ self.bps: set[int] = set()
+
+ def run(self):
+ buf = b""
+ while True:
+ try:
+ chunk = self.sock.recv(4096)
+ except OSError:
+ return
+ if not chunk:
+ return
+ buf += chunk
+ while b"#" in buf and b"$" in buf:
+ start = buf.index(b"$")
+ end = buf.index(b"#", start)
+ if len(buf) < end + 3:
+ break
+ payload = buf[start + 1 : end]
+ buf = buf[end + 3 :]
+ self.handle(payload)
+
+ def send(self, text: str):
+ data = encode_packet(text.encode())
+ self.sock.sendall(data)
+
+ def handle(self, payload: bytes):
+ cmd = payload.decode("latin1")
+ if not self.no_ack:
+ self.sock.sendall(b"+")
+ if cmd.startswith("qSupported"):
+ self.send("PacketSize=1000;qXfer:features:read+;multiprocess+")
+ elif cmd == "QStartNoAckMode":
+ self.send("")
+ self.no_ack = True
+ elif cmd == "?":
+ self.send("T05thread:p01.01;")
+ elif cmd in ("Hg0", "Hc-1"):
+ self.send("OK")
+ elif cmd.startswith("qXfer:features:read:target.xml"):
+ self.send("l" + TARGET_XML)
+ elif cmd.startswith("qXfer"):
+ self.send("E01")
+ elif cmd == "g":
+ self.send(bytes(self.regs).hex())
+ elif cmd.startswith("p"):
+ regno = int(cmd[1:], 16)
+ if regno == 32:
+ self.send(self.regs[16:24].hex())
+ else:
+ self.send(self.regs[regno * 8 : regno * 8 + 8].hex())
+ elif cmd.startswith("m"):
+ addr_s, len_s = cmd[1:].split(",")
+ addr, length = int(addr_s, 16), int(len_s, 16)
+ self.send(bytes(self.memory[addr : addr + length]).hex())
+ elif cmd.startswith("M"):
+ head, data = cmd[1:].split(":", 1)
+ addr_s, len_s = head.split(",")
+ addr, length = int(addr_s, 16), int(len_s, 16)
+ blob = bytes.fromhex(data)
+ self.memory[addr : addr + length] = blob
+ self.send("OK")
+ elif cmd.startswith("Z0"):
+ self.bps.add(int(cmd.split(",")[1], 16))
+ self.send("OK")
+ elif cmd.startswith("z0"):
+ self.bps.discard(int(cmd.split(",")[1], 16))
+ self.send("OK")
+ elif cmd.startswith("Z2") or cmd.startswith("z2"):
+ self.send("OK")
+ elif cmd in ("c", "s"):
+ self.stopped = True
+ self.send("T05thread:p01.01;")
+ else:
+ self.send("")
+
+
+class RspTests(unittest.TestCase):
+ def setUp(self):
+ self.client_sock, self.server_sock = socket.socketpair()
+ self.stub = FakeStub(self.server_sock)
+ self.stub.start()
+ self.client = RspClient(timeout=2.0)
+ self.client.sock = self.client_sock
+ self.client.sync()
+
+ def tearDown(self):
+ self.client.close()
+ self.server_sock.close()
+
+ def test_checksum_and_encode(self):
+ self.assertEqual(checksum(b"OK"), (ord("O") + ord("K")) & 0xFF)
+ self.assertEqual(encode_packet(b"OK"), b"$OK#9a")
+
+ def test_parse_stop_reply(self):
+ stop = parse_stop_reply("T05watch:0000000080001000;thread:p01.01;")
+ self.assertEqual(stop.signal, 5)
+ self.assertEqual(stop.watch_addr, 0x80001000)
+ self.assertEqual(stop.watch_kind, "watch")
+ self.assertEqual(stop.thread, "p01.01")
+ self.assertTrue(stop.is_stop)
+ exit_stop = parse_stop_reply("W00")
+ self.assertTrue(exit_stop.is_exit)
+
+ def test_target_xml_parse(self):
+ regs = _parse_target_xml(TARGET_XML)
+ self.assertEqual([r.name for r in regs], ["x0", "ra", "pc"])
+ self.assertEqual([r.number for r in regs], [0, 1, 32])
+ by_name = {r.name: r for r in regs}
+ self.assertEqual(by_name["ra"].offset, 8)
+ self.assertEqual(by_name["pc"].offset, 16)
+ self.assertEqual(len(RV64_FALLBACK_REGS), 33)
+ self.assertEqual(RV64_FALLBACK_REGS[32].name, "pc")
+
+ def test_registers_and_memory(self):
+ regs = self.client.read_registers()
+ self.assertEqual(regs["ra"], 0xAA)
+ self.assertEqual(regs["pc"], 0x80000194)
+ self.assertEqual(self.client.read_reg("pc"), 0x80000194)
+ data = self.client.read_mem(0x10, 8)
+ self.assertEqual(data, bytes(range(0x10, 0x18)))
+ self.client.write_mem(0x20, b"\xde\xad\xbe\xef")
+ self.assertEqual(self.client.read_mem(0x20, 4), b"\xde\xad\xbe\xef")
+ self.assertEqual(self.client.read_u32(0x20), 0xEFBEADDE)
+
+ def test_breakpoints_watchpoints_continue(self):
+ self.client.set_sw_break(0x80000048)
+ self.client.clear_sw_break(0x80000048)
+ self.client.set_watch(0x80000850, size=8, kind="w")
+ stop = self.client.continue_()
+ self.assertEqual(stop.signal, 5)
+ stop = self.client.step()
+ self.assertEqual(stop.signal, 5)
+
+
+if __name__ == "__main__":
+ unittest.main()