load_elf: copy linux's auxv, argv, and env layout
authorJacob Lifshay <programmerjake@gmail.com>
Thu, 7 Dec 2023 07:36:14 +0000 (23:36 -0800)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Wed, 13 Dec 2023 09:34:40 +0000 (09:34 +0000)
src/openpower/decoder/isa/caller.py
src/openpower/decoder/isa/mem.py
src/openpower/syscalls/ppc_flags.py

index 53427313c2b21247fe690d5e2b9eba92215e3723..8e476a289be52a6d43a317f3d2ce9944bdd51df8 100644 (file)
@@ -2237,6 +2237,8 @@ class ISACaller(ISACallerHelper, ISAFPHelpers, StepLoop):
         # 2. Call the HDL implementation which invokes trap.
         # 3. Reroute the guest system call to host system call.
         # 4. Force return from the interrupt as if we had guest OS.
+        # FIXME: enable PPC_FEATURE2_SCV in mem.py DEFAULT_AT_HWCAP2 when
+        # scv emulation works.
         if ((asmop in ("sc", "scv")) and
                 (self.syscall is not None) and
                 not syscall_emu_active):
index fc308cc8ab036538932f3cc26ac191add0875973..74a43ca212d93224da8b4c87706636c01ff9baad 100644 (file)
@@ -1105,8 +1105,57 @@ def raise_if_syscall_err(result):
 # TODO: change to much smaller size once GROWSDOWN is implemented
 DEFAULT_INIT_STACK_SZ = 4 << 20
 
-
-def load_elf(mem, elf_file, args=(), env=(), stack_size=DEFAULT_INIT_STACK_SZ):
+# TODO: power9 identifies PowerISA v3.0, figure out if it's best...
+DEFAULT_AT_PLATFORM = "power9"  # default value of AT_PLATFORM
+DEFAULT_AT_BASE_PLATFORM = "power9"  # default value of AT_BASE_PLATFORM
+# TODO: use correct cache block sizes
+DEFAULT_AT_DCACHEBSIZE = 128  # default value of AT_DCACHEBSIZE
+DEFAULT_AT_ICACHEBSIZE = 128  # default value of AT_ICACHEBSIZE
+DEFAULT_AT_UCACHEBSIZE = 0  # default value of AT_UCACHEBSIZE
+DEFAULT_AT_L1I_CACHESIZE = 0x8000  # default value of AT_L1I_CACHESIZE
+DEFAULT_AT_L1I_CACHEGEOMETRY = 0x80  # default value of AT_L1I_CACHEGEOMETRY
+DEFAULT_AT_L1D_CACHESIZE = 0x8000  # default value of AT_L1D_CACHESIZE
+DEFAULT_AT_L1D_CACHEGEOMETRY = 0x80  # default value of AT_L1D_CACHEGEOMETRY
+DEFAULT_AT_L2_CACHESIZE = 0  # default value of AT_L2_CACHESIZE
+DEFAULT_AT_L2_CACHEGEOMETRY = 0  # default value of AT_L2_CACHEGEOMETRY
+DEFAULT_AT_L3_CACHESIZE = 0  # default value of AT_L3_CACHESIZE
+DEFAULT_AT_L3_CACHEGEOMETRY = 0  # default value of AT_L3_CACHEGEOMETRY
+
+# default value of AT_HWCAP
+DEFAULT_AT_HWCAP = (ppc_flags.PPC_FEATURE_32 | ppc_flags.PPC_FEATURE_64 |
+    ppc_flags.PPC_FEATURE_HAS_FPU | ppc_flags.PPC_FEATURE_HAS_MMU |
+    ppc_flags.PPC_FEATURE_ARCH_2_06 | ppc_flags.PPC_FEATURE_TRUE_LE)
+
+# default value of AT_HWCAP2
+DEFAULT_AT_HWCAP2 = (ppc_flags.PPC_FEATURE2_ARCH_2_07 |
+    ppc_flags.PPC_FEATURE2_HAS_ISEL | ppc_flags.PPC_FEATURE2_HAS_TAR |
+    ppc_flags.PPC_FEATURE2_ARCH_3_00 | ppc_flags.PPC_FEATURE2_DARN)
+
+# FIXME: enable when scv emulation works
+# DEFAULT_AT_HWCAP2 |= ppc_flags.PPC_FEATURE2_SCV
+
+# FIXME: enable if/when v3.1 PO1 prefixed insns work
+# DEFAULT_AT_HWCAP2 |= ppc_flags.PPC_FEATURE2_ARCH_3_1
+
+CLOCKS_PER_SEC = 100
+
+
+def load_elf(
+    mem, elf_file, args=(), env=(), stack_size=DEFAULT_INIT_STACK_SZ,
+    platform=DEFAULT_AT_PLATFORM, base_platform=DEFAULT_AT_BASE_PLATFORM,
+    rand_byte16=None, dcachebsize=DEFAULT_AT_DCACHEBSIZE,
+    icachebsize=DEFAULT_AT_ICACHEBSIZE, ucachebsize=DEFAULT_AT_UCACHEBSIZE,
+    l1i_cachesize=DEFAULT_AT_L1I_CACHESIZE,
+    l1i_cachegeometry=DEFAULT_AT_L1I_CACHEGEOMETRY,
+    l1d_cachesize=DEFAULT_AT_L1D_CACHESIZE,
+    l1d_cachegeometry=DEFAULT_AT_L1D_CACHEGEOMETRY,
+    l2_cachesize=DEFAULT_AT_L2_CACHESIZE,
+    l2_cachegeometry=DEFAULT_AT_L2_CACHEGEOMETRY,
+    l3_cachesize=DEFAULT_AT_L3_CACHESIZE,
+    l3_cachegeometry=DEFAULT_AT_L3_CACHEGEOMETRY,
+    hwcap=DEFAULT_AT_HWCAP, hwcap2=DEFAULT_AT_HWCAP2,
+    was_setuid_like=False, execfd=None,
+):
     if not isinstance(mem, MemMMap):
         raise TypeError("MemMMap required to load ELFs")
     if not isinstance(elf_file, ELFFile):
