m1n1/proxyclient/m1n1/loadobjs.py
Martin Povišer f353017ac6 m1n1.hw.loadobjs: Accept queer call methods
Let user specify a call method to use on symbols.

Signed-off-by: Martin Povišer <povik@protonmail.com>
2022-05-30 23:26:31 +09:00

178 lines
6.3 KiB
Python

# 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)}")