summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/greeneye
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/greeneye')
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/__init__.py4
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/analyze.py6
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/cli.py38
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/device_map.py2
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/elf.py2
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/errors.py4
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/gdbridge.py22
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/ghidra.py4
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/inject.py4
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/plugins.py4
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/record.py2
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/rsp.py2
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/session.py20
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/sim_backend.py2
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/symbols.py6
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/target.py35
16 files changed, 95 insertions, 62 deletions
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/__init__.py b/debug/risc-v-gsp-rm-vm/greeneye/__init__.py
index 717160c..4d55b89 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/__init__.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/__init__.py
@@ -1,7 +1,7 @@
-"""green-eye: Frida-like runtime instrumentation for GSP/uproc firmware in a
+"""risc-v-gsp-rm-debugger: Frida-like runtime instrumentation for GSP/uproc firmware in a
RISC-V QEMU VM.
-green-eye talks to QEMU's gdbstub directly (no GDB dependency) and layers a
+risc-v-gsp-rm-debugger talks to QEMU's gdbstub directly (no GDB dependency) and layers a
scriptable instrumentation API on top: breakpoints, hooks, watchpoints,
memory inspection, structure decoding, session recording and post-run analysis.
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/analyze.py b/debug/risc-v-gsp-rm-vm/greeneye/analyze.py
index 1c7e930..cc8b8f6 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/analyze.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/analyze.py
@@ -143,9 +143,9 @@ def render_summary_from(meta: dict, events: list[dict], symbols=None, max_rows:
if trace is None:
trace = [e for e in events if _trace_kind(e) in _TRACE_KINDS]
lines: list[str] = []
- lines.append(f"# green-eye session summary: {meta.get('name', '?')}")
+ lines.append(f"# risc-v-gsp-rm-debugger session summary: {meta.get('name', '?')}")
lines.append("")
- lines.append(f"* format: `{meta.get('format', '?')}` green-eye {meta.get('greeneye', '?')}")
+ lines.append(f"* format: `{meta.get('format', '?')}` risc-v-gsp-rm-debugger {meta.get('greeneye', '?')}")
lines.append(f"* directory: `{meta.get('dir', '.')}`")
lines.append(f"* events: {len(events)}")
counts = Counter(event.get("type", "?") for event in events)
@@ -396,7 +396,7 @@ def compare_sessions(a_dir: str, b_dir: str, symbols=None, max_rows: int = 40) -
a_meta, b_meta = load_meta(a_dir), load_meta(b_dir)
a_events, b_events = load_events(a_dir), load_events(b_dir)
a_trace, b_trace = load_trace(a_dir, a_meta), load_trace(b_dir, b_meta)
- lines = [f"# green-eye session comparison", "", f"* A: {a_meta.get('name')} ({a_dir})", f"* B: {b_meta.get('name')} ({b_dir})", ""]
+ lines = [f"# risc-v-gsp-rm-debugger session comparison", "", f"* A: {a_meta.get('name')} ({a_dir})", f"* B: {b_meta.get('name')} ({b_dir})", ""]
def trace_kind(record: dict) -> str:
kind = _trace_kind(record)
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/cli.py b/debug/risc-v-gsp-rm-vm/greeneye/cli.py
index e7023ec..3f3c937 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/cli.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/cli.py
@@ -1,4 +1,4 @@
-"""green-eye command line interface.
+"""risc-v-gsp-rm-debugger command line interface.
Commands:
info environment/tooling check (incl. native simulator availability)
@@ -14,7 +14,7 @@ Commands:
ghidra serve a live session to GhidraScripts over the JSON-lines bridge
analyze render a recorded session to a symbol-annotated report
compare A/B two recorded sessions at the symbolic level
- symbols build/inspect a green-eye symbol database from Ghidra exports
+ symbols build/inspect a risc-v-gsp-rm-debugger symbol database from Ghidra exports
plugins list discoverable plugins
"""
from __future__ import annotations
@@ -108,7 +108,7 @@ def _start_session(
def _print_session_dir(session) -> None:
directory = getattr(session.recorder, "dir", None)
if directory:
- print(f"[green-eye] session: {directory}")
+ print(f"[risc-v-gsp-rm-debugger] session: {directory}")
def _add_backend_arg(parser) -> None:
@@ -176,7 +176,7 @@ def _trace_spec(args):
def cmd_info(args) -> int:
from .target import DEFAULT_QEMU
- print(f"green-eye {ge.__version__}")
+ print(f"risc-v-gsp-rm-debugger {ge.__version__}")
print(f"tool root: {TOOL_ROOT}")
qemu = os.environ.get("GREENEYE_QEMU", DEFAULT_QEMU)
import shutil
@@ -314,8 +314,8 @@ def cmd_run(args) -> int:
class GreenEyeRepl(cmd.Cmd):
- intro = "green-eye interactive instrumentation. Type 'help' for commands."
- prompt = "greeneye> "
+ intro = "risc-v-gsp-rm-debugger interactive instrumentation. Type 'help' for commands."
+ prompt = "risc-v-gsp-rm-debugger> "
def __init__(self, session, manager):
super().__init__()
@@ -519,15 +519,15 @@ def cmd_launch(args) -> int:
raise GreenEyeError(f"kernel not found: {kernel}")
session = ge.launch(kernel=str(kernel), record=args.record, name="launch", keep_alive=True)
port = session.target.port
- print(f"[green-eye] target running: {kernel}")
- print(f"[green-eye] gdbstub: 127.0.0.1:{port}")
- print(f"[green-eye] attach: ./greeneye.sh attach --port {port} --kernel {kernel}")
- print("[green-eye] Ctrl-C stops waiting; QEMU keeps running for attach.")
+ print(f"[risc-v-gsp-rm-debugger] target running: {kernel}")
+ print(f"[risc-v-gsp-rm-debugger] gdbstub: 127.0.0.1:{port}")
+ print(f"[risc-v-gsp-rm-debugger] attach: ./risc-v-gsp-rm-debugger.sh attach --port {port} --kernel {kernel}")
+ print("[risc-v-gsp-rm-debugger] Ctrl-C stops waiting; QEMU keeps running for attach.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
- print("[green-eye] detached (QEMU still running)")
+ print("[risc-v-gsp-rm-debugger] detached (QEMU still running)")
return 0
@@ -678,10 +678,10 @@ def cmd_ghidra(args) -> int:
)
bridge = GhidraBridge(session, port=args.bridge_port)
port = bridge.start()
- print(f"[green-eye] ghidra bridge on 127.0.0.1:{port}")
- print(f"[green-eye] session: {getattr(session.recorder, 'dir', None)}")
- print("[green-eye] in Ghidra: analyzeHeadless ... -postScript GreenEyeControl.java " + str(port))
- print("[green-eye] Ctrl-C to stop the bridge")
+ print(f"[risc-v-gsp-rm-debugger] ghidra bridge on 127.0.0.1:{port}")
+ print(f"[risc-v-gsp-rm-debugger] session: {getattr(session.recorder, 'dir', None)}")
+ print("[risc-v-gsp-rm-debugger] in Ghidra: analyzeHeadless ... -postScript GreenEyeControl.java " + str(port))
+ print("[risc-v-gsp-rm-debugger] Ctrl-C to stop the bridge")
try:
bridge.serve_forever()
except KeyboardInterrupt:
@@ -754,8 +754,8 @@ def cmd_plugins(args) -> int:
def build_parser() -> argparse.ArgumentParser:
- parser = argparse.ArgumentParser(prog="greeneye", description=__doc__.splitlines()[0])
- parser.add_argument("--version", action="version", version=f"green-eye {ge.__version__}")
+ parser = argparse.ArgumentParser(prog="risc-v-gsp-rm-debugger", description=__doc__.splitlines()[0])
+ parser.add_argument("--version", action="version", version=f"risc-v-gsp-rm-debugger {ge.__version__}")
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("info", help="environment/tooling check").set_defaults(fn=cmd_info)
@@ -798,7 +798,7 @@ def build_parser() -> argparse.ArgumentParser:
probe.add_argument("--no-relocate", action="store_true")
probe.add_argument("--instructions", type=int, default=0)
probe.add_argument("--duration", type=float, default=1.0)
- probe.add_argument("--symbols", help="green-eye symbol DB JSON to annotate the stop")
+ probe.add_argument("--symbols", help="risc-v-gsp-rm-debugger symbol DB JSON to annotate the stop")
probe.add_argument("--record", nargs="?", const=True, default=None)
probe.set_defaults(fn=cmd_probe)
@@ -881,7 +881,7 @@ def main(argv: list[str] | None = None) -> int:
try:
return args.fn(args)
except GreenEyeError as exc:
- print(f"green-eye error: {exc}", file=sys.stderr)
+ print(f"risc-v-gsp-rm-debugger error: {exc}", file=sys.stderr)
return 2
except KeyboardInterrupt:
print("interrupted", file=sys.stderr)
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/device_map.py b/debug/risc-v-gsp-rm-vm/greeneye/device_map.py
index 11ac656..2acb2a5 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/device_map.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/device_map.py
@@ -515,7 +515,7 @@ def _cmd_demo(args) -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
prog="python3 -m greeneye.device_map",
- description="Harvest, inspect and merge green-eye first-touch device maps.",
+ description="Harvest, inspect and merge risc-v-gsp-rm-debugger first-touch device maps.",
)
sub = parser.add_subparsers(dest="cmd", required=True)
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/elf.py b/debug/risc-v-gsp-rm-vm/greeneye/elf.py
index abfccd9..3b8f04a 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/elf.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/elf.py
@@ -7,7 +7,7 @@ Needed for three things:
* entry-point detection for the first-fault firmware probe.
This is not a full ELF library; it parses headers, PT_LOAD segments and
-``.symtab`` symbols, which is all green-eye needs.
+``.symtab`` symbols, which is all risc-v-gsp-rm-debugger needs.
"""
from __future__ import annotations
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/errors.py b/debug/risc-v-gsp-rm-vm/greeneye/errors.py
index fab5874..dc5771b 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/errors.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/errors.py
@@ -1,9 +1,9 @@
-"""green-eye error types. Keep them few and meaningful."""
+"""risc-v-gsp-rm-debugger error types. Keep them few and meaningful."""
from __future__ import annotations
class GreenEyeError(Exception):
- """Base class for all green-eye errors."""
+ """Base class for all risc-v-gsp-rm-debugger errors."""
class RspError(GreenEyeError):
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/gdbridge.py b/debug/risc-v-gsp-rm-vm/greeneye/gdbridge.py
index cc06e43..eafac39 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/gdbridge.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/gdbridge.py
@@ -1,4 +1,4 @@
-"""Ghidra control bridge: let GhidraScripts drive a green-eye session.
+"""Ghidra control bridge: let GhidraScripts drive a risc-v-gsp-rm-debugger session.
A tiny JSON-lines server over localhost. Each request is one line::
@@ -20,6 +20,9 @@ import threading
from .errors import GreenEyeError
+#: The bridge is unauthenticated and write-capable; it must never be exposed.
+LOOPBACK_HOSTS = ("127.0.0.1", "localhost", "::1")
+
class GhidraBridge:
def __init__(self, session, host: str = "127.0.0.1", port: int = 0):
@@ -34,6 +37,8 @@ class GhidraBridge:
# -- lifecycle ---------------------------------------------------------
def start(self) -> int:
+ if self.host not in LOOPBACK_HOSTS:
+ raise ValueError(f"refusing non-loopback bind {self.host!r}; the Ghidra bridge is local-only")
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((self.host, self.port))
@@ -46,12 +51,21 @@ class GhidraBridge:
def stop(self) -> None:
self._stop.set()
- if self._server is not None:
+ server = self._server
+ self._server = None
+ if server is not None:
+ # shutdown() wakes a thread blocked in accept(); close() alone does
+ # not, which left the listener up after a <close> command.
+ try:
+ server.shutdown(socket.SHUT_RDWR)
+ except OSError:
+ pass
try:
- self._server.close()
+ server.close()
except OSError:
pass
- self._server = None
+ if self._thread is not None and self._thread.is_alive() and threading.current_thread() is not self._thread:
+ self._thread.join(timeout=2.0)
def serve_forever(self) -> None:
if self._thread is None:
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/ghidra.py b/debug/risc-v-gsp-rm-vm/greeneye/ghidra.py
index 399a355..a7d547a 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/ghidra.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/ghidra.py
@@ -2,14 +2,14 @@
Two flows:
-*out of Ghidra*: export function names/addresses/sizes to a green-eye JSON
+*out of Ghidra*: export function names/addresses/sizes to a risc-v-gsp-rm-debugger JSON
symbol database (from ``names.tsv``, ``decompiled_index.tsv``, exports or ELF);
*hits*: turn a recorded session into ``greeneye_hits.tsv``
(``addr<TAB>hits<TAB>symbol``) that ``MarkGreenEyeHits.java`` can annotate in
the Ghidra project.
This is the static/dynamic join: Ghidra owns the address space and
-decompilation, green-eye owns what actually executed.
+decompilation, risc-v-gsp-rm-debugger owns what actually executed.
"""
from __future__ import annotations
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/inject.py b/debug/risc-v-gsp-rm-vm/greeneye/inject.py
index f9605fc..eb4b16a 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/inject.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/inject.py
@@ -1,4 +1,4 @@
-"""Frida-like service injection for green-eye.
+"""Frida-like service injection for risc-v-gsp-rm-debugger.
Loads a position-independent RV64 service blob into a live target, registers
its dispatch entry with the firmware (on the demo uproc: a handler-table slot
@@ -8,7 +8,7 @@ firmware's own mailbox dispatcher.
On a future GSP-RM simulator the same shape becomes "inject an RM engine and
register it with the simulated RM core"; see ``docs/12-INJECTION-SDK.md`` and
-``GREEN-EYE-ROADMAP.md``.
+``RISC-V-GSP-RM-DEBUGGER-ROADMAP.md``.
"""
from __future__ import annotations
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/plugins.py b/debug/risc-v-gsp-rm-vm/greeneye/plugins.py
index af4545f..6c597ac 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/plugins.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/plugins.py
@@ -1,4 +1,4 @@
-"""green-eye plugin system.
+"""risc-v-gsp-rm-debugger plugin system.
A plugin is a normal Python module that exposes::
@@ -7,7 +7,7 @@ A plugin is a normal Python module that exposes::
ge.add_command("mycmd", run_mycmd, help="…")
ge.hook("rpc_sum", on_enter=lambda hit: ge.log(f"sum({hit.arg(0)})"))
-Plugins can extend green-eye with:
+Plugins can extend risc-v-gsp-rm-debugger with:
* new CLI commands (including an MCP server later - roadmap);
* runtime RE tools (extra watchpoint strategies, structure probes, coverage);
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/record.py b/debug/risc-v-gsp-rm-vm/greeneye/record.py
index 0b025b5..91c7fb5 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/record.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/record.py
@@ -1,6 +1,6 @@
"""Session recording: events.jsonl + snapshots + summary.
-A recorded session is the durable artifact green-eye produces: a complete,
+A recorded session is the durable artifact risc-v-gsp-rm-debugger produces: a complete,
symbol-annotatable map of what the firmware did (hook hits, watch hits,
memory writes, snapshots, coverage steps) that can be replayed offline,
diffed between runs (bare-metal vs VFIO-mdev, old GSP vs new GSP) and
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/rsp.py b/debug/risc-v-gsp-rm-vm/greeneye/rsp.py
index 0263c63..b482f4c 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/rsp.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/rsp.py
@@ -1,6 +1,6 @@
"""Pure-Python GDB Remote Serial Protocol (RSP) client for QEMU's gdbstub.
-green-eye deliberately avoids GDB: the whole instrumentation stack is a Python
+risc-v-gsp-rm-debugger deliberately avoids GDB: the whole instrumentation stack is a Python
library, so sessions, probes and plugins are scriptable end to end. This
module implements the RSP subset QEMU needs: packet framing/acks, no-ack mode,
registers (with layout read from ``target.xml``), memory, software/hardware
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/session.py b/debug/risc-v-gsp-rm-vm/greeneye/session.py
index fffc60e..2ef13a4 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/session.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/session.py
@@ -1,4 +1,4 @@
-"""green-eye instrumentation session: hooks, breakpoints, watchpoints, events.
+"""risc-v-gsp-rm-debugger instrumentation session: hooks, breakpoints, watchpoints, events.
This is the Frida-like core. It is deliberately transport-agnostic; the RSP
client is the default backend, and a Session can also be constructed around a
@@ -18,14 +18,22 @@ from .rsp import RspClient, StopReply
from .symbols import Symbol, SymbolDB
#: Registers saved and restored around ``Session.call`` (RISC-V ABI + pc).
+#: ``fp`` is x8/s0 under QEMU's target.xml name; both spellings are listed so
+#: the restore works whichever name the transport exposes.
RESTORE_REGS = (
- ["ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1"]
+ ["ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "fp", "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"]
)
+#: Budget for the stop reply after a budget-interrupt. The default client
+#: socket timeout can be exhausted on a heavily loaded host before QEMU gets
+#: scheduled; a longer explicit budget keeps a probe from being misclassified
+#: as an error just because the host was busy.
+INTERRUPT_TIMEOUT = 30.0
+
@dataclass
class Hit:
@@ -392,7 +400,7 @@ class Session:
try:
stop = self.client.continue_(timeout=remaining)
except RspTimeout:
- self.client.interrupt()
+ self.client.interrupt(timeout=INTERRUPT_TIMEOUT)
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":
@@ -433,7 +441,7 @@ class Session:
try:
stop = self.client.continue_(timeout=remaining)
except RspTimeout:
- self.client.interrupt()
+ self.client.interrupt(timeout=INTERRUPT_TIMEOUT)
raise RspTimeout(f"call {addr:#x}: no return in {timeout}s")
hit = self._dispatch_stop(stop)
pc = self.client.pc
@@ -483,7 +491,7 @@ class Session:
except RspTimeout:
if deadline is None:
raise
- stop = self.client.interrupt()
+ stop = self.client.interrupt(timeout=INTERRUPT_TIMEOUT)
interrupted = True
stops += 1
self._dispatch_stop(stop)
@@ -496,7 +504,7 @@ class Session:
return stops
def stop(self) -> Hit | None:
- stop = self.client.interrupt()
+ stop = self.client.interrupt(timeout=INTERRUPT_TIMEOUT)
return self._dispatch_stop(stop)
# -- stop dispatch -----------------------------------------------------
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/sim_backend.py b/debug/risc-v-gsp-rm-vm/greeneye/sim_backend.py
index d17076f..a2d2c6a 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/sim_backend.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/sim_backend.py
@@ -1,4 +1,4 @@
-"""Native green-eye execution backend (M1): deterministic RV64IMAC in Python.
+"""Native risc-v-gsp-rm-debugger execution backend (M1): deterministic RV64IMAC in Python.
``ge.launch(kernel=..., backend="native")`` builds a :class:`SimTarget` around
:class:`sim.machine.Machine` and exposes a :class:`SimClient` whose method
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/symbols.py b/debug/risc-v-gsp-rm-vm/greeneye/symbols.py
index cb11f7f..d240fc4 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/symbols.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/symbols.py
@@ -1,4 +1,4 @@
-"""Symbol resolution and address translation for green-eye.
+"""Symbol resolution and address translation for risc-v-gsp-rm-debugger.
Inputs supported (all optional, merged into one database):
@@ -8,7 +8,7 @@ Inputs supported (all optional, merged into one database):
- ``decompiled_index.tsv`` / bucket TSVs
``<program_addr>\t<file_addr>\t<size>\t<name>\t<bucket>\t<line>\t<status>``,
- ``exports.tsv`` ``addr\tfile_addr\tsize\tname``,
-* green-eye native JSON (``greeneye-symbols.json``).
+* risc-v-gsp-rm-debugger native JSON (``greeneye-symbols.json``).
Address conventions (matching the project docs):
* Ghidra program address = file vaddr + image base (``0x100000`` for libcuda);
@@ -193,7 +193,7 @@ class SymbolDB:
return count
def load_json(self, path: str) -> int:
- """Load a green-eye JSON symbol DB; raise GreenEyeError on bad input."""
+ """Load a risc-v-gsp-rm-debugger JSON symbol DB; raise GreenEyeError on bad input."""
try:
with open(path, "r", encoding="utf-8") as fh:
payload = json.load(fh)
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/target.py b/debug/risc-v-gsp-rm-vm/greeneye/target.py
index 273f216..669a55d 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/target.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/target.py
@@ -1,4 +1,4 @@
-"""QEMU target lifecycle for green-eye.
+"""QEMU target lifecycle for risc-v-gsp-rm-debugger.
Owns the ``qemu-system-riscv64`` process, the gdbstub endpoint, the serial
capture and the QEMU trace log. Both firmware paths are supported:
@@ -76,8 +76,10 @@ class QemuTarget:
os.makedirs(self.workdir, exist_ok=True)
self.uart_log = uart_log or os.path.join(self.workdir, "uart.log")
self.qemu_log = qemu_log or os.path.join(self.workdir, "qemu.log")
+ self.stderr_log = os.path.join(self.workdir, "qemu.stderr.log")
self.keep_alive = keep_alive
self.process: subprocess.Popen | None = None
+ self._stderr_fh = None
self._segment_files: list[str] = []
self._cmd: list[str] = []
@@ -118,15 +120,19 @@ class QemuTarget:
def start(self, paused: bool = True, timeout: float = 15.0) -> "QemuTarget":
self._cmd = self._build_command(paused)
+ # Never use an undrained stderr=PIPE: a chatty QEMU would block once
+ # the pipe buffer fills and then stop answering the gdbstub.
+ self._stderr_fh = open(self.stderr_log, "ab")
try:
self.process = subprocess.Popen(
self._cmd,
stdout=subprocess.DEVNULL,
- stderr=subprocess.PIPE,
+ stderr=self._stderr_fh,
cwd=self.workdir,
start_new_session=True,
)
except OSError as exc:
+ self._close_stderr()
raise TargetError(f"cannot start QEMU: {exc}") from exc
try:
self.wait_for_port(timeout=timeout)
@@ -140,11 +146,11 @@ class QemuTarget:
while time.monotonic() < deadline:
if self.process is not None and self.process.poll() is not None:
stderr = b""
- if self.process.stderr is not None:
- try:
- stderr = self.process.stderr.read() or b""
- except OSError:
- pass
+ try:
+ with open(self.stderr_log, "rb") as fh:
+ stderr = fh.read()
+ except OSError:
+ pass
raise TargetError(
f"QEMU exited early rc={self.process.returncode}: "
f"{stderr.decode('utf-8', 'replace')[-2000:]}"
@@ -159,8 +165,17 @@ class QemuTarget:
def alive(self) -> bool:
return self.process is not None and self.process.poll() is None
+ def _close_stderr(self) -> None:
+ if self._stderr_fh is not None:
+ try:
+ self._stderr_fh.close()
+ except OSError:
+ pass
+ self._stderr_fh = None
+
def kill(self, timeout: float = 5.0) -> None:
if self.process is None:
+ self._close_stderr()
return
if self.process.poll() is None:
try:
@@ -181,11 +196,7 @@ class QemuTarget:
except ProcessLookupError:
pass
self.process.wait(timeout=timeout)
- if self.process.stderr is not None:
- try:
- self.process.stderr.close()
- except OSError:
- pass
+ self._close_stderr()
self.process = None
# -- observation -------------------------------------------------------