diff options
Diffstat (limited to 'tools/proxyclient/m1n1/loadobjs.py')
| -rw-r--r-- | tools/proxyclient/m1n1/loadobjs.py | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/tools/proxyclient/m1n1/loadobjs.py b/tools/proxyclient/m1n1/loadobjs.py new file mode 100644 index 0000000..ef567b3 --- /dev/null +++ b/tools/proxyclient/m1n1/loadobjs.py @@ -0,0 +1,178 @@ +# SPDX-License-Identifier: MIT +from contextlib import contextmanager, ExitStack +import sys, pathlib, os +import subprocess +import tempfile +import bisect + +from .asm import NM, LD, OBJCOPY, ARMAsm + +__all__ = ["LinkedProgram"] + + +def tool_output_lines(progname, *args): + with subprocess.Popen([progname.replace("%ARCH", ARMAsm.ARCH)] + list(args), + stdout=subprocess.PIPE) as proc: + for line in proc.stdout: + yield line.decode("ascii") + proc.wait() + if proc.returncode: + raise Exception(f"{progname} (args: {args}) exited with status {proc.returncode}") + +def run_tool(progname, *args, silent=False): + subprocess.check_call([progname.replace("%ARCH", ARMAsm.ARCH)] + list(args), + stdout=subprocess.DEVNULL if silent else None) + + +class LinkedProgram: + SOURCE_ROOT = str(pathlib.Path(__file__).resolve().parents[2]) + + def __init__(self, u, base_object=None): + self.u = u + self.symbols = [] + self.symaddrs = dict() + self.base_object = base_object + self._alloced_bases = [] + self._attrs_to_clear = [] + self._load_base_symbols() + + def _load_base_symbols(self): + if self.base_object is None: + suffix = "-raw" if not self.m1n1_is_macho() else "" + self.base_object = f"build/m1n1{suffix}.elf" + + addrs = self._load_elf_symbols(self.base_object, self.u.proxy.get_base()) + + # sanity check: compare the .rela table between ELF and m1n1 image on target + rela_base = addrs["_rela_start"] + rela_length = addrs["_rela_end"] - rela_base + rela_target = self.u.iface.readmem(rela_base, rela_length) + + tmp = os.path.join(tempfile.mkdtemp(), "bin") + path = os.path.join(self.SOURCE_ROOT, self.base_object) + run_tool(OBJCOPY, "-O", "binary", path, tmp, "--only-section=.rela.dyn") + rela_objfile = open(tmp, "rb").read() + + if rela_objfile[:len(rela_target)] != rela_target: + raise Exception(f"Mismatch between {self.base_object} and image on target") + + def m1n1_is_macho(self): + p = self.u.proxy + return p.read32(p.get_base()) == 0xfeedfacf + + def _load_elf_symbols(self, relpath, offset=0, + objname=None, ignore=""): + path = pathlib.Path(self.SOURCE_ROOT, relpath) + symaddrs = dict() + + for line in tool_output_lines(NM, "-g", path): + addr_str, t, name = line.split() + addr = int(addr_str, 16) + offset + if t in ignore: + continue + self.symbols.append((addr, name, objname)) + symaddrs[name] = addr + if t in "T" and not hasattr(self, name): + setattr(self, name, self._wrap_call_to(addr)) + if relpath != self.base_object: + self._attrs_to_clear.append(name) + self.symbols.sort() + return symaddrs + + def load_obj(self, objfile, base=None): + ALLOC_SIZE = 16*4096 + + if base is None: + base = self.u.heap.memalign(0x4000, ALLOC_SIZE) + self._alloced_bases.append(base) + + objfile = os.path.join(self.SOURCE_ROOT, objfile) + tmp = tempfile.mkdtemp() + os.sep + elffile = tmp + "elf" + ld_script = tmp + "ld" + binfile = tmp + "bin" + with open(ld_script, "w") as f: + f.write("SECTIONS {\n") + f.write(f". = 0x{base:x};\n") + f.write(".text : { *(.text .text.*) }\n") + f.write(".data : { *(.got .data .data.* .rodata .rodata.* .bss .bss.*) }\n") + f.write("}\n") + for sym in self.symbols: + f.write(f"{sym[1]} = 0x{sym[0]:x};\n") + run_tool(LD, "-EL", "-maarch64elf", "-T", ld_script, "-o", elffile, objfile) + run_tool(OBJCOPY, "-O", "binary", elffile, binfile) + #run_tool("objdump", "-d", elffile) + self._load_elf_symbols(elffile, ignore="A") + with open(binfile, "rb") as f: + buf = f.read() + assert len(buf) <= ALLOC_SIZE + self.u.iface.writemem(base, buf) + self.u.proxy.dc_cvau(base, len(buf)) + self.u.proxy.ic_ivau(base, len(buf)) + + def clear_objs(self): + for name in self._attrs_to_clear: + delattr(self, name) + self._attrs_to_clear = [] + + for base in self._alloced_bases: + self.u.free(base) + self._alloced_bases = [] + + self.symbols = [(a, b, objname) for (a, b, objname) \ + in self.symbols if objname == self.base_object] + + @contextmanager + def _copy_args_to_target(self, args): + heap = self.u.heap + with ExitStack() as stack: + args_copied = [] + for arg in args: + if type(arg) is str: + arg = arg.encode("ascii") + # fallthrough + if type(arg) is bytes: + p = stack.enter_context(heap.guarded_malloc(len(arg) + 1)) + self.u.iface.writemem(p, arg + b"\0") + args_copied.append(p) + elif type(arg) is int: + args_copied.append(arg) + else: + raise NotImplementedError(type(arg)) + yield args_copied + + def _wrap_call_to(self, addr): + def call_symbol(*args, call=self.u.proxy.call): + with self._copy_args_to_target(args) as args_copied: + return call(addr, *args_copied) + return call_symbol + + def lookup(self, addr): + idx = bisect.bisect_left(self.symbols, (addr + 1, "", "")) - 1 + if idx < 0 or idx >= len(self.symbols): + return None, None + return self.symbols[idx] + + def load_inline_c(self, source): + tmp = tempfile.mkdtemp() + cfile = tmp + ".c" + objfile = tmp + ".o" + with open(cfile, "w") as f: + f.write(source) + run_tool("make", "-C", self.SOURCE_ROOT, "invoke_cc", + f"OBJFILE={objfile}", f"CFILE={cfile}", silent=True) + self.load_obj(objfile) + + +if __name__ == "__main__": + from m1n1.setup import * + lp = LinkedProgram(u) + lp.debug_printf("hello from the other side! (%d)\n", 42) + lp.load_inline_c(''' + #include "utils.h" + int add(int a, int b) { + debug_printf("adding %d and %d\\n", a, b); + return a + b; + } + ''') + print(f"1 + 2 = {lp.add(1, 2)}") |
