Support Linux binfmt_misc and APE loading on Apple

The "no modify self" variant of Actually Portable Executable is now
supported on all platforms. If you use `$(APE_NO_MODIFY_SELF)` then
ld.bfd will embed a 4096 byte ELF binary and a 4096 byte Macho file
which are installed on the fly to ${TMPDIR:-/tmp}, which enables us
launch the executable, without needing to copy the whole executable

To prevent it from copying a tiny executable to your temp directory
you need to install the `ape` command (renamed from ape-loader), to
a system path. For example:

    # FreeBSD / NetBSD / OpenBSD
    make -j8 o//ape/ape
    cp o//ape/ape /usr/bin/ape

    # Mac OS
    # make -j8 o//ape/ape.macho
    curl https://justine.lol/ape.macho >/usr/bin/ape
    chmod +x /usr/bin/ape

On Linux you can get even more performance with the new binfmt_misc
support which makes launching non-modifying APE binaries as fast as
launching ELF executables. Running the following command:

    # Linux
    ape/apeinstall.sh

Will copy APE loader to /usr/bin/ape and register with binfmt_misc
Lastly, this change also fixes a really interesting race condition
with OpenBSD thread joining.
This commit is contained in:
Justine Tunney 2022-05-21 07:52:58 -07:00
parent 7838edae88
commit db0d8dd806
31 changed files with 1089 additions and 305 deletions

View file

