mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
1422e96b4e
There's a new program named ape/ape-m1.c which will be used to build an embeddable binary that can load ape and elf executables. The support is mostly working so far, but still chasing down ABI issues.
800 lines
23 KiB
C
800 lines
23 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 <time.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <limits.h>
|
|
#include <pthread.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/mman.h>
|
|
#include <libkern/OSCacheControl.h>
|
|
|
|
#define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24)
|
|
#define SYSLIB_VERSION 0
|
|
|
|
struct Syslib {
|
|
int magic;
|
|
int version;
|
|
void (*exit)(int) __attribute__((__noreturn__));
|
|
long (*fork)(void);
|
|
long (*read)(int, void *, size_t);
|
|
long (*pread)(int, void *, size_t, off_t);
|
|
long (*readv)(int, const struct iovec *, int);
|
|
long (*write)(int, const void *, size_t);
|
|
long (*pwrite)(int, const void *, size_t, off_t);
|
|
long (*writev)(int, const struct iovec *, int);
|
|
long (*openat)(int, const char *, int, ...);
|
|
long (*pipe)(int[2]);
|
|
long (*close)(int);
|
|
long (*clock_gettime)(int, struct timespec *);
|
|
long (*nanosleep)(const struct timespec *, struct timespec *);
|
|
long (*mmap)(void *, size_t, int, int, int, off_t);
|
|
long (*sigaction)(int, const struct sigaction *restrict, struct sigaction *restrict);
|
|
int (*pthread_jit_write_protect_supported_np)(void);
|
|
void (*pthread_jit_write_protect_np)(int);
|
|
void (*sys_icache_invalidate)(void *, size_t);
|
|
pthread_t (*pthread_self)(void);
|
|
int (*pthread_create)(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *);
|
|
int (*pthread_detach)(pthread_t);
|
|
int (*pthread_join)(pthread_t, 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 *);
|
|
int (*pthread_key_create)(pthread_key_t *, void (*)(void *));
|
|
int (*pthread_setspecific)(pthread_key_t, const void *);
|
|
void *(*pthread_getspecific)(pthread_key_t);
|
|
};
|
|
|
|
#define TROUBLESHOOT 0
|
|
|
|
#define ELFCLASS64 2
|
|
#define ELFDATA2LSB 1
|
|
#define EM_AARCH64 183
|
|
#define ET_EXEC 2
|
|
#define PT_LOAD 1
|
|
#define PT_DYNAMIC 2
|
|
#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_ALIGN (sizeof(long) * 2)
|
|
#define AUXV_BYTES (sizeof(long) * 2 * 14)
|
|
|
|
// 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 Roundup(X, K) (((X) + (K) - 1) & -(K))
|
|
#define Rounddown(X, K) ((X) & -(K))
|
|
|
|
#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 PathSearcher {
|
|
unsigned long namelen;
|
|
const char *name;
|
|
const char *syspath;
|
|
char path[1024];
|
|
};
|
|
|
|
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[0x1000];
|
|
};
|
|
|
|
union ElfPhdrBuf {
|
|
struct ElfPhdr phdr;
|
|
char buf[0x1000];
|
|
};
|
|
|
|
static int ToLower(int c) {
|
|
return 'A' <= c && c <= 'Z' ? c + ('a' - 'A') : c;
|
|
}
|
|
|
|
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 void *MemMove(void *a, const void *b, unsigned long n) {
|
|
char *d = a;
|
|
unsigned long i;
|
|
const char *s = b;
|
|
if (d > s) {
|
|
for (i = n; i--;) {
|
|
d[i] = s[i];
|
|
}
|
|
} else {
|
|
for (i = 0; i < n; ++i) {
|
|
d[i] = s[i];
|
|
}
|
|
}
|
|
return d;
|
|
}
|
|
|
|
static unsigned long StrLen(const char *s) {
|
|
unsigned long n = 0;
|
|
while (*s++) ++n;
|
|
return n;
|
|
}
|
|
|
|
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 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 void Perror(const char *c, int failed, const char *s) {
|
|
char ibuf[21];
|
|
Emit("ape error: ");
|
|
Emit(c);
|
|
Emit(": ");
|
|
Emit(s);
|
|
if (failed) {
|
|
Emit(" failed errno=");
|
|
Itoa(ibuf, errno);
|
|
Emit(ibuf);
|
|
}
|
|
Emit("\n");
|
|
}
|
|
|
|
__attribute__((__noreturn__))
|
|
static void Pexit(const char *c, int failed, const char *s) {
|
|
Perror(c, failed, s);
|
|
_exit(127);
|
|
}
|
|
|
|
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 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) ||
|
|
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);
|
|
}
|
|
|
|
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-m1", 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) {
|
|
int prot, flags;
|
|
long code, codesize;
|
|
unsigned long a, b, i;
|
|
code = 0;
|
|
codesize = 0;
|
|
for (i = e->e_phnum; i--;) {
|
|
if (p[i].p_type == PT_DYNAMIC) {
|
|
Pexit(exe, 0, "not a real executable");
|
|
}
|
|
if (p[i].p_type != PT_LOAD) {
|
|
continue;
|
|
}
|
|
if (!p[i].p_memsz) {
|
|
continue;
|
|
}
|
|
if (p[i].p_vaddr & 0x3fff) {
|
|
Pexit(exe, 0, "APE phdr addr must be 16384-aligned");
|
|
}
|
|
if (p[i].p_offset & 0x3fff) {
|
|
Pexit(exe, 0, "APE phdr offset must be 16384-aligned");
|
|
}
|
|
if ((p[i].p_flags & (PF_W | PF_X)) == (PF_W | PF_X)) {
|
|
Pexit(exe, 0, "Apple Silicon doesn't allow RWX memory");
|
|
}
|
|
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;
|
|
code = p[i].p_vaddr;
|
|
codesize = p[i].p_filesz;
|
|
}
|
|
if (p[i].p_filesz) {
|
|
if (mmap((char *)p[i].p_vaddr, p[i].p_filesz, prot,
|
|
flags, fd, p[i].p_offset) == MAP_FAILED) {
|
|
Pexit(exe, -1, "image mmap()");
|
|
}
|
|
if ((a = Min(-p[i].p_filesz & 0x3fff, p[i].p_memsz - p[i].p_filesz))) {
|
|
MemSet((void *)(p[i].p_vaddr + p[i].p_filesz), 0, a);
|
|
}
|
|
}
|
|
if ((b = Roundup(p[i].p_memsz, 0x4000)) >
|
|
(a = Roundup(p[i].p_filesz, 0x4000))) {
|
|
if (mmap((char *)p[i].p_vaddr + a, b - a,
|
|
prot, flags | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) {
|
|
Pexit(exe, -1, "bss mmap()");
|
|
}
|
|
}
|
|
}
|
|
if (!code) {
|
|
Pexit(exe, 0, "ELF needs PT_LOAD phdr w/ PF_X");
|
|
}
|
|
|
|
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 void TryElf(const char *exe, int fd, long *sp, long *bp, char *execfn,
|
|
union ElfEhdrBuf *ehdr, union ElfPhdrBuf *phdr, struct Syslib *lib) {
|
|
unsigned long n;
|
|
if (Read32(ehdr->buf) == Read32("\177ELF") &&
|
|
ehdr->ehdr.e_type == ET_EXEC &&
|
|
ehdr->ehdr.e_machine == EM_AARCH64 &&
|
|
ehdr->ehdr.e_ident[EI_CLASS] == ELFCLASS64 &&
|
|
ehdr->ehdr.e_ident[EI_DATA] == ELFDATA2LSB &&
|
|
(n = ehdr->ehdr.e_phnum * sizeof(phdr->phdr)) <= sizeof(phdr->buf) &&
|
|
pread(fd, phdr->buf, n, ehdr->ehdr.e_phoff) == n) {
|
|
long auxv[][2] = {
|
|
{AT_PHDR, (long)&phdr->phdr}, //
|
|
{AT_PHENT, ehdr->ehdr.e_phentsize}, //
|
|
{AT_PHNUM, ehdr->ehdr.e_phnum}, //
|
|
{AT_ENTRY, ehdr->ehdr.e_entry}, //
|
|
{AT_PAGESZ, 0x4000}, //
|
|
{AT_UID, getuid()}, //
|
|
{AT_EUID, geteuid()}, //
|
|
{AT_GID, getgid()}, //
|
|
{AT_EGID, getegid()}, //
|
|
{AT_HWCAP, 0xffb3ffffu}, //
|
|
{AT_HWCAP2, 0x181}, //
|
|
{AT_SECURE, issetugid()}, //
|
|
{AT_EXECFN, (long)execfn}, //
|
|
{0, 0}, //
|
|
};
|
|
_Static_assert(sizeof(auxv) == AUXV_BYTES,
|
|
"Please update the AUXV_BYTES constant");
|
|
MemMove(bp, auxv, sizeof(auxv));
|
|
Spawn(exe, fd, sp, &ehdr->ehdr, &phdr->phdr, lib);
|
|
}
|
|
}
|
|
|
|
__attribute__((__noinline__))
|
|
static long sysret(long rc) {
|
|
return rc == -1 ? -errno : rc;
|
|
}
|
|
|
|
static long sys_fork(void) {
|
|
return sysret(fork());
|
|
}
|
|
|
|
static long sys_read(int fd, void *p, size_t n) {
|
|
return sysret(read(fd, p, n));
|
|
}
|
|
|
|
static long sys_pread(int fd, void *p, size_t n, off_t o) {
|
|
return sysret(pread(fd, p, n, o));
|
|
}
|
|
|
|
static long sys_readv(int fd, const struct iovec *p, int n) {
|
|
return sysret(readv(fd, p, n));
|
|
}
|
|
|
|
static long sys_write(int fd, const void *p, size_t n) {
|
|
return sysret(write(fd, p, n));
|
|
}
|
|
|
|
static long sys_pwrite(int fd, const void *p, size_t n, off_t o) {
|
|
return sysret(pwrite(fd, p, n, o));
|
|
}
|
|
|
|
static long sys_writev(int fd, const struct iovec *p, int n) {
|
|
return sysret(writev(fd, p, n));
|
|
}
|
|
|
|
static long sys_openat(int dirfd, const char *path, int oflag, ...) {
|
|
va_list va;
|
|
unsigned mode;
|
|
va_start(va, oflag);
|
|
mode = va_arg(va, unsigned);
|
|
va_end(va);
|
|
return sysret(openat(dirfd, path, oflag, mode));
|
|
}
|
|
|
|
static long sys_pipe(int pfds[2]) {
|
|
return sysret(pipe(pfds));
|
|
}
|
|
|
|
static long sys_close(int fd) {
|
|
return sysret(close(fd));
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
static long sys_sigaction(int sig, const struct sigaction *restrict act,
|
|
struct sigaction *restrict oact) {
|
|
return sysret(sigaction(sig, act, oact));
|
|
}
|
|
|
|
int loader(int argc, char **argv, char **envp) {
|
|
long *sp, *bp, *ip;
|
|
int c, i, n, fd, rc;
|
|
char *p, *tp, *exe, *prog, *execfn;
|
|
|
|
struct {
|
|
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;
|
|
} M;
|
|
|
|
// expose some apple libraries
|
|
M.lib.magic = SYSLIB_MAGIC;
|
|
M.lib.version = SYSLIB_VERSION;
|
|
M.lib.exit = _exit;
|
|
M.lib.fork = sys_fork;
|
|
M.lib.read = sys_read;
|
|
M.lib.pread = sys_pread;
|
|
M.lib.readv = sys_readv;
|
|
M.lib.write = sys_write;
|
|
M.lib.pwrite = sys_pwrite;
|
|
M.lib.writev = sys_writev;
|
|
M.lib.openat = sys_openat;
|
|
M.lib.close = sys_close;
|
|
M.lib.clock_gettime = sys_clock_gettime;
|
|
M.lib.nanosleep = sys_nanosleep;
|
|
M.lib.mmap = sys_mmap;
|
|
M.lib.sigaction = sys_sigaction;
|
|
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_self = pthread_self;
|
|
M.lib.pthread_create = pthread_create;
|
|
M.lib.pthread_join = pthread_join;
|
|
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.pthread_key_create = pthread_key_create;
|
|
M.lib.pthread_getspecific = pthread_getspecific;
|
|
M.lib.pthread_setspecific = pthread_setspecific;
|
|
|
|
// 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-m1 PROG [ARGV1,ARGV2,...]\n"
|
|
" ape-m1 - PROG [ARGV0,ARGV1,...]\n"
|
|
"actually portable executable loader (apple arm)\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);
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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 (rc != sizeof(M.ehdr.buf)) {
|
|
Pexit(exe, 0, "too small");
|
|
}
|
|
|
|
#if TROUBLESHOOT
|
|
for (i = 0; i < argc; ++i) {
|
|
Emit("argv = ");
|
|
Emit(argv[i]);
|
|
Emit("\n");
|
|
}
|
|
#endif
|
|
|
|
// 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 4096 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='")) {
|
|
for (p = M.ehdr.buf; p < M.ehdr.buf + sizeof(M.ehdr.buf); ++p) {
|
|
if (Read64(p) != Read64("printf '")) {
|
|
continue;
|
|
}
|
|
for (i = 0, p += 8; p + 3 < M.ehdr.buf + sizeof(M.ehdr.buf) && (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.ehdr)) {
|
|
TryElf(exe, fd, sp, bp, execfn, &M.ehdr, &M.phdr, &M.lib);
|
|
}
|
|
}
|
|
}
|
|
TryElf(exe, fd, sp, bp, execfn, &M.ehdr, &M.phdr, &M.lib);
|
|
Pexit(exe, 0, "Not an acceptable APE/ELF executable for AARCH64");
|
|
}
|
|
|
|
asm(".globl\t_main\n_main:\t"
|
|
"stp\tx29,x30,[sp,#-16]!\n\t"
|
|
"mov\tx29,sp\n\t"
|
|
"mov\tx9,sp\n\t"
|
|
"and\tsp,x9,#-(2*1024*1024)\n\t"
|
|
"bl\t_loader\n\t"
|
|
"ldp\tx29,x30,[sp],#16\n\t"
|
|
"ret");
|