mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
336 lines
10 KiB
C
336 lines
10 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 "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"
|
||
|
|
||
|
/**
|
||
|
* @fileoverview APE embeddable loader for Linux and BSD, e.g.
|
||
|
*
|
||
|
* m=tiny
|
||
|
* make -j8 MODE=$m o/$m/ape o/$m/examples/printargs.com
|
||
|
* o/$m/ape/loader.elf o/$m/examples/printargs.com
|
||
|
*
|
||
|
* @note this can probably be used as a binfmt_misc interpreter
|
||
|
*/
|
||
|
|
||
|
#define LINUX 0
|
||
|
#define FREEBSD 1
|
||
|
#define NETBSD 2
|
||
|
#define OPENBSD 3
|
||
|
|
||
|
#define O_RDONLY 0
|
||
|
#define PROT_READ 1
|
||
|
#define PROT_WRITE 2
|
||
|
#define PROT_EXEC 4
|
||
|
#define MAP_SHARED 1
|
||
|
#define MAP_PRIVATE 2
|
||
|
#define MAP_FIXED 16
|
||
|
#define MAP_ANONYMOUS (os == LINUX ? 32 : 4096)
|
||
|
#define AT_EXECFN_LINUX 31
|
||
|
#define AT_EXECFN_NETBSD 2014
|
||
|
|
||
|
#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)
|
||
|
|
||
|
static wontreturn void Exit(int os, long rc) {
|
||
|
asm volatile("syscall"
|
||
|
: /* no outputs */
|
||
|
: "a"(__NR_exit), "D"(rc)
|
||
|
: "memory");
|
||
|
unreachable;
|
||
|
}
|
||
|
|
||
|
static void Close(int os, long fd) {
|
||
|
long ax;
|
||
|
asm volatile("syscall"
|
||
|
: "=a"(ax)
|
||
|
: "0"(__NR_close), "D"(fd)
|
||
|
: "rcx", "rdx", "rsi", "r8", "r9", "r10", "r11", "memory", "cc");
|
||
|
}
|
||
|
|
||
|
static long Read(int os, long fd, void *data, unsigned long size) {
|
||
|
bool cf;
|
||
|
long ax;
|
||
|
asm volatile("clc\n\t"
|
||
|
"syscall"
|
||
|
: "=@ccc"(cf), "=a"(ax)
|
||
|
: "1"(__NR_read), "D"(fd), "S"(data), "d"(size)
|
||
|
: "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) {
|
||
|
long ax;
|
||
|
asm volatile("syscall"
|
||
|
: "=a"(ax)
|
||
|
: "0"(__NR_write), "D"(fd), "S"(data), "d"(size)
|
||
|
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc");
|
||
|
}
|
||
|
|
||
|
static long Fstat(int os, long fd, union metastat *st) {
|
||
|
long ax;
|
||
|
asm volatile("syscall"
|
||
|
: "=a"(ax)
|
||
|
: "0"(__NR_fstat), "D"(fd), "S"(st)
|
||
|
: "rcx", "rdx", "r8", "r9", "r10", "r11", "memory");
|
||
|
return ax;
|
||
|
}
|
||
|
|
||
|
static void Msyscall(int os, long p, long n) {
|
||
|
long ax;
|
||
|
if (os == OPENBSD) {
|
||
|
asm volatile("syscall"
|
||
|
: "=a"(ax)
|
||
|
: "0"(37), "D"(p), "S"(n)
|
||
|
: "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static long Open(int os, const char *path, long flags, long mode) {
|
||
|
bool cf;
|
||
|
long ax;
|
||
|
asm volatile("clc\n\t"
|
||
|
"syscall"
|
||
|
: "=@ccc"(cf), "=a"(ax)
|
||
|
: "1"(__NR_open), "D"(path), "S"(flags), "d"(mode)
|
||
|
: "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;
|
||
|
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"
|
||
|
"pop\t%%r9"
|
||
|
: "=@ccc"(cf), "=a"(ax)
|
||
|
: "1"(__NR_mmap), "D"(addr), "S"(size), "d"(prot), "r"(flags_),
|
||
|
"r"(fd_), "r"(off_)
|
||
|
: "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;
|
||
|
}
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static size_t Length(const char *s) {
|
||
|
size_t n = 0;
|
||
|
while (*s++) ++n;
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
static void Emit(int os, const char *s) {
|
||
|
Write(os, 2, s, Length(s));
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
if (e->e_ident[EI_DATA] != ELFDATA2LSB) {
|
||
|
Log(os, "EI_CLASS != ELFCLASS64\n");
|
||
|
return;
|
||
|
}
|
||
|
if (e->e_machine != EM_NEXGEN32E) {
|
||
|
Log(os, "e_machine != EM_NEXGEN32E\n");
|
||
|
return;
|
||
|
}
|
||
|
if (e->e_type != ET_EXEC) {
|
||
|
Log(os, "e_type != ET_EXEC\n");
|
||
|
return;
|
||
|
}
|
||
|
if (e->e_phoff + e->e_phnum * sizeof(*p) > 0x1000) {
|
||
|
Log(os, "phnum out of bounds\n");
|
||
|
return;
|
||
|
}
|
||
|
code = 0;
|
||
|
codesize = 0;
|
||
|
for (p = (struct Elf64_Phdr *)(b + e->e_phoff), i = e->e_phnum; i--;) {
|
||
|
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;
|
||
|
}
|
||
|
prot = 0;
|
||
|
flags = MAP_FIXED;
|
||
|
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_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_PRIVATE | MAP_ANONYMOUS, -1, 0) < 0) {
|
||
|
Log(os, "bss mmap failed\n");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
if (p[i].p_filesz) {
|
||
|
flags |= prot & PROT_WRITE ? MAP_PRIVATE : MAP_SHARED;
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Close(os, fd);
|
||
|
Msyscall(os, code, codesize);
|
||
|
sp[1] = sp[0] - 1;
|
||
|
++sp;
|
||
|
asm volatile("mov\t%2,%%rsp\n\t"
|
||
|
"jmpq\t*%1"
|
||
|
: /* no outputs */
|
||
|
: "D"(os == FREEBSD ? sp : 0), "S"(e->e_entry), "d"(sp)
|
||
|
: "memory");
|
||
|
unreachable;
|
||
|
}
|
||
|
|
||
|
void loader(long di, long *sp) {
|
||
|
size_t size;
|
||
|
long rc, *auxv;
|
||
|
char *p, **argv;
|
||
|
int c, i, fd, os, argc;
|
||
|
union {
|
||
|
struct Elf64_Ehdr ehdr;
|
||
|
char p[0x1000];
|
||
|
} u;
|
||
|
os = 0;
|
||
|
if (di) {
|
||
|
os = FREEBSD;
|
||
|
sp = (long *)di;
|
||
|
}
|
||
|
argc = *sp;
|
||
|
argv = (char **)(sp + 1);
|
||
|
auxv = (long *)(argv + argc + 1);
|
||
|
for (;;) {
|
||
|
if (!*auxv++) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!auxv[0]) {
|
||
|
os = OPENBSD;
|
||
|
}
|
||
|
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 (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 ('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';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
u.p[i++] = c;
|
||
|
}
|
||
|
if (i >= 64 && READ32LE(u.p) == READ32LE("\177ELF")) {
|
||
|
Spawn(os, fd, sp, u.p, &u.ehdr);
|
||
|
Exit(os, 127);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Log(os, "could not find printf elf in first page\n");
|
||
|
}
|
||
|
Exit(os, 127);
|
||
|
}
|