summaryrefslogtreecommitdiff
path: root/tools/proxyclient/m1n1/loadobjs.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/proxyclient/m1n1/loadobjs.py')
-rw-r--r--tools/proxyclient/m1n1/loadobjs.py178
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)}")