summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/greeneye/session.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/greeneye/session.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/session.py113
1 files changed, 113 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/session.py b/debug/risc-v-gsp-rm-vm/greeneye/session.py
index 7ad6444..fa54f77 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/session.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/session.py
@@ -6,6 +6,7 @@ plugin-supplied backend later.
"""
from __future__ import annotations
+import re
import time
from dataclasses import dataclass, field as _field
from typing import Callable
@@ -16,6 +17,15 @@ from .record import NullRecorder, Recorder
from .rsp import RspClient, StopReply
from .symbols import Symbol, SymbolDB
+#: Registers saved and restored around ``Session.call`` (RISC-V ABI + pc).
+RESTORE_REGS = (
+ ["ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1"]
+ + [f"a{i}" for i in range(8)]
+ + [f"s{i}" for i in range(2, 12)]
+ + [f"t{i}" for i in range(3, 7)]
+ + ["pc"]
+)
+
@dataclass
class Hit:
@@ -195,11 +205,21 @@ class Session:
# -- symbol helpers ----------------------------------------------------
def resolve(self, target: int | str) -> int:
+ """Resolve an address, a symbol name, or a ``symbol+0xNN`` expression."""
if isinstance(target, int):
return target
if isinstance(target, str):
if target.lower().startswith("0x"):
return int(target, 16)
+ expression = re.fullmatch(
+ r"([A-Za-z_][A-Za-z0-9_.]*)\s*([+-])\s*(0x[0-9a-fA-F]+|\d+)", target.strip()
+ )
+ if expression:
+ base = self.symbols.get(expression.group(1))
+ if base is None:
+ raise SymbolNotFound(f"symbol {expression.group(1)!r} not found in symbol DB")
+ delta = int(expression.group(3), 0)
+ return base.addr + delta if expression.group(2) == "+" else base.addr - delta
sym = self.symbols.get(target)
if sym is None:
raise SymbolNotFound(f"symbol {target!r} not found in symbol DB")
@@ -338,6 +358,99 @@ class Session:
break
return hits
+ # -- Frida-like remote call / run-until -------------------------------
+
+ def _scratch_addr(self, override: int | None = None) -> int:
+ if override:
+ return override
+ if self.target is not None and getattr(self.target, "memory_mb", 0):
+ base = getattr(self.target, "ram_base", 0x80000000)
+ return base + self.target.memory_mb * 1024 * 1024 - 0x1000
+ if self.image is not None and getattr(self.image, "max_vaddr", 0):
+ return self.image.max_vaddr + 0x10000
+ return 0x801FF000
+
+ def run_until(self, target: int | str, timeout: float = 5.0) -> Hit:
+ """Run until execution reaches ``target`` (Frida's run-to-cursor).
+
+ Sets a temporary breakpoint, services intervening stops, and returns
+ the Hit for the target. Raises ``RspTimeout`` if not reached.
+ """
+ addr = self.resolve(target)
+ if self.pc == addr:
+ return Hit(kind="stop", addr=addr, regs=self.read_regs(), symbol=self.symbols.resolve(addr), session=self)
+ temp = Breakpoint(addr=addr, kind="sw", temporary=True, on_enter=None)
+ self.add_breakpoint(temp)
+ deadline = time.monotonic() + timeout
+ try:
+ while True:
+ remaining = deadline - time.monotonic()
+ if remaining <= 0:
+ raise RspTimeout(f"run_until {addr:#x}: not reached in {timeout}s")
+ try:
+ stop = self.client.continue_(timeout=remaining)
+ except RspTimeout:
+ self.client.interrupt()
+ raise RspTimeout(f"run_until {addr:#x}: not reached in {timeout}s")
+ hit = self._dispatch_stop(stop)
+ if hit is not None and hit.addr == addr and hit.kind == "breakpoint":
+ return hit
+ if stop.is_exit:
+ raise GreenEyeError(f"run_until {addr:#x}: target exited")
+ finally:
+ self.remove_breakpoint(temp)
+
+ def call(self, target: int | str, args: tuple | list = (), timeout: float = 5.0, scratch: int | None = None) -> int:
+ """Call a function in the target from the debugger (Frida NativeFunction).
+
+ Writes an ``ebreak`` trampoline into a scratch page, sets ``ra`` to it,
+ loads ``a0..a7`` and a private stack, jumps to ``target``, services
+ intervening stops (hooks/watchpoints included), and returns ``a0``.
+ All caller-saved registers and ``pc`` are restored afterwards.
+ """
+ addr = self.resolve(target)
+ scratch = self._scratch_addr(scratch)
+ stack = scratch + 0x800
+ saved = self.read_regs()
+ temp = Breakpoint(addr=scratch, kind="sw", temporary=True, purpose="user")
+ self.add_breakpoint(temp)
+ try:
+ for name in ("a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7"):
+ self.client.write_reg(name, 0)
+ for index, value in enumerate(args[:8]):
+ self.client.write_reg(f"a{index}", int(value) & 0xFFFF_FFFF_FFFF_FFFF)
+ self.client.write_reg("ra", scratch)
+ self.client.write_reg("sp", stack)
+ self.client.write_reg("pc", addr)
+ self.recorder.event("call_enter", addr=addr, args=list(args), scratch=scratch)
+ deadline = time.monotonic() + timeout
+ while True:
+ remaining = deadline - time.monotonic()
+ if remaining <= 0:
+ raise RspTimeout(f"call {addr:#x}: no return in {timeout}s")
+ try:
+ stop = self.client.continue_(timeout=remaining)
+ except RspTimeout:
+ self.client.interrupt()
+ raise RspTimeout(f"call {addr:#x}: no return in {timeout}s")
+ hit = self._dispatch_stop(stop)
+ pc = self.client.pc
+ if pc == scratch or (hit is not None and hit.addr == scratch):
+ retval = self.client.read_reg("a0")
+ self.recorder.event("call_return", addr=addr, retval=retval)
+ return retval
+ if stop.is_exit:
+ raise GreenEyeError(f"call {addr:#x}: target exited")
+ finally:
+ if temp.attached:
+ self.remove_breakpoint(temp)
+ for name in RESTORE_REGS:
+ if name in saved:
+ try:
+ self.client.write_reg(name, saved[name])
+ except Exception:
+ pass
+
def run(
self,
duration: float | None = None,