summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/greeneye/__init__.py
blob: 717160c9f50f817a1f6eeed9b771d062afc13689 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""green-eye: 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
scriptable instrumentation API on top: breakpoints, hooks, watchpoints,
memory inspection, structure decoding, session recording and post-run analysis.

Public API (stable-ish for v0.1):

    import greeneye as ge
    s = ge.launch(kernel="firmware/uproc_demo/build/uproc_demo.elf")
    fn = s.symbols.lookup("rpc_sum")
    h = s.hook(fn, on_enter=lambda hit: print(hit))
    s.run(duration=2.0)
    s.close()

Everything is development tooling; see docs/00-OVERVIEW.md.
"""
from __future__ import annotations

__version__ = "0.1.0"

from .errors import (  # noqa: F401
    GreenEyeError,
    RspError,
    RspTimeout,
    RspProtocolError,
    SymbolNotFound,
    TargetError,
    StructError,
)
from .rsp import RspClient, StopReply, Register  # noqa: F401
from .elf import FirmwareImage, Segment, ElfSymbol, load_elf  # noqa: F401
from .symbols import Symbol, SymbolDB  # noqa: F401
from .memory import Memory  # noqa: F401
from .structs import Field, Schema, decode, schema_from_json  # noqa: F401
from .record import Recorder, NullRecorder, load_events  # noqa: F401
from .session import Session, Hit, HookHandle, Breakpoint, Watchpoint  # noqa: F401
from .target import QemuTarget  # noqa: F401

__all__ = [
    "attach",
    "launch",
    "Session",
    "Hit",
    "HookHandle",
    "Breakpoint",
    "Watchpoint",
    "QemuTarget",
    "RspClient",
    "SymbolDB",
    "Symbol",
    "Memory",
    "Recorder",
]


def attach(
    host: str = "127.0.0.1",
    port: int = 4444,
    *,
    timeout: float = 10.0,
    record: str | None = None,
    symbols: SymbolDB | None = None,
    name: str = "attach",
) -> Session:
    """Attach to an already-running QEMU gdbstub and return a Session."""
    client = RspClient(host, port, timeout=timeout)
    client.connect()
    client.sync()
    return Session(client=client, symbols=symbols, record=record, name=name)


def launch(
    *,
    kernel: str | None = None,
    machine: str = "virt",
    memory_mb: int = 256,
    gdb_port: int = 0,
    bios: str = "none",
    extra_args: list[str] | None = None,
    timeout: float = 10.0,
    record: str | None = None,
    symbols: SymbolDB | None = None,
    name: str = "session",
    load_symbols: bool = True,
    keep_alive: bool = False,
    backend: str = "qemu",
    trace=None,
) -> Session:
    """Launch a target (paused), attach, and return a ready Session.

    ``backend="qemu"`` (default) starts ``qemu-system-riscv64`` exactly as
    before.  ``backend="native"`` uses the in-process deterministic RV64IMAC
    simulator (M1) via ``greeneye.sim_backend``; the Session contract is the
    same.  ``trace`` (native only) enables the R4 exec/access trace: ``True``,
    a dict of ``{"exec", "start", "end", "max_events"}``, or a
    ``sim.trace.Trace``; it is saved to the session as ``blobs/trace.jsonl``.
    The target starts paused at its reset vector; use
    Session.load_image() to load an ELF (which also sets PC), or launch with
    kernel= for the fast `-kernel` path.
    """
    if backend in ("native", "sim"):
        from .sim_backend import launch_native

        return launch_native(
            kernel=kernel,
            memory_mb=memory_mb,
            record=record,
            symbols=symbols,
            name=name,
            load_symbols=load_symbols,
            keep_alive=keep_alive,
            trace=trace,
        )
    if trace is not None:
        from .errors import TargetError

        raise TargetError(
            "trace= is native-only; for QEMU build qemu/greeneye-trace.so and "
            "pass -plugin (see qemu/README.md)"
        )
    if backend != "qemu":
        from .errors import TargetError

        raise TargetError(f"unknown backend {backend!r} (use 'qemu' or 'native')")

    target = QemuTarget(
        machine=machine,
        memory_mb=memory_mb,
        gdb_port=gdb_port,
        bios=bios,
        extra_args=extra_args,
        kernel=kernel,
    )
    if keep_alive:
        target.keep_alive = True
    target.start(paused=True)
    client = RspClient(target.host, target.port, timeout=timeout)
    client.connect()
    client.sync()

    symdb = symbols or SymbolDB()
    sess = Session(client=client, target=target, symbols=symdb, record=record, name=name)
    if kernel:
        image = load_elf(kernel)
        sess.load_image(image, load_symbols=load_symbols)
    return sess