diff options
Diffstat (limited to 'tools/proxyclient/m1n1/hv')
| -rw-r--r-- | tools/proxyclient/m1n1/hv/__init__.py | 1849 | ||||
| -rw-r--r-- | tools/proxyclient/m1n1/hv/gdbserver/__init__.py | 480 | ||||
| -rw-r--r-- | tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-core.xml | 91 | ||||
| -rw-r--r-- | tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-fpu.xml | 160 | ||||
| -rw-r--r-- | tools/proxyclient/m1n1/hv/gdbserver/features/target.xml | 8 | ||||
| -rw-r--r-- | tools/proxyclient/m1n1/hv/types.py | 60 | ||||
| -rw-r--r-- | tools/proxyclient/m1n1/hv/virtio.py | 133 | ||||
| -rw-r--r-- | tools/proxyclient/m1n1/hv/virtutils.py | 43 |
8 files changed, 2824 insertions, 0 deletions
diff --git a/tools/proxyclient/m1n1/hv/__init__.py b/tools/proxyclient/m1n1/hv/__init__.py new file mode 100644 index 0000000..407b0d1 --- /dev/null +++ b/tools/proxyclient/m1n1/hv/__init__.py @@ -0,0 +1,1849 @@ +# SPDX-License-Identifier: MIT +import io, sys, traceback, struct, array, bisect, os, plistlib, signal, runpy +from construct import * + +from ..asm import ARMAsm +from ..tgtypes import * +from ..proxy import IODEV, START, EVENT, EXC, EXC_RET, ExcInfo +from ..utils import * +from ..sysreg import * +from ..macho import MachO +from ..adt import load_adt +from .. import xnutools, shell + +from .gdbserver import * +from .types import * +from .virtutils import * +from .virtio import * + +__all__ = ["HV"] + +class HV(Reloadable): + PAC_MASK = 0xfffff00000000000 + + PTE_VALID = 1 << 0 + + PTE_MEMATTR_UNCHANGED = 0b1111 << 2 + PTE_S2AP_RW = 0b11 << 6 + PTE_SH_NS = 0b11 << 8 + PTE_ACCESS = 1 << 10 + PTE_ATTRIBUTES = PTE_ACCESS | PTE_SH_NS | PTE_S2AP_RW | PTE_MEMATTR_UNCHANGED + + SPTE_TRACE_READ = 1 << 63 + SPTE_TRACE_WRITE = 1 << 62 + SPTE_TRACE_UNBUF = 1 << 61 + SPTE_MAP = 0 << 50 + SPTE_HOOK = 1 << 50 + SPTE_PROXY_HOOK_R = 2 << 50 + SPTE_PROXY_HOOK_W = 3 << 50 + SPTE_PROXY_HOOK_RW = 4 << 50 + + MSR_REDIRECTS = { + SCTLR_EL1: SCTLR_EL12, + TTBR0_EL1: TTBR0_EL12, + TTBR1_EL1: TTBR1_EL12, + TCR_EL1: TCR_EL12, + ESR_EL1: ESR_EL12, + FAR_EL1: FAR_EL12, + AFSR0_EL1: AFSR0_EL12, + AFSR1_EL1: AFSR1_EL12, + MAIR_EL1: MAIR_EL12, + AMAIR_EL1: AMAIR_EL12, + CONTEXTIDR_EL1: CONTEXTIDR_EL12, + ACTLR_EL1: ACTLR_EL12, + AMX_CTL_EL1: AMX_CTL_EL12, + SPRR_CONFIG_EL1: SPRR_CONFIG_EL12, + SPRR_PERM_EL1: SPRR_PERM_EL12, + SPRR_PERM_EL0: SPRR_PERM_EL02, + SPRR_UNK1_EL1: SPRR_UNK1_EL12, + SPRR_UMASK0_EL1: SPRR_UMASK0_EL12, + APCTL_EL1: APCTL_EL12, + APSTS_EL1: APSTS_EL12, + KERNELKEYLO_EL1: KERNELKEYLO_EL12, + KERNELKEYHI_EL1: KERNELKEYHI_EL12, + GXF_CONFIG_EL1: GXF_CONFIG_EL12, + GXF_ABORT_EL1: GXF_ABORT_EL12, + GXF_ENTER_EL1: GXF_ENTER_EL12, + VBAR_GL1: VBAR_GL12, + SPSR_GL1: SPSR_GL12, + ASPSR_GL1: ASPSR_GL12, + ESR_GL1: ESR_GL12, + ELR_GL1: ELR_GL12, + } + + AIC_EVT_TYPE_HW = 1 + IRQTRACE_IRQ = 1 + + def __init__(self, iface, proxy, utils): + self.iface = iface + self.p = proxy + self.u = utils + self.pac_mask = self.PAC_MASK + self.user_pac_mask = self.PAC_MASK + self.vbar_el1 = None + self.want_vbar = None + self.vectors = [None] + self._bps = [None, None, None, None, None] + self._bp_hooks = dict() + self._wps = [None, None, None, None] + self._wpcs = [0, 0, 0, 0] + self.sym_offset = 0 + self.symbols = [] + self.symbol_dict = {} + self.sysreg = {0: {}} + self.novm = False + self._in_handler = False + self._sigint_pending = False + self._in_shell = False + self._gdbserver = None + self.vm_hooks = [None] + self.interrupt_map = {} + self.mmio_maps = DictRangeMap() + self.dirty_maps = BoolRangeMap() + self.tracer_caches = {} + self.shell_locals = {} + self.xnu_mode = False + self._update_shell_locals() + self.wdt_cpu = None + self.smp = True + self.hook_exceptions = False + self.started_cpus = set() + self.started = False + self.ctx = None + self.hvcall_handlers = {} + self.switching_context = False + self.show_timestamps = False + self.virtio_devs = {} + + def _reloadme(self): + super()._reloadme() + self._update_shell_locals() + + def _update_shell_locals(self): + self.shell_locals.update({ + "hv": self, + "iface": self.iface, + "p": self.p, + "u": self.u, + "trace": trace, + "TraceMode": TraceMode, + }) + + for attr in dir(self): + a = getattr(self, attr) + if callable(a): + self.shell_locals[attr] = getattr(self, attr) + + self.shell_locals["ctx"] = self.context + + def log(self, s, *args, show_cpu=True, **kwargs): + if self.ctx is not None and show_cpu: + ts="" + if self.show_timestamps: + ts = f"[{self.u.mrs(CNTPCT_EL0):#x}]" + print(ts+f"[cpu{self.ctx.cpu_id}] " + s, *args, **kwargs) + if self.print_tracer.log_file: + print(f"# {ts}[cpu{self.ctx.cpu_id}] " + s, *args, file=self.print_tracer.log_file, **kwargs) + else: + print(s, *args, **kwargs) + if self.print_tracer.log_file: + print("# " + s, *args, file=self.print_tracer.log_file, **kwargs) + + def unmap(self, ipa, size): + assert self.p.hv_map(ipa, 0, size, 0) >= 0 + + def map_hw(self, ipa, pa, size): + '''map IPA (Intermediate Physical Address) to actual PA''' + #print(f"map_hw {ipa:#x} -> {pa:#x} [{size:#x}]") + if (ipa & 0x3fff) != (pa & 0x3fff): + self.map_sw(ipa, pa, size) + return + + ipa_p = align_up(ipa) + if ipa_p != ipa: + self.map_sw(ipa, pa, min(ipa_p - ipa, size)) + pa += ipa_p - ipa + size -= ipa_p - ipa + + if size <= 0: + return + + size_p = align_down(size) + if size_p > 0: + #print(f"map_hw real {ipa_p:#x} -> {pa:#x} [{size_p:#x}]") + assert self.p.hv_map(ipa_p, pa | self.PTE_ATTRIBUTES | self.PTE_VALID, size_p, 1) >= 0 + + if size_p != size: + self.map_sw(ipa_p + size_p, pa + size_p, size - size_p) + + def map_sw(self, ipa, pa, size): + #print(f"map_sw {ipa:#x} -> {pa:#x} [{size:#x}]") + assert self.p.hv_map(ipa, pa | self.SPTE_MAP, size, 1) >= 0 + + def map_hook(self, ipa, size, read=None, write=None, **kwargs): + index = len(self.vm_hooks) + self.vm_hooks.append((read, write, ipa, kwargs)) + self.map_hook_idx(ipa, size, index, read is not None, write is not None) + + def map_hook_idx(self, ipa, size, index, read=False, write=False, flags=0): + if read: + if write: + t = self.SPTE_PROXY_HOOK_RW + else: + t = self.SPTE_PROXY_HOOK_R + elif write: + t = self.SPTE_PROXY_HOOK_W + else: + assert False + + assert self.p.hv_map(ipa, (index << 2) | flags | t, size, 0) >= 0 + + def readmem(self, va, size): + '''read from virtual memory''' + with io.BytesIO() as buffer: + while size > 0: + pa = self.p.hv_translate(va, False, False) + if pa == 0: + break + + size_in_page = 4096 - (va % 4096) + if size < size_in_page: + buffer.write(self.iface.readmem(pa, size)) + break + + buffer.write(self.iface.readmem(pa, size_in_page)) + va += size_in_page + size -= size_in_page + + return buffer.getvalue() + + def writemem(self, va, data): + '''write to virtual memory''' + written = 0 + while written < len(data): + pa = self.p.hv_translate(va, False, True) + if pa == 0: + break + + size_in_page = 4096 - (va % 4096) + if len(data) - written < size_in_page: + self.iface.writemem(pa, data[written:]) + written = len(data) + break + + self.iface.writemem(pa, data[written:written + size_in_page]) + va += size_in_page + written += size_in_page + + return written + + def trace_irq(self, device, num, count, flags): + for n in range(num, num + count): + if flags & self.IRQTRACE_IRQ: + self.interrupt_map[n] = device + else: + self.interrupt_map.pop(n, None) + + start, size = self.adt["/arm-io/aic"].get_reg(0) + zone = irange(start, size) + if len(self.interrupt_map): + self.add_tracer(zone, "AIC_IRQ", TraceMode.RESERVED) + else: + self.del_tracer(zone, "AIC_IRQ") + + assert self.p.hv_trace_irq(self.AIC_EVT_TYPE_HW, num, count, flags) > 0 + + def add_tracer(self, zone, ident, mode=TraceMode.ASYNC, read=None, write=None, **kwargs): + assert mode in (TraceMode.RESERVED, TraceMode.OFF, TraceMode.BYPASS) or read or write + self.mmio_maps[zone, ident] = (mode, ident, read, write, kwargs) + self.dirty_maps.set(zone) + + def del_tracer(self, zone, ident): + del self.mmio_maps[zone, ident] + self.dirty_maps.set(zone) + + def clear_tracers(self, ident): + for r, v in self.mmio_maps.items(): + if ident in v: + v.pop(ident) + self.dirty_maps.set(r) + + def trace_device(self, path, mode=TraceMode.ASYNC, ranges=None): + node = self.adt[path] + for index in range(len(node.reg)): + if ranges is not None and index not in ranges: + continue + addr, size = node.get_reg(index) + self.trace_range(irange(addr, size), mode) + + def trace_range(self, zone, mode=TraceMode.ASYNC, read=True, write=True, name=None): + if mode is True: + mode = TraceMode.ASYNC + if mode and mode != TraceMode.OFF: + self.add_tracer(zone, "PrintTracer", mode, + self.print_tracer.event_mmio if read else None, + self.print_tracer.event_mmio if write else None, + start=zone.start, + name=name) + else: + self.del_tracer(zone, "PrintTracer") + + def pt_update(self): + if not self.dirty_maps: + return + + self.dirty_maps.compact() + self.mmio_maps.compact() + + top = 0 + + for zone in self.dirty_maps: + if zone.stop <= top: + continue + top = max(top, zone.start) + + for mzone, maps in self.mmio_maps.overlaps(zone): + if mzone.stop <= top: + continue + if top < mzone.start: + self.unmap(top, mzone.start - top) + self.log(f"PT[{top:09x}:{mzone.start:09x}] -> *UNMAPPED*") + + top = mzone.stop + if not maps: + continue + maps = sorted(maps.values(), reverse=True) + mode, ident, read, write, kwargs = maps[0] + + need_read = any(m[2] for m in maps) + need_write = any(m[3] for m in maps) + + if mode == TraceMode.RESERVED: + self.log(f"PT[{mzone.start:09x}:{mzone.stop:09x}] -> RESERVED {ident}") + continue + elif mode in (TraceMode.HOOK, TraceMode.SYNC): + self.map_hook_idx(mzone.start, mzone.stop - mzone.start, 0, + need_read, need_write) + if mode == TraceMode.HOOK: + for m2, i2, r2, w2, k2 in maps[1:]: + if m2 == TraceMode.HOOK: + self.log(f"!! Conflict: HOOK {i2}") + elif mode == TraceMode.WSYNC: + flags = self.SPTE_TRACE_READ if need_read else 0 + self.map_hook_idx(mzone.start, mzone.stop - mzone.start, 0, + False, need_write, flags=flags) + elif mode in (TraceMode.UNBUF, TraceMode.ASYNC, TraceMode.BYPASS): + pa = mzone.start + if mode == TraceMode.UNBUF: + pa |= self.SPTE_TRACE_UNBUF + if need_read: + pa |= self.SPTE_TRACE_READ + if need_write: + pa |= self.SPTE_TRACE_WRITE + self.map_sw(mzone.start, pa, mzone.stop - mzone.start) + elif mode == TraceMode.OFF: + self.map_hw(mzone.start, mzone.start, mzone.stop - mzone.start) + self.log(f"PT[{mzone.start:09x}:{mzone.stop:09x}] -> HW:{ident}") + continue + + rest = [m[1] for m in maps[1:] if m[0] != TraceMode.OFF] + if rest: + rest = " (+ " + ", ".join(rest) + ")" + else: + rest = "" + + self.log(f"PT[{mzone.start:09x}:{mzone.stop:09x}] -> {mode.name}.{'R' if read else ''}{'W' if read else ''} {ident}{rest}") + + if top < zone.stop: + self.unmap(top, zone.stop - top) + self.log(f"PT[{top:09x}:{zone.stop:09x}] -> *UNMAPPED*") + + self.u.inst(0xd50c83df) # tlbi vmalls12e1is + self.dirty_maps.clear() + + def shellwrap(self, func, description, update=None, needs_ret=False): + + while True: + try: + return func() + except Exception: + print(f"Exception in {description}") + traceback.print_exc() + + if not self.ctx: + print("Running in asynchronous context. Target operations are not available.") + + def do_exit(i): + raise shell.ExitConsole(i) + + self.shell_locals["skip"] = lambda: do_exit(1) + self.shell_locals["cont"] = lambda: do_exit(0) + ret = self.run_shell("Entering debug shell", "Returning to tracer") + self.shell_locals["skip"] = self.skip + self.shell_locals["cont"] = self.cont + + if self.ctx: + self.cpu() # Return to the original CPU to avoid confusing things + + if ret == 1: + if needs_ret: + print("Cannot skip, return value required.") + else: + return + + if update: + update() + + def run_shell(self, entry_msg="Entering shell", exit_msg="Continuing"): + def handle_sigusr1(signal, stack): + raise shell.ExitConsole(EXC_RET.HANDLED) + + def handle_sigusr2(signal, stack): + raise shell.ExitConsole(EXC_RET.EXIT_GUEST) + + default_sigusr1 = signal.signal(signal.SIGUSR1, handle_sigusr1) + try: + default_sigusr2 = signal.signal(signal.SIGUSR2, handle_sigusr2) + try: + self._in_shell = True + try: + if not self._gdbserver is None: + self._gdbserver.notify_in_shell() + return shell.run_shell(self.shell_locals, entry_msg, exit_msg) + finally: + self._in_shell = False + finally: + signal.signal(signal.SIGUSR2, default_sigusr2) + finally: + signal.signal(signal.SIGUSR1, default_sigusr1) + + @property + def in_shell(self): + return self._in_shell + + def gdbserver(self, address="/tmp/.m1n1-unix", log=None): + '''activate gdbserver''' + if not self._gdbserver is None: + raise Exception("gdbserver is already running") + + self._gdbserver = GDBServer(self, address, log) + self._gdbserver.activate() + + def shutdown_gdbserver(self): + '''shutdown gdbserver''' + self._gdbserver.shutdown() + self._gdbserver = None + + def handle_mmiotrace(self, data): + evt = EvtMMIOTrace.parse(data) + + def do_update(): + nonlocal mode, ident, read, write, kwargs + read = lambda *args, **kwargs: None + write = lambda *args, **kwargs: None + + m = self.mmio_maps[evt.addr].get(ident, None) + if not m: + return + + mode, ident, read_, write_, kwargs = m + read = read_ or read + write = write_ or write + + maps = sorted(self.mmio_maps[evt.addr].values(), reverse=True) + for mode, ident, read, write, kwargs in maps: + if mode > TraceMode.WSYNC or (evt.flags.WRITE and mode > TraceMode.UNBUF): + print(f"ERROR: mmiotrace event but expected {mode.name} mapping") + continue + if mode == TraceMode.OFF: + continue + if evt.flags.WRITE: + if write: + self.shellwrap(lambda: write(evt, **kwargs), + f"Tracer {ident}:write ({mode.name})", update=do_update) + else: + if read: + self.shellwrap(lambda: read(evt, **kwargs), + f"Tracer {ident}:read ({mode.name})", update=do_update) + + def handle_vm_hook_mapped(self, ctx, data): + maps = sorted(self.mmio_maps[data.addr].values(), reverse=True) + + if not maps: + raise Exception(f"VM hook without a mapping at {data.addr:#x}") + + def do_update(): + nonlocal mode, ident, read, write, kwargs + read = lambda *args, **kwargs: None + write = lambda *args, **kwargs: None + + m = self.mmio_maps[data.addr].get(ident, None) + if not m: + return + + mode, ident, read_, write_, kwargs = m + read = read_ or read + write = write_ or write + + mode, ident, read, write, kwargs = maps[0] + + first = 0 + + val = data.data + + if mode not in (TraceMode.HOOK, TraceMode.SYNC, TraceMode.WSYNC): + raise Exception(f"VM hook with unexpected mapping at {data.addr:#x}: {maps[0][0].name}") + + if not data.flags.WRITE: + if mode == TraceMode.HOOK: + val = self.shellwrap(lambda: read(data.addr, 8 << data.flags.WIDTH, **kwargs), + f"Tracer {ident}:read (HOOK)", update=do_update, needs_ret=True) + + if not isinstance(val, list) and not isinstance(val, tuple): + val = [val] + first += 1 + elif mode == TraceMode.SYNC: + try: + val = self.u.read(data.addr, 8 << data.flags.WIDTH) + except: + self.log(f"MMIO read failed: {data.addr:#x} (w={data.flags.WIDTH})") + raise + if not isinstance(val, list) and not isinstance(val, tuple): + val = [val] + elif mode == TraceMode.WSYNC: + raise Exception(f"VM hook with unexpected mapping at {data.addr:#x}: {maps[0][0].name}") + + for i in range(1 << max(0, data.flags.WIDTH - 3)): + self.p.write64(ctx.data + 16 + 8 * i, val[i]) + + elif mode == TraceMode.HOOK: + first += 1 + + flags = data.flags.copy() + flags.CPU = self.ctx.cpu_id + width = data.flags.WIDTH + + if width > 3: + flags.WIDTH = 3 + flags.MULTI = 1 + + for i in range(1 << max(0, width - 3)): + evt = Container( + flags = flags, + reserved = 0, + pc = ctx.elr, + addr = data.addr + 8 * i, + data = val[i] + ) + + for mode, ident, read, write, kwargs in maps[first:]: + if flags.WRITE: + if write: + self.shellwrap(lambda: write(evt, **kwargs), + f"Tracer {ident}:write ({mode.name})", update=do_update) + else: + if read: + self.shellwrap(lambda: read(evt, **kwargs), + f"Tracer {ident}:read ({mode.name})", update=do_update) + + if data.flags.WRITE: + mode, ident, read, write, kwargs = maps[0] + + if data.flags.WIDTH <= 3: + wval = val[0] + else: + wval = val + + if mode == TraceMode.HOOK: + self.shellwrap(lambda: write(data.addr, wval, 8 << data.flags.WIDTH, **kwargs), + f"Tracer {ident}:write (HOOK)", update=do_update) + elif mode in (TraceMode.SYNC, TraceMode.WSYNC): + try: + self.u.write(data.addr, wval, 8 << data.flags.WIDTH) + except: + if data.flags.WIDTH > 3: + wval = wval[0] + self.log(f"MMIO write failed: {data.addr:#x} = {wval} (w={data.flags.WIDTH})") + raise + + return True + + def handle_vm_hook(self, ctx): + data = self.iface.readstruct(ctx.data, VMProxyHookData) + + if data.id == 0: + return self.handle_vm_hook_mapped(ctx, data) + + rfunc, wfunc, base, kwargs = self.vm_hooks[data.id] + + d = data.data + if data.flags.WIDTH < 3: + d = d[0] + + if data.flags.WRITE: + wfunc(base, data.addr - base, d, 8 << data.flags.WIDTH, **kwargs) + else: + val = rfunc(base, data.addr - base, 8 << data.flags.WIDTH, **kwargs) + if not isinstance(val, list) and not isinstance(val, tuple): + val = [val] + for i in range(1 << max(0, data.flags.WIDTH - 3)): + self.p.write64(ctx.data + 16 + 8 * i, val[i]) + + return True + + def handle_irqtrace(self, data): + evt = EvtIRQTrace.parse(data) + + if evt.type == self.AIC_EVT_TYPE_HW and evt.flags & self.IRQTRACE_IRQ: + dev = self.interrupt_map[int(evt.num)] + print(f"IRQ: {dev}: {evt.num}") + + def addr(self, addr): + unslid_addr = addr + self.sym_offset + if self.xnu_mode and (addr < self.tba.virt_base or unslid_addr < self.macho.vmin): + return f"0x{addr:x}" + + saddr, name = self.sym(addr) + + if name is None: + return f"0x{addr:x} (0x{unslid_addr:x})" + + return f"0x{addr:x} ({name}+0x{unslid_addr - saddr:x})" + + def resolve_symbol(self, name): + return self.symbol_dict[name] - self.sym_offset + + def sym(self, addr): + unslid_addr = addr + self.sym_offset + + if self.xnu_mode and (addr < self.tba.virt_base or unslid_addr < self.macho.vmin): + return None, None + + idx = bisect.bisect_left(self.symbols, (unslid_addr + 1, "")) - 1 + if idx < 0 or idx >= len(self.symbols): + return None, None + + return self.symbols[idx] + + def get_sym(self, addr): + a, name = self.sym(addr) + if addr == a: + return name + else: + return None + + def handle_msr(self, ctx, iss=None): + if iss is None: + iss = ctx.esr.ISS + iss = ESR_ISS_MSR(iss) + enc = iss.Op0, iss.Op1, iss.CRn, iss.CRm, iss.Op2 + + name = sysreg_name(enc) + + skip = set() + shadow = { + #SPRR_CONFIG_EL1, + #SPRR_PERM_EL0, + #SPRR_PERM_EL1, + VMSA_LOCK_EL1, + #SPRR_UNK1_EL1, + #SPRR_UNK2_EL1, + MDSCR_EL1, + } + ro = { + ACC_CFG_EL1, + ACC_OVRD_EL1, + } + xlate = { + DC_CIVAC, + } + for i in range(len(self._bps)): + shadow.add(DBGBCRn_EL1(i)) + shadow.add(DBGBVRn_EL1(i)) + for i in range(len(self._wps)): + shadow.add(DBGWCRn_EL1(i)) + shadow.add(DBGWVRn_EL1(i)) + + value = 0 + if enc in shadow: + if iss.DIR == MSR_DIR.READ: + value = self.sysreg[self.ctx.cpu_id].setdefault(enc, 0) + self.log(f"Shadow: mrs x{iss.Rt}, {name} = {value:x}") + if iss.Rt != 31: + ctx.regs[iss.Rt] = value + else: + if iss.Rt != 31: + value = ctx.regs[iss.Rt] + self.log(f"Shadow: msr {name}, x{iss.Rt} = {value:x}") + self.sysreg[self.ctx.cpu_id][enc] = value + elif enc in skip or (enc in ro and iss.DIR == MSR_DIR.WRITE): + if iss.DIR == MSR_DIR.READ: + self.log(f"Skip: mrs x{iss.Rt}, {name} = 0") + if iss.Rt != 31: + ctx.regs[iss.Rt] = 0 + else: + if iss.Rt != 31: + value = ctx.regs[iss.Rt] + self.log(f"Skip: msr {name}, x{iss.Rt} = {value:x}") + else: + if iss.DIR == MSR_DIR.READ: + enc2 = self.MSR_REDIRECTS.get(enc, enc) + value = self.u.mrs(enc2) + self.log(f"Pass: mrs x{iss.Rt}, {name} = {value:x} ({sysreg_name(enc2)})") + if iss.Rt != 31: + ctx.regs[iss.Rt] = value + else: + if iss.Rt != 31: + value = ctx.regs[iss.Rt] + enc2 = self.MSR_REDIRECTS.get(enc, enc) + sys.stdout.flush() + if enc in xlate: + value = self.p.hv_translate(value, True, False) + self.u.msr(enc2, value, call=self.p.gl2_call) + self.log(f"Pass: msr {name}, x{iss.Rt} = {value:x} (OK) ({sysreg_name(enc2)})") + + ctx.elr += 4 + + if self.hook_exceptions: + self.patch_exception_handling() + + return True + + def handle_impdef(self, ctx): + if ctx.esr.ISS == 0x20: + return self.handle_msr(ctx, ctx.afsr1) + + code = struct.unpack("<I", self.iface.readmem(ctx.elr_phys, 4)) + c = ARMAsm(".inst " + ",".join(str(i) for i in code), ctx.elr_phys) + insn = "; ".join(c.disassemble()) + + self.log(f"IMPDEF exception on: {insn}") + + return False + + def handle_hvc(self, ctx): + idx = ctx.esr.ISS + if idx == 0: + return False + + vector, target = self.vectors[idx] + if target is None: + self.log(f"EL1: Exception #{vector} with no target") + target = 0 + ok = False + else: + ctx.elr = target + ctx.elr_phys = self.p.hv_translate(target, False, False) + ok = True + + if (vector & 3) == EXC.SYNC: + spsr = SPSR(self.u.mrs(SPSR_EL12)) + esr = ESR(self.u.mrs(ESR_EL12)) + elr = self.u.mrs(ELR_EL12) + elr_phys = self.p.hv_translate(elr, False, False) + sp_el1 = self.u.mrs(SP_EL1) + sp_el0 = self.u.mrs(SP_EL0) + far = None + if esr.EC == ESR_EC.DABORT or esr.EC == ESR_EC.IABORT: + far = self.u.mrs(FAR_EL12) + if self.sym(elr)[1] != "com.apple.kernel:_panic_trap_to_debugger": + self.log("Page fault") + return ok + + self.log(f"EL1: Exception #{vector} ({esr.EC!s}) to {self.addr(target)} from {spsr.M.name}") + self.log(f" ELR={self.addr(elr)} (0x{elr_phys:x})") + self.log(f" SP_EL1=0x{sp_el1:x} SP_EL0=0x{sp_el0:x}") + if far is not None: + self.log(f" FAR={self.addr(far)}") + if elr_phys: + self.u.disassemble_at(elr_phys - 4 * 4, 9 * 4, elr - 4 * 4, elr, sym=self.get_sym) + if self.sym(elr)[1] == "com.apple.kernel:_panic_trap_to_debugger": + self.log("Panic! Trying to decode panic...") + try: + self.decode_panic_call() + except: + self.log("Error decoding panic.") + try: + self.bt() + except: + pass + return False + if esr.EC == ESR_EC.UNKNOWN: + instr = self.p.read32(elr_phys) + if instr == 0xe7ffdeff: + self.log("Debugger break! Trying to decode panic...") + try: + self.decode_dbg_panic() + except: + self.log("Error decoding panic.") + try: + self.bt() + except: + pass + return False + return False + else: + elr = self.u.mrs(ELR_EL12) + self.log(f"Guest: {str(EXC(vector & 3))} at {self.addr(elr)}") + + return ok + + def handle_step(self, ctx): + # not sure why MDSCR_EL1.SS needs to be disabled here but otherwise + # if also SPSR.SS=0 no instruction will be executed after eret + # and instead a debug exception is generated again + self.u.msr(MDSCR_EL1, MDSCR(MDE=1).value) + + # enable all breakpoints again + for i, vaddr in enumerate(self._bps): + if vaddr is None: + continue + self.u.msr(DBGBCRn_EL1(i), DBGBCR(E=1, PMC=0b11, BAS=0xf).value) + + # enable all watchpoints again + for i, wpc in enumerate(self._wpcs): + self.u.msr(DBGWCRn_EL1(i), wpc) + + return True + + def handle_break(self, ctx): + # disable all breakpoints so that we don't get stuck + for i in range(5): + self.u.msr(DBGBCRn_EL1(i), 0) + + # we'll need to single step to enable these breakpoints again + self.u.msr(MDSCR_EL1, MDSCR(SS=1, MDE=1).value) + self.ctx.spsr.SS = 1 + + if ctx.elr in self._bp_hooks: + if self._bp_hooks[ctx.elr](ctx): + return True + + def handle_watch(self, ctx): + # disable all watchpoints so that we don't get stuck + for i in range(len(self._wps)): + self.u.msr(DBGWCRn_EL1(i), 0) + + # we'll need to single step to enable these watchpoints again + self.u.msr(MDSCR_EL1, MDSCR(SS=1, MDE=1).value) + self.ctx.spsr.SS = 1 + + def add_hvcall(self, callid, handler): + self.hvcall_handlers[callid] = handler + + def handle_brk(self, ctx): + iss = ctx.esr.ISS + if iss != 0x4242: + return self._lower() + + # HV call from EL0/1 + callid = ctx.regs[0] + handler = self.hvcall_handlers.get(callid, None) + if handler is None: + self.log(f"Undefined HV call #{callid}") + return False + + ok = handler(ctx) + if ok: + ctx.elr += 4 + return ok + + def handle_dabort(self, ctx): + insn = self.p.read32(ctx.elr_phys) + far_phys = self.p.hv_translate(ctx.far, True, False) + + if insn & 0x3b200c00 == 0x38200000: + page = far_phys & ~0x3fff + + before = self.p.read32(far_phys) + self.map_hw(page, page, 0x4000) + r0b = self.ctx.regs[0] + self.log(f"-ELR={self.ctx.elr:#x} LR={self.ctx.regs[30]:#x}") + self.step() + self.log(f"+ELR={self.ctx.elr:#x}") + r0a = self.ctx.regs[0] + self.dirty_maps.set(irange(page, 0x4000)) + self.pt_update() + after = self.p.read32(far_phys) + self.log(f"Unhandled atomic: @{far_phys:#x} {before:#x} -> {after:#x} | r0={r0b:#x} -> {r0a:#x}") + return True + + if insn & 0x3f000000 == 0x08000000: + page = far_phys & ~0x3fff + before = self.p.read32(far_phys) + self.map_hw(page, page, 0x4000) + r0b = self.ctx.regs[0] + self.log(f"-ELR={self.ctx.elr:#x} LR={self.ctx.regs[30]:#x}") + self.step() + self.log(f"+ELR={self.ctx.elr:#x}") + r0a = self.ctx.regs[0] + self.dirty_maps.set(irange(page, 0x4000)) + self.pt_update() + after = self.p.read32(far_phys) + self.log(f"Unhandled exclusive: @{far_phys:#x} {before:#x} -> {after:#x} | r0={r0b:#x} -> {r0a:#x}") + return True + + def handle_sync(self, ctx): + if ctx.esr.EC == ESR_EC.MSR: + return self.handle_msr(ctx) + + if ctx.esr.EC == ESR_EC.IMPDEF: + return self.handle_impdef(ctx) + + if ctx.esr.EC == ESR_EC.HVC: + return self.handle_hvc(ctx) + + if ctx.esr.EC == ESR_EC.SSTEP_LOWER: + return self.handle_step(ctx) + + if ctx.esr.EC == ESR_EC.BKPT_LOWER: + return self.handle_break(ctx) + + if ctx.esr.EC == ESR_EC.WATCH_LOWER: + return self.handle_watch(ctx) + + if ctx.esr.EC == ESR_EC.BRK: + return self.handle_brk(ctx) + + if ctx.esr.EC == ESR_EC.DABORT_LOWER: + return self.handle_dabort(ctx) + + def _load_context(self): + self._info_data = self.iface.readmem(self.exc_info, ExcInfo.sizeof()) + self.ctx = ExcInfo.parse(self._info_data) + return self.ctx + + def _commit_context(self): + new_info = ExcInfo.build(self.ctx) + if new_info != self._info_data: + self.iface.writemem(self.exc_info, new_info) + self._info_data = new_info + + def handle_exception(self, reason, code, info): + self.exc_info = info + self.exc_reason = reason + if reason in (START.EXCEPTION_LOWER, START.EXCEPTION): + code = EXC(code) + elif reason == START.HV: + code = HV_EVENT(code) + self.exc_code = code + self.is_fault = reason == START.EXCEPTION_LOWER and code in (EXC.SYNC, EXC.SERROR) + + # Nested context switch is handled by the caller + if self.switching_context: + self.switching_context = False + return + + self._in_handler = True + + ctx = self._load_context() + self.exc_orig_cpu = self.ctx.cpu_id + + handled = False + user_interrupt = False + + try: + if reason == START.EXCEPTION_LOWER: + if code == EXC.SYNC: + handled = self.handle_sync(ctx) + elif code == EXC.FIQ: + self.u.msr(CNTV_CTL_EL0, 0) + self.u.print_context(ctx, False, sym=self.get_sym) + handled = True + elif reason == START.HV: + code = HV_EVENT(code) + if code == HV_EVENT.HOOK_VM: + handled = self.handle_vm_hook(ctx) + elif code == HV_EVENT.USER_INTERRUPT: + handled = True + user_interrupt = True + except Exception as e: + self.log(f"Python exception while handling guest exception:") + traceback.print_exc() + + if handled: + ret = EXC_RET.HANDLED + if self._sigint_pending: + self.update_pac_mask() + self.log("User interrupt") + else: + self.log(f"Guest exception: {reason.name}/{code.name}") + self.update_pac_mask() + self.u.print_context(ctx, self.is_fault, sym=self.get_sym) + + if self._sigint_pending or not handled or user_interrupt: + self._sigint_pending = False + + signal.signal(signal.SIGINT, self.default_sigint) + ret = self.run_shell("Entering hypervisor shell", "Returning from exception") + signal.signal(signal.SIGINT, self._handle_sigint) + + if ret is None: + ret = EXC_RET.HANDLED + + self.pt_update() + + self._commit_context() + self.ctx = None + self.exc_orig_cpu = None + self.p.exit(ret) + + self._in_handler = False + if self._sigint_pending: + self._handle_sigint() + + def handle_bark(self, reason, code, info): + self._in_handler = True + self._sigint_pending = False + + signal.signal(signal.SIGINT, self.default_sigint) + ret = self.run_shell("Entering panic shell", "Exiting") + signal.signal(signal.SIGINT, self._handle_sigint) + + self.p.exit(0) + + def attach_virtio(self, dev, base=None, irq=None, verbose=False): + if base is None: + base = alloc_mmio_base(self.adt, 0x1000) + if irq is None: + irq = alloc_aic_irq(self.adt) + + data = dev.config_data + data_base = self.u.heap.malloc(len(data)) + self.iface.writemem(data_base, data) + + config = VirtioConfig.build({ + "irq": irq, + "devid": dev.devid, + "feats": dev.feats, + "num_qus": dev.num_qus, + "data": data_base, + "data_len": len(data), + "verbose": verbose, + }) + + config_base = self.u.heap.malloc(len(config)) + self.iface.writemem(config_base, config) + + name = None + for i in range(16): + n = "/arm-io/virtio%d" % i + if n not in self.adt: + name = n + break + if name is None: + raise ValueError("Too many virtios in ADT") + + print(f"Adding {n} @ 0x{base:x}, irq {irq}") + + node = self.adt.create_node(name) + node.reg = [Container(addr=node.to_bus_addr(base), size=0x1000)] + node.interrupt_parent = getattr(self.adt["/arm-io/aic"], "AAPL,phandle") + node.interrupts = (irq,) + node.compatible = ["virtio,mmio"] + + self.p.hv_map_virtio(base, config_base) + self.add_tracer(irange(base, 0x1000), "VIRTIO", TraceMode.RESERVED) + + dev.base = base + dev.hv = self + self.virtio_devs[base] = dev + + def handle_virtio(self, reason, code, info): + ctx = self.iface.readstruct(info, ExcInfo) + self.virtio_ctx = info = self.iface.readstruct(ctx.data, VirtioExcInfo) + + try: + handled = self.virtio_devs[info.devbase].handle_exc(info) + except: + self.log(f"Python exception from within virtio handler") + traceback.print_exc() + handled = False + + if not handled: + signal.signal(signal.SIGINT, self.default_sigint) + self.run_shell("Entering hypervisor shell", "Returning") + signal.signal(signal.SIGINT, self._handle_sigint) + + self.p.exit(EXC_RET.HANDLED) + + def skip(self): + self.ctx.elr += 4 + self.cont() + + def cont(self): + os.kill(os.getpid(), signal.SIGUSR1) + + def _lower(self): + if not self.is_fault: + print("Cannot lower non-fault exception") + return False + + self.u.msr(ELR_EL12, self.ctx.elr) + self.u.msr(SPSR_EL12, self.ctx.spsr.value) + self.u.msr(ESR_EL12, self.ctx.esr.value) + self.u.msr(FAR_EL12, self.ctx.far) + + exc_off = 0x80 * self.exc_code + + if self.ctx.spsr.M == SPSR_M.EL0t: + exc_off += 0x400 + elif self.ctx.spsr.M == SPSR_M.EL1t: + pass + elif self.ctx.spsr.M == SPSR_M.EL1h: + exc_off += 0x200 + else: + print(f"Unknown exception level {self.ctx.spsr.M}") + return False + + self.ctx.spsr.M = SPSR_M.EL1h + self.ctx.spsr.D = 1 + self.ctx.spsr.A = 1 + self.ctx.spsr.I = 1 + self.ctx.spsr.F = 1 + self.ctx.elr = self.u.mrs(VBAR_EL12) + exc_off + + return True + + def lower(self, step=False): + self.cpu() # Return to exception CPU + + if not self._lower(): + return + elif step: + self.step() + else: + self.cont() + + def step(self): + self.u.msr(MDSCR_EL1, MDSCR(SS=1, MDE=1).value) + self.ctx.spsr.SS = 1 + self.p.hv_pin_cpu(self.ctx.cpu_id) + self._switch_context() + self.p.hv_pin_cpu(0xffffffffffffffff) + + def _switch_context(self, exit=EXC_RET.HANDLED): + # Flush current CPU context out to HV + self._commit_context() + self.exc_info = None + self.ctx = None + + self.switching_context = True + # Exit out of the proxy + self.p.exit(exit) + # Wait for next proxy entry + self.iface.wait_and_handle_boot() + if self.switching_context: + raise Exception(f"Failed to switch context") + + # Fetch new context + self._load_context() + + def cpu(self, cpu=None): + if cpu is None: + cpu = self.exc_orig_cpu + if cpu == self.ctx.cpu_id: + return + + if not self.p.hv_switch_cpu(cpu): + raise ValueError(f"Invalid or inactive CPU #{cpu}") + + self._switch_context() + if self.ctx.cpu_id != cpu: + raise Exception(f"Switching to CPU #{cpu} but ended on #{self.ctx.cpu_id}") + + def add_hw_bp(self, vaddr, hook=None): + if None not in self._bps: + raise ValueError("Cannot add more HW breakpoints") + + i = self._bps.index(None) + cpu_id = self.ctx.cpu_id + try: + for cpu in self.cpus(): + self.u.msr(DBGBCRn_EL1(i), DBGBCR(E=1, PMC=0b11, BAS=0xf).value) + self.u.msr(DBGBVRn_EL1(i), vaddr) + finally: + self.cpu(cpu_id) + self._bps[i] = vaddr + if hook is not None: + self._bp_hooks[vaddr] = hook + + def remove_hw_bp(self, vaddr): + idx = self._bps.index(vaddr) + self._bps[idx] = None + cpu_id = self.ctx.cpu_id + try: + for cpu in self.cpus(): + self.u.msr(DBGBCRn_EL1(idx), 0) + self.u.msr(DBGBVRn_EL1(idx), 0) + finally: + self.cpu(cpu_id) + if vaddr in self._bp_hooks: + del self._bp_hooks[vaddr] + + def add_sym_bp(self, name, hook=None): + return self.add_hw_bp(self.resolve_symbol(name), hook=hook) + + def remove_sym_bp(self, name): + return self.remove_hw_bp(self.resolve_symbol(name)) + + def clear_hw_bps(self): + for vaddr in self._bps: + self.remove_hw_bp(vaddr) + + def add_hw_wp(self, vaddr, bas, lsc): + for i, i_vaddr in enumerate(self._wps): + if i_vaddr is None: + self._wps[i] = vaddr + self._wpcs[i] = DBGWCR(E=1, PAC=0b11, BAS=bas, LSC=lsc).value + cpu_id = self.ctx.cpu_id + try: + for cpu in self.cpus(): + self.u.msr(DBGWCRn_EL1(i), self._wpcs[i]) + self.u.msr(DBGWVRn_EL1(i), vaddr) + finally: + self.cpu(cpu_id) + return + raise ValueError("Cannot add more HW watchpoints") + + def get_wp_bas(self, vaddr): + for i, i_vaddr in enumerate(self._wps): + if i_vaddr == vaddr: + return self._wpcs[i].BAS + + def remove_hw_wp(self, vaddr): + idx = self._wps.index(vaddr) + self._wps[idx] = None + self._wpcs[idx] = 0 + cpu_id = self.ctx.cpu_id + try: + for cpu in self.cpus(): + self.u.msr(DBGWCRn_EL1(idx), 0) + self.u.msr(DBGWVRn_EL1(idx), 0) + finally: + self.cpu(cpu_id) + + def exit(self): + os.kill(os.getpid(), signal.SIGUSR2) + + def reboot(self): + print("Hard rebooting the system") + self.p.reboot() + sys.exit(0) + + def hvc(self, arg): + assert 0 <= arg <= 0xffff + return 0xd4000002 | (arg << 5) + + def decode_dbg_panic(self): + xnutools.decode_debugger_state(self.u, self.ctx) + + def decode_panic_call(self): + xnutools.decode_panic_call(self.u, self.ctx) + + def context(self): + f = f" (orig: #{self.exc_orig_cpu})" if self.ctx.cpu_id != self.exc_orig_cpu else "" + print(f" == On CPU #{self.ctx.cpu_id}{f} ==") + print(f" Reason: {self.exc_reason.name}/{self.exc_code.name}") + self.u.print_context(self.ctx, self.is_fault, sym=self.get_sym) + + def bt(self, frame=None, lr=None): + if frame is None: + frame = self.ctx.regs[29] + if lr is None: + lr = self.unpac(self.ctx.elr) + 4 + + print("Stack trace:") + frames = set() + while frame: + if frame in frames: + print("Stack loop detected!") + break + frames.add(frame) + print(f" - {self.addr(lr - 4)}") + lrp = self.p.hv_translate(frame + 8) + fpp = self.p.hv_translate(frame) + if not fpp: + break + lr = self.unpac(self.p.read64(lrp)) + frame = self.p.read64(fpp) + + def cpus(self): + for i in sorted(self.started_cpus): + self.cpu(i) + yield i + + def patch_exception_handling(self): + if self.ctx.cpu_id != 0: + return + + if self.want_vbar is not None: + vbar = self.want_vbar + else: + vbar = self.u.mrs(VBAR_EL12) + + if vbar == self.vbar_el1: + return + + if vbar == 0: + return + + if self.u.mrs(SCTLR_EL12) & 1: + vbar_phys = self.p.hv_translate(vbar, False, False) + if vbar_phys == 0: + self.log(f"VBAR vaddr 0x{vbar:x} translation failed!") + if self.vbar_el1 is not None: + self.want_vbar = vbar + self.u.msr(VBAR_EL12, self.vbar_el1) + return + else: + if vbar & (1 << 63): + self.log(f"VBAR vaddr 0x{vbar:x} without translation enabled") + if self.vbar_el1 is not None: + self.want_vbar = vbar + self.u.msr(VBAR_EL12, self.vbar_el1) + return + + vbar_phys = vbar + + if self.want_vbar is not None: + self.want_vbar = None + self.u.msr(VBAR_EL12, vbar) + + self.log(f"New VBAR paddr: 0x{vbar_phys:x}") + + #for i in range(16): + for i in [0, 3, 4, 7, 8, 11, 12, 15]: + idx = 0 + addr = vbar_phys + 0x80 * i + orig = self.p.read32(addr) + if (orig & 0xfc000000) != 0x14000000: + self.log(f"Unknown vector #{i}:\n") + self.u.disassemble_at(addr, 16) + else: + idx = len(self.vectors) + delta = orig & 0x3ffffff + if delta == 0: + target = None + self.log(f"Vector #{i}: Loop\n") + else: + target = (delta << 2) + vbar + 0x80 * i + self.log(f"Vector #{i}: 0x{target:x}\n") + self.vectors.append((i, target)) + self.u.disassemble_at(addr, 16) + self.p.write32(addr, self.hvc(idx)) + + self.p.dc_cvau(vbar_phys, 0x800) + self.p.ic_ivau(vbar_phys, 0x800) + + self.vbar_el1 = vbar + + def set_logfile(self, fd): + self.print_tracer.log_file = fd + + def init(self): + self.adt = load_adt(self.u.get_adt()) + self.iodev = self.p.iodev_whoami() + self.tba = self.u.ba.copy() + self.device_addr_tbl = self.adt.build_addr_lookup() + self.print_tracer = trace.PrintTracer(self, self.device_addr_tbl) + + # disable unused USB iodev early so interrupts can be reenabled in hv_init() + for iodev in IODEV: + if iodev >= IODEV.USB0 and iodev != self.iodev: + print(f"Disable iodev {iodev!s}") + self.p.iodev_set_usage(iodev, 0) + + print("Initializing hypervisor over iodev %s" % self.iodev) + self.p.hv_init() + + self.iface.set_handler(START.EXCEPTION_LOWER, EXC.SYNC, self.handle_exception) + self.iface.set_handler(START.EXCEPTION_LOWER, EXC.IRQ, self.handle_exception) + self.iface.set_handler(START.EXCEPTION_LOWER, EXC.FIQ, self.handle_exception) + self.iface.set_handler(START.EXCEPTION_LOWER, EXC.SERROR, self.handle_exception) + self.iface.set_handler(START.EXCEPTION, EXC.FIQ, self.handle_exception) + self.iface.set_handler(START.HV, HV_EVENT.USER_INTERRUPT, self.handle_exception) + self.iface.set_handler(START.HV, HV_EVENT.HOOK_VM, self.handle_exception) + self.iface.set_handler(START.HV, HV_EVENT.VTIMER, self.handle_exception) + self.iface.set_handler(START.HV, HV_EVENT.WDT_BARK, self.handle_bark) + self.iface.set_handler(START.HV, HV_EVENT.CPU_SWITCH, self.handle_exception) + self.iface.set_handler(START.HV, HV_EVENT.VIRTIO, self.handle_virtio) + self.iface.set_event_handler(EVENT.MMIOTRACE, self.handle_mmiotrace) + self.iface.set_event_handler(EVENT.IRQTRACE, self.handle_irqtrace) + + # Map MMIO ranges as HW by default + for r in self.adt["/arm-io"].ranges: + print(f"Mapping MMIO range: {r.parent_addr:#x} .. {r.parent_addr + r.size:#x}") + self.add_tracer(irange(r.parent_addr, r.size), "HW", TraceMode.OFF) + + hcr = HCR(self.u.mrs(HCR_EL2)) + if self.novm: + hcr.VM = 0 + hcr.AMO = 0 + else: + hcr.TACR = 1 + hcr.TIDCP = 0 + hcr.TVM = 0 + hcr.FMO = 1 + hcr.IMO = 0 + hcr.TTLBOS = 1 + self.u.msr(HCR_EL2, hcr.value) + + # Trap dangerous things + hacr = HACR(0) + if not self.novm: + #hacr.TRAP_CPU_EXT = 1 + #hacr.TRAP_SPRR = 1 + #hacr.TRAP_GXF = 1 + hacr.TRAP_CTRR = 1 + hacr.TRAP_EHID = 1 + hacr.TRAP_HID = 1 + hacr.TRAP_ACC = 1 + hacr.TRAP_IPI = 1 + hacr.TRAP_SERROR_INFO = 1 # M1RACLES mitigation + hacr.TRAP_PM = 1 + self.u.msr(HACR_EL2, hacr.value) + + # enable and route debug exceptions to EL2 + mdcr = MDCR(0) + mdcr.TDE = 1 + mdcr.TDA = 1 + mdcr.TDOSA = 1 + mdcr.TDRA = 1 + self.u.msr(MDCR_EL2, mdcr.value) + self.u.msr(MDSCR_EL1, MDSCR(MDE=1).value) + + # Enable AMX + amx_ctl = AMX_CTL(self.u.mrs(AMX_CTL_EL1)) + amx_ctl.EN_EL1 = 1 + self.u.msr(AMX_CTL_EL1, amx_ctl.value) + + # Set guest AP keys + self.u.msr(APVMKEYLO_EL2, 0x4E7672476F6E6147) + self.u.msr(APVMKEYHI_EL2, 0x697665596F755570) + self.u.msr(APSTS_EL12, 1) + + self.map_vuart() + + actlr = ACTLR(self.u.mrs(ACTLR_EL12)) + actlr.EnMDSB = 1 + self.u.msr(ACTLR_EL12, actlr.value) + + self.setup_adt() + + def map_vuart(self): + node = base = self.adt["/arm-io/uart0"] + base = node.get_reg(0)[0] + + zone = irange(base, 0x4000) + irq = node.interrupts[0] + self.p.hv_map_vuart(base, irq, self.iodev) + self.add_tracer(zone, "VUART", TraceMode.RESERVED) + + def map_essential(self): + # Things we always map/take over, for the hypervisor to work + _pmgr = {} + + def wh(base, off, data, width): + self.log(f"PMGR W {base:x}+{off:x}:{width} = 0x{data:x}: Dangerous write") + self.p.mask32(base + off, 0x3ff, (data | 0xf) & ~(0x80000400)) + _pmgr[base + off] = (data & 0xfffffc0f) | ((data & 0xf) << 4) + + def rh(base, off, width): + data = self.p.read32(base + off) + ret = _pmgr.setdefault(base + off, data) + self.log(f"PMGR R {base:x}+{off:x}:{width} = 0x{data:x} -> 0x{ret:x}") + return ret + + atc = f"ATC{self.iodev - IODEV.USB0}_USB" + + hook_devs = ["UART0", atc] + + pmgr = self.adt["/arm-io/pmgr"] + dev_by_name = {dev.name: dev for dev in pmgr.devices} + dev_by_id = {dev.id: dev for dev in pmgr.devices} + + pmgr_hooks = [] + + def hook_pmgr_dev(dev): + ps = pmgr.ps_regs[dev.psreg] + if dev.psidx or dev.psreg: + addr = pmgr.get_reg(ps.reg)[0] + ps.offset + dev.psidx * 8 + pmgr_hooks.append(addr) + for idx in dev.parents: + if idx in dev_by_id: + hook_pmgr_dev(dev_by_id[idx]) + + for name in hook_devs: + dev = dev_by_name[name] + hook_pmgr_dev(dev) + + pmgr0_start = pmgr.get_reg(0)[0] + + for addr in pmgr_hooks: + self.map_hook(addr, 4, write=wh, read=rh) + #TODO : turn into a real tracer + self.add_tracer(irange(addr, 4), "PMGR HACK", TraceMode.RESERVED) + + pg_overrides = { + 0x23d29c05c: 0xc000000, + 0x23d29c044: 0xc000000, + } + + for addr in pg_overrides: + self.map_hook(addr, 4, read=lambda base, off, width: pg_overrides[base + off]) + self.add_tracer(irange(addr, 4), "PMGR HACK", TraceMode.RESERVED) + + def cpustart_wh(base, off, data, width): + self.log(f"CPUSTART W {base:x}+{off:x}:{width} = 0x{data:x}") + if off >= 8: + assert width == 32 + die = base // 0x20_0000_0000 + cluster = (off - 8) // 4 + for i in range(32): + if data & (1 << i): + self.start_secondary(die, cluster, i) + + die_count = self.adt["/arm-io"].die_count if hasattr(self.adt["/arm-io"], "die-count") else 1 + + for die in range(0, die_count): + if self.u.adt["/chosen"].chip_id in (0x8103, 0x6000, 0x6001, 0x6002): + cpu_start = 0x54000 + die * 0x20_0000_0000 + elif self.u.adt["/chosen"].chip_id in (0x8112,): + cpu_start = 0x34000 + die * 0x20_0000_0000 + else: + self.log("CPUSTART unknown for this SoC!") + break + + zone = irange(pmgr0_start + cpu_start, 0x20) + self.map_hook(pmgr0_start + cpu_start, 0x20, write=cpustart_wh) + self.add_tracer(zone, "CPU_START", TraceMode.RESERVED) + + def start_secondary(self, die, cluster, cpu): + self.log(f"Starting guest secondary {die}:{cluster}:{cpu}") + + for node in list(self.adt["cpus"]): + if ((die << 11) | (cluster << 8) | cpu) == node.reg: + break + else: + self.log("CPU not found!") + return + + entry = self.p.read64(node.cpu_impl_reg[0]) & 0xfffffffffff + index = node.cpu_id + self.log(f" CPU #{index}: RVBAR = {entry:#x}") + + self.sysreg[index] = {} + self.started_cpus.add(index) + self.p.hv_start_secondary(index, entry) + + def setup_adt(self): + self.adt["product"].product_name += " on m1n1 hypervisor" + self.adt["product"].product_description += " on m1n1 hypervisor" + soc_name = "Virtual " + self.adt["product"].product_soc_name + " on m1n1 hypervisor" + self.adt["product"].product_soc_name = soc_name + + if self.iodev >= IODEV.USB0: + idx = self.iodev - IODEV.USB0 + for prefix in ("/arm-io/dart-usb%d", + "/arm-io/atc-phy%d", + "/arm-io/usb-drd%d", + "/arm-io/acio%d", + "/arm-io/acio-cpu%d", + "/arm-io/dart-acio%d", + "/arm-io/apciec%d", + "/arm-io/dart-apciec%d", + "/arm-io/apciec%d-piodma", + "/arm-io/i2c0/hpmBusManager/hpm%d", + "/arm-io/atc%d-dpxbar", + "/arm-io/atc%d-dpphy", + "/arm-io/atc%d-dpin0", + "/arm-io/atc%d-dpin1", + "/arm-io/atc-phy%d", + ): + name = prefix % idx + print(f"Removing ADT node {name}") + try: + del self.adt[name] + except KeyError: + pass + + if self.wdt_cpu is not None: + name = f"/cpus/cpu{self.wdt_cpu}" + print(f"Removing ADT node {name}") + try: + del self.adt[name] + except KeyError: + pass + + if not self.smp: + for cpu in list(self.adt["cpus"]): + if cpu.name != "cpu0": + print(f"Removing ADT node {cpu._path}") + try: + del self.adt["cpus"][cpu.name] + except KeyError: + pass + + def set_bootargs(self, boot_args): + if "-v" in boot_args.split(): + self.tba.video.display = 0 + else: + self.tba.video.display = 1 + print(f"Setting boot arguments to {boot_args!r}") + self.tba.cmdline = boot_args + + def unmap_carveouts(self): + print(f"Unmapping TZ carveouts...") + carveout_p = self.p.mcc_get_carveouts() + while True: + base = self.p.read64(carveout_p) + size = self.p.read64(carveout_p + 8) + if not base: + break + print(f" Unmap [{base:#x}..{base + size - 1:#x}]") + self.del_tracer(irange(base, size), "RAM-LOW") + self.del_tracer(irange(base, size), "RAM-HIGH") + carveout_p += 16 + + def enable_time_stealing(self): + self.p.hv_set_time_stealing(True) + + def disable_time_stealing(self): + self.p.hv_set_time_stealing(False) + + + def load_raw(self, image, entryoffset=0x800, use_xnu_symbols=False, vmin=0): + sepfw_start, sepfw_length = self.u.adt["chosen"]["memory-map"].SEPFW + tc_start, tc_size = self.u.adt["chosen"]["memory-map"].TrustCache + if hasattr(self.u.adt["chosen"]["memory-map"], "preoslog"): + preoslog_start, preoslog_size = self.u.adt["chosen"]["memory-map"].preoslog + else: + preoslog_size = 0 + + image_size = align(len(image)) + sepfw_off = image_size + image_size += align(sepfw_length) + preoslog_off = image_size + image_size += preoslog_size + self.bootargs_off = image_size + bootargs_size = 0x4000 + image_size += bootargs_size + + print(f"Total region size: 0x{image_size:x} bytes") + + self.phys_base = phys_base = guest_base = self.u.heap_top + self.ram_base = self.phys_base & ~0xffffffff + self.ram_size = self.u.ba.mem_size_actual + guest_base += 16 << 20 # ensure guest starts within a 16MB aligned region of mapped RAM + self.adt_base = guest_base + guest_base += align(self.u.ba.devtree_size) + tc_base = guest_base + guest_base += align(tc_size) + self.guest_base = guest_base + mem_top = self.u.ba.phys_base + self.u.ba.mem_size + mem_size = mem_top - phys_base + + print(f"Physical memory: 0x{phys_base:x} .. 0x{mem_top:x}") + print(f"Guest region start: 0x{guest_base:x}") + + self.entry = guest_base + entryoffset + + print(f"Mapping guest physical memory...") + self.add_tracer(irange(self.ram_base, self.u.ba.phys_base - self.ram_base), "RAM-LOW", TraceMode.OFF) + self.add_tracer(irange(phys_base, self.u.ba.mem_size_actual - phys_base + self.ram_base), "RAM-HIGH", TraceMode.OFF) + self.unmap_carveouts() + + print(f"Loading kernel image (0x{len(image):x} bytes)...") + self.u.compressed_writemem(guest_base, image, True) + self.p.dc_cvau(guest_base, len(image)) + self.p.ic_ivau(guest_base, len(image)) + + print(f"Copying SEPFW (0x{sepfw_length:x} bytes)...") + self.p.memcpy8(guest_base + sepfw_off, sepfw_start, sepfw_length) + + print(f"Copying TrustCache (0x{tc_size:x} bytes)...") + self.p.memcpy8(tc_base, tc_start, tc_size) + + if hasattr(self.u.adt["chosen"]["memory-map"], "preoslog"): + print(f"Copying preoslog (0x{preoslog_size:x} bytes)...") + self.p.memcpy8(guest_base + preoslog_off, preoslog_start, preoslog_size) + + print(f"Adjusting addresses in ADT...") + self.adt["chosen"]["memory-map"].SEPFW = (guest_base + sepfw_off, sepfw_length) + self.adt["chosen"]["memory-map"].TrustCache = (tc_base, tc_size) + self.adt["chosen"]["memory-map"].DeviceTree = (self.adt_base, align(self.u.ba.devtree_size)) + self.adt["chosen"]["memory-map"].BootArgs = (guest_base + self.bootargs_off, bootargs_size) + if hasattr(self.u.adt["chosen"]["memory-map"], "preoslog"): + self.adt["chosen"]["memory-map"].preoslog = (guest_base + preoslog_off, preoslog_size) + + print(f"Setting up bootargs at 0x{guest_base + self.bootargs_off:x}...") + + self.tba.mem_size = mem_size + self.tba.phys_base = phys_base + self.tba.virt_base = 0xfffffe0010000000 + (phys_base & (32 * 1024 * 1024 - 1)) + self.tba.devtree = self.adt_base - phys_base + self.tba.virt_base + self.tba.top_of_kernel_data = guest_base + image_size + + if use_xnu_symbols == True: + self.sym_offset = vmin - guest_base + self.tba.phys_base - self.tba.virt_base + + self.iface.writemem(guest_base + self.bootargs_off, BootArgs.build(self.tba)) + + print("Setting secondary CPU RVBARs...") + rvbar = self.entry & ~0xfff + for cpu in self.adt["cpus"][1:]: + addr, size = cpu.cpu_impl_reg + print(f" {cpu.name}: [0x{addr:x}] = 0x{rvbar:x}") + self.p.write64(addr, rvbar) + + def _load_macho_symbols(self): + self.symbol_dict = self.macho.symbols + self.symbols = [(v, k) for k, v in self.macho.symbols.items()] + self.symbols.sort() + + def load_macho(self, data, symfile=None): + if isinstance(data, str): + data = open(data, "rb") + + self.macho = macho = MachO(data) + if symfile is not None: + if isinstance(symfile, str): + symfile = open(symfile, "rb") + syms = MachO(symfile) + macho.add_symbols("com.apple.kernel", syms) + self.xnu_mode = True + + self._load_macho_symbols() + + def load_hook(data, segname, size, fileoff, dest): + if segname != "__TEXT_EXEC": + return data + + print(f"Patching segment {segname}...") + + a = array.array("I", data) + + output = [] + + p = 0 + while (p := data.find(b"\x20\x00", p)) != -1: + if (p & 3) != 2: + p += 1 + continue + + opcode = a[p // 4] + inst = self.hvc((opcode & 0xffff)) + off = fileoff + (p & ~3) + if off >= 0xbfcfc0: + print(f" 0x{off:x}: 0x{opcode:04x} -> hvc 0x{opcode:x} (0x{inst:x})") + a[p // 4] = inst + p += 4 + + print("Done.") + return a.tobytes() + + #image = macho.prepare_image(load_hook) + image = macho.prepare_image() + self.load_raw(image, entryoffset=(macho.entry - macho.vmin), use_xnu_symbols=self.xnu_mode, vmin=macho.vmin) + + + def update_pac_mask(self): + tcr = TCR(self.u.mrs(TCR_EL12)) + valid_bits = (1 << (64 - tcr.T1SZ)) - 1 + self.pac_mask = 0xffffffffffffffff & ~valid_bits + valid_bits = (1 << (64 - tcr.T0SZ)) - 1 + self.user_pac_mask = 0xffffffffffffffff & ~valid_bits + + def unpac(self, v): + if v & (1 << 55): + return v | self.pac_mask + else: + return v & ~self.user_pac_mask + + def load_system_map(self, path): + # Assume Linux + self.sym_offset = 0 + self.xnu_mode = False + self.symbols = [] + self.symbol_dict = {} + with open(path) as fd: + for line in fd.readlines(): + addr, t, name = line.split() + addr = int(addr, 16) + self.symbols.append((addr, name)) + self.symbol_dict[name] = addr + self.symbols.sort() + + def add_kext_symbols(self, kext, demangle=False): + info_plist = plistlib.load(open(f"{kext}/Contents/Info.plist", "rb")) + identifier = info_plist["CFBundleIdentifier"] + name = info_plist["CFBundleName"] + macho = MachO(open(f"{kext}/Contents/MacOS/{name}", "rb")) + self.macho.add_symbols(identifier, macho, demangle=demangle) + self._load_macho_symbols() + + def _handle_sigint(self, signal=None, stack=None): + self._sigint_pending = True + self.interrupt() + + def interrupt(self): + if self._in_handler: + return + + # Kick the proxy to break out of the hypervisor + self.iface.dev.write(b"!") + + def run_script(self, path): + new_locals = runpy.run_path(path, init_globals=self.shell_locals, run_name="<hv_script>") + self.shell_locals.clear() + self.shell_locals.update(new_locals) + + def run_code(self, code): + exec(code, self.shell_locals) + + def start(self): + print("Disabling other iodevs...") + for iodev in IODEV: + if iodev != self.iodev: + print(f" - {iodev!s}") + self.p.iodev_set_usage(iodev, 0) + + print("Doing essential MMIO remaps...") + self.map_essential() + + print("Updating page tables...") + self.pt_update() + + adt_blob = self.adt.build() + print(f"Uploading ADT (0x{len(adt_blob):x} bytes)...") + self.iface.writemem(self.adt_base, adt_blob) + + print("Improving logo...") + self.p.fb_improve_logo() + + print("Shutting down framebuffer...") + self.p.fb_shutdown(True) + + print("Enabling SPRR...") + self.u.msr(SPRR_CONFIG_EL1, 1) + + print("Enabling GXF...") + self.u.msr(GXF_CONFIG_EL1, 1) + + print(f"Jumping to entrypoint at 0x{self.entry:x}") + + self.iface.dev.timeout = None + self.default_sigint = signal.signal(signal.SIGINT, self._handle_sigint) + + set_sigquit_stackdump_handler() + + if self.wdt_cpu is not None: + self.p.hv_wdt_start(self.wdt_cpu) + # Does not return + + self.started = True + self.started_cpus.add(0) + self.p.hv_start(self.entry, self.guest_base + self.bootargs_off) + +from .. import trace diff --git a/tools/proxyclient/m1n1/hv/gdbserver/__init__.py b/tools/proxyclient/m1n1/hv/gdbserver/__init__.py new file mode 100644 index 0000000..ade807f --- /dev/null +++ b/tools/proxyclient/m1n1/hv/gdbserver/__init__.py @@ -0,0 +1,480 @@ +# SPDX-License-Identifier: MIT +import errno, io, os, pkgutil, re, selectors, socketserver, threading, traceback +from construct import Array, BytesInteger, Container, Int32ul, Int64ul, Struct + +from ...proxy import * +from ...sysreg import * +from ...utils import * + +from ..types import * + +__all__ = ["GDBServer"] + +class GDBServer: + __g = Struct( + "regs" / Array(32, Int64ul), + "pc" / Int64ul, + "spsr" / Int32ul, + "q" / Array(32, BytesInteger(16, swapped=True)), + "fpsr" / Int32ul, + "fpcr" / Int32ul, + ) + __seperator = re.compile("[,;:]") + + def __init__(self, hv, address, log): + self.__hc = None + self.__hg = None + self.__hv = hv + self.__interrupt_eventfd = os.eventfd(0, flags=os.EFD_CLOEXEC | os.EFD_NONBLOCK) + self.__interrupt_selector = selectors.DefaultSelector() + self.__request = None + self.log = log + + self.__interrupt_selector.register(self.__interrupt_eventfd, selectors.EVENT_READ) + + handle = self.__handle + + class Handler(socketserver.BaseRequestHandler): + def handle(self): + handle(self.request) + + self.__server = socketserver.UnixStreamServer(address, Handler, False) + self.__thread = threading.Thread(target=self.__server.serve_forever,) + + def __add_wp(self, addr, kind, lsc): + start = addr & 7 + if start + kind > 8: + return b"E01" + + self.__hv.add_hw_wp(addr & ~7, ((1 << kind) - 1) << start, lsc) + return b"OK" + + def __remove_wp(self, addr): + self.__hv.remove_hw_wp(addr & ~7) + return b"OK" + + def __cpu(self, cpu): + if cpu is None: + return + + self.__hv.cpu(cpu) + + def __stop_reply(self): + self.__hc = None + self.__hg = None + + prefix = b"T05thread:" + + if self.__hv.exc_reason == START.EXCEPTION_LOWER: + if self.__hv.exc_code == EXC.SYNC: + if self.__hv.ctx.esr.EC == ESR_EC.BKPT_LOWER: + prefix = b"T05hwbreak:;thread:" + elif self.__hv.ctx.esr.EC == ESR_EC.WATCH_LOWER: + bas = self.__hv.get_wp_bas(self.__hv.ctx.far) + if not bas is None and bas != 0: + offset = 0 + while (bas & (1 << offset)) == 0: + offset += 1 + addr = self.__hv.ctx.far + offset + formatted_addr = bytes(format(addr, "x"), "utf-8") + prefix = b"T05watch:" + formatted_addr + b";thread:" + elif self.__hv.exc_reason == START.HV: + if self.__hv.exc_code == HV_EVENT.USER_INTERRUPT: + prefix = b"T02thread:" + + return prefix + bytes(format(self.__hv.ctx.cpu_id, "x"), "utf-8") + b";" + + def __wait_shell(self): + try: + os.eventfd_read(self.__interrupt_eventfd) + except BlockingIOError: + pass + + while not self.__interrupt_eventfd in (key.fileobj for key, mask in self.__interrupt_selector.select()): + recv = self.__request.recv(1) + if not recv: + break + + for byte in recv: + if byte in b"\1\3": + self.__hv.interrupt() + break + + def __eval(self, data): + if self.log: + self.log(f"eval: {data}") + + if len(data) < 1: + return b"" + + if data[0] in b"?": + return self.__stop_reply() + + if data[0] in b"c": + if len(data) != 1: + self.__cpu(self.__hc) + self.__hv.ctx.elr = int(data[1:].decode(), 16) + + self.__hv.cont() + self.__wait_shell() + return self.__stop_reply() + + if data[0] in b"g": + self.__cpu(self.__hg) + g = Container() + g.regs = self.__hv.ctx.regs.copy() + g.regs[31] = self.__hv.ctx.sp[1] + g.pc = self.__hv.ctx.elr + g.spsr = self.__hv.ctx.spsr.value + g.q = self.__hv.u.q + g.fpsr = self.__hv.u.mrs(FPSR) + g.fpcr = self.__hv.u.mrs(FPCR) + + return bytes(GDBServer.__g.build(g).hex(), "utf-8") + + if data[0] in b"G": + g = GDBServer.__g.parse(bytes.fromhex(data[1:].decode())) + self.__cpu(self.__hg) + + for index in range(31): + self.__hv.ctx.regs[index] = g.regs[index] + + self.__hv.ctx.sp[1] = g.regs[31] + self.__hv.ctx.elr = g.pc + self.__hv.ctx.spsr = g.spsr.value + + q = self.__hv.u.q + for index, value in enumerate(g.q): + q[index] = value + self.__hv.u.push_simd() + + self.__hv.u.msr(FPSR, g.fpsr, silent=True) + self.__hv.u.msr(FPCR, g.fpsr, silent=True) + + return b"OK" + + if data[0] in b"H": + if len(data) > 1: + if data[1] in b"c": + cpu_id = int(data[2:].decode(), 16) + if cpu_id in self.__hv.started_cpus: + self.__hc = cpu_id + return b"OK" + + return b"E01" + + if data[1] in b"g": + cpu_id = int(data[2:].decode(), 16) + if cpu_id in self.__hv.started_cpus: + self.__hg = cpu_id + return b"OK" + + return b"E01" + + return b"" + + if data[0] in b"krR": + self.__hv.reboot() + + if data[0] in b"m": + split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=1) + fields = [int(field, 16) for field in split] + return bytes(self.__hv.readmem(fields[0], fields[1]).hex(), "utf-8") + + if data[0] in b"M": + split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=2) + mem = bytes.fromhex(split[2])[:int(split[1], 16)] + if self.__hv.writemem(int(split[0], 16), mem) < len(mem): + return "E22" + + return b"OK" + + if data[0] in b"p": + number = int(data[1:].decode(), 16) + self.__cpu(self.__hg) + if number < 31: + reg = GDBServer.__g.regs.subcon.subcon.build(self.__hv.ctx.regs[number]) + elif number == 31: + reg = GDBServer.__g.regs.subcon.subcon.build(self.__hv.ctx.sp[1]) + elif number == 32: + reg = GDBServer.__g.pc.build(self.__hv.ctx.elr) + elif number == 33: + reg = GDBServer.__g.spsr.build(self.__hv.ctx.spsr.value) + elif number < 66: + reg = GDBServer.__g.q.subcon.subcon.build(self.__hv.u.q[number - 34]) + elif number == 66: + reg = GDBServer.__g.fpsr.build(self.__hv.u.mrs(FPSR)) + elif number == 67: + reg = GDBServer.__g.fpcr.build(self.__hv.u.mrs(FPCR)) + else: + return b"E01" + + return bytes(reg.hex(), "utf-8") + + if data[0] in b"P": + partition = data[1:].partition(b"=") + number = int(partition[0].decode(), 16) + reg = bytes.fromhex(partition[2].decode()) + self.__cpu(self.__hg) + if number < 31: + self.__hv.ctx.regs[number] = GDBServer.__g.regs.subcon.subcon.unpack(reg) + elif number == 31: + self.__hv.ctx.regs[1] = GDBServer.__g.regs.subcon.subcon.unpack(reg) + elif number == 32: + self.__hv.ctx.elr = GDBServer.__g.pc.parse(reg) + elif number == 33: + self.__hv.ctx.spsr.value = GDBServer.__g.spsr.parse(reg) + elif number < 66: + self.__hv.u.q[number - 34] = GDBServer.__g.q.subcon.subcon.parse(reg) + self.__hv.u.push_simd() + elif number == 66: + self.__hv.u.msr(FPSR, GDBServer.__g.fpsr.parse(reg), silent=True) + elif number == 67: + self.__hv.u.msr(FPCR, GDBServer.__g.fpcr.parse(reg), silent=True) + else: + return b"E01" + + return b"OK" + + if data[0] in b"q": + split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=1) + if split[0] == "C": + cpu_id = self.__hg or self.__hv.ctx.cpu_id + return b"QC" + bytes(format(cpu_id, "x"), "utf-8") + + if split[0] == "fThreadInfo": + cpu_ids = b",".join(bytes(format(cpu.cpu_id, "x"), "utf-8") for cpu in self.__hv.adt["cpus"]) + return b"m" + cpu_ids + + if split[0] == "sThreadInfo": + return b"l" + + if split[0] == "Rcmd": + self.__cpu(self.__hg) + self.__hv.run_code(split[1]) + return b"OK" + + if split[0] == "Supported": + return b"PacketSize=65536;qXfer:features:read+;hwbreak+" + + if split[0] == "ThreadExtraInfo": + thread_id = int(split[1], 16) + for node in self.__hv.adt["cpus"]: + if node.cpu_id == thread_id: + return bytes(bytes(str(node), "utf-8").hex(), "utf-8") + + return b"" + + if split[0] == "Xfer": + xfer = GDBServer.__seperator.split(split[1], maxsplit=4) + if xfer[0] == "features" and xfer[1] == "read": + resource = os.path.join("features", xfer[2]) + annex = pkgutil.get_data(__name__, resource) + if annex is None: + return b"E00" + + request_offset = int(xfer[3], 16) + request_len = int(xfer[4], 16) + read = annex[request_offset:request_offset + request_len] + return (b"l" if len(read) < request_len else b"m") + read + + return b"" + + if split[0] == "HostInfo": + addressing_bits = bytes(str(64 - self.__hv.pac_mask.bit_count()), "utf-8") + return b"cputype:16777228;cpusubtype:2;endian:little;ptrsize:64;watchpoint_exceptions_received:before;addressing_bits:" + addressing_bits + b";" + + return b"" + + if data[0] in b"s": + self.__cpu(self.__hc) + + if len(data) != 1: + self.__hv.ctx.elr = int(data[1:].decode(), 16) + + self.__hv.step() + return self.__stop_reply() + + if data[0] in b"T": + if int(data[1:].decode(), 16) in self.__hv.started_cpus: + return b"OK" + + return b"E01" + + if data[0] in b"X": + partition = data[1:].partition(b":") + split = GDBServer.__seperator.split(partition[0].decode(), maxsplit=1) + mem = partition[2][:int(split[1], 16)] + if self.__hv.writemem(int(split[0], 16), mem) < len(mem): + return b"E22" + + return b"OK" + + if data[0] in b"z": + split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=2) + if split[0] == "1": + self.__hv.remove_hw_bp(int(split[1], 16)) + return b"OK" + + if split[0] == "2": + return self.__remove_wp(int(split[1], 16)) + + if split[0] == "3": + return self.__remove_wp(int(split[1], 16)) + + if split[0] == "4": + return self.__remove_wp(int(split[1], 16)) + + return b"" + + if data[0] in b"Z": + split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=2) + if split[0] == "1": + self.__hv.add_hw_bp(int(split[1], 16)) + return b"OK" + + if split[0] == "2": + addr = int(split[1], 16) + kind = int(split[2], 16) + return self.__add_wp(addr, kind, DBGWCR_LSC.S) + + if split[0] == "3": + addr = int(split[1], 16) + kind = int(split[2], 16) + return self.__add_wp(addr, kind, DBGWCR_LSC.L) + + if split[0] == "4": + addr = int(split[1], 16) + kind = int(split[2], 16) + return self.__add_wp(addr, kind, DBGWCR_LSC.S | DBGWCR_LSC.L) + + return b"" + + return b"" + + def __send(self, prefix, data): + with io.BytesIO(prefix) as buffer: + buffer.write(prefix) + + last = 0 + for index, byte in enumerate(data): + if not byte in b"#$}*": + continue + + buffer.write(data[last:index]) + buffer.write(b"}") + buffer.write(bytes([byte ^ 0x20])) + last = index + 1 + + buffer.write(data[last:]) + checksum = (sum(buffer.getvalue()) - sum(prefix)) % 256 + + buffer.write(b"#") + buffer.write(bytes(format(checksum, "02x"), "utf-8")) + + value = buffer.getvalue() + + if self.log: + self.log(f"send: {value}") + + self.__request.send(value) + + def __handle(self, request): + self.__request = request + input_buffer = b"" + + if not self.__hv.in_shell: + self.__hv.interrupt() + self.__wait_shell() + + self.__interrupt_selector.register(self.__request, selectors.EVENT_READ) + try: + while True: + recv = self.__request.recv(65536) + if not recv: + break + + input_buffer += recv + + while True: + dollar = input_buffer.find(b"$") + if dollar < 0: + input_buffer = b"" + break + + sharp = input_buffer.find(b"#", dollar) + if sharp < 0 or len(input_buffer) < sharp + 3: + input_buffer = input_buffer[dollar:] + break + + input_data = input_buffer[dollar + 1:sharp] + input_checksum = input_buffer[sharp + 1:sharp + 3] + input_buffer = input_buffer[sharp + 3:] + + try: + parsed_input_checksum = int(input_checksum.decode(), 16) + except ValueError as error: + print(error) + continue + + if (sum(input_data) % 256) != parsed_input_checksum: + self.__request.send(b"-") + continue + + self.__request.send(b"+") + + with io.BytesIO() as input_decoded: + input_index = 0 + input_last = 0 + while input_index < len(input_data): + if input_data[input_index] == b"*": + input_decoded.write(input_data[input_last:input_index]) + instance = input_decoded.getvalue()[-1] + input_index += 1 + input_run_len = input_data[input_index] - 29 + input_run = bytes([instance]) * input_run_len + input_decoded.write(input_run) + input_index += 1 + input_last = input_index + elif input_data[input_index] == b"}": + input_decoded.write(input_data[input_last:input_index]) + input_index += 1 + input_decoded.write(bytes([input_data[input_index] ^ 0x20])) + input_index += 1 + input_last = input_index + else: + input_index += 1 + + input_decoded.write(input_data[input_last:]) + + try: + output_decoded = self.__eval(input_decoded.getvalue()) + except Exception: + output_decoded = b"E." + bytes(traceback.format_exc(), "utf-8") + + self.__send(b"$", output_decoded) + finally: + self.__interrupt_selector.unregister(self.__request) + + def notify_in_shell(self): + os.eventfd_write(self.__interrupt_eventfd, 1) + + def activate(self): + try: + self.__server.server_bind() + except OSError as error: + if error.errno != errno.EADDRINUSE: + raise + + os.remove(self.__server.server_address) + self.__server.server_bind() + + self.__server.server_activate() + self.__thread.start() + + def shutdown(self): + os.close(self.__interrupt_eventfd) + self.__interrupt_selector.close() + self.__server.shutdown() + self.__server.server_close() + self.__thread.join() diff --git a/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-core.xml b/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-core.xml new file mode 100644 index 0000000..b6d344f --- /dev/null +++ b/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-core.xml @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc. + Contributed by ARM Ltd. + + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> + +<!DOCTYPE feature SYSTEM "gdb-target.dtd"> +<feature name="org.gnu.gdb.aarch64.core"> + <reg name="x0" bitsize="64"/> + <reg name="x1" bitsize="64"/> + <reg name="x2" bitsize="64"/> + <reg name="x3" bitsize="64"/> + <reg name="x4" bitsize="64"/> + <reg name="x5" bitsize="64"/> + <reg name="x6" bitsize="64"/> + <reg name="x7" bitsize="64"/> + <reg name="x8" bitsize="64"/> + <reg name="x9" bitsize="64"/> + <reg name="x10" bitsize="64"/> + <reg name="x11" bitsize="64"/> + <reg name="x12" bitsize="64"/> + <reg name="x13" bitsize="64"/> + <reg name="x14" bitsize="64"/> + <reg name="x15" bitsize="64"/> + <reg name="x16" bitsize="64"/> + <reg name="x17" bitsize="64"/> + <reg name="x18" bitsize="64"/> + <reg name="x19" bitsize="64"/> + <reg name="x20" bitsize="64"/> + <reg name="x21" bitsize="64"/> + <reg name="x22" bitsize="64"/> + <reg name="x23" bitsize="64"/> + <reg name="x24" bitsize="64"/> + <reg name="x25" bitsize="64"/> + <reg name="x26" bitsize="64"/> + <reg name="x27" bitsize="64"/> + <reg name="x28" bitsize="64"/> + <reg name="x29" bitsize="64"/> + <reg name="x30" bitsize="64"/> + <reg name="sp" bitsize="64" type="data_ptr"/> + + <reg name="pc" bitsize="64" type="code_ptr"/> + + <flags id="cpsr_flags" size="4"> + <!-- Stack Pointer. --> + <field name="SP" start="0" end="0"/> + + <!-- Exception Level. --> + <field name="EL" start="2" end="3"/> + <!-- Execution state. --> + <field name="nRW" start="4" end="4"/> + + <!-- FIQ interrupt mask. --> + <field name="F" start="6" end="6"/> + <!-- IRQ interrupt mask. --> + <field name="I" start="7" end="7"/> + <!-- SError interrupt mask. --> + <field name="A" start="8" end="8"/> + <!-- Debug exception mask. --> + <field name="D" start="9" end="9"/> + + <!-- ARMv8.0-A: Speculative Store Bypass. --> + <field name="SSBS" start="12" end="12"/> + + <!-- Illegal Execution state. --> + <field name="IL" start="20" end="20"/> + <!-- Software Step. --> + <field name="SS" start="21" end="21"/> + <!-- ARMv8.1-A: Privileged Access Never. --> + <field name="PAN" start="22" end="22"/> + <!-- ARMv8.2-A: User Access Override. --> + <field name="UAO" start="23" end="23"/> + <!-- ARMv8.4-A: Data Independent Timing. --> + <field name="DIT" start="24" end="24"/> + <!-- ARMv8.5-A: Tag Check Override. --> + <field name="TCO" start="25" end="25"/> + + <!-- Overflow Condition flag. --> + <field name="V" start="28" end="28"/> + <!-- Carry Condition flag. --> + <field name="C" start="29" end="29"/> + <!-- Zero Condition flag. --> + <field name="Z" start="30" end="30"/> + <!-- Negative Condition flag. --> + <field name="N" start="31" end="31"/> + </flags> + <reg name="cpsr" bitsize="32" type="cpsr_flags"/> + +</feature> diff --git a/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-fpu.xml b/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-fpu.xml new file mode 100644 index 0000000..4db5c50 --- /dev/null +++ b/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-fpu.xml @@ -0,0 +1,160 @@ +<?xml version="1.0"?> +<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc. + Contributed by ARM Ltd. + + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. --> + +<!DOCTYPE feature SYSTEM "gdb-target.dtd"> +<feature name="org.gnu.gdb.aarch64.fpu"> + <vector id="v2d" type="ieee_double" count="2"/> + <vector id="v2u" type="uint64" count="2"/> + <vector id="v2i" type="int64" count="2"/> + <vector id="v4f" type="ieee_single" count="4"/> + <vector id="v4u" type="uint32" count="4"/> + <vector id="v4i" type="int32" count="4"/> + <vector id="v8f" type="ieee_half" count="8"/> + <vector id="v8u" type="uint16" count="8"/> + <vector id="v8i" type="int16" count="8"/> + <vector id="v8bf16" type="bfloat16" count="8"/> + <vector id="v16u" type="uint8" count="16"/> + <vector id="v16i" type="int8" count="16"/> + <vector id="v1u" type="uint128" count="1"/> + <vector id="v1i" type="int128" count="1"/> + <union id="vnd"> + <field name="f" type="v2d"/> + <field name="u" type="v2u"/> + <field name="s" type="v2i"/> + </union> + <union id="vns"> + <field name="f" type="v4f"/> + <field name="u" type="v4u"/> + <field name="s" type="v4i"/> + </union> + <union id="vnh"> + <field name="bf" type="v8bf16"/> + <field name="f" type="v8f"/> + <field name="u" type="v8u"/> + <field name="s" type="v8i"/> + </union> + <union id="vnb"> + <field name="u" type="v16u"/> + <field name="s" type="v16i"/> + </union> + <union id="vnq"> + <field name="u" type="v1u"/> + <field name="s" type="v1i"/> + </union> + <union id="aarch64v"> + <field name="d" type="vnd"/> + <field name="s" type="vns"/> + <field name="h" type="vnh"/> + <field name="b" type="vnb"/> + <field name="q" type="vnq"/> + </union> + <reg name="v0" bitsize="128" type="aarch64v" regnum="34"/> + <reg name="v1" bitsize="128" type="aarch64v" /> + <reg name="v2" bitsize="128" type="aarch64v" /> + <reg name="v3" bitsize="128" type="aarch64v" /> + <reg name="v4" bitsize="128" type="aarch64v" /> + <reg name="v5" bitsize="128" type="aarch64v" /> + <reg name="v6" bitsize="128" type="aarch64v" /> + <reg name="v7" bitsize="128" type="aarch64v" /> + <reg name="v8" bitsize="128" type="aarch64v" /> + <reg name="v9" bitsize="128" type="aarch64v" /> + <reg name="v10" bitsize="128" type="aarch64v"/> + <reg name="v11" bitsize="128" type="aarch64v"/> + <reg name="v12" bitsize="128" type="aarch64v"/> + <reg name="v13" bitsize="128" type="aarch64v"/> + <reg name="v14" bitsize="128" type="aarch64v"/> + <reg name="v15" bitsize="128" type="aarch64v"/> + <reg name="v16" bitsize="128" type="aarch64v"/> + <reg name="v17" bitsize="128" type="aarch64v"/> + <reg name="v18" bitsize="128" type="aarch64v"/> + <reg name="v19" bitsize="128" type="aarch64v"/> + <reg name="v20" bitsize="128" type="aarch64v"/> + <reg name="v21" bitsize="128" type="aarch64v"/> + <reg name="v22" bitsize="128" type="aarch64v"/> + <reg name="v23" bitsize="128" type="aarch64v"/> + <reg name="v24" bitsize="128" type="aarch64v"/> + <reg name="v25" bitsize="128" type="aarch64v"/> + <reg name="v26" bitsize="128" type="aarch64v"/> + <reg name="v27" bitsize="128" type="aarch64v"/> + <reg name="v28" bitsize="128" type="aarch64v"/> + <reg name="v29" bitsize="128" type="aarch64v"/> + <reg name="v30" bitsize="128" type="aarch64v"/> + <reg name="v31" bitsize="128" type="aarch64v"/> + + <flags id="fpsr_flags" size="4"> + <!-- Invalid Operation cumulative floating-point exception bit. --> + <field name="IOC" start="0" end="0"/> + <!-- Divide by Zero cumulative floating-point exception bit. --> + <field name="DZC" start="1" end="1"/> + <!-- Overflow cumulative floating-point exception bit. --> + <field name="OFC" start="2" end="2"/> + <!-- Underflow cumulative floating-point exception bit. --> + <field name="UFC" start="3" end="3"/> + <!-- Inexact cumulative floating-point exception bit.. --> + <field name="IXC" start="4" end="4"/> + <!-- Input Denormal cumulative floating-point exception bit. --> + <field name="IDC" start="7" end="7"/> + <!-- Cumulative saturation bit, Advanced SIMD only. --> + <field name="QC" start="27" end="27"/> + <!-- When AArch32 is supported at any Exception level and AArch32 + floating-point is implemented: Overflow condition flag for AArch32 + floating-point comparison operations. --> + <field name="V" start="28" end="28"/> + <!-- When AArch32 is supported at any Exception level and AArch32 + floating-point is implemented: + Carry condition flag for AArch32 floating-point comparison operations. + --> + <field name="C" start="29" end="29"/> + <!-- When AArch32 is supported at any Exception level and AArch32 + floating-point is implemented: + Zero condition flag for AArch32 floating-point comparison operations. + --> + <field name="Z" start="30" end="30"/> + <!-- When AArch32 is supported at any Exception level and AArch32 + floating-point is implemented: + Negative condition flag for AArch32 floating-point comparison + operations. --> + <field name="N" start="31" end="31"/> + </flags> + <reg name="fpsr" bitsize="32" type="fpsr_flags"/> + + <flags id="fpcr_flags" size="4"> + <!-- Flush Inputs to Zero (part of Armv8.7). --> + <field name="FIZ" start="0" end="0"/> + <!-- Alternate Handling (part of Armv8.7). --> + <field name="AH" start="1" end="1"/> + <!-- Controls how the output elements other than the lowest element of the + vector are determined for Advanced SIMD scalar instructions (part of + Armv8.7). --> + <field name="NEP" start="2" end="2"/> + <!-- Invalid Operation floating-point exception trap enable. --> + <field name="IOE" start="8" end="8"/> + <!-- Divide by Zero floating-point exception trap enable. --> + <field name="DZE" start="9" end="9"/> + <!-- Overflow floating-point exception trap enable. --> + <field name="OFE" start="10" end="10"/> + <!-- Underflow floating-point exception trap enable. --> + <field name="UFE" start="11" end="11"/> + <!-- Inexact floating-point exception trap enable. --> + <field name="IXE" start="12" end="12"/> + <!-- Input Denormal floating-point exception trap enable. --> + <field name="IDE" start="15" end="15"/> + <!-- Flush-to-zero mode control bit on half-precision data-processing + instructions. --> + <field name="FZ16" start="19" end="19"/> + <!-- Rounding Mode control field. --> + <field name="RMode" start="22" end="23"/> + <!-- Flush-to-zero mode control bit. --> + <field name="FZ" start="24" end="24"/> + <!-- Default NaN mode control bit. --> + <field name="DN" start="25" end="25"/> + <!-- Alternative half-precision control bit. --> + <field name="AHP" start="26" end="26"/> + </flags> + <reg name="fpcr" bitsize="32" type="fpcr_flags"/> +</feature> diff --git a/tools/proxyclient/m1n1/hv/gdbserver/features/target.xml b/tools/proxyclient/m1n1/hv/gdbserver/features/target.xml new file mode 100644 index 0000000..ca0454a --- /dev/null +++ b/tools/proxyclient/m1n1/hv/gdbserver/features/target.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<!-- SPDX-License-Identifier: MIT --> +<!DOCTYPE target SYSTEM "gdb-target.dtd"> +<target version="1.0"> + <architecture>aarch64</architecture> + <xi:include href="aarch64-core.xml" /> + <xi:include href="aarch64-fpu.xml" /> +</target> diff --git a/tools/proxyclient/m1n1/hv/types.py b/tools/proxyclient/m1n1/hv/types.py new file mode 100644 index 0000000..0c3142e --- /dev/null +++ b/tools/proxyclient/m1n1/hv/types.py @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: MIT +from construct import * +from enum import IntEnum + +from ..utils import * + +__all__ = [ + "MMIOTraceFlags", "EvtMMIOTrace", "EvtIRQTrace", "HV_EVENT", + "VMProxyHookData", "TraceMode", +] + +class MMIOTraceFlags(Register32): + ATTR = 31, 24 + CPU = 23, 16 + SH = 15, 14 + WIDTH = 4, 0 + WRITE = 5 + MULTI = 6 + +EvtMMIOTrace = Struct( + "flags" / RegAdapter(MMIOTraceFlags), + "reserved" / Int32ul, + "pc" / Hex(Int64ul), + "addr" / Hex(Int64ul), + "data" / Hex(Int64ul), +) + +EvtIRQTrace = Struct( + "flags" / Int32ul, + "type" / Hex(Int16ul), + "num" / Int16ul, +) + +class HV_EVENT(IntEnum): + HOOK_VM = 1 + VTIMER = 2 + USER_INTERRUPT = 3 + WDT_BARK = 4 + CPU_SWITCH = 5 + VIRTIO = 6 + +VMProxyHookData = Struct( + "flags" / RegAdapter(MMIOTraceFlags), + "id" / Int32ul, + "addr" / Hex(Int64ul), + "data" / Array(8, Hex(Int64ul)), +) + +class TraceMode(IntEnum): + ''' +Different types of Tracing ''' + + OFF = 0 + BYPASS = 1 + ASYNC = 2 + UNBUF = 3 + WSYNC = 4 + SYNC = 5 + HOOK = 6 + RESERVED = 7 diff --git a/tools/proxyclient/m1n1/hv/virtio.py b/tools/proxyclient/m1n1/hv/virtio.py new file mode 100644 index 0000000..790bb26 --- /dev/null +++ b/tools/proxyclient/m1n1/hv/virtio.py @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: MIT +from construct import Struct, Int8ul, Int16ul, Int32sl, Int32ul, Int64ul +from subprocess import Popen, PIPE +import pathlib +import struct +import os +import sys + +from ..utils import * + +VirtioConfig = Struct( + "irq" / Int32sl, + "devid" / Int32ul, + "feats" / Int64ul, + "num_qus" / Int32ul, + "data" / Int64ul, + "data_len" / Int64ul, + "verbose" / Int8ul, +) + +class VirtioDescFlags(Register16): + WRITE = 1 + NEXT = 0 + +VirtioDesc = Struct( + "addr" / Int64ul, + "len" / Int32ul, + "flags" / RegAdapter(VirtioDescFlags), + "next" / Int16ul, +) + +VirtioExcInfo = Struct( + "devbase" / Int64ul, + "qu" / Int16ul, + "idx" / Int16ul, + "pad" / Int32ul, + "descbase" / Int64ul, +) + +class VirtioDev: + def __init__(self): + self.base, self.hv = None, None # assigned by HV object + + def read_buf(self, desc): + return self.hv.iface.readmem(desc.addr, desc.len) + + def read_desc(self, ctx, idx): + off = VirtioDesc.sizeof() * idx + return self.hv.iface.readstruct(ctx.descbase + off, VirtioDesc) + + @property + def config_data(self): + return b"" + + @property + def devid(self): + return 0 + + @property + def num_qus(self): + return 1 + + @property + def feats(self): + return 0 + +class Virtio9PTransport(VirtioDev): + def __init__(self, tag="m1n1", root=None): + p_stdin, self.fin = os.pipe() + self.fout, p_stdout = os.pipe() + if root is None: + root = str(pathlib.Path(__file__).resolve().parents[3]) + if type(tag) is str: + self.tag = tag.encode("ascii") + else: + self.tag = tag + self.p = Popen([ + "u9fs", + "-a", "none", # no auth + "-n", # not a network conn + "-u", os.getlogin(), # single user + root, + ], stdin=p_stdin, stdout=p_stdout, stderr=sys.stderr) + + @property + def config_data(self): + return struct.pack("=H", len(self.tag)) + self.tag + + @property + def devid(self): + return 9 + + @property + def num_qus(self): + return 1 + + @property + def feats(self): + return 1 + + def call(self, req): + os.write(self.fin, req) + resp = os.read(self.fout, 4) + length = int.from_bytes(resp, byteorder="little") + resp += os.read(self.fout, length - 4) + return resp + + def handle_exc(self, ctx): + head = self.read_desc(ctx, ctx.idx) + assert not head.flags.WRITE + + req = bytearray() + + while not head.flags.WRITE: + req += self.read_buf(head) + + if not head.flags.NEXT: + break + head = self.read_desc(ctx, head.next) + + resp = self.call(bytes(req)) + resplen = len(resp) + + while len(resp): + self.hv.iface.writemem(head.addr, resp[:head.len]) + resp = resp[head.len:] + if not head.flags.NEXT: + break + head = self.read_desc(ctx, head.next) + + self.hv.p.virtio_put_buffer(ctx.devbase, ctx.qu, ctx.idx, resplen) + + return True diff --git a/tools/proxyclient/m1n1/hv/virtutils.py b/tools/proxyclient/m1n1/hv/virtutils.py new file mode 100644 index 0000000..934abc0 --- /dev/null +++ b/tools/proxyclient/m1n1/hv/virtutils.py @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: MIT +from m1n1.utils import align_up + +def collect_aic_irqs_in_use(adt): + used = set() + aic_phandle = getattr(adt["/arm-io/aic"], "AAPL,phandle") + for node in adt.walk_tree(): + if not hasattr(node, "interrupt_parent") or \ + node.interrupt_parent != aic_phandle: + continue + for no in node.interrupts: + used.add(no) + return used + +def usable_aic_irq_range(adt): + # These are too optimistic but since we allocate + # from the bottom of the range it doesn't matter much. + return { + "aic,1": range(0, 0x400), + "aic,2": range(0, 0x1000), + }.get(adt["/arm-io/aic"].compatible[0]) + +def alloc_aic_irq(adt): + used = collect_aic_irqs_in_use(adt) + for no in usable_aic_irq_range(adt): + if no not in used: + return no + return None + +def usable_mmio_range(adt): + arm_io_range = adt["arm-io"].ranges[0] + return range(arm_io_range.parent_addr, arm_io_range.parent_addr + arm_io_range.size) + +def alloc_mmio_base(adt, size, alignment=0x4000): + span = usable_mmio_range(adt) + la = adt.build_addr_lookup() + for zone, devs in la.populate(span): + if len(devs) != 0: + continue + base = align_up(zone.start, alignment) + if zone.stop > base + size: + return base + return None |