@@ -1115,7 +1164,9 @@ def load_elf(mem, elf_file, args=(), env=(), stack_size=DEFAULT_INIT_STACK_SZ):
         raise NotImplementedError("dynamic binaries aren't implemented")
     fd = elf_file.stream.fileno()
     heap_start = -1
+    phnum = 0
     for segment in elf_file.iter_segments():
+        phnum += 1
         if segment.header['p_type'] in ('PT_DYNAMIC', 'PT_INTERP'):
             raise NotImplementedError("dynamic binaries aren't implemented")
         elif segment.header['p_type'] == 'PT_LOAD':
@@ -1182,6 +1233,30 @@ def load_elf(mem, elf_file, args=(), env=(), stack_size=DEFAULT_INIT_STACK_SZ):
         stack_low, stack_size, prot, flags, fd=-1, offset=0, is_mmap2=False)
     raise_if_syscall_err(result)
 
+    def copy_to_stack(buf):
+        nonlocal stack_top
+        l = len(buf)
+        stack_top -= l
+        addr = stack_top
+        if l > 0:
+            mem.get_ctypes(addr, l, True)[:] = buf
+        return addr
+
+    def copy_cstr_to_stack(s):
+        if s is None:
+            return None
+        if isinstance(s, str):
+            s = s.encode()
+        else:
+            s = bytes(s)
+        if b"\0" in s:
+            raise ValueError("c string can't contain NUL")
+        s += b"\0"
+        return copy_to_stack(s)
+
+    stack_top -= 7  # weird, but matches linux
+    program_name = copy_cstr_to_stack(os.readlink("/proc/self/fd/%i" % fd))
+
     env_bytes = bytearray()
     env_offsets = []
     for env_entry in env:
