From 3d172c99fe1c782d2bcfe5da1d470c8fe50c3aed Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 23 Jul 2023 17:07:38 -0700 Subject: [PATCH] Mint APE Loader v1.3 This version has better error messages and safety checks. It supports loading static position-independent executables. It correctly handles more kinds of weird ELF program header layouts. A force flag has been added to avoid system execve(). Finally the longstanding misalignment with our ELF PT_NOTE section has been addressed. --- Makefile | 5 + ape/ape.S | 14 +- ape/ape.lds | 6 - ape/ape.mk | 36 +-- ape/apeuninstall.sh | 1 + ape/launch.S | 53 ++++ ape/loader-elf.S | 199 -------------- ape/loader-macho.S | 83 +++--- ape/loader.c | 456 ++++++++++++++++++++------------- ape/loader.lds | 84 ++++-- ape/start.S | 51 ++++ ape/systemcall.S | 38 +++ build/bootstrap/ape.elf | Bin 7781 -> 9460 bytes build/bootstrap/ape.macho | Bin 7345 -> 9460 bytes build/bootstrap/objbincopy.com | Bin 0 -> 49456 bytes build/definitions.mk | 3 +- libc/calls/execve-sysv.c | 4 +- tool/build/assimilate.c | 6 +- tool/build/objbincopy.c | 432 +++++++++++++++++++++++++++++++ 19 files changed, 1001 insertions(+), 470 deletions(-) create mode 100644 ape/launch.S delete mode 100644 ape/loader-elf.S create mode 100644 ape/start.S create mode 100644 ape/systemcall.S create mode 100755 build/bootstrap/objbincopy.com create mode 100644 tool/build/objbincopy.c diff --git a/Makefile b/Makefile index 1580df774..da7cb8eae 100644 --- a/Makefile +++ b/Makefile @@ -86,9 +86,14 @@ o/$(MODE): \ o/$(MODE)/examples \ o/$(MODE)/third_party +ifneq ($(LANDLOCKMAKE_VERSION),) +ifeq ($(wildcard /usr/bin/ape),) +$(error please run ape/apeinstall.sh if you intend to use landlock make) +endif ifeq ($(USE_SYSTEM_TOOLCHAIN),) .STRICT = 1 endif +endif .PLEDGE = stdio rpath wpath cpath fattr proc .UNVEIL = \ diff --git a/ape/ape.S b/ape/ape.S index 8fb9a9198..62c82d01a 100644 --- a/ape/ape.S +++ b/ape/ape.S @@ -610,7 +610,7 @@ apesh: .ascii "\n@\n#'\"\n" // sixth edition shebang // extract the loader into a temp folder, and use it to // load the APE without modifying it. .ascii "[ x\"$1\" != x--assimilate ] && {\n" - .ascii "t=\"${TMPDIR:-${HOME:-.}}/.ape-1.1\"\n" + .ascii "t=\"${TMPDIR:-${HOME:-.}}/.ape-1.3\"\n" .ascii "[ -x \"$t\" ] || {\n" .ascii "mkdir -p \"${t%/*}\" &&\n" .ascii "dd if=\"$o\" of=\"$t.$$\" skip=" @@ -622,8 +622,8 @@ apesh: .ascii "\n@\n#'\"\n" // sixth edition shebang .ascii "[ -d /Applications ] && " .ascii "dd if=\"$t.$$\"" .ascii " of=\"$t.$$\"" - .ascii " skip=6" - .ascii " count=6" + .ascii " skip=5" + .ascii " count=8" .ascii " bs=64" .ascii " conv=notrunc" .ascii " 2>/dev/null\n" @@ -801,17 +801,18 @@ ape_loader_end: .long PT_NOTE .long PF_R .stub ape_note_offset,quad - .stub ape_note_vaddr,quad - .stub ape_note_paddr,quad + .quad 0 + .quad 0 .stub ape_note_filesz,quad .stub ape_note_memsz,quad - .stub ape_note_align,quad + .quad 4 #endif .previous #endif /* SupportsSystemv() || SupportsMetal() */ #if SupportsOpenbsd() .section .note.openbsd.ident,"a",@progbits + .balign 4 openbsd.ident: .long 2f-1f .long 4f-3f @@ -826,6 +827,7 @@ openbsd.ident: #if SupportsNetbsd() .section .note.netbsd.ident,"a",@progbits + .balign 4 netbsd.ident: .long 2f-1f .long 4f-3f diff --git a/ape/ape.lds b/ape/ape.lds index 405de1ee1..cb0dfbec2 100644 --- a/ape/ape.lds +++ b/ape/ape.lds @@ -581,11 +581,8 @@ ape_stack_memsz2 = ape_stack_memsz * 2; ape_stack_align = 16; ape_note_offset = ape_cod_offset + (ape_note - ape_cod_vaddr); -ape_note_vaddr = ape_note; -ape_note_paddr = ape_cod_paddr + ape_note_offset; ape_note_filesz = ape_note_end - ape_note; ape_note_memsz = ape_note_filesz; -ape_note_align = __SIZEOF_POINTER__; ape_text_offset = ape_cod_offset + LOADADDR(.text) - ape_cod_paddr; ape_text_paddr = LOADADDR(.text); @@ -665,13 +662,10 @@ CHURN(ape_elf_shoff); CHURN(ape_elf_shstrndx); CHURN(ape_macho_end); CHURN(ape_note); -CHURN(ape_note_align); CHURN(ape_note_end); CHURN(ape_note_filesz); CHURN(ape_note_memsz); CHURN(ape_note_offset); -CHURN(ape_note_paddr); -CHURN(ape_note_vaddr); CHURN(ape_ram_align); CHURN(ape_ram_filesz); CHURN(ape_ram_memsz); diff --git a/ape/ape.mk b/ape/ape.mk index 40739d700..6432fcf6f 100644 --- a/ape/ape.mk +++ b/ape/ape.mk @@ -181,41 +181,41 @@ o/$(MODE)/ape/ape-copy-self.o: \ -DAPE_NO_MODIFY_SELF $< o/$(MODE)/ape/loader.o: ape/loader.c - @$(COMPILE) -AOBJECTIFY.c $(CC) -DSUPPORT_VECTOR=0b01111001 -g $(APE_LOADER_FLAGS) + @$(COMPILE) -AOBJECTIFY.c $(CC) -DSUPPORT_VECTOR=121 -g $(APE_LOADER_FLAGS) o/$(MODE)/ape/loader-gcc.asm: ape/loader.c - @$(COMPILE) -AOBJECTIFY.c $(CC) -DSUPPORT_VECTOR=0b01111001 -S -g0 $(APE_LOADER_FLAGS) + @$(COMPILE) -AOBJECTIFY.c $(CC) -DSUPPORT_VECTOR=121 -S -g0 $(APE_LOADER_FLAGS) o/$(MODE)/ape/loader-clang.asm: ape/loader.c - @$(COMPILE) -AOBJECTIFY.c $(CLANG) -DSUPPORT_VECTOR=0b01111001 -S -g0 $(APE_LOADER_FLAGS) + @$(COMPILE) -AOBJECTIFY.c $(CLANG) -DSUPPORT_VECTOR=121 -S -g0 $(APE_LOADER_FLAGS) o/$(MODE)/ape/loader-xnu.o: ape/loader.c - @$(COMPILE) -AOBJECTIFY.c $(CC) -DSUPPORT_VECTOR=0b00001000 -g $(APE_LOADER_FLAGS) + @$(COMPILE) -AOBJECTIFY.c $(CC) -DSUPPORT_VECTOR=8 -g $(APE_LOADER_FLAGS) o/$(MODE)/ape/loader-xnu-gcc.asm: ape/loader.c - @$(COMPILE) -AOBJECTIFY.c $(CC) -DSUPPORT_VECTOR=0b00001000 -S -g0 $(APE_LOADER_FLAGS) + @$(COMPILE) -AOBJECTIFY.c $(CC) -DSUPPORT_VECTOR=8 -S -g0 $(APE_LOADER_FLAGS) o/$(MODE)/ape/loader-xnu-clang.asm: ape/loader.c - @$(COMPILE) -AOBJECTIFY.c $(CLANG) -DSUPPORT_VECTOR=0b00001000 -S -g0 $(APE_LOADER_FLAGS) + @$(COMPILE) -AOBJECTIFY.c $(CLANG) -DSUPPORT_VECTOR=8 -S -g0 $(APE_LOADER_FLAGS) o/$(MODE)/ape/ape.elf: o/$(MODE)/ape/ape.elf.dbg -o/$(MODE)/ape/ape.macho: o/$(MODE)/ape/ape.macho.dbg + @$(COMPILE) -AOBJBINCOPY -w build/bootstrap/objbincopy.com -f -o $@ $< + +o/$(MODE)/ape/ape.macho: o/$(MODE)/ape/ape.elf.dbg + @$(COMPILE) -AOBJBINCOPY -w build/bootstrap/objbincopy.com -fm -o $@ $< APE_LOADER_LDFLAGS = \ -static \ - -no-pie \ -nostdlib \ --no-dynamic-linker \ - -zcommon-page-size=0x1000 \ - -zmax-page-size=0x1000 + -z separate-code \ + -z common-page-size=0x1000 \ + -z max-page-size=0x10000 o/$(MODE)/ape/ape.elf.dbg: \ - o/$(MODE)/ape/loader.o \ - o/$(MODE)/ape/loader-elf.o \ - ape/loader.lds - @$(COMPILE) -ALINK.elf $(LINK) -T ape/loader.lds $(APE_LOADER_LDFLAGS) -o $@ o/$(MODE)/ape/loader-elf.o o/$(MODE)/ape/loader.o - -o/$(MODE)/ape/ape.macho.dbg: \ - o/$(MODE)/ape/loader-xnu.o \ o/$(MODE)/ape/loader-macho.o \ + o/$(MODE)/ape/start.o \ + o/$(MODE)/ape/loader.o \ + o/$(MODE)/ape/launch.o \ + o/$(MODE)/ape/systemcall.o \ ape/loader.lds - @$(COMPILE) -ALINK.elf $(LINK) -T ape/loader.lds $(APE_LOADER_LDFLAGS) -o $@ o/$(MODE)/ape/loader-macho.o o/$(MODE)/ape/loader-xnu.o + @$(COMPILE) -ALINK.elf $(LINK) $(APE_LOADER_LDFLAGS) -o $@ $(patsubst %.lds,-T %.lds,$^) .PHONY: o/$(MODE)/ape o/$(MODE)/ape: $(APE_CHECKS) \ diff --git a/ape/apeuninstall.sh b/ape/apeuninstall.sh index a1526ad89..49889aac6 100755 --- a/ape/apeuninstall.sh +++ b/ape/apeuninstall.sh @@ -35,6 +35,7 @@ rm -f o/tmp/ape /tmp/ape "${TMPDIR:-/tmp}/ape" # ad-hoc installations for x in .ape \ .ape-1.1 \ + .ape-1.3 \ .ape-blink-0.9.2 \ .ape-blink-1.0.0; do rm -f \ diff --git a/ape/launch.S b/ape/launch.S new file mode 100644 index 000000000..581f66996 --- /dev/null +++ b/ape/launch.S @@ -0,0 +1,53 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.internal.h" + +// Calls _start() function of loaded program. +// +// When the program entrypoint is called, all registers shall be +// cleared, with the exception of (1) %rdi will be equal to %rsp +// on FreeBSD and (2) %cl will contain the detected host OS code +// +// We clear all the general registers we can to have some wiggle +// room, to extend the behavior of this loader in the future. We +// don't need to clear the XMM registers because your APE loader +// should be compiled using gcc/clang's -mgeneral-regs-only flag +// +// @param rdi is passed through as-is +// @param rsi is address of entrypoint (becomes zero) +// @param rdx is stack pointer (becomes zero) +// @param rcx is passed through as-is +// @noreturn +Launch: xor %r8d,%r8d + xor %r9d,%r9d + xor %r10d,%r10d + xor %r11d,%r11d + xor %r12d,%r12d + xor %r13d,%r13d + xor %r14d,%r14d + xor %r15d,%r15d + mov %rdx,%rsp + xor %edx,%edx + push %rsi + xor %esi,%esi + xor %ebp,%ebp + xor %ebx,%ebx + xor %eax,%eax + ret + .endfn Launch,globl diff --git a/ape/loader-elf.S b/ape/loader-elf.S deleted file mode 100644 index 37a4086e3..000000000 --- a/ape/loader-elf.S +++ /dev/null @@ -1,199 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/elf/def.h" -#include "libc/sysv/consts/prot.h" -#include "libc/macho.internal.h" -#include "libc/dce.h" -#include "libc/macros.internal.h" - -// APE Loader Executable Structure -// Linux, FreeBSD, NetBSD, OpenBSD - - .section .head - - .balign 8 -ehdr: .ascii "\177ELF" - .byte ELFCLASS64 - .byte ELFDATA2LSB - .byte 1 - .byte ELFOSABI_FREEBSD - .quad 0 - .word ET_EXEC // e_type - .word EM_NEXGEN32E // e_machine - .long 1 // e_version - .quad _start // e_entry - .quad phdrs - ehdr // e_phoff - .quad 0 // e_shoff - .long 0 // e_flags - .word 64 // e_ehsize - .word 56 // e_phentsize - .word 4 // e_phnum - .word 0 // e_shentsize - .word 0 // e_shnum - .word 0 // e_shstrndx - .endobj ehdr,globl - - .balign 8 -phdrs: .long PT_LOAD // p_type - .long PF_R // p_flags - .quad 0 // p_offset - .quad ehdr // p_vaddr - .quad ehdr // p_paddr - .quad rosize // p_filesz - .quad rosize // p_memsz - .quad 4096 // p_align - - .long PT_LOAD // p_type - .long PF_X // p_flags - .quad rosize // p_offset - .quad text // p_vaddr - .quad text // p_paddr - .quad textsz // p_filesz - .quad textsz // p_memsz - .quad 4096 // p_align - - .long PT_GNU_STACK // p_type - .long PF_R|PF_W // p_flags - .quad 0 // p_offset - .quad 0 // p_vaddr - .quad 0 // p_paddr - .quad 0 // p_filesz - .quad 8*1024*1024 // p_memsz - .quad 16 // p_align - - .long PT_NOTE // p_type - .long PF_R // p_flags - .quad note - ehdr // p_offset - .quad note // p_vaddr - .quad note // p_paddr - .quad notesize // p_filesz - .quad notesize // p_memsz - .quad 8 // p_align - .endobj phdrs - -note: .long 2f-1f - .long 4f-3f - .long 1 -1: .asciz "OpenBSD" -2: .balign 4 -3: .long 0 -4: .long 2f-1f - .long 4f-3f - .long 1 -1: .asciz "NetBSD" -2: .balign 4 -3: .long 901000000 -4: .endobj note - notesize = . - note - - .balign 64,0 // for ape.S dd - .org 64*6 // for ape.S dd - -// APE Loader XNU Header -// -// This header is dd'd backwards by the APE shell script when -// running on Mac OS X. -// -// @see ape/ape.S -macho: .long 0xFEEDFACE+1 - .long MAC_CPU_NEXGEN32E - .long MAC_CPU_NEXGEN32E_ALL - .long MAC_EXECUTE - .long 4 // number of load commands - .long 50f-10f // size of all load commands - .long MAC_NOUNDEFS // flags - .long 0 // reserved -10: .long MAC_LC_SEGMENT_64 - .long 20f-10b // unmaps first page dir - .ascin "__PAGEZERO",16 // consistent with linux - .quad 0,0x200000,0,0 // which forbids mem <2m - .long 0,0,0,0 -20: .long MAC_LC_SEGMENT_64 - .long 30f-20b - .ascin "__TEXT",16 - .quad ehdr // vaddr - .quad filesz // memsz - .quad 0 // file offset - .quad filesz // file size - .long PROT_EXEC|PROT_READ|PROT_WRITE // maxprot - .long PROT_EXEC|PROT_READ // initprot - .long 0 // segment section count - .long 0 // flags -30: .long MAC_LC_UUID - .long 40f-30b - .quad 0x3fb29ee4ac6c87aa // uuid1 - .quad 0xdd2c9bb866d9eef9 // uuid2 -40: .long MAC_LC_UNIXTHREAD - .long 50f-40b // cmdsize - .long MAC_THREAD_NEXGEN32E // flavaflav - .long (420f-410f)/4 // count -410: .quad 0 // rax - .quad 0 // rbx - .quad 0 // rcx - .quad 0 // rdx - .quad 0 // rdi - .quad 0 // rsi - .quad 0 // rbp - .quad 0 // rsp - .quad 0 // r8 - .quad 0 // r9 - .quad 0 // r10 - .quad 0 // r11 - .quad 0 // r12 - .quad 0 // r13 - .quad 0 // r14 - .quad 0 // r15 - .quad _apple // rip - .quad 0 // rflags - .quad 0 // cs - .quad 0 // fs - .quad 0 // gs -420: -50: - .endobj macho - - .balign 64,0 // for ape.S dd - .org 64*12 // for ape.S dd - - .text -_apple: mov $_HOSTXNU,%dl // xnu's not unix! -_start: mov %rsp,%rsi // save real stack - sub $1024*1024,%rsp // room for allocs - jmp ApeLoader - .endfn _start,globl - .endfn _apple,globl - -// Invokes system call. -// -// This function has eight parameters. The first seven are for -// arguments passed along to the system call. The eight is for -// the magic number that indicates which system call is called -// -// The return value follows the Linux kernel convention, where -// errors are returned as `-errno`. BSD systems are normalized -// to follow this convention automatically. -SystemCall: - mov %rcx,%r10 - mov 16(%rsp),%eax - clc - syscall - jnc 1f - neg %rax -1: ret - .endfn SystemCall,globl diff --git a/ape/loader-macho.S b/ape/loader-macho.S index 9adc82d42..7d27739ed 100644 --- a/ape/loader-macho.S +++ b/ape/loader-macho.S @@ -21,18 +21,19 @@ #include "libc/dce.h" #include "libc/macros.internal.h" -// APE Loader Executable Structure for XNU - - .section .head +// Apple Mach-O Executable Headers +// Fixups are applied by objbincopy.com +// There must exist a MAC_LC_SEGMENT_64 for every PT_LOAD + .section .macho,"a",@progbits .balign 64 -macho: .long 0xFEEDFACE+1 + .long 0xFEEDFACE+1 .long MAC_CPU_NEXGEN32E .long MAC_CPU_NEXGEN32E_ALL .long MAC_EXECUTE - .long 4 // number of load commands - .long 50f-10f // size of all load commands - .long MAC_NOUNDEFS // flags + .long 5 // number of load commands + .long 60f-10f // size of all load commands + .long MAC_NOUNDEFS|MAC_SPLIT_SEGS // flags .long 0 // reserved 10: .long MAC_LC_SEGMENT_64 .long 20f-10b // unmaps first page dir @@ -42,23 +43,34 @@ macho: .long 0xFEEDFACE+1 20: .long MAC_LC_SEGMENT_64 .long 30f-20b .ascin "__TEXT",16 - .quad macho // vaddr - .quad filesz // memsz + .quad 0 // vaddr + .quad 0 // memsz .quad 0 // file offset - .quad filesz // file size - .long PROT_EXEC|PROT_READ|PROT_WRITE // maxprot - .long PROT_EXEC|PROT_READ // initprot + .quad 0 // file size + .long 0 // maxprot + .long 0 // initprot .long 0 // segment section count .long 0 // flags -30: .long MAC_LC_UUID +30: .long MAC_LC_SEGMENT_64 .long 40f-30b - .quad 0x3fb29ee4ac6c87aa // uuid1 - .quad 0xdd2c9bb866d9eef8 // uuid2 -40: .long MAC_LC_UNIXTHREAD - .long 50f-40b // cmdsize + .ascin "__RODATA",16 + .quad 0 // vaddr + .quad 0 // memsz + .quad 0 // file offset + .quad 0 // file size + .long 0 // maxprot + .long 0 // initprot + .long 0 // segment section count + .long 0 // flags +40: .long MAC_LC_UUID + .long 50f-40b + .quad 0x4527148ba7a513ef // uuid1 + .quad 0x56fa865940665e8f // uuid2 +50: .long MAC_LC_UNIXTHREAD + .long 60f-50b // cmdsize .long MAC_THREAD_NEXGEN32E // flavaflav - .long (420f-410f)/4 // count -410: .quad 0 // rax + .long (520f-510f)/4 // count +510: .quad 0 // rax .quad 0 // rbx .quad 0 // rcx .quad 0 // rdx @@ -74,37 +86,10 @@ macho: .long 0xFEEDFACE+1 .quad 0 // r13 .quad 0 // r14 .quad 0 // r15 - .quad _start // rip + .quad XnuEntrypoint // rip .quad 0 // rflags .quad 0 // cs .quad 0 // fs .quad 0 // gs -420: -50: - .endobj macho,globl - - .balign 64 -_start: mov $_HOSTXNU,%dl // xnu's not unix! - mov %rsp,%rsi // save real stack - sub $1024*1024,%rsp // room for allocs - jmp ApeLoader - .endfn _start,globl - -// Invokes system call. -// -// This function has eight parameters. The first seven are for -// arguments passed along to the system call. The eight is for -// the magic number that indicates which system call is called -// -// The return value follows the Linux kernel convention, where -// errors are returned as `-errno`. BSD systems are normalized -// to follow this convention automatically. -SystemCall: - mov %rcx,%r10 - mov 16(%rsp),%eax - clc - syscall - jnc 1f - neg %rax -1: ret - .endfn SystemCall,globl +520: +60: diff --git a/ape/loader.c b/ape/loader.c index 5de00f11f..a92fcd3ca 100644 --- a/ape/loader.c +++ b/ape/loader.c @@ -77,9 +77,6 @@ * @note this can probably be used as a binfmt_misc interpreter */ -#define PAGE_SIZE 4096 -#define NULL_PAGE 2097152 - #define LINUX 1 #define XNU 8 #define OPENBSD 16 @@ -102,6 +99,7 @@ #define IsNetbsd() (SupportsNetbsd() && os == NETBSD) #define O_RDONLY 0 +#define PROT_NONE 0 #define PROT_READ 1 #define PROT_WRITE 2 #define PROT_EXEC 4 @@ -109,10 +107,12 @@ #define MAP_PRIVATE 2 #define MAP_FIXED 16 #define MAP_ANONYMOUS (IsLinux() ? 32 : 4096) +#define MAP_NORESERVE (IsLinux() ? 16384 : 0) #define ELFCLASS32 1 #define ELFDATA2LSB 1 #define EM_NEXGEN32E 62 #define ET_EXEC 2 +#define ET_DYN 3 #define PT_LOAD 1 #define PT_DYNAMIC 2 #define PT_INTERP 3 @@ -124,6 +124,7 @@ #define AT_PHDR 3 #define AT_PHENT 4 #define AT_PHNUM 5 +#define AT_PAGESZ 6 #define AT_EXECFN_LINUX 31 #define AT_EXECFN_NETBSD 2014 #define X_OK 1 @@ -132,11 +133,11 @@ #define PR_SET_MM 35 #define PR_SET_MM_EXE_FILE 13 -#define Read32(S) \ +#define READ32(S) \ ((unsigned)(255 & (S)[3]) << 030 | (unsigned)(255 & (S)[2]) << 020 | \ (unsigned)(255 & (S)[1]) << 010 | (unsigned)(255 & (S)[0]) << 000) -#define Read64(S) \ +#define READ64(S) \ ((unsigned long)(255 & (S)[7]) << 070 | \ (unsigned long)(255 & (S)[6]) << 060 | \ (unsigned long)(255 & (S)[5]) << 050 | \ @@ -146,6 +147,13 @@ (unsigned long)(255 & (S)[1]) << 010 | \ (unsigned long)(255 & (S)[0]) << 000) +#define DEBUG(VAR) \ + { \ + char ibuf[19] = {0}; \ + Utox(ibuf, VAR); \ + Print(os, 2, #VAR " ", ibuf, "\n", 0l); \ + } + struct ElfEhdr { unsigned char e_ident[16]; unsigned short e_type; @@ -176,7 +184,7 @@ struct ElfPhdr { union ElfEhdrBuf { struct ElfEhdr ehdr; - char buf[4096]; + char buf[8192]; }; union ElfPhdrBuf { @@ -199,8 +207,8 @@ struct ApeLoader { char path[1024]; }; -long SystemCall(long arg1, long arg2, long arg3, long arg4, long arg5, - long arg6, long arg7, long magi); +long SystemCall(long, long, long, long, long, long, long, int); +void Launch(void *, long, void *, int) __attribute__((__noreturn__)); extern char __executable_start[]; extern char _end[]; @@ -215,6 +223,27 @@ static unsigned long StrLen(const char *s) { return n; } +static int StrCmp(const char *l, const char *r) { + unsigned long i = 0; + while (l[i] == r[i] && r[i]) ++i; + return (l[i] & 255) - (r[i] & 255); +} + +static void Bzero(void *a, unsigned long n) { + long z; + char *p, *e; + p = (char *)a; + e = p + n; + z = 0; + while (p + sizeof(z) <= e) { + __builtin_memcpy(p, &z, sizeof(z)); + p += sizeof(z); + } + while (p < e) { + *p++ = 0; + } +} + static const char *MemChr(const char *s, unsigned char c, unsigned long n) { for (; n; --n, ++s) { if ((*s & 255) == c) { @@ -225,15 +254,29 @@ static const char *MemChr(const char *s, unsigned char c, unsigned long n) { } static void *MemMove(void *a, const void *b, unsigned long n) { - char *d = a; + long w; + char *d; + const char *s; unsigned long i; - const char *s = b; + d = (char *)a; + s = (const char *)b; if (d > s) { - for (i = n; i--;) { - d[i] = s[i]; + while (n >= sizeof(w)) { + n -= sizeof(w); + __builtin_memcpy(&w, s + n, sizeof(n)); + __builtin_memcpy(d + n, &w, sizeof(n)); + } + while (n--) { + d[n] = s[n]; } } else { - for (i = 0; i < n; ++i) { + i = 0; + while (i + sizeof(w) <= n) { + __builtin_memcpy(&w, s + i, sizeof(i)); + __builtin_memcpy(d + i, &w, sizeof(i)); + i += sizeof(w); + } + for (; i < n; ++i) { d[i] = s[i]; } } @@ -260,6 +303,23 @@ static char *GetEnv(char **p, const char *s) { return 0; } +static char *Utox(char p[19], unsigned long x) { + int i; + if (x) { + *p++ = '0'; + *p++ = 'x'; + i = (__builtin_clzl(x) ^ (sizeof(long) * 8 - 1)) + 1; + i = (i + 3) & -4; + do { + *p++ = "0123456789abcdef"[(x >> (i -= 4)) & 15]; + } while (i); + } else { + *p++ = '0'; + } + *p = 0; + return p; +} + static char *Utoa(char p[21], unsigned long x) { char t; unsigned long i, a, b; @@ -284,15 +344,22 @@ static char *Itoa(char p[21], long x) { return Utoa(p, x); } -__attribute__((__noreturn__)) static void Exit(int rc, int os) { - SystemCall(rc, 0, 0, 0, 0, 0, 0, - (IsLinux() ? 60 : 1) | (IsXnu() ? 0x2000000 : 0)); +__attribute__((__noinline__)) static long CallSystem(long arg1, long arg2, + long arg3, long arg4, + long arg5, long arg6, + long arg7, int numba, + char os) { + if (IsXnu()) numba |= 0x2000000; + return SystemCall(arg1, arg2, arg3, arg4, arg5, arg6, arg7, numba); +} + +__attribute__((__noreturn__)) static void Exit(long rc, int os) { + CallSystem(rc, 0, 0, 0, 0, 0, 0, IsLinux() ? 60 : 1, os); __builtin_unreachable(); } static int Close(int fd, int os) { - return SystemCall(fd, 0, 0, 0, 0, 0, 0, - (IsLinux() ? 3 : 6) | (IsXnu() ? 0x2000000 : 0)); + return CallSystem(fd, 0, 0, 0, 0, 0, 0, IsLinux() ? 3 : 6, os); } static long Pread(int fd, void *data, unsigned long size, long off, int os) { @@ -314,18 +381,15 @@ static long Pread(int fd, void *data, unsigned long size, long off, int os) { } static long Write(int fd, const void *data, unsigned long size, int os) { - return SystemCall(fd, (long)data, size, 0, 0, 0, 0, - (IsLinux() ? 1 : 4) | (IsXnu() ? 0x2000000 : 0)); + return CallSystem(fd, (long)data, size, 0, 0, 0, 0, IsLinux() ? 1 : 4, os); } static int Execve(const char *prog, char **argv, char **envp, int os) { - return SystemCall((long)prog, (long)argv, (long)envp, 0, 0, 0, 0, - 59 | (IsXnu() ? 0x2000000 : 0)); + return CallSystem((long)prog, (long)argv, (long)envp, 0, 0, 0, 0, 59, os); } static int Access(const char *path, int mode, int os) { - return SystemCall((long)path, mode, 0, 0, 0, 0, 0, - (IsLinux() ? 21 : 33) | (IsXnu() ? 0x2000000 : 0)); + return CallSystem((long)path, mode, 0, 0, 0, 0, 0, IsLinux() ? 21 : 33, os); } static int Msyscall(long p, unsigned long n, int os) { @@ -337,13 +401,12 @@ static int Msyscall(long p, unsigned long n, int os) { } static int Open(const char *path, int flags, int mode, int os) { - return SystemCall((long)path, flags, mode, 0, 0, 0, 0, - (IsLinux() ? 2 : 5) | (IsXnu() ? 0x2000000 : 0)); + return CallSystem((long)path, flags, mode, 0, 0, 0, 0, IsLinux() ? 2 : 5, os); } static int Mprotect(void *addr, unsigned long size, int prot, int os) { - return SystemCall((long)addr, size, prot, 0, 0, 0, 0, - (IsLinux() ? 10 : 74) | (IsXnu() ? 0x2000000 : 0)); + return CallSystem((long)addr, size, prot, 0, 0, 0, 0, IsLinux() ? 10 : 74, + os); } static long Mmap(void *addr, unsigned long size, int prot, int flags, int fd, @@ -396,21 +459,6 @@ __attribute__((__noreturn__)) static void Pexit(int os, const char *c, int rc, Exit(127, os); } -static int StrCmp(const char *l, const char *r) { - unsigned long i = 0; - while (l[i] == r[i] && r[i]) ++i; - return (l[i] & 255) - (r[i] & 255); -} - -static void *MemSet(void *a, int c, unsigned long n) { - char *d = a; - unsigned long i; - for (i = 0; i < n; ++i) { - d[i] = c; - } - return d; -} - static char EndsWithIgnoreCase(const char *p, unsigned long n, const char *s) { unsigned long i, m; if (n >= (m = StrLen(s))) { @@ -466,8 +514,6 @@ static char FindCommand(struct PathSearcher *ps, const char *suffix) { MemChr(ps->name, '\\', ps->namelen)) { ps->path[0] = 0; return AccessCommand(ps, suffix, 0); - } else { - if (AccessCommand(ps, suffix, 0)) return 1; } return SearchPath(ps, suffix); } @@ -486,7 +532,8 @@ static char *Commandv(struct PathSearcher *ps, int os, const char *name, } __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd, - long *sp, struct ElfEhdr *e, + long *sp, unsigned long pagesz, + struct ElfEhdr *e, struct ElfPhdr *p) { long rc; int prot; @@ -494,21 +541,26 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd, int found_code; int found_entry; long code, codesize; + unsigned long dynbase; + unsigned long virtmin, virtmax; unsigned long a, b, c, d, i, j; - /* load elf */ + /* validate elf */ code = 0; codesize = 0; found_code = 0; found_entry = 0; + virtmin = virtmax = 0; + if (!pagesz) pagesz = 4096; + if (pagesz & (pagesz - 1)) { + Pexit(os, exe, 0, "AT_PAGESZ isn't two power"); + } for (i = 0; i < e->e_phnum; ++i) { - - /* validate program header */ if (p[i].p_type == PT_INTERP) { - Pexit(os, exe, 0, "ELF has PT_INTERP which is unsupported"); + Pexit(os, exe, 0, "ELF has PT_INTERP which isn't supported"); } if (p[i].p_type == PT_DYNAMIC) { - Pexit(os, exe, 0, "ELF has PT_DYNAMIC which is unsupported"); + Pexit(os, exe, 0, "ELF has PT_DYNAMIC which isn't supported"); } if (p[i].p_type != PT_LOAD) { continue; @@ -517,48 +569,34 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd, continue; } if (p[i].p_filesz > p[i].p_memsz) { - Pexit(os, exe, 0, "ELF phdr filesz exceeds memsz"); + Pexit(os, exe, 0, "ELF p_filesz exceeds p_memsz"); } - if ((p[i].p_vaddr & (PAGE_SIZE - 1)) != (p[i].p_offset & (PAGE_SIZE - 1))) { - Pexit(os, exe, 0, "ELF phdr virt/off skew mismatch w.r.t. pagesize"); + if ((p[i].p_vaddr & (pagesz - 1)) != (p[i].p_offset & (pagesz - 1))) { + Pexit(os, exe, 0, "ELF p_vaddr incongruent w/ p_offset modulo AT_PAGESZ"); } if (p[i].p_vaddr + p[i].p_memsz < p[i].p_vaddr || - p[i].p_vaddr + p[i].p_memsz + (PAGE_SIZE - 1) < p[i].p_vaddr) { - Pexit(os, exe, 0, "ELF phdr vaddr+memsz overflow"); + p[i].p_vaddr + p[i].p_memsz + (pagesz - 1) < p[i].p_vaddr) { + Pexit(os, exe, 0, "ELF p_vaddr + p_memsz overflow"); } if (p[i].p_vaddr + p[i].p_filesz < p[i].p_vaddr || - p[i].p_vaddr + p[i].p_filesz + (PAGE_SIZE - 1) < p[i].p_vaddr) { - Pexit(os, exe, 0, "ELF phdr vaddr+files overflow"); - } - a = p[i].p_vaddr & -PAGE_SIZE; - b = (p[i].p_vaddr + p[i].p_memsz + (PAGE_SIZE - 1)) & -PAGE_SIZE; - if (MAX(a, 0) < MIN(b, NULL_PAGE)) { - Pexit(os, exe, 0, "ELF overlaps NULL page"); + p[i].p_vaddr + p[i].p_filesz + (pagesz - 1) < p[i].p_vaddr) { + Pexit(os, exe, 0, "ELF p_vaddr + p_filesz overflow"); } + a = p[i].p_vaddr & -pagesz; + b = (p[i].p_vaddr + p[i].p_memsz + (pagesz - 1)) & -pagesz; if (MAX(a, (unsigned long)__executable_start) < MIN(b, (unsigned long)_end)) { - Pexit(os, exe, 0, "ELF overlaps your APE loader"); + Pexit(os, exe, 0, "ELF segments overlap your APE loader"); } for (j = i + 1; j < e->e_phnum; ++j) { if (p[j].p_type != PT_LOAD) continue; - c = p[j].p_vaddr & -PAGE_SIZE; - d = (p[j].p_vaddr + p[j].p_memsz + (PAGE_SIZE - 1)) & -PAGE_SIZE; + c = p[j].p_vaddr & -pagesz; + d = (p[j].p_vaddr + p[j].p_memsz + (pagesz - 1)) & -pagesz; if (MAX(a, c) < MIN(b, d)) { - Pexit(os, exe, 0, "ELF overlaps its own vaspace"); + Pexit(os, exe, 0, "ELF segments overlap each others virtual memory"); } } - - /* configure mapping */ - prot = 0; - flags = MAP_FIXED | MAP_PRIVATE; - if (p[i].p_flags & PF_R) { - prot |= PROT_READ; - } - if (p[i].p_flags & PF_W) { - prot |= PROT_WRITE; - } if (p[i].p_flags & PF_X) { - prot |= PROT_EXEC; if (!found_code) { code = p[i].p_vaddr; codesize = p[i].p_filesz; @@ -568,30 +606,65 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd, found_entry = 1; } } + if (p[i].p_vaddr < virtmin) { + virtmin = p[i].p_vaddr; + } + if (p[i].p_vaddr + p[i].p_memsz > virtmax) { + virtmax = p[i].p_vaddr + p[i].p_memsz; + } + } + if (!found_entry) { + Pexit(os, exe, 0, "ELF entrypoint not found in PT_LOAD with PF_X"); + } + + /* choose loading address for dynamic elf executables + that maintains relative distances between segments */ + if (e->e_type == ET_DYN) { + rc = Mmap(0, virtmax - virtmin, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0, os); + if (rc < 0) Pexit(os, exe, rc, "pie mmap"); + dynbase = rc; + if (dynbase & (pagesz - 1)) { + Pexit(os, exe, 0, "OS mmap incongruent w/ AT_PAGESZ"); + } + if (dynbase + virtmin < dynbase) { + Pexit(os, exe, 0, "ELF dynamic base overflow"); + } + } else { + dynbase = 0; + } + + /* load elf */ + for (i = 0; i < e->e_phnum; ++i) { + if (p[i].p_type != PT_LOAD) continue; + if (!p[i].p_memsz) continue; + + /* configure mapping */ + prot = 0; + flags = MAP_FIXED | MAP_PRIVATE; + if (p[i].p_flags & PF_R) prot |= PROT_READ; + if (p[i].p_flags & PF_W) prot |= PROT_WRITE; + if (p[i].p_flags & PF_X) prot |= PROT_EXEC; /* load from file */ if (p[i].p_filesz) { void *addr; + int prot1, prot2; unsigned long size; - int dirty, prot1, prot2; - dirty = 0; prot1 = prot; prot2 = prot; - a = p[i].p_vaddr + p[i].p_filesz; - b = (a + (PAGE_SIZE - 1)) & -PAGE_SIZE; - c = p[i].p_vaddr + p[i].p_memsz; + a = p[i].p_vaddr + p[i].p_filesz; /* end of file content */ + b = (a + (pagesz - 1)) & -pagesz; /* first pure bss page */ + c = p[i].p_vaddr + p[i].p_memsz; /* end of segment data */ if (b > c) b = c; - if (c > b) { - dirty = 1; - if (~prot1 & PROT_WRITE) { - prot1 = PROT_READ | PROT_WRITE; - } + if (c > b && (~prot1 & PROT_WRITE)) { + prot1 = PROT_READ | PROT_WRITE; } - addr = (void *)(p[i].p_vaddr & -PAGE_SIZE); - size = (p[i].p_vaddr & (PAGE_SIZE - 1)) + p[i].p_filesz; - rc = Mmap(addr, size, prot1, flags, fd, p[i].p_offset & -PAGE_SIZE, os); + addr = (void *)(dynbase + (p[i].p_vaddr & -pagesz)); + size = (p[i].p_vaddr & (pagesz - 1)) + p[i].p_filesz; + rc = Mmap(addr, size, prot1, flags, fd, p[i].p_offset & -pagesz, os); if (rc < 0) Pexit(os, exe, rc, "prog mmap"); - if (dirty) MemSet((void *)a, 0, b - a); + if (c > b) Bzero((void *)(dynbase + a), b - a); if (prot2 != prot1) { rc = Mprotect(addr, size, prot2, os); if (rc < 0) Pexit(os, exe, rc, "prog mprotect"); @@ -600,83 +673,100 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd, /* allocate extra bss */ a = p[i].p_vaddr + p[i].p_filesz; - a = (a + (PAGE_SIZE - 1)) & -PAGE_SIZE; + a = (a + (pagesz - 1)) & -pagesz; b = p[i].p_vaddr + p[i].p_memsz; if (b > a) { - rc = Mmap((void *)a, b - a, prot, flags | MAP_ANONYMOUS, 0, 0, os); + flags |= MAP_ANONYMOUS; + rc = Mmap((void *)(dynbase + a), b - a, prot, flags, -1, 0, os); if (rc < 0) Pexit(os, exe, rc, "bss mmap"); } } /* finish up */ - if (!found_entry) { - Pexit(os, exe, 0, "ELF entrypoint not found in PT_LOAD with PF_X"); - } Close(fd, os); - Msyscall(code, codesize, os); + Msyscall(dynbase + code, codesize, os); - /* we clear all the general registers we can to have some wiggle room - to extend the behavior of this loader in the future. we don't need - to clear the xmm registers since the ape loader should be compiled - with the -mgeneral-regs-only flag. */ - asm volatile("xor\t%%eax,%%eax\n\t" - "xor\t%%r8d,%%r8d\n\t" - "xor\t%%r9d,%%r9d\n\t" - "xor\t%%r10d,%%r10d\n\t" - "xor\t%%r11d,%%r11d\n\t" - "xor\t%%ebx,%%ebx\n\t" /* netbsd doesnt't clear this */ - "xor\t%%r12d,%%r12d\n\t" /* netbsd doesnt't clear this */ - "xor\t%%r13d,%%r13d\n\t" /* netbsd doesnt't clear this */ - "xor\t%%r14d,%%r14d\n\t" /* netbsd doesnt't clear this */ - "xor\t%%r15d,%%r15d\n\t" /* netbsd doesnt't clear this */ - "mov\t%%rdx,%%rsp\n\t" - "xor\t%%edx,%%edx\n\t" - "push\t%%rsi\n\t" - "xor\t%%esi,%%esi\n\t" - "xor\t%%ebp,%%ebp\n\t" - "ret" - : /* no outputs */ - : "D"(IsFreebsd() ? sp : 0), "S"(e->e_entry), "d"(sp), "c"(os) - : "memory"); - __builtin_unreachable(); + /* call program entrypoint */ + Launch(IsFreebsd() ? sp : 0, dynbase + e->e_entry, sp, os); } -static void TryElf(struct ApeLoader *M, const char *exe, int fd, long *sp, - long *auxv, int os) { - unsigned size = M->ehdr.ehdr.e_phnum; - if (Read32(M->ehdr.buf) == Read32("\177ELF") && - M->ehdr.ehdr.e_type == ET_EXEC && - M->ehdr.ehdr.e_machine == EM_NEXGEN32E && - M->ehdr.ehdr.e_ident[EI_CLASS] != ELFCLASS32 && - M->ehdr.ehdr.e_phentsize >= sizeof(M->phdr.phdr) && - (size *= M->ehdr.ehdr.e_phentsize) <= sizeof(M->phdr.buf) && - Pread(fd, M->phdr.buf, size, M->ehdr.ehdr.e_phoff, os) == size) { - for (; *auxv; auxv += 2) { - switch (*auxv) { - case AT_PHDR: - auxv[1] = (unsigned long)&M->phdr; - break; - case AT_PHENT: - auxv[1] = M->ehdr.ehdr.e_phentsize; - break; - case AT_PHNUM: - auxv[1] = M->ehdr.ehdr.e_phnum; - break; - default: - break; - } - } - Spawn(os, exe, fd, sp, &M->ehdr.ehdr, &M->phdr.phdr); +static const char *TryElf(struct ApeLoader *M, const char *exe, int fd, + long *sp, long *auxv, unsigned long pagesz, int os) { + long rc; + unsigned size; + if (READ32(M->ehdr.buf) != READ32("\177ELF")) { + return "didn't embed ELF magic"; } + if (M->ehdr.ehdr.e_ident[EI_CLASS] == ELFCLASS32) { + return "32-bit ELF isn't supported"; + } + if (M->ehdr.ehdr.e_type != ET_EXEC && M->ehdr.ehdr.e_type != ET_DYN) { + return "ELF not ET_EXEC or ET_DYN"; + } + if (M->ehdr.ehdr.e_machine != EM_NEXGEN32E) { + return "couldn't find ELF header with x86-64 machine type"; + } + if (M->ehdr.ehdr.e_phentsize < sizeof(M->phdr.phdr)) { + return "e_phentsize is too small"; + } + size = M->ehdr.ehdr.e_phnum; + if ((size *= M->ehdr.ehdr.e_phentsize) > sizeof(M->phdr.buf)) { + return "too many ELF program headers"; + } + rc = Pread(fd, M->phdr.buf, size, M->ehdr.ehdr.e_phoff, os); + if (rc < 0) return "failed to read ELF program headers"; + if (rc != size) return "truncated read of ELF program headers"; + for (; *auxv; auxv += 2) { + switch (*auxv) { + case AT_PHDR: + auxv[1] = (unsigned long)&M->phdr; + break; + case AT_PHENT: + auxv[1] = M->ehdr.ehdr.e_phentsize; + break; + case AT_PHNUM: + auxv[1] = M->ehdr.ehdr.e_phnum; + break; + default: + break; + } + } + Spawn(os, exe, fd, sp, pagesz, &M->ehdr.ehdr, &M->phdr.phdr); +} + +static __attribute__((__noreturn__)) void ShowUsage(int os, int fd, int rc) { + Print(os, fd, + "NAME\n" + "\n" + " actually portable executable loader v1.3\n" + " copyright 2023 justine alexandra roberts tunney\n" + " https://justine.lol/ape.html\n" + "\n" + "USAGE\n" + "\n" + " ape [FLAGS] PROG [ARGV1,ARGV2,...]\n" + " ape [FLAGS] - PROG [ARGV0,ARGV1,...]\n" + "\n" + "FLAGS\n" + "\n" + " -h show this help\n" + " -f force loading of program (do not use execve)\n" + "\n", + 0l); + Exit(rc, os); } __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) { int rc; unsigned i, n; + int usetheforce; int c, fd, os, argc; struct ApeLoader *M; + unsigned long pagesz; long *auxv, *ap, *ew; - char *p, *exe, *prog, **argv, **envp; + char *p, *pe, *exe, *ape, *prog, **argv, **envp; + + (void)Utox; /* detect freebsd */ if (SupportsXnu() && dl == XNU) { @@ -699,14 +789,21 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) { } } + /* determine ape loader program name */ + ape = argv[0]; + if (!ape) ape = "ape"; + /* detect openbsd */ if (SupportsOpenbsd() && !os && !auxv[0]) { os = OPENBSD; } /* detect netbsd and find end of words */ + pagesz = 0; for (ap = auxv; ap[0]; ap += 2) { - if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) { + if (ap[0] == AT_PAGESZ) { + pagesz = ap[1]; + } else if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) { os = NETBSD; } } @@ -723,6 +820,23 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) { os = LINUX; } + /* parse flags */ + usetheforce = 0; + while (argc > 1) { + if (argv[1][0] != '-') break; /* normal argument */ + if (!argv[1][1]) break; /* hyphen argument */ + if (!StrCmp(argv[1], "-h") || !StrCmp(argv[1], "--help")) { + ShowUsage(os, 1, 0); + } else if (!StrCmp(argv[1], "-f")) { + usetheforce = 1; + } else { + Print(os, 2, ape, ": invalid flag (pass -h for help)\n", 0l); + Exit(1, os); + } + *++sp = --argc; + ++argv; + } + /* we can load via shell, shebang, or binfmt_misc */ if (argc >= 3 && !StrCmp(argv[1], "-")) { /* if the first argument is a hyphen then we give the user the @@ -733,13 +847,7 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) { argc = sp[3] = sp[0] - 3; argv = (char **)((sp += 3) + 1); } else if (argc < 2) { - Print(os, 2, - "usage: ape PROG [ARGV1,ARGV2,...]\n" - " ape - PROG [ARGV0,ARGV1,...]\n" - "αcτµαlly pδrταblε εxεcµταblε loader v1.2\n" - "copyright 2022 justine alexandra roberts tunney\n" - "https://justine.lol/ape.html\n", - 0l); + Print(os, 2, ape, ": missing command name (pass -h for help)\n", 0l); Exit(1, os); } else { prog = (char *)sp[2]; @@ -749,14 +857,15 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) { /* resolve path of executable and read its first page */ if (!(exe = Commandv(&M->ps, os, prog, GetEnv(envp, "PATH")))) { - Pexit(os, prog, 0, "not found (maybe chmod +x)"); + Pexit(os, prog, 0, "not found (maybe chmod +x or ./ needed)"); } else if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) { Pexit(os, exe, fd, "open"); } else if ((rc = Pread(fd, M->ehdr.buf, sizeof(M->ehdr.buf), 0, os)) < 0) { Pexit(os, exe, rc, "read"); - } else if (rc != sizeof(M->ehdr.buf)) { + } else if ((unsigned long)rc < sizeof(M->ehdr.ehdr)) { Pexit(os, exe, 0, "too small"); } + pe = M->ehdr.buf + rc; /* change argv[0] to resolved path if it's ambiguous */ if (argc > 0 && *prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) { @@ -767,21 +876,24 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) { 1. if file is a native executable, try to run it natively 2. if ape, will scan shell script for elf printf statements 3. shell script may have multiple lines producing elf headers - 4. all elf printf lines must exist in the first 4096 bytes of file + 4. all elf printf lines must exist in the first 8192 bytes of file 5. elf program headers may appear anywhere in the binary */ - if ((IsXnu() && Read32(M->ehdr.buf) == 0xFEEDFACE + 1) || - (!IsXnu() && Read32(M->ehdr.buf) == Read32("\177ELF"))) { + if (!usetheforce && + ((IsXnu() && READ32(M->ehdr.buf) == 0xFEEDFACE + 1) || + (!IsXnu() && READ32(M->ehdr.buf) == READ32("\177ELF")))) { Close(fd, os); Execve(exe, argv, envp, os); + if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) { + Pexit(os, exe, rc, "execve and open failed"); + } } - if (Read64(M->ehdr.buf) == Read64("MZqFpD='") || - Read64(M->ehdr.buf) == Read64("jartsr='")) { - for (p = M->ehdr.buf; p < M->ehdr.buf + sizeof(M->ehdr.buf); ++p) { - if (Read64(p) != Read64("printf '")) { + if (READ64(M->ehdr.buf) == READ64("MZqFpD='") || + READ64(M->ehdr.buf) == READ64("jartsr='")) { + for (p = M->ehdr.buf; p < pe; ++p) { + if (READ64(p) != READ64("printf '")) { continue; } - for (i = 0, p += 8; - p + 3 < M->ehdr.buf + sizeof(M->ehdr.buf) && (c = *p++) != '\'';) { + for (i = 0, p += 8; p + 3 < pe && (c = *p++) != '\'';) { if (c == '\\') { if ('0' <= *p && *p <= '7') { c = *p++ - '0'; @@ -796,12 +908,14 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) { } } M->ehdr.buf[i++] = c; + if (i >= sizeof(M->ehdr.buf)) { + break; + } } if (i >= sizeof(M->ehdr.ehdr)) { - TryElf(M, exe, fd, sp, auxv, os); + TryElf(M, exe, fd, sp, auxv, pagesz, os); } } } - TryElf(M, exe, fd, sp, auxv, os); - Pexit(os, exe, 0, "Not an acceptable APE/ELF executable for x86-64"); + Pexit(os, exe, 0, TryElf(M, exe, fd, sp, auxv, pagesz, os)); } diff --git a/ape/loader.lds b/ape/loader.lds index 7507beec2..f01d07eae 100644 --- a/ape/loader.lds +++ b/ape/loader.lds @@ -1,7 +1,7 @@ /*-*- mode: ld-script; indent-tabs-mode: nil; tab-width: 2; coding: utf-8 -*-│ │vi: set et sts=2 tw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ @@ -16,26 +16,80 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -ENTRY(_start) + +ENTRY(ElfEntrypoint) + +PHDRS { + text PT_LOAD FLAGS(1) FILEHDR PHDRS; /* PF_X */ + rodata PT_LOAD FLAGS(4); /* PF_R */ + stack PT_GNU_STACK FLAGS(6); /* PF_W|PF_R */ + note PT_NOTE FLAGS(4); /* PF_R */ +} SECTIONS { - . = 0x7fff0000; + + . = SEGMENT_START("text-segment", 0x7f000000); __executable_start = .; - .rodata : { - KEEP(*(.head)) - *(.rodata .rodata.*) - . = ALIGN(4096); - } + . += SIZEOF_HEADERS; + + .macho : { + KEEP(*(.macho)) + } :text + + .note : { + KEEP(*(.note)) + } :text :note + .text : { - text = .; - *(.text) - } + *(.text .text.* .gnu.linkonce.t.*) + } :text + + .rodata ALIGN(CONSTANT(COMMONPAGESIZE)) : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } :rodata + + .stack : { + *(.stack) + } :stack + _end = .; + + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + .debug_macro 0 : { *(.debug_macro) } + .debug_addr 0 : { *(.debug_addr) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_extra 0 : { *(.debug_line_str) } + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .gnu.attributes 0 : { KEEP(*(.gnu.attributes)) } + /DISCARD/ : { *(.*) } } - -textsz = SIZEOF(.text); -rosize = SIZEOF(.rodata); -filesz = SIZEOF(.rodata) + SIZEOF(.text); diff --git a/ape/start.S b/ape/start.S new file mode 100644 index 000000000..0c2e5ee80 --- /dev/null +++ b/ape/start.S @@ -0,0 +1,51 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/macros.internal.h" + +XnuEntrypoint: + mov $_HOSTXNU,%dl // xnu's not unix! +ElfEntrypoint: + mov %rsp,%rsi // save real stack + sub $1024*1024,%rsp // room for allocs + call ApeLoader + .endfn ElfEntrypoint,globl + .endfn XnuEntrypoint,globl + + .section .note,"a",@progbits + .balign 4 +openbsd.ident: + .long 2f-1f + .long 4f-3f + .long 1 +1: .asciz "OpenBSD" +2: .balign 4 +3: .long 0 +4: .size openbsd.ident,.-openbsd.ident + .type openbsd.ident,@object + .balign 4 +netbsd.ident: + .long 2f-1f + .long 4f-3f + .long 1 +1: .asciz "NetBSD" +2: .balign 4 +3: .long 901000000 +4: .size netbsd.ident,.-netbsd.ident + .type netbsd.ident,@object diff --git a/ape/systemcall.S b/ape/systemcall.S new file mode 100644 index 000000000..22652d800 --- /dev/null +++ b/ape/systemcall.S @@ -0,0 +1,38 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.internal.h" + +// Invokes system call. +// +// This function has eight parameters. The first seven are for +// arguments passed along to the system call. The eight is for +// the magic number that indicates which system call is called +// +// The return value follows the Linux kernel convention, where +// errors are returned as `-errno`. BSD systems are normalized +// to follow this convention automatically. +SystemCall: + mov %rcx,%r10 + mov 16(%rsp),%eax + clc + syscall + jnc 1f + neg %rax +1: ret + .endfn SystemCall,globl diff --git a/build/bootstrap/ape.elf b/build/bootstrap/ape.elf index 34bde40e691d4837a2746f554e190f12d9ab47b5..d95feed052bf9c4f7e7b8d1a33418c4011f7e406 100755 GIT binary patch literal 9460 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 literal 7781 zcmeHMe{d7monOhq$i}dOKw>7BbkkFUxe%M>Ok$BhyddGX%?bpV@FN5wWEq>2EmvAu zkdk8GS{Fa7`$un*x!iTynKpMbZF6^RdWAUV05(=Z*M>`i90hPmgN<`| z@AFo2LNd){?ysAZnKAXth|1xctw`t>Y|swmOWXj8)BU zpZisFRh%u)W4bfXIaaP8$K_i`Uh#7+>&Gtc6aI{Tt_?28^_yvD^tQaK))nSNUvh6V z#|85=i*wFxqPy{2itESEhyL-M@BQrYn^%8R-~F8x7nx2sE{4^g;rc)S<@5K; z#v#=E8$-c5AzmQ_Ba!B?!1-H(>@gf!BXC$8LW4gl_-gmbn`%85y3)yUzNaAUJ7G>288J{)VV6B?TtyvkRuwaLeIH+mWRTj3XH3~L2U{Zb3Un-aOHt;6Za`7WljFW`-4KDp($J^Ja}k1R~|eRG?mKh z_8;KFs6<>O=&$3{a9BWoghJdVAnI=x{DDBQMfL9w1=-wHn6MuT24Xo@eK;ZB5tv0_7J*p=W)YZ0U>1Q{1pZ$l@H0E1?~(Qo*o*ksIeZ3?>6xV4>6ql_ zL<`8oulvLy{kRG&|qwy1Y$({}a} zX?>kx-NW92c~5V4ZqH+Q5>rJZBw%>NA&;0MFNs6qsCdJ7kjs3{FbrS!kDwxHlMeO% zqYlX!?EYJrk-8Uxo8`e5y8nqObYFl`nRlmrIa4wngatb+5bYPGeLi=x*6HXLO(bMTu?0s3G-*OU= zMDK7ugI3YMi?~hTf9tb)wJN_#q4P1EJsT zoX3&GklJ{(m`wbJ=&EyGwwQ4rV$UV8DV-=b0~{1b6*?eKeCXAyEn0fz`p()ygU~aC z_7ZKXD1LE$zpW6{Ok9)E#a)XFyGqt|mJ|{r)wy^bG0tW$!>xB5ZuPYT*A3$!cajZL zq33oe`kc)W&}#(I+`X0POCAB~6H}RZZZ-sZ^%VP@m3a{nnW?tOgrbLiO3FcjEMwnb zzA($)`S1L^N;{+{rR`Gf_HBs2q5birc*WvhtEr;3&T4`Qtl|^@4uso}I@q@~LO!0%xrBQLSsCEbE4NNa8jrZs^bApUPJ~0QKv|nZ< zah)Ul#&g8=W|V2`OnWJ`6@7l!`w;6(GWfEnxXvp4hO>EP*>%oTjx*&CSmGuZJH2#F z(RW+Ct`DO-CGGVp#Au0m)(_MzWoeCq1}v~0poW%xGOo13*NiCWu|&GjZ#CDLbOP0j zlX%bhY@wT8_lOr181S{f$54KK5oB(<$GJzb^h;Z}D@nz<(v2<2C+CnRRCQLd=~;9H|Z za}1kP)=pQA{TPIH1Pc-JpFqxc41&Yz0==3k`xwGoj@W$%?`JEC?UDykM^qFIR@5^O zVAwlFV|R9*2t9%D?jry(87(D%sfvz?Dd?F)8pU*o7d+xS9`TAtyx|eC6n-U+J}KT1 zuk>M)YkrbX`f=Bkoo5mm?~tHx^P3= z5zAL9)S?ajZi`SM8!0*Yb-P#JA}I9I6Z&J;OdQL~TOgIn`Zf!pS5003JSl0>CN1h6 z+N8bbK3GF(*yfInVoL7>`nMeJJk4wO&Vh(<1Iyn5&!OZLQ1Ei=>9C+(2wcz(-zgZ-Vs_N}V6DZ9#(grV@B z0W$I1rB}3f1#i+J2hxgOavBNDJ)!>z&W3wX^0YoX(Y`Us#l{m?Vh@N{i1sK~&A)KE z_g!1|F0gp8*8V5{DabJk(E%%|0LdJCo9Nr@vfll>H&OerpDj;k2C!DpY0evF5zwZr z-<0KK_c#=YzWP2C#|-@l8mZV0S#O&J+nfBM+34Neuc5>aN^-J|StMGfK&GY=xXx^? z`lsfZ05}gJH}xgI8iTngCKLNVbC-D$reQXdGs~QM^_=h9%^HKS%5<33C-e@x=}cd{ zm6dMra7ODTNecp0my4s-U6prLcif20Z+pzKHnxc9ZigkiuzF3UBZjkbI{RCq_1d#% z=_{EDbVp!wageziBU9)rza>;KGA2M z*(Z@YGzg*q#9DTy;1k1ejLPWbzWG(D?K!SOy}RuM1EM0fNKQVYV0}FT?*P8QOPAru z6Dw&y!JM)G-m=BHXLgEPow%amluX(#%L%w^1(>ssq6uj@HP{vC`En}c1v z;nlqbFylJg8j$#t9!}Q1D1Xb;t=iK}=Lof1kjZ(+I^BBio% I+B_ru50OW*8UO$Q diff --git a/build/bootstrap/ape.macho b/build/bootstrap/ape.macho index 41d4aa21d96a57368c0ca3a15cf45d5311bdf213..5ab2342f9d0fab2b794d969ba2d37c022f764f6d 100755 GIT binary patch literal 9460 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 diff --git a/build/bootstrap/objbincopy.com b/build/bootstrap/objbincopy.com new file mode 100755 index 0000000000000000000000000000000000000000..7b995000f8573aad233edb79de57ba3f861b4806 GIT binary patch literal 49456 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≫{xmRcyBWR2hJWYHE1an^%#2l?{N|J2%b9iO>v z0o&O$5V-xWI~KOA2w!{6r#pj*?W_FD8a6HcLi4R_zPNtlh8ym@uCB3vQS#Zk&)uXf z-h6%6(CW73^V(9wzIML*^PgQwj|g*LzFW4m-dx|aZQIrxyO)%Cxm-q*PuKgdzx$QC zMnuZ~a^oMA{WEz;XVit(g8sXkekU+u=0CG@#h1WC@qy2$Z1AvW>6+$Scdg3i#0?sF zxNXtrI(Of+dhzJ>E&F^shL+D8`mMb_-!SjJ@7|SN1Gnh+-(2r}!6@7~xI{LM=Fw=m zY1?^UOQ4yqIdR~d*}Nx$9ZiF?MKJIQB1o^^P9jhtf?*N?9`-X!1iNR6;AkC0aF#?+ zt|J)$%Z*oIV|UYzS#0b~U6qYp)seVw^}S$Y@P4o{^vNUf6tVFnvEjc87v~o>Oe@bT z&nbVa{GBonr;WZB7nFV9SN=(PLAls?OgZ$p@8C7ZeCO+b>^q~pu6!l%g8v!iF=g;a zji;3Zbq_7}zt?zNc}+Q~d`~&0yi)g~a%4f>^S(dzUGv5Y_FbjLcjHS+{i8m1RC!i8 z+xS-Fv&tX)wM8eC!k^sv_sVyaf86jxg}vYYvhokg@}vGAD9X5lN!W4M^`u#`{Nj({MIc1e_machine != EM_NEXGEN32E) { + Die(inpath, ".macho section only supported for ELF x86_64"); + } + if (!macho) Die(inpath, "corrupted .macho section content"); + if (shdr->sh_size < sizeof(struct MachoHeader)) { + Die(inpath, ".macho section too small for mach-o header"); + } + if (macho->magic != 0xFEEDFACE + 1) { + Die(inpath, ".macho header magic wasn't 0xFEEDFACE+1"); + } + if (macho->arch != MAC_CPU_NEXGEN32E) { + Die(inpath, "mach-o arch wasn't MAC_CPU_NEXGEN32E"); + } + if (shdr->sh_size != sizeof(struct MachoHeader) + macho->loadsize) { + Die(inpath, ".macho section size not equal to sizeof(header) + loadsize"); + } + lastvaddr = 0; + lastoffset = 0; + found_uuid = false; + found_segment = false; + found_pagezero = false; + found_unixthread = false; + end = (char *)(macho + 1) + macho->loadsize; + cmd = (struct MachoLoadCommand *)(macho + 1); + for (i = 0; i < macho->loadcount; ++i) { + if ((char *)cmd + sizeof(struct MachoLoadCommand *) > end || + (char *)cmd + cmd->size > end) { + Die(inpath, "mach-o load commands overflowed loadsize"); + } + if (cmd->command == MAC_LC_SEGMENT_64) { + size_t namelen; + struct MachoLoadSegment *loadseg; + loadseg = (struct MachoLoadSegment *)cmd; + if (loadseg->sectioncount) { + Die(inpath, "don't bother with mach-o sections"); + } + namelen = strnlen(loadseg->name, sizeof(loadseg->name)); + if (!loadseg->name) { + Die(inpath, "mach-o load segment missing name"); + } + if (filesize || (loadseg->vaddr && loadseg->memsz)) { + if (loadseg->vaddr < lastvaddr) { + Die(inpath, + "the virtual memory regions defined by mach-o load segment " + "commands aren't allowed to overlap and must be specified " + "monotonically"); + } + if (loadseg->vaddr + loadseg->memsz < loadseg->vaddr) { + Die(inpath, "mach-o segment memsz overflows"); + } + if (loadseg->filesz > loadseg->memsz) { + Die(inpath, "mach-o segment filesz exceeds memsz"); + } + lastvaddr = loadseg->vaddr + loadseg->memsz; + if (loadseg->vaddr & 4095) { + Die(inpath, "mach-o segment vaddr must be page aligned"); + } + } + if (filesize) { + if (loadseg->offset < lastoffset) { + Die(inpath, + "the file segments defined by mach-o load segment commands " + "aren't allowed to overlap and must be specified monotonically"); + } + if (loadseg->filesz > filesize) { + Die(inpath, "mach-o segment filesz exceeds file size"); + } + if (loadseg->offset + loadseg->filesz < loadseg->offset) { + Die(inpath, "mach-o segment offset + filesz overflows"); + } + if (loadseg->offset + loadseg->filesz > filesize) { + Die(inpath, "mach-o segment overlaps end of file"); + } + lastoffset = loadseg->offset + loadseg->filesz; + } + if (namelen == strlen("__PAGEZERO") && + !memcmp(loadseg->name, "__PAGEZERO", namelen)) { + found_pagezero = true; + if (i != 0) { + Die(inpath, "mach-o __PAGEZERO must be first load command"); + } + } else { + if (!found_segment) { + found_segment = true; + if (loadseg->offset) { + Die(inpath, "the first mach-o load segment (that isn't page zero) " + "must begin loading the executable from offset zero"); + } + } + } + } else if (cmd->command == MAC_LC_UUID) { + uint64_t *uuid; + found_uuid = true; + if (cmd->size != sizeof(*cmd) + 16) { + Die(inpath, "MAC_LC_UUID size wrong"); + } + uuid = (uint64_t *)(cmd + 1); + if (!uuid[0] && !uuid[1]) { + uuid[0] = _rand64(); + uuid[1] = _rand64(); + } + } else if (cmd->command == MAC_LC_UNIXTHREAD) { + uint64_t *registers; + struct MachoLoadThreadCommand *thread; + if (cmd->size != sizeof(*thread) + 21 * 8) { + Die(inpath, "MAC_LC_UNIXTHREAD size should be 4+4+4+4+21*8"); + } + thread = (struct MachoLoadThreadCommand *)cmd; + if (thread->flavor != MAC_THREAD_NEXGEN32E) { + Die(inpath, "MAC_LC_UNIXTHREAD flavor should be MAC_THREAD_NEXGEN32E"); + } + if (thread->count != 21 * 8 / 4) { + Die(inpath, "MAC_LC_UNIXTHREAD count should be 21*8/4"); + } + registers = (uint64_t *)(thread + 1); + if (!registers[16]) { + Die(inpath, "MAC_LC_UNIXTHREAD doesn't specify RIP register"); + } + found_unixthread = true; + } else { + Die(inpath, "unsupported mach-o load command"); + } + cmd = (struct MachoLoadCommand *)((char *)cmd + cmd->size); + } + if (!found_uuid) { + Die(inpath, "mach-o missing MAC_LC_UUID"); + } + if (!found_unixthread) { + Die(inpath, "mach-o missing MAC_LC_UNIXTHREAD"); + } + if (!found_pagezero) { + Die(inpath, "mach-o missing __PAGEZERO load segment command"); + } + if ((char *)cmd != end) { + Die(inpath, "mach-o loadsize greater than load commands"); + } +} + +static struct MachoLoadSegment *GetNextMachoLoadSegment( + struct MachoLoadCommand **load, int *count) { + struct MachoLoadCommand *cmd; + while (*count) { + --*count; + cmd = *load; + *load = (struct MachoLoadCommand *)((char *)cmd + cmd->size); + if (cmd->command == MAC_LC_SEGMENT_64) { + struct MachoLoadSegment *loadseg; + loadseg = (struct MachoLoadSegment *)cmd; + if (!IsStaticStringEqual(loadseg->name, "__PAGEZERO")) { + return loadseg; + } + } + } + return 0; +} + +static void HandleElf(const char *inpath, Elf64_Ehdr *elf, size_t esize) { + char *secstrs; + int i, loadcount; + Elf64_Off maxoff; + Elf64_Phdr *phdr; + char empty[64] = {0}; + Elf64_Shdr *macho_shdr; + struct MachoHeader *macho; + struct MachoLoadCommand *loadcommand; + struct MachoLoadSegment *loadsegment; + if (elf->e_type != ET_EXEC && elf->e_type != ET_DYN) { + Die(inpath, "elf binary isn't an executable"); + } + if (!(secstrs = GetElfSectionNameStringTable(elf, esize))) { + Die(inpath, "elf section name string table not found"); + } + macho_shdr = FindElfSectionByName(elf, esize, secstrs, ".macho"); + macho = GetElfSectionAddress(elf, esize, macho_shdr); + // ValidateMachoSection(inpath, elf, macho_shdr, macho, 0); + loadcommand = (struct MachoLoadCommand *)(macho + 1); + loadcount = macho->loadcount; + if (want_freebsd) { + elf->e_ident[EI_OSABI] = ELFOSABI_FREEBSD; + } + elf->e_shoff = 0; + elf->e_shnum = 0; + elf->e_shstrndx = 0; + elf->e_shentsize = 0; + for (maxoff = i = 0; i < elf->e_phnum; ++i) { + phdr = GetElfProgramHeaderAddress(elf, esize, i); + if (!phdr) Die(inpath, "corrupted elf header"); + if (phdr->p_type == PT_INTERP) Die(inpath, "PT_INTERP isn't supported"); + if (phdr->p_type == PT_DYNAMIC) Die(inpath, "PT_DYNAMIC isn't supported"); + if (!phdr->p_filesz) continue; + maxoff = MAX(maxoff, phdr->p_offset + phdr->p_filesz); + if (macho && phdr->p_type == PT_LOAD) { + if (!(loadsegment = GetNextMachoLoadSegment(&loadcommand, &loadcount))) { + Die(inpath, "there must exist a MAC_LC_SEGMENT_64 for every PT_LOAD " + "when the .macho section is defined"); + } + loadsegment->vaddr = phdr->p_vaddr; + loadsegment->memsz = phdr->p_memsz; + loadsegment->offset = phdr->p_offset; + loadsegment->filesz = phdr->p_filesz; + loadsegment->initprot |= PhdrFlagsToProt(phdr->p_flags); + if (loadsegment->initprot == PROT_EXEC) { + loadsegment->initprot |= PROT_READ; + } + loadsegment->maxprot |= loadsegment->initprot; + } + } + // ValidateMachoSection(inpath, elf, macho_shdr, macho, maxoff); + Write((char *)elf, maxoff); + if (want_macho) { + if (!macho_shdr || !macho) { + Die(inpath, "requested Mach-O output but .macho section not found"); + } + if (lseek(outfd, 0, SEEK_SET)) { + DieSys(inpath); + } + // TODO(jart): Add a check that ensures we aren't overwriting + // anything except ELF headers and the old machoo + Write((char *)elf + macho_shdr->sh_offset, macho_shdr->sh_size); + } +} + +static void HandleInput(const char *inpath) { + int infd; + void *map; + ssize_t size; + if ((infd = open(inpath, O_RDONLY)) == -1) { + DieSys(inpath); + } + if ((size = lseek(infd, 0, SEEK_END)) == -1) { + DieSys(inpath); + } + if (size) { + if ((map = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, infd, 0)) == + MAP_FAILED) { + DieSys(inpath); + } + if (IsElf64Binary(map, size)) { + HandleElf(inpath, map, size); + } else { + Die(prog, "not an elf64 binary"); + } + if (munmap(map, size)) { + DieSys(inpath); + } + } + if (close(infd)) { + DieSys(inpath); + } +} + +int main(int argc, char *argv[]) { + int i, opt; + prog = argv[0]; + if (!prog) prog = "objbincopy"; + GetOpts(argc, argv); + if ((outfd = creat(outpath, 0755)) == -1) { + DieSys(outpath); + } + for (i = optind; i < argc; ++i) { + HandleInput(argv[i]); + } + if (close(outfd)) { + DieSys(outpath); + } +}