cosmopolitan/ape/ape-m1.c
Justine Tunney 1a5ef5ba13
Mint APE Loader 1.6
This change fixes a bug with loading pure bss program headers.
2023-08-09 00:27:26 -07:00

960 lines
29 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 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 <dispatch/dispatch.h>
#include <errno.h>
#include <fcntl.h>
#include <libkern/OSCacheControl.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/random.h>
#include <sys/uio.h>
#include <time.h>
#include <unistd.h>
#define pagesz 16384
#define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24)
#define SYSLIB_VERSION 1
struct Syslib {
int magic;
int version;
long (*fork)(void);
long (*pipe)(int[2]);
long (*clock_gettime)(int, struct timespec *);
long (*nanosleep)(const struct timespec *, struct timespec *);
long (*mmap)(void *, size_t, int, int, int, off_t);
int (*pthread_jit_write_protect_supported_np)(void);
void (*pthread_jit_write_protect_np)(int);
void (*sys_icache_invalidate)(void *, size_t);
int (*pthread_create)(pthread_t *, const pthread_attr_t *, void *(*)(void *),
void *);
void (*pthread_exit)(void *);
int (*pthread_kill)(pthread_t, int);
int (*pthread_sigmask)(int, const sigset_t *restrict, sigset_t *restrict);
int (*pthread_setname_np)(const char *);
dispatch_semaphore_t (*dispatch_semaphore_create)(long);
long (*dispatch_semaphore_signal)(dispatch_semaphore_t);
long (*dispatch_semaphore_wait)(dispatch_semaphore_t, dispatch_time_t);
dispatch_time_t (*dispatch_walltime)(const struct timespec *, int64_t);
};
#define ELFCLASS32 1
#define ELFDATA2LSB 1
#define EM_AARCH64 183
#define ET_EXEC 2
#define ET_DYN 3
#define PT_LOAD 1
#define PT_DYNAMIC 2
#define PT_INTERP 3
#define EI_CLASS 4
#define EI_DATA 5
#define PF_X 1
#define PF_W 2
#define PF_R 4
#define AT_PHDR 3
#define AT_PHENT 4
#define AT_PHNUM 5
#define AT_PAGESZ 6
#define AT_BASE 7
#define AT_ENTRY 9
#define AT_UID 11
#define AT_EUID 12
#define AT_GID 13
#define AT_EGID 14
#define AT_HWCAP 16
#define AT_HWCAP2 16
#define AT_SECURE 23
#define AT_RANDOM 25
#define AT_EXECFN 31
#define STACK_SIZE (8ul * 1024 * 1024)
#define STACK_ALIGN (sizeof(long) * 2)
#define AUXV_BYTES (sizeof(long) * 2 * 15)
// from the xnu codebase
#define _COMM_PAGE_START_ADDRESS 0x0000000FFFFFC000ul
#define _COMM_PAGE_APRR_SUPPORT (_COMM_PAGE_START_ADDRESS + 0x10C)
#define _COMM_PAGE_APRR_WRITE_ENABLE (_COMM_PAGE_START_ADDRESS + 0x110)
#define _COMM_PAGE_APRR_WRITE_DISABLE (_COMM_PAGE_START_ADDRESS + 0x118)
#define MIN(X, Y) ((Y) > (X) ? (X) : (Y))
#define MAX(X, Y) ((Y) < (X) ? (X) : (Y))
#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) \
((unsigned long)(255 & (S)[7]) << 070 | \
(unsigned long)(255 & (S)[6]) << 060 | \
(unsigned long)(255 & (S)[5]) << 050 | \
(unsigned long)(255 & (S)[4]) << 040 | \
(unsigned long)(255 & (S)[3]) << 030 | \
(unsigned long)(255 & (S)[2]) << 020 | \
(unsigned long)(255 & (S)[1]) << 010 | \
(unsigned long)(255 & (S)[0]) << 000)
struct ElfEhdr {
unsigned char e_ident[16];
unsigned short e_type;
unsigned short e_machine;
unsigned e_version;
unsigned long e_entry;
unsigned long e_phoff;
unsigned long e_shoff;
unsigned e_flags;
unsigned short e_ehsize;
unsigned short e_phentsize;
unsigned short e_phnum;
unsigned short e_shentsize;
unsigned short e_shnum;
unsigned short e_shstrndx;
};
struct ElfPhdr {
unsigned p_type;
unsigned p_flags;
unsigned long p_offset;
unsigned long p_vaddr;
unsigned long p_paddr;
unsigned long p_filesz;
unsigned long p_memsz;
unsigned long p_align;
};
union ElfEhdrBuf {
struct ElfEhdr ehdr;
char buf[8192];
};
union ElfPhdrBuf {
struct ElfPhdr phdr;
char buf[4096];
};
struct PathSearcher {
unsigned long namelen;
const char *name;
const char *syspath;
char path[1024];
};
struct ApeLoader {
union ElfEhdrBuf ehdr;
struct PathSearcher ps;
// this memory shall be discarded by the guest
//////////////////////////////////////////////
// this memory shall be known to guest program
union {
char argblock[ARG_MAX];
long numblock[ARG_MAX / sizeof(long)];
};
union ElfPhdrBuf phdr;
struct Syslib lib;
char rando[16];
};
static int ToLower(int c) {
return 'A' <= c && c <= 'Z' ? c + ('a' - 'A') : c;
}
static unsigned long StrLen(const char *s) {
unsigned long n = 0;
while (*s++) ++n;
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 const char *BaseName(const char *s) {
int c;
const char *b = "";
if (s) {
while ((c = *s++)) {
if (c == '/') {
b = s;
}
}
}
return b;
}
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) {
return s;
}
}
return 0;
}
static void *MemMove(void *a, const void *b, unsigned long n) {
long w;
char *d;
const char *s;
unsigned long i;
d = (char *)a;
s = (const char *)b;
if (d > s) {
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 {
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];
}
}
return d;
}
static char *GetEnv(char **p, const char *s) {
unsigned long i, j;
if (p) {
for (i = 0; p[i]; ++i) {
for (j = 0;; ++j) {
if (!s[j]) {
if (p[i][j] == '=') {
return p[i] + j + 1;
}
break;
}
if (s[j] != p[i][j]) {
break;
}
}
}
}
return 0;
}
static char *Utoa(char p[21], unsigned long x) {
char t;
unsigned long i, a, b;
i = 0;
do {
p[i++] = x % 10 + '0';
x = x / 10;
} while (x > 0);
p[i] = '\0';
if (i) {
for (a = 0, b = i - 1; a < b; ++a, --b) {
t = p[a];
p[a] = p[b];
p[b] = t;
}
}
return p + i;
}
static char *Itoa(char p[21], long x) {
if (x < 0) *p++ = '-', x = -(unsigned long)x;
return Utoa(p, x);
}
static void Emit(const char *s) {
write(2, s, StrLen(s));
}
static long Print(int fd, const char *s, ...) {
int c;
unsigned n;
char b[512];
__builtin_va_list va;
__builtin_va_start(va, s);
for (n = 0; s; s = __builtin_va_arg(va, const char *)) {
while ((c = *s++)) {
if (n < sizeof(b)) {
b[n++] = c;
}
}
}
__builtin_va_end(va);
return write(fd, b, n);
}
static void Perror(const char *thing, long rc, const char *reason) {
char ibuf[21];
ibuf[0] = 0;
if (rc) Itoa(ibuf, -rc);
Print(2, "ape error: ", thing, ": ", reason, rc ? " failed w/ errno " : "",
ibuf, "\n", 0l);
}
__attribute__((__noreturn__)) static void Pexit(const char *c, int failed,
const char *s) {
Perror(c, failed, s);
_exit(127);
}
static char EndsWithIgnoreCase(const char *p, unsigned long n, const char *s) {
unsigned long i, m;
if (n >= (m = StrLen(s))) {
for (i = n - m; i < n; ++i) {
if (ToLower(p[i]) != *s++) {
return 0;
}
}
return 1;
} else {
return 0;
}
}
static char IsComPath(struct PathSearcher *ps) {
return EndsWithIgnoreCase(ps->name, ps->namelen, ".com") ||
EndsWithIgnoreCase(ps->name, ps->namelen, ".exe") ||
EndsWithIgnoreCase(ps->name, ps->namelen, ".com.dbg");
}
static char AccessCommand(struct PathSearcher *ps, const char *suffix,
unsigned long pathlen) {
unsigned long suffixlen;
suffixlen = StrLen(suffix);
if (pathlen + 1 + ps->namelen + suffixlen + 1 > sizeof(ps->path)) return 0;
if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/';
MemMove(ps->path + pathlen, ps->name, ps->namelen);
MemMove(ps->path + pathlen + ps->namelen, suffix, suffixlen + 1);
return !access(ps->path, X_OK);
}
static char SearchPath(struct PathSearcher *ps, const char *suffix) {
const char *p;
unsigned long i;
for (p = ps->syspath;;) {
for (i = 0; p[i] && p[i] != ':'; ++i) {
if (i < sizeof(ps->path)) {
ps->path[i] = p[i];
}
}
if (AccessCommand(ps, suffix, i)) {
return 1;
} else if (p[i] == ':') {
p += i + 1;
} else {
return 0;
}
}
}
static char FindCommand(struct PathSearcher *ps, const char *suffix) {
if (MemChr(ps->name, '/', ps->namelen)) {
ps->path[0] = 0;
return AccessCommand(ps, suffix, 0);
}
return SearchPath(ps, suffix);
}
static char *Commandv(struct PathSearcher *ps, const char *name,
const char *syspath) {
ps->syspath = syspath ? syspath : "/bin:/usr/local/bin:/usr/bin";
if (!(ps->namelen = StrLen((ps->name = name)))) return 0;
if (ps->namelen + 1 > sizeof(ps->path)) return 0;
if (FindCommand(ps, "") || (!IsComPath(ps) && FindCommand(ps, ".com"))) {
return ps->path;
} else {
return 0;
}
}
static void pthread_jit_write_protect_np_workaround(int enabled) {
int count_start = 8192;
volatile int count = count_start;
unsigned long *addr, *other, val, val2, reread = -1;
addr = (unsigned long *)(!enabled ? _COMM_PAGE_APRR_WRITE_ENABLE
: _COMM_PAGE_APRR_WRITE_DISABLE);
other = (unsigned long *)(enabled ? _COMM_PAGE_APRR_WRITE_ENABLE
: _COMM_PAGE_APRR_WRITE_DISABLE);
switch (*(volatile unsigned char *)_COMM_PAGE_APRR_SUPPORT) {
case 1:
do {
val = *addr;
reread = -1;
asm volatile("msr\tS3_4_c15_c2_7,%0\n"
"isb\tsy\n"
: /* no outputs */
: "r"(val)
: "memory");
val2 = *addr;
asm volatile("mrs\t%0,S3_4_c15_c2_7\n"
: "=r"(reread)
: /* no inputs */
: "memory");
if (val2 == reread) {
return;
}
usleep(10);
} while (count-- > 0);
break;
case 3:
do {
val = *addr;
reread = -1;
asm volatile("msr\tS3_6_c15_c1_5,%0\n"
"isb\tsy\n"
: /* no outputs */
: "r"(val)
: "memory");
val2 = *addr;
asm volatile("mrs\t%0,S3_6_c15_c1_5\n"
: "=r"(reread)
: /* no inputs */
: "memory");
if (val2 == reread) {
return;
}
usleep(10);
} while (count-- > 0);
break;
default:
pthread_jit_write_protect_np(enabled);
return;
}
Pexit("ape", 0, "failed to set jit write protection");
}
__attribute__((__noreturn__)) static void Spawn(const char *exe, int fd,
long *sp, struct ElfEhdr *e,
struct ElfPhdr *p,
struct Syslib *lib) {
long rc;
int prot;
int flags;
int found_entry;
unsigned long dynbase;
unsigned long virtmin, virtmax;
unsigned long a, b, c, d, i, j;
/* validate elf */
found_entry = 0;
virtmin = virtmax = 0;
for (i = 0; i < e->e_phnum; ++i) {
if (p[i].p_type != PT_LOAD) {
continue;
}
if (p[i].p_filesz > p[i].p_memsz) {
Pexit(exe, 0, "ELF p_filesz exceeds p_memsz");
}
if ((p[i].p_flags & (PF_W | PF_X)) == (PF_W | PF_X)) {
Pexit(exe, 0, "Apple Silicon doesn't allow RWX memory");
}
if ((p[i].p_vaddr & (pagesz - 1)) != (p[i].p_offset & (pagesz - 1))) {
Pexit(exe, 0, "ELF p_vaddr incongruent w/ p_offset modulo 16384");
}
if (p[i].p_vaddr + p[i].p_memsz < p[i].p_vaddr ||
p[i].p_vaddr + p[i].p_memsz + (pagesz - 1) < p[i].p_vaddr) {
Pexit(exe, 0, "ELF p_vaddr + p_memsz overflow");
}
if (p[i].p_offset + p[i].p_filesz < p[i].p_offset ||
p[i].p_offset + p[i].p_filesz + (pagesz - 1) < p[i].p_offset) {
Pexit(exe, 0, "ELF p_offset + p_filesz overflow");
}
a = p[i].p_vaddr & -pagesz;
b = (p[i].p_vaddr + p[i].p_memsz + (pagesz - 1)) & -pagesz;
for (j = i + 1; j < e->e_phnum; ++j) {
if (p[j].p_type != PT_LOAD) continue;
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(exe, 0, "ELF segments overlap each others virtual memory");
}
}
if (p[i].p_flags & PF_X) {
if (p[i].p_vaddr <= e->e_entry &&
e->e_entry < p[i].p_vaddr + p[i].p_memsz) {
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(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 = (long)mmap(0, virtmax - virtmin, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (rc < 0) Pexit(exe, rc, "pie mmap");
dynbase = rc;
if (dynbase & (pagesz - 1)) {
Pexit(exe, 0, "OS mmap incongruent w/ AT_PAGESZ");
}
if (dynbase + virtmin < dynbase) {
Pexit(exe, 0, "ELF dynamic base overflow");
}
} else {
dynbase = 0;
}
/* load elf */
for (i = 0; i < e->e_phnum; ++i) {
void *addr;
unsigned long size;
if (p[i].p_type != PT_LOAD) 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) {
int prot1, prot2;
unsigned long wipe;
prot1 = prot;
prot2 = prot;
/*
* when we ask the system to map the interval [vaddr,vaddr+filesz)
* it might schlep extra file content into memory on both the left
* and the righthand side. that's because elf doesn't require that
* either side of the interval be aligned on the system page size.
*
* normally we can get away with ignoring these junk bytes. but if
* the segment defines bss memory (i.e. memsz > filesz) then we'll
* need to clear the extra bytes in the page, if they exist.
*
* since we can't do that if we're mapping a read-only page, we'll
* actually map it with write permissions and protect it afterward
*/
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 */
wipe = MIN(b - a, c - a);
if (wipe && (~prot1 & PROT_WRITE)) {
prot1 = PROT_READ | PROT_WRITE;
}
addr = (void *)(dynbase + (p[i].p_vaddr & -pagesz));
size = (p[i].p_vaddr & (pagesz - 1)) + p[i].p_filesz;
rc = (long)mmap(addr, size, prot1, flags, fd, p[i].p_offset & -pagesz);
if (rc < 0) Pexit(exe, rc, "prog mmap");
if (wipe) Bzero((void *)(dynbase + a), wipe);
if (prot2 != prot1) {
rc = mprotect(addr, size, prot2);
if (rc < 0) Pexit(exe, rc, "prog mprotect");
}
/* allocate extra bss */
if (c > b) {
flags |= MAP_ANONYMOUS;
rc = (long)mmap((void *)(dynbase + b), c - b, prot, flags, -1, 0);
if (rc < 0) Pexit(exe, rc, "extra bss mmap");
}
} else {
/* allocate pure bss */
addr = (void *)(dynbase + (p[i].p_vaddr & -pagesz));
size = (p[i].p_vaddr & (pagesz - 1)) + p[i].p_memsz;
flags |= MAP_ANONYMOUS;
rc = (long)mmap(addr, size, prot, flags, -1, 0);
if (rc < 0) Pexit(exe, rc, "bss mmap");
}
}
/* finish up */
close(fd);
register long *x0 asm("x0") = sp;
register struct Syslib *x15 asm("x15") = lib;
register long x16 asm("x16") = e->e_entry;
asm volatile("mov\tx1,#0\n\t"
"mov\tx2,#0\n\t"
"mov\tx3,#0\n\t"
"mov\tx4,#0\n\t"
"mov\tx5,#0\n\t"
"mov\tx6,#0\n\t"
"mov\tx7,#0\n\t"
"mov\tx8,#0\n\t"
"mov\tx9,#0\n\t"
"mov\tx10,#0\n\t"
"mov\tx11,#0\n\t"
"mov\tx12,#0\n\t"
"mov\tx13,#0\n\t"
"mov\tx14,#0\n\t"
"mov\tx17,#0\n\t"
"mov\tx19,#0\n\t"
"mov\tx20,#0\n\t"
"mov\tx21,#0\n\t"
"mov\tx22,#0\n\t"
"mov\tx23,#0\n\t"
"mov\tx24,#0\n\t"
"mov\tx25,#0\n\t"
"mov\tx26,#0\n\t"
"mov\tx27,#0\n\t"
"mov\tx28,#0\n\t"
"mov\tx29,#0\n\t"
"mov\tx30,#0\n\t"
"mov\tsp,x0\n\t"
"mov\tx0,#0\n\t"
"br\tx16"
: /* no outputs */
: "r"(x0), "r"(x15), "r"(x16)
: "memory");
__builtin_unreachable();
}
static const char *TryElf(struct ApeLoader *M, const char *exe, int fd,
long *sp, long *bp, char *execfn) {
long i, rc;
unsigned size;
struct ElfEhdr *e;
struct ElfPhdr *p;
/* validate elf header */
e = &M->ehdr.ehdr;
if (READ32(M->ehdr.buf) != READ32("\177ELF")) {
return "didn't embed ELF magic";
}
if (e->e_ident[EI_CLASS] == ELFCLASS32) {
return "32-bit ELF isn't supported";
}
if (e->e_type != ET_EXEC && e->e_type != ET_DYN) {
return "ELF not ET_EXEC or ET_DYN";
}
if (e->e_machine != EM_AARCH64) {
return "couldn't find ELF header with ARM64 machine type";
}
if (e->e_phentsize != sizeof(struct ElfPhdr)) {
Pexit(exe, 0, "e_phentsize is wrong");
}
size = e->e_phnum;
if ((size *= sizeof(struct ElfPhdr)) > sizeof(M->phdr.buf)) {
Pexit(exe, 0, "too many ELF program headers");
}
/* read program headers */
rc = pread(fd, M->phdr.buf, size, M->ehdr.ehdr.e_phoff);
if (rc < 0) return "failed to read ELF program headers";
if (rc != size) return "truncated read of ELF program headers";
/* bail on recoverable program header errors */
p = &M->phdr.phdr;
for (i = 0; i < e->e_phnum; ++i) {
if (p[i].p_type == PT_INTERP) {
return "ELF has PT_INTERP which isn't supported";
}
if (p[i].p_type == PT_DYNAMIC) {
return "ELF has PT_DYNAMIC which isn't supported";
}
}
/* remove empty program headers */
for (i = 0; i < e->e_phnum;) {
if (p[i].p_type == PT_LOAD && !p[i].p_memsz) {
if (i + 1 < e->e_phnum) {
MemMove(p + i, p + i + 1,
(e->e_phnum - (i + 1)) * sizeof(struct ElfPhdr));
}
--e->e_phnum;
} else {
++i;
}
}
/*
* merge adjacent loads that are contiguous with equal protection,
* which prevents our program header overlap check from needlessly
* failing later on; it also shaves away a microsecond of latency,
* since every program header requires invoking at least 1 syscall
*/
for (i = 0; i + 1 < e->e_phnum;) {
if (p[i].p_type == PT_LOAD && p[i + 1].p_type == PT_LOAD &&
((p[i].p_flags & (PF_R | PF_W | PF_X)) ==
(p[i + 1].p_flags & (PF_R | PF_W | PF_X))) &&
((p[i].p_offset + p[i].p_filesz + (pagesz - 1)) & -pagesz) -
(p[i + 1].p_offset & -pagesz) <=
pagesz &&
((p[i].p_vaddr + p[i].p_memsz + (pagesz - 1)) & -pagesz) -
(p[i + 1].p_vaddr & -pagesz) <=
pagesz) {
p[i].p_memsz = (p[i + 1].p_vaddr + p[i + 1].p_memsz) - p[i].p_vaddr;
p[i].p_filesz = (p[i + 1].p_offset + p[i + 1].p_filesz) - p[i].p_offset;
if (i + 2 < e->e_phnum) {
MemMove(p + i + 1, p + i + 2,
(e->e_phnum - (i + 2)) * sizeof(struct ElfPhdr));
}
--e->e_phnum;
} else {
++i;
}
}
/* simulate linux auxiliary values */
long auxv[][2] = {
{AT_PHDR, (long)&M->phdr.phdr}, //
{AT_PHENT, M->ehdr.ehdr.e_phentsize}, //
{AT_PHNUM, M->ehdr.ehdr.e_phnum}, //
{AT_ENTRY, M->ehdr.ehdr.e_entry}, //
{AT_PAGESZ, pagesz}, //
{AT_UID, getuid()}, //
{AT_EUID, geteuid()}, //
{AT_GID, getgid()}, //
{AT_EGID, getegid()}, //
{AT_HWCAP, 0xffb3ffffu}, //
{AT_HWCAP2, 0x181}, //
{AT_SECURE, issetugid()}, //
{AT_RANDOM, (long)M->rando}, //
{AT_EXECFN, (long)execfn}, //
{0, 0}, //
};
_Static_assert(sizeof(auxv) == AUXV_BYTES,
"Please update the AUXV_BYTES constant");
MemMove(bp, auxv, sizeof(auxv));
/* we're now ready to load */
Spawn(exe, fd, sp, e, p, &M->lib);
}
__attribute__((__noinline__)) static long sysret(long rc) {
return rc == -1 ? -errno : rc;
}
static long sys_fork(void) {
return sysret(fork());
}
static long sys_pipe(int pfds[2]) {
return sysret(pipe(pfds));
}
static long sys_clock_gettime(int clock, struct timespec *ts) {
return sysret(clock_gettime(clock, ts));
}
static long sys_nanosleep(const struct timespec *req, struct timespec *rem) {
return sysret(nanosleep(req, rem));
}
static long sys_mmap(void *addr, size_t size, int prot, int flags, int fd,
off_t off) {
return sysret((long)mmap(addr, size, prot, flags, fd, off));
}
int main(int argc, char **argv, char **envp) {
long z;
void *map;
long *sp, *bp, *ip;
int c, i, n, fd, rc;
struct ApeLoader *M;
unsigned char rando[24];
char *p, *pe, *tp, *exe, *prog, *execfn;
// generate some hard random data
if (getentropy(rando, sizeof(rando))) {
Pexit(argv[0], -1, "getentropy");
}
// make the stack look like a linux one
map = mmap((void *)(0x7f0000000000 | (long)rando[23] << 32), STACK_SIZE,
PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (map == MAP_FAILED) {
Pexit(argv[0], -1, "stack mmap");
}
// put argument block at top of allocated stack
z = (long)map;
z += STACK_SIZE - sizeof(struct ApeLoader);
z &= -_Alignof(struct ApeLoader);
M = (struct ApeLoader *)z;
// expose screwy apple libs
M->lib.magic = SYSLIB_MAGIC;
M->lib.version = SYSLIB_VERSION;
M->lib.fork = sys_fork;
M->lib.pipe = sys_pipe;
M->lib.clock_gettime = sys_clock_gettime;
M->lib.nanosleep = sys_nanosleep;
M->lib.mmap = sys_mmap;
M->lib.pthread_jit_write_protect_supported_np =
pthread_jit_write_protect_supported_np;
M->lib.pthread_jit_write_protect_np = pthread_jit_write_protect_np_workaround;
M->lib.pthread_create = pthread_create;
M->lib.pthread_exit = pthread_exit;
M->lib.pthread_kill = pthread_kill;
M->lib.pthread_sigmask = pthread_sigmask;
M->lib.pthread_setname_np = pthread_setname_np;
M->lib.dispatch_semaphore_create = dispatch_semaphore_create;
M->lib.dispatch_semaphore_signal = dispatch_semaphore_signal;
M->lib.dispatch_semaphore_wait = dispatch_semaphore_wait;
M->lib.dispatch_walltime = dispatch_walltime;
// copy system provided argument block
bp = M->numblock;
tp = M->argblock + sizeof(M->argblock);
*bp++ = argc;
for (i = 0; i < argc; ++i) {
tp -= (n = StrLen(argv[i]) + 1);
MemMove(tp, argv[i], n);
*bp++ = (long)tp;
}
*bp++ = 0;
for (i = 0; envp[i]; ++i) {
tp -= (n = StrLen(envp[i]) + 1);
MemMove(tp, envp[i], n);
*bp++ = (long)tp;
}
*bp++ = 0;
// get arguments that point into our block
sp = M->numblock;
argc = *sp;
argv = (char **)(sp + 1);
envp = (char **)(sp + 1 + argc + 1);
// xnu stores getauxval(at_execfn) in getenv("_")
execfn = argc > 0 ? argv[0] : 0;
for (i = 0; envp[i]; ++i) {
if (envp[i][0] == '_' && envp[i][1] == '=') {
execfn = envp[i] + 2;
break;
}
}
// interpret command line arguments
if (argc >= 3 && !StrCmp(argv[1], "-")) {
// if the first argument is a hyphen then we give the user the
// power to change argv[0] or omit it entirely. most operating
// systems don't permit the omission of argv[0] but we do, b/c
// it's specified by ANSI X3.159-1988.
prog = (char *)sp[3];
argc = sp[3] = sp[0] - 3;
argv = (char **)((sp += 3) + 1);
} else if (argc < 2) {
Emit("usage: ape PROG [ARGV1,ARGV2,...]\n"
" ape - PROG [ARGV0,ARGV1,...]\n"
"actually portable executable loader silicon 1.6\n"
"copyright 2023 justine alexandra roberts tunney\n"
"https://justine.lol/ape.html\n");
_exit(1);
} else {
prog = (char *)sp[2];
argc = sp[1] = sp[0] - 1;
argv = (char **)((sp += 1) + 1);
}
// search for executable
if (!(exe = Commandv(&M->ps, prog, GetEnv(envp, "PATH")))) {
Pexit(prog, 0, "not found (maybe chmod +x)");
} else if ((fd = openat(AT_FDCWD, exe, O_RDONLY)) < 0) {
Pexit(exe, -1, "open");
} else if ((rc = read(fd, M->ehdr.buf, sizeof(M->ehdr.buf))) < 0) {
Pexit(exe, -1, "read");
} else if ((unsigned long)rc < sizeof(M->ehdr.ehdr)) {
Pexit(exe, 0, "too small");
}
pe = M->ehdr.buf + rc;
// resolve argv[0] to reflect path search
if ((argc > 0 && *prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) ||
!StrCmp(BaseName(prog), argv[0])) {
tp -= (n = StrLen(exe) + 1);
MemMove(tp, exe, n);
argv[0] = tp;
}
// squeeze and align the argument block
ip = (long *)(((long)tp - AUXV_BYTES) & -sizeof(long));
ip -= (n = bp - sp);
ip = (long *)((long)ip & -STACK_ALIGN);
MemMove(ip, sp, n * sizeof(long));
bp = ip + n;
sp = ip;
// relocate the guest's random numbers
MemMove(M->rando, rando, sizeof(M->rando));
Bzero(rando, sizeof(rando));
// ape intended behavior
// 1. if file is an elf executable, it'll be used as-is
// 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 8192 bytes of file
// 5. elf program headers may appear anywhere in the binary
if (READ64(M->ehdr.buf) == READ64("MZqFpD='") ||
READ64(M->ehdr.buf) == READ64("jartsr='") ||
READ64(M->ehdr.buf) == READ64("APEDBG='")) {
for (p = M->ehdr.buf; p < pe; ++p) {
if (READ64(p) != READ64("printf '")) {
continue;
}
for (i = 0, p += 8; p + 3 < pe && (c = *p++) != '\'';) {
if (c == '\\') {
if ('0' <= *p && *p <= '7') {
c = *p++ - '0';
if ('0' <= *p && *p <= '7') {
c *= 8;
c += *p++ - '0';
if ('0' <= *p && *p <= '7') {
c *= 8;
c += *p++ - '0';
}
}
}
}
M->ehdr.buf[i++] = c;
if (i >= sizeof(M->ehdr.buf)) {
break;
}
}
if (i >= sizeof(M->ehdr.ehdr)) {
TryElf(M, exe, fd, sp, bp, execfn);
}
}
}
Pexit(exe, 0, TryElf(M, exe, fd, sp, bp, execfn));
}