@@ -1204,7 +1279,7 @@ def load_elf(mem, elf_file, args=(), env=(), stack_size=DEFAULT_INIT_STACK_SZ):
 
     args = tuple(args)
     if len(args) == 0:
-        args = ("program",)  # glibc depends on argc != 0
+        args = ("",)  # glibc depends on argc != 0
     args_bytes = bytearray()
     arg_offsets = []
     for arg in args:
@@ -1222,46 +1297,88 @@ def load_elf(mem, elf_file, args=(), env=(), stack_size=DEFAULT_INIT_STACK_SZ):
     args_bytes_addr = stack_top
     mem.get_ctypes(args_bytes_addr, len(args_bytes), True)[:] = args_bytes
 
-    stack_top -= stack_top % 8  # align stack top for auxv
-
-    auxv_t = struct.Struct("<QQ")
-    auxv = 0
-    def write_auxv_entry(a_type, a_un):
-        nonlocal stack_top, auxv
-        stack_top -= auxv_t.size
-        auxv = stack_top
-        buf = mem.get_ctypes(stack_top, auxv_t.size, True)
-        auxv_t.pack_into(buf, 0, a_type, a_un)
+    stack_top -= stack_top % 16  # align stack top for auxv
 
-    # TODO: put more in auxv
+    platform = copy_cstr_to_stack(platform)
+    base_platform = copy_cstr_to_stack(base_platform)
 
-    write_auxv_entry(ppc_flags.AT_NULL, 0)  # final auxv entry
+    if rand_byte16 is not None:
+        rand_byte16 = bytes(rand_byte16)
+        if len(rand_byte16) != 16:
+            raise ValueError("rand_byte16 has wrong length, must be 16 bytes")
+        rand_byte16 = copy_to_stack(rand_byte16)
 
-    # final envp entry
-    stack_top -= 8
-    mem.get_ctypes(stack_top, 8, True)[:] = bytes(8)
+    auxv_entries = []
 