@ -16,28 +16,74 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/bits.h"
#include "libc/calls/struct/metastat.internal.h"
#include "libc/calls/struct/stat.h"
#include "libc/elf/def.h"
#include "libc/elf/struct/ehdr.h"
#include "libc/elf/struct/phdr.h"
#include "libc/sysv/consts/prot.h"
#include "ape/loader.h"
#define TROUBLESHOOT 0
#define TROUBLESHOOT_OS LINUX
/**
* @fileoverview APE embeddable loader for Linux and BSD, e.g.
* @fileoverview APE Loader for GNU/Systemd and FreeBSD/NetBSD/OpenBSD
*
* We recommend using the normal APE design, where binaries assimilate
* themselves once by self-modifying the first 64 bytes. If that can't
* meet your requirements then we provide an excellent alternative.
*
* m=tiny
* make -j8 MODE=$m o/$m/ape o/$m/examples/printargs.com
* o/$m/ape/loader.elf o/$m/examples/printargs.com
* o/$m/ape/ape o/$m/examples/printargs.com
*
* This is an embeddable Actually Portable Executable interpreter. The
* `ape/ape.S` bootloader embeds this binary inside each binary that's
* linked using `$(APE_NO_MODIFY_SELF)` so it is an automated seamless
* process. the shell script at the top of the .COM files will copy it
* to `${TMPDIR:-/tmp}/ape` and call execve(). It's a zero copy
* operation in praxis since this payload uses mmap() to load the rest
* of your executable the same way the kernel does, based on ELF phdrs
* which are located in accordance with the first sh printf statement.
*
* APE executables will look for this program on the system path first
* so your APE loader may be installed to your system as follows:
*
* m=tiny
* make -j8 MODE=$m o/$m/ape/ape
* sudo cp o/$m/ape/ape /usr/bin/ape
*
* Your APE loader may be used as a shebang interpreter by doing this:
*
* #!/usr/bin/ape python.com
* # -*- python -*-
* print("hello world")
*
* However you won't need to do that, if your APE Loader is registered
* as a binfmt_misc interpreter. You can do that as follows with root:
*
* sudo cp -f o/$m/ape/ape /usr/bin
* f=/proc/sys/fs/binfmt_misc/register
* sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >$f"
*
* If the register file doesn't exist on your Linux machine then you'd
* load it using the following commands:
*
* sudo modprobe binfmt_misc
* sudo mount -t binfmt_misc none /proc/sys/fs/binfmt_misc
*
* You should now experience a performance boost, and you can also now
* use the regular shebang form:
*
* #!/usr/bin/python.com
* # -*- python -*-
* print("hello world")
*
* @note this can probably be used as a binfmt_misc interpreter
*/
#define LINUX 0
#define FREEBSD 1
#define NETBSD 2
#define OPENBSD 3
#define LINUX 1
#define METAL 2
#define WINDOWS 4
#define XNU 8
#define OPENBSD 16
#define FREEBSD 32
#define NETBSD 64
#define O_RDONLY 0
#define PROT_READ 1
@ -49,170 +95,394 @@
#define MAP_ANONYMOUS (os == LINUX ? 32 : 4096)
#define AT_EXECFN_LINUX 31
#define AT_EXECFN_NETBSD 2014
#define ELFCLASS64 2
#define ELFDATA2LSB 1
#define EM_NEXGEN32E 62
#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 X_OK 1
#define XCR0_SSE 2
#define XCR0_AVX 4
#define __NR_read (os == LINUX ? 0 : 3)
#define __NR_write (os == LINUX ? 1 : 4)
#define __NR_open (os == LINUX ? 2 : 5)
#define __NR_close (os == LINUX ? 3 : 6)
#define __NR_exit (os == LINUX ? 60 : 1)
#define __NR_mmap (os == LINUX ? 9 : os == FREEBSD ? 477 : 197)
#define __NR_fstat \
(os == LINUX ? 5 : os == FREEBSD ? 551 : os == OPENBSD ? 53 : 440)
#define Read32(S) \
((unsigned)(255 & (S)[3]) << 030 | (unsigned)(255 & (S)[2]) << 020 | \
(unsigned)(255 & (S)[1]) << 010 | (unsigned)(255 & (S)[0]) << 000)
static wontreturn void Exit(int os, long rc) {
asm volatile("syscall"
: /* no outputs */
: "a"(__NR_exit), "D"(rc)
: "memory");
unreachable;
#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 {
char os;
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;
};
static void *syscall;
static struct PathSearcher ps;
extern char __syscall_loader[];
static int ToLower(int c) {
return 'A' <= c && c <= 'Z' ? c + ('a' - 'A') : c;
}
static void Close(int os, long fd) {
static char *MemCpy(char *d, const char *s, unsigned long n) {
unsigned long i = 0;
for (; i < n; ++i) d[i] = s[i];
return d + n;
}
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);
}
#if TROUBLESHOOT
const char *DescribeOs(int os) {
if (os == LINUX) {
return "GNU/SYSTEMD";
} else if (os == XNU) {
return "XNU";
} else if (os == FREEBSD) {
return "FREEBSD";
} else if (os == OPENBSD) {
return "OPENBSD";
} else if (os == NETBSD) {
return "NETBSD";
} else {
return "WUT";
}
}
#endif
__attribute__((__noreturn__)) static void Exit(long rc, int os) {
asm volatile("call\t*%2"
: /* no outputs */
: "a"((os == LINUX ? 60 : 1) | (os == XNU ? 0x2000000 : 0)),
"D"(rc), "m"(syscall)
: "memory");
__builtin_unreachable();
}
static void Close(long fd, int os) {
long ax, di;
asm volatile("syscall"
asm volatile("call\t*%4"
: "=a"(ax), "=D"(di)
: "0"(__NR_close), "1"(fd)
: "0"((os == LINUX ? 3 : 6) | (os == XNU ? 0x2000000 : 0)),
"1"(fd), "m"(syscall)
: "rcx", "rdx", "rsi", "r8", "r9", "r10", "r11", "memory", "cc");
}
static long Read(int os, long fd, void *data, unsigned long size) {
bool cf;
static long Read(long fd, void *data, unsigned long size, int os) {
long ax, di, si, dx;
asm volatile("clc\n\t"
"syscall"
: "=@ccc"(cf), "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "1"(__NR_read), "2"(fd), "3"(data), "4"(size)
asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"((os == LINUX ? 0 : 3) | (os == XNU ? 0x2000000 : 0)),
"1"(fd), "2"(data), "3"(size), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory");
if (cf) ax = -ax;
return ax;
}
static void Write(int os, long fd, const void *data, unsigned long size) {
static void Write(long fd, const void *data, unsigned long size, int os) {
long ax, di, si, dx;
asm volatile("syscall"
asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"(__NR_write), "1"(fd), "2"(data), "3"(size)
: "0"((os == LINUX ? 1 : 4) | (os == XNU ? 0x2000000 : 0)),
"1"(fd), "2"(data), "3"(size), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc");
}
static long Fstat(int os, long fd, union metastat *st) {
long ax, di, si;
asm volatile("syscall"
: "=a"(ax), "=D"(di), "=S"(si)
: "0"(__NR_fstat), "1"(fd), "2"(st)
: "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc");
static void Execve(const char *prog, char **argv, char **envp, int os) {
long ax, di, si, dx;
asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"((59) | (os == XNU ? 0x2000000 : 0)), "1"(prog), "2"(argv),
"3"(envp), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc");
}
static long Access(const char *path, int mode, int os) {
long ax, dx, di, si;
asm volatile("call\t*%7"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"((os == LINUX ? 21 : 33) | (os == XNU ? 0x2000000 : 0)),
"1"(path), "2"(mode), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc");
return ax;
}
static void Msyscall(int os, long p, long n) {
static void Msyscall(long p, long n, int os) {
long ax, di, si;
if (os == OPENBSD) {
asm volatile("syscall"
asm volatile("call\t*%6"
: "=a"(ax), "=D"(di), "=S"(si)
: "0"(37), "1"(p), "2"(n)
: "0"(37), "1"(p), "2"(n), "m"(syscall)
: "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc");
}
}
static long Open(int os, const char *path, long flags, long mode) {
bool cf;
static long Open(const char *path, long flags, long mode, int os) {
long ax, di, si, dx;
asm volatile("clc\n\t"
"syscall"
: "=@ccc"(cf), "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "1"(__NR_open), "2"(path), "3"(flags), "4"(mode)
asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"((os == LINUX ? 2 : 5) | (os == XNU ? 0x2000000 : 0)),
"1"(path), "2"(flags), "3"(mode), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory");
if (cf) ax = -ax;
return ax;
}
static long Mmap(int os, long addr, long size, long prot, long flags, long fd,
long off) {
bool cf;
__attribute__((__noinline__)) long Mmap(long addr, long size, long prot,
long flags, long fd, long off, int os) {
long ax;
register long flags_ asm("r10") = flags;
register long fd_ asm("r8") = fd;
register long off_ asm("r9") = off;
asm volatile("push\t%%r9\n\t"
"push\t%%r9\n\t"
"clc\n\t"
"syscall\n\t"
"pop\t%%r9\n\t"
"call\t*%8\n\t"
"pop\t%%r9"
: "=@ccc"(cf), "=a"(ax)
: "1"(__NR_mmap), "D"(addr), "S"(size), "d"(prot), "r"(flags_),
"r"(fd_), "r"(off_)
: "=a"(ax)
: "0"((os == LINUX ? 9
: os == FREEBSD ? 477
: 197) |
(os == XNU ? 0x2000000 : 0)),
"D"(addr), "S"(size), "d"(prot), "r"(flags_), "r"(fd_),
"r"(off_), "m"(syscall)
: "rcx", "r11", "memory");
if (cf) ax = -ax;
return ax;
}
static size_t GetFdSize(int os, int fd) {
union metastat st;
if (!Fstat(os, fd, &st)) {
if (os == LINUX) {
return st.linux.st_size;
} else if (os == FREEBSD) {
return st.freebsd.st_size;
} else if (os == OPENBSD) {
return st.openbsd.st_size;
} else {
return st.netbsd.st_size;
static void Emit(int os, const char *s) {
Write(2, s, StrLen(s), os);
}
static void Perror(int os, const char *c, int rc, const char *s) {
char ibuf[21];
Emit(os, "ape error: ");
Emit(os, c);
Emit(os, ": ");
Emit(os, s);
if (rc) {
Emit(os, " failed errno=");
Itoa(ibuf, -rc);
Emit(os, ibuf);
}
Emit(os, "\n");
}
__attribute__((__noreturn__)) static void Pexit(int os, const char *c, int rc,
const char *s) {
Perror(os, c, rc, s);
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 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 size_t Length(const char *s) {
size_t n = 0;
while (*s++) ++n;
return n;
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 void Emit(int os, const char *s) {
Write(os, 2, s, Length(s));
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++] = '/';
MemCpy(ps->path + pathlen, ps->name, ps->namelen);
MemCpy(ps->path + pathlen + ps->namelen, suffix, suffixlen + 1);
return !Access(ps->path, X_OK, ps->os);
}
static void Log(int os, const char *s) {
#ifndef NDEBUG
Emit(os, "ape loader error: ");
Emit(os, s);
#endif
}
static void Spawn(int os, int fd, long *sp, char *b, struct Elf64_Ehdr *e) {
size_t i;
int prot, flags;
long code, codesize;
struct Elf64_Phdr *p;
if (e->e_ident[EI_CLASS] != ELFCLASS64) {
Log(os, "EI_CLASS != ELFCLASS64\n");
return;
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;
}
}
if (e->e_ident[EI_DATA] != ELFDATA2LSB) {
Log(os, "EI_CLASS != ELFCLASS64\n");
return;
}
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, int os, const char *name,
const char *syspath) {
ps->os = os;
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;
}
}
__attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd,
long *sp, char *page,
struct ElfEhdr *e) {
long rc;
unsigned long i;
int prot, flags;
struct ElfPhdr *p;
long code, codesize;
if (e->e_type != ET_EXEC) {
Pexit(os, exe, 0, "ELF e_type != ET_EXEC");
}
if (e->e_machine != EM_NEXGEN32E) {
Log(os, "e_machine != EM_NEXGEN32E\n");
return;
Pexit(os, exe, 0, "ELF e_machine != EM_NEXGEN32E");
}
if (e->e_type != ET_EXEC) {
Log(os, "e_type != ET_EXEC\n");
return;
if (e->e_ident[EI_CLASS] != ELFCLASS64) {
Pexit(os, exe, 0, "ELF e_ident[EI_CLASS] != ELFCLASS64");
}
if (e->e_ident[EI_DATA] != ELFDATA2LSB) {
Pexit(os, exe, 0, "ELF e_ident[EI_DATA] != ELFDATA2LSB");
}
if (e->e_phoff + e->e_phnum * sizeof(*p) > 0x1000) {
Log(os, "phnum out of bounds\n");
return;
Pexit(os, exe, 0, "ELF phdrs need to be in first page");
}
code = 0;
codesize = 0;
for (p = (struct Elf64_Phdr *)(b + e->e_phoff), i = e->e_phnum; i--;) {
for (p = (struct ElfPhdr *)(page + e->e_phoff), i = e->e_phnum; i--;) {
if (p[i].p_type == PT_DYNAMIC) {
Pexit(os, exe, 0, "not a real executable");
}
if (p[i].p_type != PT_LOAD) continue;
if ((p[i].p_vaddr | p[i].p_filesz | p[i].p_memsz | p[i].p_offset) & 0xfff) {
Log(os, "ape requires strict page size padding and alignment\n");
return;
Pexit(os, exe, 0, "APE phdrs must be 4096-aligned and 4096-padded");
}
prot = 0;
flags = MAP_FIXED | MAP_PRIVATE;
@ -228,107 +498,189 @@ static void Spawn(int os, int fd, long *sp, char *b, struct Elf64_Ehdr *e) {
codesize = p[i].p_filesz;
}
if (p[i].p_memsz > p[i].p_filesz) {
if (Mmap(os, p[i].p_vaddr + p[i].p_filesz, p[i].p_memsz - p[i].p_filesz,
prot, flags | MAP_ANONYMOUS, -1, 0) < 0) {
Log(os, "bss mmap failed\n");
return;
if ((rc = Mmap(p[i].p_vaddr + p[i].p_filesz, p[i].p_memsz - p[i].p_filesz,
prot, flags | MAP_ANONYMOUS, -1, 0, os)) < 0) {
Pexit(os, exe, rc, "bss mmap()");
}
}
if (p[i].p_filesz) {
if (Mmap(os, p[i].p_vaddr, p[i].p_filesz, prot, flags, fd,
p[i].p_offset) < 0) {
Log(os, "image mmap failed\n");
return;
if ((rc = Mmap(p[i].p_vaddr, p[i].p_filesz, prot, flags, fd,
p[i].p_offset, os)) < 0) {
Pexit(os, exe, rc, "image mmap()");
}
}
}
Close(os, fd);
Msyscall(os, code, codesize);
sp[1] = sp[0] - 1;
++sp;
asm volatile("mov\t%2,%%rsp\n\t"
"jmpq\t*%1"
if (!code) {
Pexit(os, exe, 0, "ELF needs PT_LOAD phdr w/ PF_X");
}
Close(fd, os);
Msyscall(code, codesize, os);
#if TROUBLESHOOT
Emit(TROUBLESHOOT_OS, "preparing to jump\n");
#endif
register long r8 asm("r8") = syscall;
asm volatile("xor\t%%eax,%%eax\n\t"
"xor\t%%ebx,%%ebx\n\t"
"xor\t%%r9d,%%r9d\n\t"
"xor\t%%r10d,%%r10d\n\t"
"xor\t%%r11d,%%r11d\n\t"
"xor\t%%r12d,%%r12d\n\t"
"xor\t%%r13d,%%r13d\n\t"
"xor\t%%r14d,%%r14d\n\t"
"xor\t%%r15d,%%r15d\n\t"
"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"(os == FREEBSD ? sp : 0), "S"(e->e_entry), "d"(sp)
: "D"(os == FREEBSD ? sp : 0), "S"(e->e_entry), "d"(sp), "c"(os),
"r"(r8)
: "memory");
unreachable;
__builtin_unreachable();
}
void loader(long di, long *sp) {
size_t size;
__attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl,
struct ApeLoader *handoff) {
long rc, *auxv;
char *p, **argv;
struct ElfEhdr *ehdr;
int c, i, fd, os, argc;
union {
struct Elf64_Ehdr ehdr;
char *p, *exe, *prog, **argv, **envp, *page;
static union {
struct ElfEhdr ehdr;
char p[0x1000];
} u;
os = 0;
// detect freebsd
if (di) {
os = FREEBSD;
sp = (long *)di;
} else if (dl == XNU) {
os = XNU;
} else {
os = LINUX;
}
// extract arguments
argc = *sp;
argv = (char **)(sp + 1);
auxv = (long *)(argv + argc + 1);
envp = (char **)(sp + 1 + argc + 1);
auxv = (long *)(sp + 1 + argc + 1);
for (;;) {
if (!*auxv++) {
break;
}
}
if (!auxv[0]) {
os = OPENBSD;
// get syscall function pointer
if (handoff && handoff->syscall) {
syscall = handoff->syscall;
} else {
syscall = __syscall_loader;
}
for (; auxv[0]; auxv += 2) {
if (!os) {
if (auxv[0] == AT_EXECFN_NETBSD) {
os = NETBSD;
if (argc > 1) {
auxv[1] = (long)argv[1];
}
} else if (auxv[0] == AT_EXECFN_LINUX) {
if (argc > 1) {
auxv[1] = (long)argv[1];
if (handoff) {
// we were called by ape_execve()
// no argument parsing is needed
// no path searching is needed
exe = handoff->prog;
fd = handoff->fd;
os = handoff->os;
exe = handoff->prog;
page = handoff->page;
ehdr = (struct ElfEhdr *)handoff->page;
} else {
// detect openbsd
if (!auxv[0]) {
os = OPENBSD;
}
// detect netbsd
if (os == LINUX) {
for (; auxv[0]; auxv += 2) {
if (auxv[0] == AT_EXECFN_NETBSD) {
os = NETBSD;
break;
}
}
}
// 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
// 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(os, "usage: ape PROG [ARGV1,ARGV2,...]\n"
" ape - PROG [ARGV0,ARGV1,...]\n"
"αcτµαlly pδrταblε εxεcµταblε loader v1.o\n"
"copyright 2022 justine alexandra roberts tunney\n"
"https://justine.lol/ape.html\n");
Exit(1, os);
} else {
prog = (char *)sp[2];
argc = sp[1] = sp[0] - 1;
argv = (char **)((sp += 1) + 1);
}
if (!(exe = Commandv(&ps, os, prog, GetEnv(envp, "PATH")))) {
Pexit(os, prog, 0, "not found (maybe chmod +x)");
} else if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) {
Pexit(os, exe, fd, "open");
} else if ((rc = Read(fd, u.p, sizeof(u.p), os)) < 0) {
Pexit(os, exe, rc, "read");
} else if (rc != sizeof(u.p) && Read32(u.p) != Read32("\177ELF")) {
Pexit(os, exe, 0, "too small");
}
page = u.p;
ehdr = &u.ehdr;
}
if (argc < 2) {
Emit(os, "usage: loader PROG [ARGS...]\n");
} else if ((fd = Open(os, argv[1], O_RDONLY, 0)) < 0) {
Log(os, "open failed\n");
} else if ((rc = Read(os, fd, u.p, sizeof(u.p))) < 0) {
Log(os, "read failed\n");
} else if (rc != sizeof(u.p)) {
Log(os, "file too small\n");
} else if (READ32LE(u.p) == READ32LE("\177ELF")) {
Spawn(os, fd, sp, u.p, &u.ehdr);
} else {
for (p = u.p; p < u.p + sizeof(u.p); ++p) {
if (READ64LE(p) == READ64LE("printf '")) {
for (i = 0, p += 8; p + 3 < u.p + sizeof(u.p) && (c = *p++) != '\'';) {
if (c == '\\') {
#if TROUBLESHOOT
Emit(TROUBLESHOOT_OS, "os = ");
Emit(TROUBLESHOOT_OS, DescribeOs(os));
Emit(TROUBLESHOOT_OS, "\n");
for (i = 0; i < argc; ++i) {
Emit(TROUBLESHOOT_OS, "argv = ");
Emit(TROUBLESHOOT_OS, argv[i]);
Emit(TROUBLESHOOT_OS, "\n");
}
#endif
if (Read32(page) == Read32("\177ELF") || Read32(page) == 0xFEEDFACE + 1) {
Close(fd, os);
Execve(exe, argv, envp, os);
}
for (p = page; p < page + sizeof(u.p); ++p) {
if (Read64(p) != Read64("printf '")) continue;
for (i = 0, p += 8; p + 3 < page + sizeof(u.p) && (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 = *p++ - '0';
if ('0' <= *p && *p <= '7') {
c *= 8;
c += *p++ - '0';
if ('0' <= *p && *p <= '7') {
c *= 8;
c += *p++ - '0';
}
}
c *= 8;
c += *p++ - '0';
}
}
u.p[i++] = c;
}
if (i >= 64 && READ32LE(u.p) == READ32LE("\177ELF")) {
Spawn(os, fd, sp, u.p, &u.ehdr);
Exit(os, 127);
}
}
page[i++] = c;
}
if (i >= 64 && Read32(page) == Read32("\177ELF")) {
Spawn(os, exe, fd, sp, page, ehdr);
}
Log(os, "could not find printf elf in first page\n");
}
Exit(os, 127);
Pexit(os, exe, 0, "could not find printf elf in first page");
}