summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/greeneye/inject.py
blob: eb4b16a4cf8020c5aa105863791b553f4d50abb0 (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
"""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
that commands 16..23 dispatch to), and invokes it either directly through
``Session.call`` (a debugger-side remote function call) or through the
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
``RISC-V-GSP-RM-DEBUGGER-ROADMAP.md``.
"""
from __future__ import annotations

from dataclasses import dataclass

from .elf import load_elf
from .errors import GreenEyeError

#: Symbol the injector calls in a service blob.
ENTRY_SYMBOL = "ge_demo_service_dispatch"

#: Demo uproc control-block field offsets (see firmware/uproc_demo).
CTRL_HANDLERS = 0x150
CTRL_MAILBOX = 0x20
CTRL_ARG0 = 0x28
CTRL_ARG1 = 0x30
CTRL_ARG2 = 0x38
CTRL_RESULT = 0x40
INJECT_CMD_BASE = 16


@dataclass
class InjectedService:
    path: str
    name: str
    load_addr: int
    entry_addr: int
    dispatch_offset: int
    slot: int
    size: int

    def summary(self) -> str:
        return (
            f"{self.name}: {self.size} bytes at {self.load_addr:#x}, "
            f"entry {self.entry_addr:#x} (offset {self.dispatch_offset:#x}), slot {self.slot}"
        )


def load_service(
    session,
    service_elf: str,
    *,
    load_addr: int | None = None,
    slot: int = 6,
    entry_symbol: str = ENTRY_SYMBOL,
) -> InjectedService:
    """Load a service blob and register it with the demo firmware."""
    image = load_elf(service_elf)
    entry = None
    for symbol in image.symbols:
        if symbol.name == entry_symbol:
            entry = symbol
            break
    if entry is None:
        available = ", ".join(sorted(s.name for s in image.symbols if s.is_function)) or "<none>"
        raise GreenEyeError(f"service {service_elf}: symbol {entry_symbol!r} not found (functions: {available})")
    if not 0 <= slot <= 7:
        raise GreenEyeError(f"slot must be 0..7 (got {slot})")
    if load_addr is None:
        base = getattr(session.target, "ram_base", 0x80000000) if session.target else 0x80000000
        size = getattr(session.target, "memory_mb", 256) * 1024 * 1024 if session.target else 256 * 1024 * 1024
        load_addr = base + 0x01000000
        if load_addr + image.max_vaddr + 0x1000 >= base + size:
            load_addr = base + max(0x1000, size - image.max_vaddr - 0x2000)
    if getattr(session.target, "memory_mb", 0):
        ram_end = getattr(session.target, "ram_base", 0x80000000) + session.target.memory_mb * 1024 * 1024
        if load_addr + image.max_vaddr > ram_end:
            raise GreenEyeError(
                f"service does not fit: load {load_addr:#x} + {image.max_vaddr:#x} exceeds RAM end {ram_end:#x}"
            )

    total = 0
    for segment in image.load_segments:
        data = image.segment_bytes(segment)
        if data:
            session.memory.write(load_addr + segment.p_vaddr, data)
            total += len(data)
    entry_addr = load_addr + entry.value

    ctrl = session.symbols.get("gsp_ctrl")
    if ctrl is not None:
        session.memory.write_u64(ctrl.addr + CTRL_HANDLERS + slot * 8, entry_addr)

    service = InjectedService(
        path=service_elf,
        name=service_elf.rsplit("/", 1)[-1],
        load_addr=load_addr,
        entry_addr=entry_addr,
        dispatch_offset=entry.value,
        slot=slot,
        size=total,
    )
    session.recorder.event(
        "service_injected",
        path=service.path,
        name=service.name,
        load_addr=load_addr,
        entry_addr=entry_addr,
        slot=slot,
        size=total,
    )
    return service


def invoke_remote(session, service: InjectedService, cmd: int, a0: int = 0, a1: int = 0, timeout: float = 3.0) -> int:
    """Call the service directly from the debugger (Frida NativeFunction)."""
    return session.call(service.entry_addr, (cmd, a0, a1), timeout=timeout)


def invoke_via_firmware(session, service: InjectedService, cmd: int, a0: int = 0, a1: int = 0, settle: float = 0.25) -> int:
    """Invoke the service through the firmware's mailbox dispatcher."""
    ctrl = session.symbols.lookup("gsp_ctrl")
    session.memory.write_u64(ctrl + CTRL_ARG0, cmd)
    session.memory.write_u64(ctrl + CTRL_ARG1, a0)
    session.memory.write_u64(ctrl + CTRL_ARG2, a1)
    session.memory.write_u64(ctrl + CTRL_MAILBOX, INJECT_CMD_BASE + service.slot)
    session.run(duration=settle)
    return session.memory.read_u64(ctrl + CTRL_RESULT)