-    for env_offset in reversed(env_offsets):
-        stack_top -= 8
-        env_addr = env_offset + env_bytes_addr
-        mem.get_ctypes(stack_top, 8, True)[:] = env_addr.to_bytes(8, 'little')
-
-    envp = stack_top
-
-    # final argv entry
-    stack_top -= 8
-    mem.get_ctypes(stack_top, 8, True)[:] = bytes(8)
-
-    for arg_offset in reversed(arg_offsets):
-        stack_top -= 8
-        arg_addr = arg_offset + args_bytes_addr
-        mem.get_ctypes(stack_top, 8, True)[:] = arg_addr.to_bytes(8, 'little')
+    def auxv_entry(a_type, a_un):
+        if a_type is None or a_un is None:
+            return
+        auxv_entries.append((a_type, a_un))
+
+    auxv_entry(ppc_flags.AT_IGNOREPPC, ppc_flags.AT_IGNOREPPC)
+    auxv_entry(ppc_flags.AT_IGNOREPPC, ppc_flags.AT_IGNOREPPC)
+    auxv_entry(ppc_flags.AT_DCACHEBSIZE, dcachebsize)
+    auxv_entry(ppc_flags.AT_ICACHEBSIZE, icachebsize)
+    auxv_entry(ppc_flags.AT_UCACHEBSIZE, ucachebsize)
+    vdso_addr = None  # TODO: add vdso
+    auxv_entry(ppc_flags.AT_SYSINFO_EHDR, vdso_addr)
+    auxv_entry(ppc_flags.AT_L1I_CACHESIZE, l1i_cachesize)
+    auxv_entry(ppc_flags.AT_L1I_CACHEGEOMETRY, l1i_cachegeometry)
+    auxv_entry(ppc_flags.AT_L1D_CACHESIZE, l1d_cachesize)
+    auxv_entry(ppc_flags.AT_L1D_CACHEGEOMETRY, l1d_cachegeometry)
+    auxv_entry(ppc_flags.AT_L2_CACHESIZE, l2_cachesize)
+    auxv_entry(ppc_flags.AT_L2_CACHEGEOMETRY, l2_cachegeometry)
+    auxv_entry(ppc_flags.AT_L3_CACHESIZE, l3_cachesize)
+    auxv_entry(ppc_flags.AT_L3_CACHEGEOMETRY, l3_cachegeometry)
+
+    # TODO: latest linux kernel has AT_MINSIGSTKSZ,
+    # ignoring for now since it's not included in Debian 10's kernel.
+
+    auxv_entry(ppc_flags.AT_HWCAP, hwcap)
+    auxv_entry(ppc_flags.AT_PAGESZ, MMAP_PAGE_SIZE)
+    auxv_entry(ppc_flags.AT_CLKTCK, CLOCKS_PER_SEC)
+    auxv_entry(ppc_flags.AT_PHDR, 0)  # FIXME: use correct value
+    auxv_entry(ppc_flags.AT_PHENT, 56)  # sizeof(elf_phdr)
+    auxv_entry(ppc_flags.AT_PHNUM, phnum)
+    auxv_entry(ppc_flags.AT_BASE, 0)  # FIXME: use interpreter address
+    flags = 0
+    # FIXME: if using emulated binfmt_misc bit-or in AT_FLAGS_PRESERVE_ARGV0
+    auxv_entry(ppc_flags.AT_FLAGS, flags)
+    auxv_entry(ppc_flags.AT_ENTRY, elf_file.header['e_entry'])
+    auxv_entry(ppc_flags.AT_UID, os.getuid())
+    auxv_entry(ppc_flags.AT_EUID, os.geteuid())
+    auxv_entry(ppc_flags.AT_GID, os.getgid())
+    auxv_entry(ppc_flags.AT_EGID, os.getegid())
+    auxv_entry(ppc_flags.AT_SECURE, bool(was_setuid_like))
+    auxv_entry(ppc_flags.AT_RANDOM, rand_byte16)
+    auxv_entry(ppc_flags.AT_HWCAP2, hwcap2)
+    auxv_entry(ppc_flags.AT_NULL, 0)  # final auxv entry
+
+    total_sz = ((len(arg_offsets) + 1) + (len(env_offsets) + 1) + 1) * 8
+    total_sz += 16 * len(auxv_entries)
+    stack_top -= total_sz
+    stack_top -= stack_top % 16  # align stack
+    write_addr = stack_top
+
+    def write8(v):
+        nonlocal write_addr
+        mem.get_ctypes(write_addr, 8, True)[:] = v.to_bytes(8, 'little')
+        write_addr += 8
 
-    argv = stack_top
     argc = len(arg_offsets)
-
-    stack_top -= 8
-    mem.get_ctypes(stack_top, 8, True)[:] = argc.to_bytes(8, 'little')
+    write8(argc)
+    argv = write_addr
+    for arg_offset in arg_offsets:
+        write8(arg_offset + args_bytes_addr)
+    write8(0)
+    envp = write_addr
+    for env_offset in env_offsets:
+        write8(env_offset + env_bytes_addr)
+    write8(0)
+    auxv = write_addr
+    for a_type, a_un in auxv_entries:
+        write8(a_type)
+        write8(a_un)
 
     gprs = {}
 
index 7f2fe507efc5983f48d92759ba4f115525ae6af5..1abf15c8c3b0a65152e56d49ed9033700263f243 100644 (file)
@@ -22,6 +22,7 @@ def parse_defines(flags, compiler):
 #include <fcntl.h>
 #include <linux/utsname.h>
 #include <linux/auxvec.h>
+#include <sys/auxv.h>
 """
     if isinstance(compiler, str):
         compiler = [compiler]