mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
2271 lines
81 KiB
C
2271 lines
81 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||
│ vi: set et ft=c ts=2 sts=2 sw=2 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 "ape/ape.h"
|
||
#include "libc/assert.h"
|
||
#include "libc/calls/calls.h"
|
||
#include "libc/ctype.h"
|
||
#include "libc/dce.h"
|
||
#include "libc/dos.h"
|
||
#include "libc/elf/def.h"
|
||
#include "libc/elf/elf.h"
|
||
#include "libc/elf/scalar.h"
|
||
#include "libc/elf/struct/ehdr.h"
|
||
#include "libc/elf/struct/phdr.h"
|
||
#include "libc/fmt/conv.h"
|
||
#include "libc/fmt/itoa.h"
|
||
#include "libc/limits.h"
|
||
#include "libc/macho.h"
|
||
#include "libc/macros.h"
|
||
#include "libc/mem/mem.h"
|
||
#include "libc/nt/pedef.internal.h"
|
||
#include "libc/nt/struct/imageimportbyname.internal.h"
|
||
#include "libc/nt/struct/imageimportdescriptor.internal.h"
|
||
#include "libc/nt/struct/imagentheaders.internal.h"
|
||
#include "libc/nt/struct/imagesectionheader.internal.h"
|
||
#include "libc/runtime/runtime.h"
|
||
#include "libc/runtime/symbols.internal.h"
|
||
#include "libc/serialize.h"
|
||
#include "libc/stdalign.h"
|
||
#include "libc/stdckdint.h"
|
||
#include "libc/stdio/stdio.h"
|
||
#include "libc/str/blake2.h"
|
||
#include "libc/str/str.h"
|
||
#include "libc/sysv/consts/map.h"
|
||
#include "libc/sysv/consts/o.h"
|
||
#include "libc/sysv/consts/prot.h"
|
||
#include "libc/zip.h"
|
||
#include "third_party/getopt/getopt.internal.h"
|
||
#include "third_party/zlib/zlib.h"
|
||
#include "tool/build/lib/lib.h"
|
||
|
||
#define VERSION \
|
||
"apelink v0.1\n" \
|
||
"copyright 2023 justine tunney\n" \
|
||
"https://github.com/jart/cosmopolitan\n"
|
||
|
||
#define MANUAL \
|
||
" -o OUTPUT INPUT...\n" \
|
||
"\n" \
|
||
"DESCRIPTION\n" \
|
||
"\n" \
|
||
" actually portable executable linker\n" \
|
||
"\n" \
|
||
" this program may be used to turn elf executables into\n" \
|
||
" ape executables. it's useful for creating fat binaries\n" \
|
||
" that run on multiple architectures.\n" \
|
||
"\n" \
|
||
"FLAGS\n" \
|
||
"\n" \
|
||
" -h show usage\n" \
|
||
"\n" \
|
||
" -v show version\n" \
|
||
"\n" \
|
||
" -o OUTPUT set output path\n" \
|
||
"\n" \
|
||
" -s never embed symbol table\n" \
|
||
"\n" \
|
||
" -l PATH bundle ape loader executable [repeatable]\n" \
|
||
" if no ape loaders are specified then your\n" \
|
||
" executable will self-modify its header on\n" \
|
||
" the first run, to use the platform format\n" \
|
||
"\n" \
|
||
" -M PATH bundle ape loader source code file for m1\n" \
|
||
" processors running the xnu kernel so that\n" \
|
||
" it can be compiled on the fly by xcode\n" \
|
||
"\n" \
|
||
" -V BITS set OS support vector\n" \
|
||
"\n" \
|
||
" the default value is -1 which sets the bits\n" \
|
||
" for all supported operating systems to true\n" \
|
||
" of which the following are defined:\n" \
|
||
"\n" \
|
||
" - 1: linux\n" \
|
||
" - 2: metal\n" \
|
||
" - 4: windows\n" \
|
||
" - 8: xnu\n" \
|
||
" - 16: openbsd\n" \
|
||
" - 32: freebsd\n" \
|
||
" - 64: netbsd\n" \
|
||
"\n" \
|
||
" for example, `-V 0b1110001` may be used to\n" \
|
||
" produce ELF binaries that only support the\n" \
|
||
" truly open unix systems. in this case when\n" \
|
||
" a single input executable is supplied, the\n" \
|
||
" output file will end up being portable elf\n" \
|
||
"\n" \
|
||
" when the default of -1 is supplied, certain\n" \
|
||
" operating systems can have support detected\n" \
|
||
" by examining your symbols and sections. for\n" \
|
||
" example, xnu is detected by looking for the\n" \
|
||
" _apple() entrypoint which is defined on elf\n" \
|
||
" platforms as being e_entry. if only the XNU\n" \
|
||
" platform is supported then e_entry is used.\n" \
|
||
"\n" \
|
||
" in addition to accepting numbers, you could\n" \
|
||
" also pass strings in a variety of intuitive\n" \
|
||
" supported representations. for example, bsd\n" \
|
||
" will enable freebsd+netbsd+openbsd and that\n" \
|
||
" string too is a legal input. the -V flag is\n" \
|
||
" also repeatable, e.g. `-V nt -V xnu` to use\n" \
|
||
" the union of the two.\n" \
|
||
"\n" \
|
||
" since the support vector controls the file\n" \
|
||
" format that's outputted too you can supply\n" \
|
||
" the keywords `pe`, `elf`, & 'macho' to use\n" \
|
||
" a support vector targetting whichever OSes\n" \
|
||
" use it as a native format.\n" \
|
||
"\n" \
|
||
" -g generate a debugging friendly ape binary\n" \
|
||
"\n" \
|
||
" -G don't $PATH lookup systemwide ape loader\n" \
|
||
"\n" \
|
||
" -S CODE add custom code to start of shell script\n" \
|
||
" for example you can use `-S 'set -ex' to\n" \
|
||
" troubleshoot any issues with your script\n" \
|
||
"\n" \
|
||
" -B force bypassing of any binfmt_misc loader\n" \
|
||
" by using alternative 'APEDBG=' file magic\n" \
|
||
"\n" \
|
||
"ARGUMENTS\n" \
|
||
"\n" \
|
||
" OUTPUT is your ape executable\n" \
|
||
" INPUT specifies multiple ELF builds of the same\n" \
|
||
" program, for different architectures that\n" \
|
||
" shall be merged into a single output file\n" \
|
||
"\n"
|
||
|
||
#define ALIGN(p, a) (char *)ROUNDUP((uintptr_t)(p), (a))
|
||
|
||
enum Strategy {
|
||
kElf,
|
||
kApe,
|
||
kMacho,
|
||
kPe,
|
||
};
|
||
|
||
struct MachoLoadThread64 {
|
||
struct MachoLoadThreadCommand thread;
|
||
uint64_t regs[21];
|
||
};
|
||
|
||
struct OffsetRelocs {
|
||
int n;
|
||
uint64_t *p[64];
|
||
};
|
||
|
||
struct Input {
|
||
union {
|
||
char *map;
|
||
Elf64_Ehdr *elf;
|
||
unsigned char *umap;
|
||
};
|
||
size_t size;
|
||
const char *path;
|
||
Elf64_Off machoff;
|
||
Elf64_Off minload;
|
||
Elf64_Off maxload;
|
||
char *printf_phoff;
|
||
char *printf_phnum;
|
||
char *ddarg_macho_skip;
|
||
char *ddarg_macho_count;
|
||
int pe_offset;
|
||
int pe_e_lfanew;
|
||
int pe_SizeOfHeaders;
|
||
int size_of_pe_headers;
|
||
bool we_are_generating_pe;
|
||
bool we_must_skew_pe_vaspace;
|
||
struct NtImageNtHeaders *pe;
|
||
struct OffsetRelocs offsetrelocs;
|
||
struct MachoLoadSegment *first_macho_load;
|
||
};
|
||
|
||
struct Inputs {
|
||
int n;
|
||
struct Input p[16];
|
||
};
|
||
|
||
struct Loader {
|
||
int os;
|
||
int machine;
|
||
bool used;
|
||
void *map;
|
||
size_t size;
|
||
const char *path;
|
||
int macho_offset;
|
||
int macho_length;
|
||
char *ddarg_skip1;
|
||
char *ddarg_size1;
|
||
char *ddarg_skip2;
|
||
char *ddarg_size2;
|
||
};
|
||
|
||
struct Loaders {
|
||
int n;
|
||
struct Loader p[16];
|
||
};
|
||
|
||
struct Asset {
|
||
unsigned char *cfile;
|
||
unsigned char *lfile;
|
||
};
|
||
|
||
struct Assets {
|
||
int n;
|
||
struct Asset *p;
|
||
size_t total_centraldir_bytes;
|
||
size_t total_local_file_bytes;
|
||
};
|
||
|
||
static int outfd;
|
||
static int hashes;
|
||
static const char *prog;
|
||
static bool want_stripped;
|
||
static int support_vector;
|
||
static int macholoadcount;
|
||
static const char *outpath;
|
||
static struct Assets assets;
|
||
static struct Inputs inputs;
|
||
static char ape_heredoc[15];
|
||
static enum Strategy strategy;
|
||
static struct Loaders loaders;
|
||
static const char *custom_sh_code;
|
||
static bool force_bypass_binfmt_misc;
|
||
static bool generate_debuggable_binary;
|
||
static bool dont_path_lookup_ape_loader;
|
||
_Alignas(4096) static char prologue[1048576];
|
||
static uint8_t hashpool[BLAKE2B256_DIGEST_LENGTH];
|
||
static const char *macos_silicon_loader_source_path;
|
||
static const char *macos_silicon_loader_source_text;
|
||
static char *macos_silicon_loader_source_ddarg_skip;
|
||
static char *macos_silicon_loader_source_ddarg_size;
|
||
|
||
static Elf64_Off noteoff;
|
||
static Elf64_Xword notesize;
|
||
|
||
static char *r_off32_e_lfanew;
|
||
|
||
#include "libc/mem/tinymalloc.inc"
|
||
|
||
static wontreturn void Die(const char *thing, const char *reason) {
|
||
tinyprint(2, thing, ": ", reason, "\n", NULL);
|
||
exit(1);
|
||
}
|
||
|
||
static wontreturn void DieSys(const char *thing) {
|
||
perror(thing);
|
||
exit(1);
|
||
}
|
||
|
||
static wontreturn void ShowUsage(int rc, int fd) {
|
||
tinyprint(fd, "USAGE\n\n ", prog, MANUAL, NULL);
|
||
exit(rc);
|
||
}
|
||
|
||
static wontreturn void DieOom(void) {
|
||
Die("apelink", "out of memory");
|
||
}
|
||
|
||
static void *Malloc(size_t n) {
|
||
void *p;
|
||
if (!(p = malloc(n)))
|
||
DieOom();
|
||
return p;
|
||
}
|
||
|
||
static void *Realloc(void *p, size_t n) {
|
||
if (!(p = realloc(p, n)))
|
||
DieOom();
|
||
return p;
|
||
}
|
||
|
||
// By convention, Cosmopolitan's GetSymbolTable() function will inspect
|
||
// its own executable image to see if it's also a zip archive, in which
|
||
// case it tries to load the symbol table from an architecture-specific
|
||
// filename below, and falls back to the ".symtab" when it isn't found.
|
||
// the file format for these symbol files is unique to cosmopiltan libc
|
||
static const char *ConvertElfMachineToSymtabName(Elf64_Ehdr *e) {
|
||
switch (e->e_machine) {
|
||
case EM_NEXGEN32E:
|
||
return ".symtab.amd64";
|
||
case EM_AARCH64:
|
||
return ".symtab.arm64";
|
||
case EM_PPC64:
|
||
return ".symtab.ppc64";
|
||
case EM_S390:
|
||
return ".symtab.s390x";
|
||
case EM_RISCV:
|
||
return ".symtab.riscv";
|
||
default:
|
||
Die(prog, "unsupported architecture");
|
||
}
|
||
}
|
||
|
||
static const char *DescribePhdrType(Elf64_Word p_type) {
|
||
static char buf[12];
|
||
switch (p_type) {
|
||
case PT_LOAD:
|
||
return "LOAD";
|
||
case PT_TLS:
|
||
return "TLS";
|
||
case PT_PHDR:
|
||
return "PHDR";
|
||
case PT_NOTE:
|
||
return "NOTE";
|
||
case PT_INTERP:
|
||
return "INTERP";
|
||
case PT_DYNAMIC:
|
||
return "DYNAMIC";
|
||
case PT_GNU_STACK:
|
||
return "GNU_STACK";
|
||
default:
|
||
FormatInt32(buf, p_type);
|
||
return buf;
|
||
}
|
||
}
|
||
|
||
static bool IsBinary(const char *p, size_t n) {
|
||
size_t i;
|
||
for (i = 0; i < n; ++i) {
|
||
if ((p[i] & 255) < '\t') {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static void BlendHashes(uint8_t out[static BLAKE2B256_DIGEST_LENGTH],
|
||
uint8_t inp[static BLAKE2B256_DIGEST_LENGTH]) {
|
||
int i;
|
||
for (i = 0; i < BLAKE2B256_DIGEST_LENGTH; ++i) {
|
||
out[i] ^= inp[i];
|
||
}
|
||
}
|
||
|
||
static void HashInput(const void *data, size_t size) {
|
||
uint8_t digest[BLAKE2B256_DIGEST_LENGTH];
|
||
uint32_t hash = crc32_z(hashes, data, size); // 30 GB/s
|
||
BLAKE2B256(&hash, sizeof(hash), digest); // .6 GB/s
|
||
BlendHashes(hashpool, digest);
|
||
++hashes;
|
||
}
|
||
|
||
static void HashInputString(const char *str) {
|
||
HashInput(str, strlen(str));
|
||
}
|
||
|
||
static char *LoadSourceCode(const char *path) {
|
||
int fd;
|
||
size_t i;
|
||
char *text;
|
||
ssize_t rc, size;
|
||
if ((fd = open(path, O_RDONLY)) == -1)
|
||
DieSys(path);
|
||
if ((size = lseek(fd, 0, SEEK_END)) == -1)
|
||
DieSys(path);
|
||
text = Malloc(size + 1);
|
||
text[size] = 0;
|
||
for (i = 0; i < size; i += rc) {
|
||
if ((rc = pread(fd, text, size - i, i)) <= 0) {
|
||
DieSys(path);
|
||
}
|
||
if (IsBinary(text, rc)) {
|
||
Die(path, "source code contains binary data");
|
||
}
|
||
}
|
||
if (close(fd))
|
||
DieSys(path);
|
||
HashInput(text, size);
|
||
return text;
|
||
}
|
||
|
||
static void Pwrite(const void *data, size_t size, uint64_t offset) {
|
||
ssize_t rc;
|
||
const char *p, *e;
|
||
for (p = data, e = p + size; p < e; p += (size_t)rc, offset += (size_t)rc) {
|
||
if ((rc = pwrite(outfd, p, e - p, offset)) == -1) {
|
||
DieSys(outpath);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void LogElfPhdrs(FILE *f, Elf64_Phdr *p, size_t n) {
|
||
size_t i;
|
||
fprintf(f, "Type "
|
||
"Offset "
|
||
"VirtAddr "
|
||
"PhysAddr "
|
||
"FileSiz "
|
||
"MemSiz "
|
||
"Flg Align\n");
|
||
for (i = 0; i < n; ++i) {
|
||
fprintf(f,
|
||
"%-14s 0x%06lx 0x%016lx 0x%016lx 0x%06lx 0x%06lx %c%c%c 0x%04lx\n",
|
||
DescribePhdrType(p[i].p_type), p[i].p_offset, p[i].p_vaddr,
|
||
p[i].p_paddr, p[i].p_filesz, p[i].p_memsz,
|
||
p[i].p_flags & PF_R ? 'R' : ' ', p[i].p_flags & PF_W ? 'W' : ' ',
|
||
p[i].p_flags & PF_X ? 'E' : ' ', p[i].p_align);
|
||
}
|
||
}
|
||
|
||
static void LogPeSections(FILE *f, struct NtImageSectionHeader *p, size_t n) {
|
||
size_t i;
|
||
fprintf(f, "Name "
|
||
"Offset "
|
||
"RelativeVirtAddr "
|
||
"PhysAddr "
|
||
"FileSiz "
|
||
"MemSiz "
|
||
"Flg\n");
|
||
for (i = 0; i < n; ++i) {
|
||
fprintf(f, "%-14.8s 0x%06lx 0x%016lx 0x%016lx 0x%06lx 0x%06lx %c%c%c\n",
|
||
p[i].Name, p[i].PointerToRawData, p[i].VirtualAddress,
|
||
p[i].VirtualAddress, p[i].SizeOfRawData, p[i].Misc.VirtualSize,
|
||
p[i].Characteristics & kNtPeSectionMemRead ? 'R' : ' ',
|
||
p[i].Characteristics & kNtPeSectionMemWrite ? 'W' : ' ',
|
||
p[i].Characteristics & kNtPeSectionMemExecute ? 'E' : ' ');
|
||
}
|
||
}
|
||
|
||
static void ValidateElfImage(Elf64_Ehdr *e, Elf64_Off esize, //
|
||
const char *epath, bool isloader) {
|
||
|
||
// validate elf header
|
||
if (e->e_type != ET_EXEC && e->e_type != ET_DYN)
|
||
Die(epath, "elf binary isn't an executable");
|
||
if (!e->e_phnum)
|
||
Die(epath, "elf executable needs at least one program header");
|
||
if (e->e_phnum > 65534)
|
||
Die(epath, "elf with more than 65534 phdrs not supported");
|
||
if (e->e_phentsize != sizeof(Elf64_Phdr))
|
||
Die(epath, "elf e_phentsize isn't sizeof(Elf64_Phdr)");
|
||
if (e->e_phoff > esize)
|
||
Die(epath, "elf program header offset points past image eof");
|
||
if (e->e_phoff & 7)
|
||
Die(epath, "elf e_phoff must be aligned on an 8-byte boundary");
|
||
if (e->e_phoff + e->e_phoff + e->e_phnum * sizeof(Elf64_Phdr) > esize)
|
||
Die(epath, "elf program header array overlaps image eof");
|
||
|
||
// determine microprocessor page size requirement
|
||
//
|
||
// even though operating systems (windows) and c libraries (cosmo)
|
||
// sometimes impose a larger page size requirement than the actual
|
||
// microprocessor itself, the cpu page size is still the only page
|
||
// size that actually matters when it comes to executable loading.
|
||
unsigned long pagesz;
|
||
if (e->e_machine == EM_AARCH64) {
|
||
pagesz = 16384; // apple m1 (xnu, linux)
|
||
} else { //
|
||
pagesz = 4096; // x86-64, raspberry pi
|
||
}
|
||
|
||
// remove empty segment loads
|
||
// empty loads should be inconsequential; linux ignores them
|
||
// however they tend to trip up many systems such as openbsd
|
||
int i, j;
|
||
Elf64_Phdr *p = (Elf64_Phdr *)((char *)e + e->e_phoff);
|
||
|
||
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(*p));
|
||
}
|
||
--e->e_phnum;
|
||
} else {
|
||
++i;
|
||
}
|
||
}
|
||
|
||
// oracle says loadable segment entries in the program header table
|
||
// appear in ascending order, sorted on the p_vaddr member.
|
||
int found_load = 0;
|
||
Elf64_Addr last_vaddr = 0;
|
||
for (i = 0; i < e->e_phnum; ++i) {
|
||
if (p[i].p_type != PT_LOAD)
|
||
continue;
|
||
if (found_load && p[i].p_vaddr <= last_vaddr) {
|
||
Die(epath, "ELF PT_LOAD segments must be ordered by p_vaddr");
|
||
}
|
||
last_vaddr = p[i].p_vaddr;
|
||
found_load = 1;
|
||
}
|
||
if (!found_load) {
|
||
Die(epath, "ELF must have at least one PT_LOAD segment");
|
||
}
|
||
|
||
// merge adjacent loads that are contiguous with equal protection
|
||
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(*p));
|
||
}
|
||
--e->e_phnum;
|
||
} else {
|
||
++i;
|
||
}
|
||
}
|
||
|
||
// validate program headers
|
||
bool found_entry = false;
|
||
for (i = 0; i < e->e_phnum; ++i) {
|
||
if (!isloader && p[i].p_type == PT_INTERP) {
|
||
Die(epath, "ELF has PT_INTERP which isn't supported");
|
||
}
|
||
if (!isloader && p[i].p_type == PT_DYNAMIC) {
|
||
Die(epath, "ELF has PT_DYNAMIC which isn't supported");
|
||
}
|
||
if (p[i].p_type != PT_LOAD) {
|
||
continue;
|
||
}
|
||
if (!p[i].p_memsz) {
|
||
Die(epath, "ELF PT_LOAD p_memsz was zero");
|
||
}
|
||
if (p[i].p_offset > esize) {
|
||
Die(epath, "ELF PT_LOAD p_offset points past EOF");
|
||
}
|
||
if (p[i].p_filesz > p[i].p_memsz) {
|
||
Die(epath, "ELF PT_LOAD p_filesz exceeds p_memsz");
|
||
}
|
||
if (p[i].p_align > 1 && (p[i].p_align & (p[i].p_align - 1))) {
|
||
Die(epath, "ELF PT_LOAD p_align must be two power");
|
||
}
|
||
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) {
|
||
Die(epath, "ELF PT_LOAD 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) {
|
||
Die(epath, "ELF PT_LOAD p_offset + p_filesz overflow");
|
||
}
|
||
if (p[i].p_align > 1 && ((p[i].p_vaddr & (p[i].p_align - 1)) !=
|
||
(p[i].p_offset & (p[i].p_align - 1)))) {
|
||
Die(epath, "ELF p_vaddr incongruent w/ p_offset modulo p_align");
|
||
}
|
||
if ((p[i].p_vaddr & (pagesz - 1)) != (p[i].p_offset & (pagesz - 1))) {
|
||
Die(epath, "ELF p_vaddr incongruent w/ p_offset modulo AT_PAGESZ");
|
||
}
|
||
if (p[i].p_offset + p[i].p_filesz > esize) {
|
||
Die(epath, "ELF PT_LOAD p_offset and p_filesz overlaps image EOF");
|
||
}
|
||
Elf64_Off a = p[i].p_vaddr & -pagesz;
|
||
Elf64_Off 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;
|
||
Elf64_Off c = p[j].p_vaddr & -pagesz;
|
||
Elf64_Off d = (p[j].p_vaddr + p[j].p_memsz + (pagesz - 1)) & -pagesz;
|
||
if (MAX(a, c) < MIN(b, d)) {
|
||
Die(epath,
|
||
"ELF PT_LOAD phdrs %d and %d overlap each others virtual memory");
|
||
}
|
||
}
|
||
if (e->e_machine == EM_AARCH64 &&
|
||
p[i].p_vaddr + p[i].p_memsz > 0x0001000000000000) {
|
||
Die(epath, "this is an ARM ELF program but it loads to virtual"
|
||
" memory addresses outside the legal range [0,2^48)");
|
||
}
|
||
if (e->e_machine == EM_NEXGEN32E &&
|
||
!(-140737488355328 <= (Elf64_Sxword)p[i].p_vaddr &&
|
||
(Elf64_Sxword)(p[i].p_vaddr + p[i].p_memsz) <= 140737488355328)) {
|
||
Die(epath, "this is an x86-64 ELF program, but it loads to virtual"
|
||
" memory addresses outside the legal range [-2^47,2^47)");
|
||
}
|
||
if (e->e_type == ET_EXEC && //
|
||
(support_vector & _HOSTMETAL) && //
|
||
(Elf64_Sxword)p[i].p_vaddr < 0x200000) {
|
||
Die(epath, "ELF program header loads to address less than 2mb even"
|
||
" though you have userspace OSes in your support vector");
|
||
}
|
||
if (!isloader && //
|
||
e->e_type == ET_EXEC && //
|
||
!(p[i].p_vaddr >> 32) && //
|
||
e->e_machine == EM_AARCH64 && //
|
||
(support_vector & _HOSTXNU)) {
|
||
Die(epath, "ELF program header loads to address less than 4gb even"
|
||
" though you have XNU on AARCH64 in your support vector"
|
||
" which forbids 32-bit pointers without any compromises");
|
||
}
|
||
if ((p[i].p_flags & (PF_W | PF_X)) == (PF_W | PF_X)) {
|
||
if (support_vector & _HOSTOPENBSD) {
|
||
Die(epath, "found RWX ELF program segment, but you have OpenBSD in"
|
||
" your support vector, which totally forbids RWX memory");
|
||
}
|
||
if (e->e_machine == EM_AARCH64 && (support_vector & _HOSTXNU)) {
|
||
Die(epath, "found RWX ELF program segment on an AARCH64 executable"
|
||
" with XNU support, which completely forbids RWX memory");
|
||
}
|
||
}
|
||
if ((p[i].p_flags & PF_X) && //
|
||
p[i].p_vaddr <= e->e_entry &&
|
||
e->e_entry < p[i].p_vaddr + p[i].p_memsz) {
|
||
found_entry = 1;
|
||
}
|
||
}
|
||
if (!found_entry) {
|
||
Die(epath, "ELF entrypoint not found in PT_LOAD with PF_X");
|
||
}
|
||
}
|
||
|
||
static bool IsSymbolWorthyOfSymtab(Elf64_Sym *sym) {
|
||
return sym->st_size > 0 && (ELF64_ST_TYPE(sym->st_info) == STT_FUNC ||
|
||
ELF64_ST_TYPE(sym->st_info) == STT_OBJECT);
|
||
}
|
||
|
||
static void AppendZipAsset(unsigned char *lfile, unsigned char *cfile) {
|
||
if (assets.n == 65534)
|
||
Die(outpath, "fat binary has >65534 zip assets");
|
||
assets.p = Realloc(assets.p, (assets.n + 1) * sizeof(*assets.p));
|
||
assets.p[assets.n].cfile = cfile;
|
||
assets.p[assets.n].lfile = lfile;
|
||
assets.total_local_file_bytes += ZIP_LFILE_SIZE(lfile);
|
||
assets.total_centraldir_bytes += ZIP_CFILE_HDRSIZE(cfile);
|
||
++assets.n;
|
||
}
|
||
|
||
static void *Compress(const void *data, size_t size, size_t *out_size, int wb) {
|
||
void *res;
|
||
z_stream zs;
|
||
zs.zfree = 0;
|
||
zs.zalloc = 0;
|
||
zs.opaque = 0;
|
||
unassert(deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, wb, DEF_MEM_LEVEL,
|
||
Z_DEFAULT_STRATEGY) == Z_OK);
|
||
zs.next_in = data;
|
||
zs.avail_in = size;
|
||
zs.avail_out = compressBound(size);
|
||
zs.next_out = res = Malloc(zs.avail_out);
|
||
unassert(deflate(&zs, Z_FINISH) == Z_STREAM_END);
|
||
unassert(deflateEnd(&zs) == Z_OK);
|
||
*out_size = zs.total_out;
|
||
return res;
|
||
}
|
||
|
||
static void *Deflate(const void *data, size_t size, size_t *out_size) {
|
||
return Compress(data, size, out_size, -MAX_WBITS);
|
||
}
|
||
|
||
static void *Gzip(const void *data, size_t size, size_t *out_size) {
|
||
return Compress(data, size, out_size, MAX_WBITS + 16);
|
||
}
|
||
|
||
static void LoadSymbols(Elf64_Ehdr *e, Elf64_Off size, const char *path) {
|
||
const char *name = ConvertElfMachineToSymtabName(e);
|
||
size_t name_size = strlen(name);
|
||
struct SymbolTable *st = OpenSymbolTable(path);
|
||
if (!st)
|
||
Die(path, "could not load elf symbol table");
|
||
size_t data_size;
|
||
void *data = Deflate(st, st->size, &data_size);
|
||
uint32_t crc = crc32_z(0, st, st->size);
|
||
size_t cfile_size = kZipCfileHdrMinSize + name_size;
|
||
unsigned char *cfile = Malloc(cfile_size);
|
||
bzero(cfile, cfile_size);
|
||
WRITE32LE(cfile, kZipCfileHdrMagic);
|
||
WRITE16LE(cfile + kZipCfileOffsetVersionMadeBy,
|
||
kZipOsUnix << 8 | kZipCosmopolitanVersion);
|
||
WRITE16LE(cfile + kZipCfileOffsetVersionNeeded, kZipEra2001);
|
||
WRITE16LE(cfile + kZipCfileOffsetGeneralflag, kZipGflagUtf8);
|
||
WRITE16LE(cfile + kZipCfileOffsetCompressionmethod, kZipCompressionDeflate);
|
||
WRITE16LE(cfile + kZipCfileOffsetLastmodifieddate, DOS_DATE(2023, 7, 29));
|
||
WRITE16LE(cfile + kZipCfileOffsetLastmodifiedtime, DOS_TIME(0, 0, 0));
|
||
WRITE32LE(cfile + kZipCfileOffsetCompressedsize, data_size);
|
||
WRITE32LE(cfile + kZipCfileOffsetUncompressedsize, st->size);
|
||
WRITE32LE(cfile + kZipCfileOffsetCrc32, crc);
|
||
WRITE16LE(cfile + kZipCfileOffsetNamesize, name_size);
|
||
memcpy(cfile + kZipCfileHdrMinSize, name, name_size);
|
||
unassert(ZIP_CFILE_HDRSIZE(cfile) == cfile_size);
|
||
size_t lfile_size = kZipLfileHdrMinSize + name_size + data_size;
|
||
unsigned char *lfile = Malloc(lfile_size);
|
||
bzero(lfile, lfile_size);
|
||
WRITE32LE(lfile, kZipLfileHdrMagic);
|
||
WRITE16LE(lfile + kZipLfileOffsetVersionNeeded, kZipEra2001);
|
||
WRITE16LE(lfile + kZipLfileOffsetGeneralflag, kZipGflagUtf8);
|
||
WRITE16LE(lfile + kZipLfileOffsetCompressionmethod, kZipCompressionDeflate);
|
||
WRITE16LE(lfile + kZipLfileOffsetLastmodifieddate, DOS_DATE(2023, 7, 29));
|
||
WRITE16LE(lfile + kZipLfileOffsetLastmodifiedtime, DOS_TIME(0, 0, 0));
|
||
WRITE32LE(lfile + kZipLfileOffsetCompressedsize, data_size);
|
||
WRITE32LE(lfile + kZipLfileOffsetUncompressedsize, st->size);
|
||
WRITE32LE(lfile + kZipLfileOffsetCrc32, crc);
|
||
WRITE16LE(lfile + kZipLfileOffsetNamesize, name_size);
|
||
memcpy(lfile + kZipLfileHdrMinSize, name, name_size);
|
||
memcpy(lfile + kZipLfileHdrMinSize + name_size, data, data_size);
|
||
unassert(ZIP_LFILE_SIZE(lfile) == lfile_size);
|
||
free(data);
|
||
AppendZipAsset(lfile, cfile);
|
||
}
|
||
|
||
// resolves portable executable relative virtual address
|
||
//
|
||
// this is a trivial process when an executable has been loaded properly
|
||
// i.e. a separate mmap() call was made for each individual section; but
|
||
// we've only mapped the executable file itself into memory; thus, we'll
|
||
// need to remap a virtual address into a file offset to get the pointer
|
||
//
|
||
// returns pointer to image data, or null on error
|
||
static void *GetPeRva(char *map, struct NtImageNtHeaders *pe, uint32_t rva) {
|
||
struct NtImageSectionHeader *sections =
|
||
(struct NtImageSectionHeader *)((char *)&pe->OptionalHeader +
|
||
pe->FileHeader.SizeOfOptionalHeader);
|
||
for (int i = 0; i < pe->FileHeader.NumberOfSections; ++i) {
|
||
if (sections[i].VirtualAddress <= rva &&
|
||
rva < sections[i].VirtualAddress + sections[i].Misc.VirtualSize) {
|
||
return map + sections[i].PointerToRawData +
|
||
(rva - sections[i].VirtualAddress);
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int ValidatePeImage(char *img, size_t imgsize, //
|
||
struct NtImageNtHeaders *pe, //
|
||
const char *path) {
|
||
|
||
int pagesz = 4096;
|
||
|
||
// sanity check pe header
|
||
if (pe->Signature != ('P' | 'E' << 8))
|
||
Die(path, "PE Signature must be 0x00004550");
|
||
if (!(pe->FileHeader.Characteristics & kNtPeFileExecutableImage))
|
||
Die(path, "PE Characteristics must have executable bit set");
|
||
if (pe->FileHeader.Characteristics & kNtPeFileDll)
|
||
Die(path, "PE Characteristics can't have DLL bit set");
|
||
if (pe->FileHeader.NumberOfSections < 1)
|
||
Die(path, "PE NumberOfSections >= 1 must be the case");
|
||
if (pe->OptionalHeader.Magic != kNtPe64bit)
|
||
Die(path, "PE OptionalHeader Magic must be 0x020b");
|
||
if (pe->OptionalHeader.FileAlignment < 512)
|
||
Die(path, "PE FileAlignment must be at least 512");
|
||
if (pe->OptionalHeader.FileAlignment > 65536)
|
||
Die(path, "PE FileAlignment can't exceed 65536");
|
||
if (pe->OptionalHeader.FileAlignment & (pe->OptionalHeader.FileAlignment - 1))
|
||
Die(path, "PE FileAlignment must be a two power");
|
||
if (pe->OptionalHeader.SectionAlignment &
|
||
(pe->OptionalHeader.SectionAlignment - 1))
|
||
Die(path, "PE SectionAlignment must be a two power");
|
||
if (pe->OptionalHeader.SectionAlignment < pe->OptionalHeader.FileAlignment)
|
||
Die(path, "PE SectionAlignment >= FileAlignment must be the case");
|
||
if (pe->OptionalHeader.SectionAlignment < pagesz &&
|
||
pe->OptionalHeader.SectionAlignment != pe->OptionalHeader.FileAlignment)
|
||
Die(path, "PE SectionAlignment must equal FileAlignment if it's less than "
|
||
"the microprocessor architecture's page size");
|
||
if (pe->OptionalHeader.ImageBase & 65535)
|
||
Die(path, "PE ImageBase must be multiple of 65536");
|
||
if (pe->OptionalHeader.ImageBase > INT_MAX &&
|
||
!(pe->FileHeader.Characteristics & kNtImageFileLargeAddressAware))
|
||
Die(path, "PE FileHeader.Characteristics needs "
|
||
"IMAGE_FILE_LARGE_ADDRESS_AWARE if ImageBase > INT_MAX");
|
||
if (!(support_vector & _HOSTMETAL) &&
|
||
pe->OptionalHeader.Subsystem == kNtImageSubsystemEfiApplication)
|
||
Die(path, "PE Subsystem is EFI but Metal wasn't in support_vector");
|
||
if (!(support_vector & _HOSTWINDOWS) &&
|
||
(pe->OptionalHeader.Subsystem == kNtImageSubsystemWindowsCui ||
|
||
pe->OptionalHeader.Subsystem == kNtImageSubsystemWindowsGui))
|
||
Die(path, "PE Subsystem is Windows but Windows wasn't in support_vector");
|
||
|
||
// fixup pe header
|
||
int len;
|
||
if (ckd_mul(&len, pe->OptionalHeader.NumberOfRvaAndSizes, 8) ||
|
||
ckd_add(&len, len, sizeof(struct NtImageOptionalHeader)) ||
|
||
pe->FileHeader.SizeOfOptionalHeader < len)
|
||
Die(path, "PE SizeOfOptionalHeader too small");
|
||
if (len > imgsize || (char *)&pe->OptionalHeader + len > img + imgsize)
|
||
Die(path, "PE OptionalHeader overflows image");
|
||
|
||
// perform even more pe validation
|
||
if (pe->OptionalHeader.SizeOfImage &
|
||
(pe->OptionalHeader.SectionAlignment - 1))
|
||
Die(path, "PE SizeOfImage must be multiple of SectionAlignment");
|
||
if (pe->OptionalHeader.SizeOfHeaders & (pe->OptionalHeader.FileAlignment - 1))
|
||
Die(path, "PE SizeOfHeaders must be multiple of FileAlignment");
|
||
if (pe->OptionalHeader.SizeOfHeaders > pe->OptionalHeader.AddressOfEntryPoint)
|
||
Die(path, "PE SizeOfHeaders <= AddressOfEntryPoint must be the case");
|
||
if (pe->OptionalHeader.SizeOfHeaders >= pe->OptionalHeader.SizeOfImage)
|
||
Die(path, "PE SizeOfHeaders < SizeOfImage must be the case");
|
||
if (pe->OptionalHeader.SizeOfStackCommit >> 32)
|
||
Die(path, "PE SizeOfStackCommit can't exceed 4GB");
|
||
if (pe->OptionalHeader.SizeOfStackReserve >> 32)
|
||
Die(path, "PE SizeOfStackReserve can't exceed 4GB");
|
||
if (pe->OptionalHeader.SizeOfHeapCommit >> 32)
|
||
Die(path, "PE SizeOfHeapCommit can't exceed 4GB");
|
||
if (pe->OptionalHeader.SizeOfHeapReserve >> 32)
|
||
Die(path, "PE SizeOfHeapReserve can't exceed 4GB");
|
||
|
||
// check pe section headers
|
||
struct NtImageSectionHeader *sections =
|
||
(struct NtImageSectionHeader *)((char *)&pe->OptionalHeader +
|
||
pe->FileHeader.SizeOfOptionalHeader);
|
||
for (int i = 0; i < pe->FileHeader.NumberOfSections; ++i) {
|
||
if (sections[i].SizeOfRawData & (pe->OptionalHeader.FileAlignment - 1))
|
||
Die(path, "PE SizeOfRawData should be multiple of FileAlignment");
|
||
if (sections[i].PointerToRawData & (pe->OptionalHeader.FileAlignment - 1))
|
||
Die(path, "PE PointerToRawData must be multiple of FileAlignment");
|
||
if (img + sections[i].PointerToRawData >= img + imgsize)
|
||
Die(path, "PE PointerToRawData points outside image");
|
||
if (img + sections[i].PointerToRawData + sections[i].SizeOfRawData >
|
||
img + imgsize)
|
||
Die(path, "PE SizeOfRawData overlaps end of image");
|
||
if (!sections[i].VirtualAddress)
|
||
Die(path, "PE VirtualAddress shouldn't be zero");
|
||
if (sections[i].VirtualAddress & (pe->OptionalHeader.SectionAlignment - 1))
|
||
Die(path, "PE VirtualAddress must be multiple of SectionAlignment");
|
||
if ((sections[i].Characteristics &
|
||
(kNtPeSectionCntCode | kNtPeSectionCntInitializedData |
|
||
kNtPeSectionCntUninitializedData)) ==
|
||
kNtPeSectionCntUninitializedData) {
|
||
if (sections[i].SizeOfRawData)
|
||
Die(path, "PE SizeOfRawData should be zero for pure BSS section");
|
||
if (sections[i].PointerToRawData)
|
||
Die(path, "PE PointerToRawData should be zero for pure BSS section");
|
||
}
|
||
if (!i) {
|
||
if (sections[i].VirtualAddress !=
|
||
((pe->OptionalHeader.SizeOfHeaders +
|
||
(pe->OptionalHeader.SectionAlignment - 1)) &
|
||
-pe->OptionalHeader.SectionAlignment))
|
||
Die(path, "PE VirtualAddress of first section must be SizeOfHeaders "
|
||
"rounded up to SectionAlignment");
|
||
} else {
|
||
if (sections[i].VirtualAddress !=
|
||
sections[i - 1].VirtualAddress +
|
||
((sections[i - 1].Misc.VirtualSize +
|
||
(pe->OptionalHeader.SectionAlignment - 1)) &
|
||
-pe->OptionalHeader.SectionAlignment))
|
||
Die(path, "PE sections must be in ascending order and the virtual "
|
||
"memory they define must be adjacent after VirtualSize is "
|
||
"rounded up to the SectionAlignment");
|
||
}
|
||
}
|
||
|
||
int size_of_pe_headers =
|
||
(char *)(sections + pe->FileHeader.NumberOfSections) -
|
||
(char *)&pe->Signature;
|
||
return size_of_pe_headers;
|
||
}
|
||
|
||
void FixupPeImage(char *map, size_t size, //
|
||
struct NtImageNtHeaders *pe, //
|
||
const char *path, //
|
||
Elf64_Sxword rva_skew, //
|
||
Elf64_Sxword off_skew) {
|
||
assert(!(rva_skew & 65535));
|
||
|
||
// fixup pe header
|
||
if (ckd_sub(&pe->OptionalHeader.ImageBase, //
|
||
pe->OptionalHeader.ImageBase, rva_skew))
|
||
Die(path, "skewing PE ImageBase VA overflowed");
|
||
if (ckd_add(&pe->OptionalHeader.AddressOfEntryPoint,
|
||
pe->OptionalHeader.AddressOfEntryPoint, rva_skew))
|
||
Die(path, "skewing PE AddressOfEntryPoint RVA overflowed");
|
||
if (ckd_add(&pe->OptionalHeader.SizeOfImage, //
|
||
pe->OptionalHeader.SizeOfImage, rva_skew))
|
||
Die(path, "skewing PE SizeOfImage VSZ overflowed");
|
||
if (ckd_add(&pe->OptionalHeader.SizeOfHeaders, //
|
||
pe->OptionalHeader.SizeOfHeaders, off_skew))
|
||
Die(path, "skewing PE SizeOfHeaders FSZ overflowed");
|
||
|
||
// fixup dll imports
|
||
//
|
||
// this implementation currently only works on simple pe file layouts
|
||
// where relative virtual addresses are equivalent to file offsets
|
||
struct NtImageDataDirectory *ddImports =
|
||
pe->OptionalHeader.DataDirectory + kNtImageDirectoryEntryImport;
|
||
if (pe->OptionalHeader.NumberOfRvaAndSizes >= 2 && ddImports->Size) {
|
||
struct NtImageImportDescriptor *idt;
|
||
if (!(idt = GetPeRva(map, pe, ddImports->VirtualAddress)))
|
||
Die(path, "couldn't resolve VirtualAddress/Size RVA of PE Import "
|
||
"Directory Table to within a defined PE section");
|
||
for (int i = 0; idt[i].ImportLookupTable; ++i) {
|
||
uint64_t *ilt, *iat;
|
||
if (!(ilt = GetPeRva(map, pe, idt[i].ImportLookupTable)))
|
||
Die(path, "PE ImportLookupTable RVA didn't resolve to a section");
|
||
for (int j = 0; ilt[j]; ++j) {
|
||
if (ckd_add(&ilt[j], ilt[j], rva_skew))
|
||
Die(path, "skewing PE Import Lookup Table RVA overflowed");
|
||
}
|
||
if (!(iat = GetPeRva(map, pe, idt[i].ImportAddressTable)))
|
||
Die(path, "PE ImportAddressTable RVA didn't resolve to a section");
|
||
for (int j = 0; iat[j]; ++j) {
|
||
if (ckd_add(&iat[j], iat[j], rva_skew))
|
||
Die(path, "skewing PE Import Lookup Table RVA overflowed");
|
||
}
|
||
if (ckd_add(&idt[i].DllNameRva, idt[i].DllNameRva, rva_skew))
|
||
Die(path, "skewing PE IDT DllNameRva RVA overflowed");
|
||
if (ckd_add(&idt[i].ImportLookupTable, idt[i].ImportLookupTable,
|
||
rva_skew))
|
||
Die(path, "skewing PE IDT ImportLookupTable RVA overflowed");
|
||
if (ckd_add(&idt[i].ImportAddressTable, idt[i].ImportAddressTable,
|
||
rva_skew))
|
||
Die(path, "skewing PE IDT ImportAddressTable RVA overflowed");
|
||
}
|
||
if (ckd_add(&ddImports->VirtualAddress, ddImports->VirtualAddress,
|
||
rva_skew))
|
||
Die(path, "skewing PE IMAGE_DIRECTORY_ENTRY_IMPORT RVA overflowed");
|
||
}
|
||
|
||
// fixup pe segments table
|
||
struct NtImageSectionHeader *sections =
|
||
(struct NtImageSectionHeader *)((char *)&pe->OptionalHeader +
|
||
pe->FileHeader.SizeOfOptionalHeader);
|
||
for (int i = 0; i < pe->FileHeader.NumberOfSections; ++i) {
|
||
if (ckd_add(§ions[i].VirtualAddress, sections[i].VirtualAddress,
|
||
rva_skew))
|
||
Die(path, "skewing PE section VirtualAddress overflowed");
|
||
if (ckd_add(§ions[i].PointerToRawData, sections[i].PointerToRawData,
|
||
off_skew))
|
||
Die(path, "skewing PE section PointerToRawData overflowed");
|
||
}
|
||
}
|
||
|
||
static bool FindLoaderEmbeddedMachoHeader(struct Loader *ape) {
|
||
size_t i;
|
||
struct MachoHeader *macho;
|
||
for (i = 0; i + sizeof(struct MachoHeader) < ape->size; i += 64) {
|
||
if (READ32LE((char *)ape->map + i) == 0xFEEDFACE + 1) {
|
||
macho = (struct MachoHeader *)((char *)ape->map + i);
|
||
ape->macho_offset = i;
|
||
ape->macho_length = sizeof(*macho) + macho->loadsize;
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static struct Input *GetInput(int machine) {
|
||
int i;
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
if (inputs.p[i].elf->e_machine == machine) {
|
||
return inputs.p + i;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static struct Loader *GetLoader(int machine, int os) {
|
||
int i;
|
||
for (i = 0; i < loaders.n; ++i) {
|
||
if ((loaders.p[i].os & os) && loaders.p[i].machine == machine) {
|
||
return loaders.p + i;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void AddLoader(const char *path) {
|
||
if (loaders.n == ARRAYLEN(loaders.p)) {
|
||
Die(prog, "too many loaders");
|
||
}
|
||
loaders.p[loaders.n++].path = path;
|
||
}
|
||
|
||
static void GetOpts(int argc, char *argv[]) {
|
||
int opt, bits;
|
||
bool got_support_vector = false;
|
||
while ((opt = getopt(argc, argv, "hvgsGBo:l:S:M:V:")) != -1) {
|
||
switch (opt) {
|
||
case 'o':
|
||
outpath = optarg;
|
||
break;
|
||
case 's':
|
||
HashInputString("-s");
|
||
want_stripped = true;
|
||
break;
|
||
case 'V':
|
||
HashInputString("-V");
|
||
HashInputString(optarg);
|
||
if (ParseSupportVector(optarg, &bits)) {
|
||
support_vector |= bits;
|
||
} else {
|
||
Die(prog, "unrecognized token passed to -V support vector flag");
|
||
exit(1);
|
||
}
|
||
got_support_vector = true;
|
||
break;
|
||
case 'l':
|
||
HashInputString("-l");
|
||
AddLoader(optarg);
|
||
break;
|
||
case 'S':
|
||
HashInputString("-S");
|
||
HashInputString(optarg);
|
||
custom_sh_code = optarg;
|
||
break;
|
||
case 'B':
|
||
HashInputString("-B");
|
||
force_bypass_binfmt_misc = true;
|
||
break;
|
||
case 'g':
|
||
HashInputString("-g");
|
||
generate_debuggable_binary = true;
|
||
break;
|
||
case 'G':
|
||
HashInputString("-G");
|
||
dont_path_lookup_ape_loader = true;
|
||
break;
|
||
case 'M':
|
||
HashInputString("-M");
|
||
macos_silicon_loader_source_path = optarg;
|
||
macos_silicon_loader_source_text = LoadSourceCode(optarg);
|
||
break;
|
||
case 'v':
|
||
tinyprint(0, VERSION, NULL);
|
||
exit(0);
|
||
case 'h':
|
||
ShowUsage(0, 1);
|
||
default:
|
||
ShowUsage(1, 2);
|
||
}
|
||
}
|
||
if (!outpath) {
|
||
Die(prog, "need output path");
|
||
}
|
||
if (optind == argc) {
|
||
Die(prog, "missing input argument");
|
||
}
|
||
if (!got_support_vector) {
|
||
support_vector = -1;
|
||
}
|
||
}
|
||
|
||
static void OpenLoader(struct Loader *ldr) {
|
||
int fd;
|
||
Elf64_Ehdr *elf;
|
||
struct MachoHeader *macho;
|
||
if ((fd = open(ldr->path, O_RDONLY)) == -1) {
|
||
DieSys(ldr->path);
|
||
}
|
||
if ((ldr->size = lseek(fd, 0, SEEK_END)) == -1) {
|
||
DieSys(ldr->path);
|
||
}
|
||
ldr->map = mmap(0, ldr->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||
if (ldr->map == MAP_FAILED) {
|
||
DieSys(ldr->path);
|
||
}
|
||
HashInput(ldr->map, ldr->size);
|
||
close(fd);
|
||
if (IsElf64Binary((elf = ldr->map), ldr->size)) {
|
||
ValidateElfImage(ldr->map, ldr->size, ldr->path, true);
|
||
ldr->os = _HOSTLINUX | _HOSTFREEBSD | _HOSTNETBSD | _HOSTOPENBSD;
|
||
ldr->machine = elf->e_machine;
|
||
if (ldr->machine == EM_NEXGEN32E && FindLoaderEmbeddedMachoHeader(ldr)) {
|
||
ldr->os |= _HOSTXNU;
|
||
}
|
||
} else if (READ32LE(ldr->map) == 0xFEEDFACE + 1) {
|
||
macho = (struct MachoHeader *)ldr->map;
|
||
if (macho->arch == MAC_CPU_NEXGEN32E) {
|
||
Die(ldr->path, "there's no reason to embed the x86 ape.macho loader, "
|
||
"because ape.elf for x86-64 works on x86-64 macos too");
|
||
} else if (macho->arch == MAC_CPU_ARM64) {
|
||
ldr->os = _HOSTXNU;
|
||
ldr->machine = EM_AARCH64;
|
||
if (macos_silicon_loader_source_path) {
|
||
Die(ldr->path,
|
||
"it doesn't make sense to embed a prebuilt ape loader executable "
|
||
"for macos silicon when its source code was already passed so it "
|
||
"can be built by the ape shell script on the fly");
|
||
}
|
||
} else {
|
||
Die(ldr->path, "unrecognized mach-o ape loader architecture");
|
||
}
|
||
} else {
|
||
Die(ldr->path, "ape loader file format must be elf or mach-o");
|
||
}
|
||
}
|
||
|
||
static int PhdrFlagsToProt(Elf64_Word flags) {
|
||
int prot = PROT_NONE;
|
||
if (flags & PF_R)
|
||
prot |= PROT_READ;
|
||
if (flags & PF_W)
|
||
prot |= PROT_WRITE;
|
||
if (flags & PF_X)
|
||
prot |= PROT_EXEC;
|
||
return prot;
|
||
}
|
||
|
||
static char *EncodeWordAsPrintf(char *p, uint64_t w, int bytes) {
|
||
int c;
|
||
while (bytes-- > 0) {
|
||
c = w & 255;
|
||
w >>= 8;
|
||
if (isascii(c) && isprint(c) && !isdigit(c) && c != '\\' && c != '%') {
|
||
*p++ = c;
|
||
} else {
|
||
*p++ = '\\';
|
||
if ((c & 0700))
|
||
*p++ = '0' + ((c & 0700) >> 6);
|
||
if ((c & 0770))
|
||
*p++ = '0' + ((c & 070) >> 3);
|
||
*p++ = '0' + (c & 7);
|
||
}
|
||
}
|
||
return p;
|
||
}
|
||
|
||
static char *FixupWordAsBinary(char *p, uint64_t w) {
|
||
do {
|
||
*p++ = w;
|
||
} while ((w >>= 8));
|
||
return p;
|
||
}
|
||
|
||
static char *FixupWordAsDecimal(char *p, uint64_t w) {
|
||
char buf[21];
|
||
return mempcpy(p, buf, FormatInt64(buf, w) - buf);
|
||
}
|
||
|
||
static char *FixupWordAsPrintf(char *p, uint64_t w) {
|
||
unassert(READ32LE(p) == READ32LE("\\000"));
|
||
do {
|
||
*p++ = '\\';
|
||
*p++ = '0' + ((w & 0700) >> 6);
|
||
*p++ = '0' + ((w & 070) >> 3);
|
||
*p++ = '0' + ((w & 07));
|
||
} while ((w >>= 8));
|
||
return p;
|
||
}
|
||
|
||
static char *FixupPrintf(char *p, uint64_t w) {
|
||
switch (strategy) {
|
||
case kPe:
|
||
return p;
|
||
case kElf:
|
||
case kMacho:
|
||
return FixupWordAsBinary(p, w);
|
||
case kApe:
|
||
return FixupWordAsPrintf(p, w);
|
||
default:
|
||
__builtin_unreachable();
|
||
}
|
||
}
|
||
|
||
static int PickElfOsAbi(void) {
|
||
switch (support_vector) {
|
||
case _HOSTLINUX:
|
||
return ELFOSABI_LINUX;
|
||
case _HOSTOPENBSD:
|
||
return ELFOSABI_OPENBSD;
|
||
case _HOSTNETBSD:
|
||
return ELFOSABI_NETBSD;
|
||
default:
|
||
// If we're supporting multiple operating systems, then FreeBSD
|
||
// has the only kernel that actually checks this field.
|
||
return ELFOSABI_FREEBSD;
|
||
}
|
||
}
|
||
|
||
static void AddOffsetReloc(struct Input *in, uint64_t *r) {
|
||
if (in->offsetrelocs.n == ARRAYLEN(in->offsetrelocs.p)) {
|
||
Die(in->path, "ran out of offset relocations");
|
||
}
|
||
in->offsetrelocs.p[in->offsetrelocs.n++] = r;
|
||
}
|
||
|
||
static char *GenerateDecimalOffsetRelocation(char *p) {
|
||
size_t i, n = 10;
|
||
for (i = 0; i < n; ++i) {
|
||
*p++ = ' ';
|
||
}
|
||
return p;
|
||
}
|
||
|
||
static char *GenerateElf(char *p, struct Input *in) {
|
||
Elf64_Ehdr *e;
|
||
p = ALIGN(p, alignof(Elf64_Ehdr));
|
||
e = (Elf64_Ehdr *)p;
|
||
e->e_ident[EI_MAG0] = ELFMAG0;
|
||
e->e_ident[EI_MAG1] = ELFMAG1;
|
||
e->e_ident[EI_MAG2] = ELFMAG2;
|
||
e->e_ident[EI_MAG3] = ELFMAG3;
|
||
e->e_ident[EI_CLASS] = ELFCLASS64;
|
||
e->e_ident[EI_DATA] = ELFDATA2LSB;
|
||
e->e_ident[EI_VERSION] = 1;
|
||
e->e_ident[EI_OSABI] = PickElfOsAbi();
|
||
e->e_ident[EI_ABIVERSION] = 0;
|
||
e->e_type = ET_EXEC;
|
||
e->e_machine = in->elf->e_machine;
|
||
e->e_version = 1;
|
||
e->e_entry = in->elf->e_entry;
|
||
e->e_ehsize = sizeof(Elf64_Ehdr);
|
||
e->e_phentsize = sizeof(Elf64_Phdr);
|
||
in->printf_phoff = (char *)&e->e_phoff;
|
||
in->printf_phnum = (char *)&e->e_phnum;
|
||
return p + sizeof(*e);
|
||
}
|
||
|
||
static bool IsPhdrAllocated(struct Input *in, Elf64_Phdr *phdr) {
|
||
int i;
|
||
Elf64_Shdr *shdr;
|
||
if (1)
|
||
return true;
|
||
for (i = 0; i < in->elf->e_shnum; ++i) {
|
||
if (!(shdr = GetElfSectionHeaderAddress(in->elf, in->size, i))) {
|
||
Die(in->path, "elf section header overflow");
|
||
}
|
||
if ((shdr->sh_flags & SHF_ALLOC) &&
|
||
MAX(shdr->sh_addr, phdr->p_vaddr) <
|
||
MIN(shdr->sh_addr + shdr->sh_size, phdr->p_vaddr + phdr->p_memsz)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// finds non-local elf symbol by name
|
||
static struct Elf64_Sym *GetElfSymbol(struct Input *in, const char *name) {
|
||
char *ss;
|
||
Elf64_Sym *st;
|
||
Elf64_Shdr *sh;
|
||
Elf64_Xword i, n;
|
||
ss = GetElfStringTable(in->elf, in->size, ".strtab");
|
||
sh = GetElfSymbolTable(in->elf, in->size, SHT_SYMTAB, &n);
|
||
st = GetElfSectionAddress(in->elf, in->size, sh);
|
||
if (!st || !ss)
|
||
Die(in->path, "missing elf symbol table");
|
||
for (i = sh->sh_info; i < n; ++i) {
|
||
if (st[i].st_name && !strcmp(ss + st[i].st_name, name)) {
|
||
return st + i;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static char *DefineMachoNull(char *p) {
|
||
struct MachoLoadSegment *load;
|
||
load = (struct MachoLoadSegment *)p;
|
||
++macholoadcount;
|
||
load->command = MAC_LC_SEGMENT_64;
|
||
load->size = sizeof(*load);
|
||
strcpy(load->name, "__PAGEZERO");
|
||
load->memsz = 0x200000;
|
||
return p + sizeof(*load);
|
||
}
|
||
|
||
static char *DefineMachoUuid(char *p) {
|
||
struct MachoLoadUuid *load;
|
||
load = (struct MachoLoadUuid *)p;
|
||
++macholoadcount;
|
||
load->command = MAC_LC_UUID;
|
||
load->size = sizeof(*load);
|
||
if (!hashes)
|
||
Die(outpath, "won't generate macho uuid");
|
||
memcpy(load->uuid, hashpool, sizeof(load->uuid));
|
||
return p + sizeof(*load);
|
||
}
|
||
|
||
static char *DefineMachoEntrypoint(char *p, struct Input *in) {
|
||
struct Elf64_Sym *start;
|
||
struct MachoLoadThread64 *load;
|
||
if (!(start = GetElfSymbol(in, "_apple")) &&
|
||
!(start = GetElfSymbol(in, "_start"))) {
|
||
Die(in->path, "couldn't find _apple() or _start() function in elf "
|
||
"executable which can serve as the macho entrypoint");
|
||
}
|
||
load = (struct MachoLoadThread64 *)p;
|
||
++macholoadcount;
|
||
load->thread.command = MAC_LC_UNIXTHREAD;
|
||
load->thread.size = sizeof(*load);
|
||
load->thread.flavor = MAC_THREAD_NEXGEN32E;
|
||
load->thread.count = sizeof(load->regs) / 4;
|
||
load->regs[16] = start->st_value;
|
||
return p + sizeof(*load);
|
||
}
|
||
|
||
static char *GenerateMachoSegment(char *p, struct Input *in, Elf64_Phdr *phdr) {
|
||
struct MachoLoadSegment *load;
|
||
load = (struct MachoLoadSegment *)p;
|
||
load->command = MAC_LC_SEGMENT_64;
|
||
load->size = sizeof(*load);
|
||
FormatInt32(__veil("r", stpcpy(load->name, "__APE")), macholoadcount);
|
||
++macholoadcount;
|
||
load->vaddr = phdr->p_vaddr;
|
||
load->memsz = phdr->p_memsz;
|
||
load->offset = phdr->p_offset;
|
||
load->filesz = phdr->p_filesz;
|
||
load->maxprot = PROT_EXEC | PROT_READ | PROT_WRITE;
|
||
load->initprot = PhdrFlagsToProt(phdr->p_flags);
|
||
if (load->initprot & PROT_EXEC) {
|
||
load->initprot |= PROT_READ; // xnu wants it
|
||
}
|
||
AddOffsetReloc(in, &load->offset);
|
||
if (!in->first_macho_load) {
|
||
in->first_macho_load = load;
|
||
}
|
||
return p + sizeof(*load);
|
||
}
|
||
|
||
static char *GenerateMachoLoads(char *p, struct Input *in) {
|
||
int i;
|
||
struct Elf64_Phdr *phdr;
|
||
p = DefineMachoNull(p);
|
||
for (i = 0; i < in->elf->e_phnum; ++i) {
|
||
phdr = GetElfProgramHeaderAddress(in->elf, in->size, i);
|
||
if (phdr->p_type == PT_LOAD) {
|
||
if (!IsPhdrAllocated(in, phdr)) {
|
||
continue;
|
||
}
|
||
p = GenerateMachoSegment(p, in, phdr);
|
||
}
|
||
}
|
||
p = DefineMachoUuid(p);
|
||
p = DefineMachoEntrypoint(p, in);
|
||
return p;
|
||
}
|
||
|
||
static char *GenerateMacho(char *p, struct Input *in) {
|
||
char *pMacho, *pLoads;
|
||
struct MachoHeader *macho;
|
||
pMacho = p = ALIGN(p, 8);
|
||
macho = (struct MachoHeader *)p;
|
||
macho->magic = 0xFEEDFACE + 1;
|
||
macho->arch = MAC_CPU_NEXGEN32E;
|
||
macho->arch2 = MAC_CPU_NEXGEN32E_ALL;
|
||
macho->filetype = MAC_EXECUTE;
|
||
macho->flags = MAC_NOUNDEFS;
|
||
macho->__reserved = 0;
|
||
pLoads = p += sizeof(struct MachoHeader);
|
||
p = GenerateMachoLoads(p, in);
|
||
macho->loadcount = macholoadcount;
|
||
macho->loadsize = p - pLoads;
|
||
if (in->ddarg_macho_skip)
|
||
FixupWordAsDecimal(in->ddarg_macho_skip, pMacho - prologue);
|
||
if (in->ddarg_macho_count)
|
||
FixupWordAsDecimal(in->ddarg_macho_count, p - pMacho);
|
||
return p;
|
||
}
|
||
|
||
static void ValidatePortableExecutable(struct Input *in) {
|
||
unassert(r_off32_e_lfanew);
|
||
unassert(in->elf->e_machine == EM_NEXGEN32E);
|
||
Elf64_Sym *ape_pe;
|
||
if (!(ape_pe = GetElfSymbol(in, "ape_pe"))) {
|
||
if (support_vector & _HOSTWINDOWS) {
|
||
Die(in->path, "elf image needs to define `ape_pe` when windows is "
|
||
"in the support vector");
|
||
}
|
||
return;
|
||
}
|
||
int off;
|
||
Elf64_Shdr *shdr =
|
||
GetElfSectionHeaderAddress(in->elf, in->size, ape_pe->st_shndx);
|
||
if (!shdr || ckd_sub(&off, ape_pe->st_value, shdr->sh_addr) ||
|
||
ckd_add(&off, off, shdr->sh_offset) || off >= in->size ||
|
||
off + sizeof(struct NtImageNtHeaders) > in->size)
|
||
Die(in->path, "ape_pe symbol headers corrupted");
|
||
in->pe_offset = off;
|
||
in->pe = (struct NtImageNtHeaders *)(in->map + off);
|
||
in->size_of_pe_headers = ValidatePeImage(
|
||
in->map + in->minload, in->maxload - in->minload, in->pe, in->path);
|
||
}
|
||
|
||
static void LinkPortableExecutableHeader(struct Input *in, //
|
||
Elf64_Sxword rva_skew, //
|
||
Elf64_Sxword off_skew) {
|
||
if (!in->pe) {
|
||
// this can happen if (1) strategy is ape, (2) metal is in the
|
||
// support vector and (3) cosmo's efi entrypoint wasn't linked
|
||
return;
|
||
}
|
||
WRITE32LE(r_off32_e_lfanew, in->pe_offset - in->minload + off_skew);
|
||
FixupPeImage(in->map + in->minload, in->maxload - in->minload, in->pe,
|
||
in->path, rva_skew, off_skew);
|
||
}
|
||
|
||
static char *GenerateElfNote(char *p, const char *name, int type, int desc) {
|
||
p = WRITE32LE(p, strlen(name) + 1);
|
||
p = WRITE32LE(p, 4);
|
||
p = WRITE32LE(p, type);
|
||
p = stpcpy(p, name);
|
||
p = ALIGN(p, 4);
|
||
p = WRITE32LE(p, desc);
|
||
return p;
|
||
}
|
||
|
||
static char *GenerateElfNotes(char *p) {
|
||
char *save;
|
||
save = p = ALIGN(p, 4);
|
||
noteoff = p - prologue;
|
||
p = GenerateElfNote(p, "APE", 1, APE_VERSION_NOTE);
|
||
if (support_vector & _HOSTOPENBSD) {
|
||
p = GenerateElfNote(p, "OpenBSD", 1, 0);
|
||
}
|
||
if (support_vector & _HOSTNETBSD) {
|
||
p = GenerateElfNote(p, "NetBSD", 1, 901000000);
|
||
}
|
||
notesize = p - save;
|
||
return p;
|
||
}
|
||
|
||
static char *SecondPass(char *p, struct Input *in) {
|
||
int i, phnum;
|
||
Elf64_Phdr *phdr, *phdr2;
|
||
|
||
// plan elf program headers
|
||
phnum = 0;
|
||
in->minload = -1;
|
||
p = ALIGN(p, alignof(Elf64_Phdr));
|
||
FixupPrintf(in->printf_phoff, p - prologue);
|
||
for (i = 0; i < in->elf->e_phnum; ++i) {
|
||
phdr = GetElfProgramHeaderAddress(in->elf, in->size, i);
|
||
if (phdr->p_type == PT_LOAD && !IsPhdrAllocated(in, phdr))
|
||
continue;
|
||
*(phdr2 = (Elf64_Phdr *)p) = *phdr;
|
||
p += sizeof(Elf64_Phdr);
|
||
if (phdr->p_filesz) {
|
||
in->minload = MIN(in->minload, phdr->p_offset);
|
||
in->maxload = MAX(in->maxload, phdr->p_offset + phdr->p_filesz);
|
||
AddOffsetReloc(in, &phdr2->p_offset);
|
||
}
|
||
++phnum;
|
||
}
|
||
if (noteoff && notesize) {
|
||
phdr = (Elf64_Phdr *)p;
|
||
p += sizeof(Elf64_Phdr);
|
||
phdr->p_type = PT_NOTE;
|
||
phdr->p_flags = PF_R;
|
||
phdr->p_offset = noteoff;
|
||
phdr->p_vaddr = 0;
|
||
phdr->p_paddr = 0;
|
||
phdr->p_filesz = notesize;
|
||
phdr->p_memsz = notesize;
|
||
phdr->p_align = 4;
|
||
++phnum;
|
||
}
|
||
FixupPrintf(in->printf_phnum, phnum);
|
||
|
||
// plan embedded mach-o executable
|
||
if (strategy == kApe && //
|
||
(support_vector & _HOSTXNU) && //
|
||
in->elf->e_machine == EM_NEXGEN32E) {
|
||
p = GenerateMacho(p, in);
|
||
}
|
||
|
||
return p;
|
||
}
|
||
|
||
static char *SecondPass2(char *p, struct Input *in) {
|
||
|
||
// plan embedded portable executable
|
||
in->we_are_generating_pe = (strategy == kApe || strategy == kPe) &&
|
||
in->elf->e_machine == EM_NEXGEN32E &&
|
||
(support_vector & (_HOSTWINDOWS | _HOSTMETAL));
|
||
if (in->we_are_generating_pe) {
|
||
ValidatePortableExecutable(in);
|
||
// if the elf image has a non-alloc prologue that's being stripped
|
||
// away then there's a chance we can avoid adding 64kb of bloat to
|
||
// the new file size. that's only possible if all the fat ape hdrs
|
||
// we generate are able to fit inside the prologue.
|
||
p = ALIGN(p, 8);
|
||
// TODO(jart): Figure out why not skewing corrupts pe import table
|
||
in->we_must_skew_pe_vaspace =
|
||
1 || ROUNDUP(p - prologue + in->size_of_pe_headers,
|
||
(int)in->pe->OptionalHeader.FileAlignment) > in->minload;
|
||
if (!in->we_must_skew_pe_vaspace) {
|
||
in->pe_e_lfanew = p - prologue;
|
||
in->pe_SizeOfHeaders = in->pe->OptionalHeader.SizeOfHeaders;
|
||
struct NtImageNtHeaders *pe2 = (struct NtImageNtHeaders *)p;
|
||
p += in->size_of_pe_headers;
|
||
memcpy(pe2, in->pe, in->size_of_pe_headers);
|
||
in->pe = pe2;
|
||
p = ALIGN(p, (int)in->pe->OptionalHeader.FileAlignment);
|
||
}
|
||
}
|
||
|
||
return p;
|
||
}
|
||
|
||
// on the third pass, we stop generating prologue content and begin
|
||
// focusing on embedding the executable files passed via the flags.
|
||
static Elf64_Off ThirdPass(Elf64_Off offset, struct Input *in) {
|
||
int i;
|
||
Elf64_Addr vaddr;
|
||
Elf64_Xword image_align;
|
||
|
||
// determine microprocessor page size
|
||
unsigned long pagesz;
|
||
if (in->elf->e_machine == EM_AARCH64) {
|
||
pagesz = 16384; // apple m1 (xnu, linux)
|
||
} else { //
|
||
pagesz = 4096; // x86-64, raspberry pi
|
||
}
|
||
|
||
// determine file alignment of image
|
||
//
|
||
// 1. elf requires that file offsets be congruent with virtual
|
||
// addresses modulo the cpu page size. so when adding stuff
|
||
// to the beginning of the file, we need to round up to the
|
||
// page size in order to maintain elf's invariant, although
|
||
// no such roundup is required on the program segments once
|
||
// the invariant is restored. elf loaders will happily mmap
|
||
// program headers from arbitrary file intervals (which may
|
||
// overlap) onto arbitrarily virtual intervals (which don't
|
||
// need to be contiguous). in order to do that, the loaders
|
||
// will generally use unix's mmap() function which needs to
|
||
// have page aligned addresses. since program headers start
|
||
// and stop at potentially any byte, elf loaders shall work
|
||
// around the mmap() requirements by rounding out intervals
|
||
// as necessary in order to ensure both the mmap() size and
|
||
// offset parameters are page size aligned. this means with
|
||
// elf, we never need to insert any empty space into a file
|
||
// when we don't want to. we can simply allow the offset to
|
||
// to drift apart from the virtual offset.
|
||
//
|
||
// 2. pe doesn't care about congruency and instead specifies a
|
||
// second kind of alignment. the minimum alignment of files
|
||
// is 512 because that's what dos used. where it gets hairy
|
||
// with pe is SizeOfHeaders which has complex and confusing
|
||
// requirements we're still figuring out. in cases where we
|
||
// determine that it's necessary to skew the pe image base,
|
||
// windows imposes a separate 64kb alignment requirement on
|
||
// the image base.
|
||
//
|
||
// 3. macho is the strictest executable format. xnu won't even
|
||
// load executables that do anything special with alignment
|
||
// which must conform to the cpu page size. it applies with
|
||
// both the image base and the segments. xnu also wants the
|
||
// segments to be contiguous similar to portable executable
|
||
// except that applies to both the file and virtual spaces.
|
||
//
|
||
vaddr = 0;
|
||
if (in->we_are_generating_pe) {
|
||
if (in->we_must_skew_pe_vaspace) {
|
||
image_align = 65535;
|
||
} else {
|
||
image_align = in->pe->OptionalHeader.FileAlignment;
|
||
}
|
||
image_align = MAX(image_align, pagesz);
|
||
} else {
|
||
image_align = pagesz;
|
||
}
|
||
while ((offset & (image_align - 1)) != (vaddr & (image_align - 1))) {
|
||
++offset;
|
||
}
|
||
|
||
// fixup the program header offsets
|
||
for (i = 0; i < in->offsetrelocs.n; ++i) {
|
||
*in->offsetrelocs.p[i] += offset - in->minload;
|
||
}
|
||
|
||
// fixup macho loads to account for skew
|
||
if (in->first_macho_load) {
|
||
if (in->first_macho_load->offset) {
|
||
in->first_macho_load->vaddr -= in->first_macho_load->offset;
|
||
in->first_macho_load->memsz += in->first_macho_load->offset;
|
||
in->first_macho_load->filesz += in->first_macho_load->offset;
|
||
in->first_macho_load->offset = 0;
|
||
}
|
||
}
|
||
|
||
// fixup and link assembler generated portable executable headers
|
||
if (in->we_are_generating_pe) {
|
||
if (in->we_must_skew_pe_vaspace) {
|
||
LinkPortableExecutableHeader(in, offset, offset);
|
||
} else {
|
||
LinkPortableExecutableHeader(in, 0, offset);
|
||
WRITE32LE(r_off32_e_lfanew, in->pe_e_lfanew);
|
||
in->pe->OptionalHeader.SizeOfHeaders = in->pe_SizeOfHeaders;
|
||
}
|
||
}
|
||
|
||
// copy the stripped executable into the output
|
||
Pwrite(in->map + in->minload, in->maxload - in->minload, offset);
|
||
offset += in->maxload - in->minload;
|
||
|
||
return offset;
|
||
}
|
||
|
||
static void OpenInput(const char *path) {
|
||
int fd;
|
||
struct Input *in;
|
||
if (inputs.n == ARRAYLEN(inputs.p))
|
||
Die(prog, "too many input files");
|
||
in = inputs.p + inputs.n++;
|
||
in->path = path;
|
||
if ((fd = open(path, O_RDONLY)) == -1)
|
||
DieSys(path);
|
||
if ((in->size = lseek(fd, 0, SEEK_END)) == -1)
|
||
DieSys(path);
|
||
in->map = mmap(0, in->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||
if (in->map == MAP_FAILED)
|
||
DieSys(path);
|
||
if (!IsElf64Binary(in->elf, in->size))
|
||
Die(path, "not an elf64 binary");
|
||
HashInput(in->map, in->size);
|
||
close(fd);
|
||
}
|
||
|
||
static char *GenerateScriptIfMachine(char *p, struct Input *in) {
|
||
if (in->elf->e_machine == EM_NEXGEN32E) {
|
||
return stpcpy(p, "if [ \"$m\" = x86_64 ] || [ \"$m\" = amd64 ]; then\n");
|
||
} else if (in->elf->e_machine == EM_AARCH64) {
|
||
return stpcpy(p, "if [ \"$m\" = aarch64 ] || [ \"$m\" = arm64 ]; then\n");
|
||
} else if (in->elf->e_machine == EM_PPC64) {
|
||
return stpcpy(p, "if [ \"$m\" = ppc64le ]; then\n");
|
||
} else {
|
||
Die(in->path, "unsupported cpu architecture");
|
||
}
|
||
}
|
||
|
||
static char *FinishGeneratingDosHeader(char *p) {
|
||
p = WRITE16LE(p, 0x1000); // 10: MZ: lowers upper bound load / 16
|
||
p = WRITE16LE(p, 0xf800); // 12: MZ: roll greed on bss
|
||
p = WRITE16LE(p, 0); // 14: MZ: lower bound on stack segment
|
||
p = WRITE16LE(p, 0); // 16: MZ: initialize stack pointer
|
||
p = WRITE16LE(p, 0); // 18: MZ: ∑bₙ checksum don't bother
|
||
p = WRITE16LE(p, 0x0100); // 20: MZ: initial ip value
|
||
p = WRITE16LE(p, 0x0800); // 22: MZ: increases cs load lower bound
|
||
p = WRITE16LE(p, 0x0040); // 24: MZ: reloc table offset
|
||
p = WRITE16LE(p, 0); // 26: MZ: overlay number
|
||
p = WRITE16LE(p, 0); // 28: MZ: overlay information
|
||
p = WRITE16LE(p, 0); // 30
|
||
p = WRITE16LE(p, 0); // 32
|
||
p = WRITE16LE(p, 0); // 34
|
||
p = WRITE16LE(p, 0); // 36
|
||
p = WRITE16LE(p, 0); // 38
|
||
|
||
// terminate the shell quote started earlier in the ape magic. the big
|
||
// concern with shell script quoting, is that binary content might get
|
||
// generated in the dos stub which has an ascii value that is the same
|
||
// as the end of quote. using a longer terminator reduces it to a very
|
||
// low order of probability. tacking on an unpredictable deterministic
|
||
// value makes it nearly impossible to break even with intent for that
|
||
// another terminator exists, which dates back to every version of ape
|
||
// ever released, which is "#'\"\n". programs wanting a simple way for
|
||
// scanning over the actually portable executable mz stub can use that
|
||
char *q = ape_heredoc;
|
||
q = stpcpy(q, "justine");
|
||
uint64_t w = READ64LE(hashpool);
|
||
for (int i = 0; i < 6; ++i) {
|
||
*q++ = "0123456789abcdefghijklmnopqrstuvwxyz"[w % 36];
|
||
w /= 36;
|
||
}
|
||
p = stpcpy(p, "' <<'");
|
||
p = stpcpy(p, ape_heredoc);
|
||
p = stpcpy(p, "'\n");
|
||
|
||
// here's our first unpredictable binary value, which is the offset of
|
||
// the portable executable headers.
|
||
r_off32_e_lfanew = p;
|
||
return WRITE32LE(p, 0);
|
||
}
|
||
|
||
static char *CopyMasterBootRecord(char *p) {
|
||
Elf64_Off off;
|
||
Elf64_Shdr *shdr;
|
||
struct Input *in;
|
||
struct Elf64_Sym *stub;
|
||
unassert(p == prologue + 64);
|
||
if ((in = GetInput(EM_NEXGEN32E)) && (stub = GetElfSymbol(in, "stub"))) {
|
||
shdr = GetElfSectionHeaderAddress(in->elf, in->size, stub->st_shndx);
|
||
if (!shdr || ckd_sub(&off, stub->st_value, shdr->sh_addr) ||
|
||
ckd_add(&off, off, shdr->sh_offset) || off >= in->size ||
|
||
off + (512 - 64) > in->size) {
|
||
Die(in->path, "corrupt symbol: stub");
|
||
}
|
||
p = mempcpy(p, in->map + off, 512 - 64);
|
||
}
|
||
return p;
|
||
}
|
||
|
||
static bool IsElfCarryingZipAssets(struct Input *in) {
|
||
return !!FindElfSectionByName(in->elf, in->size,
|
||
GetElfSectionNameStringTable(in->elf, in->size),
|
||
".zip");
|
||
}
|
||
|
||
static bool IsZipFileNamed(unsigned char *lfile, const char *name) {
|
||
return ZIP_LFILE_NAMESIZE(lfile) == strlen(name) &&
|
||
!memcmp(ZIP_LFILE_NAME(lfile), name, ZIP_LFILE_NAMESIZE(lfile));
|
||
}
|
||
|
||
static bool IsZipFileNameEqual(unsigned char *lfile1, unsigned char *lfile2) {
|
||
return ZIP_LFILE_NAMESIZE(lfile1) == ZIP_LFILE_NAMESIZE(lfile2) &&
|
||
!memcmp(ZIP_LFILE_NAME(lfile1), ZIP_LFILE_NAME(lfile2),
|
||
ZIP_LFILE_NAMESIZE(lfile1));
|
||
}
|
||
|
||
static bool IsZipFileContentEqual(unsigned char *lfile1,
|
||
unsigned char *lfile2) {
|
||
return ZIP_LFILE_CRC32(lfile1) == ZIP_LFILE_CRC32(lfile2) &&
|
||
ZIP_LFILE_COMPRESSEDSIZE(lfile1) == ZIP_LFILE_COMPRESSEDSIZE(lfile2) &&
|
||
!memcmp(lfile1 + ZIP_LFILE_HDRSIZE(lfile1),
|
||
lfile2 + ZIP_LFILE_HDRSIZE(lfile2),
|
||
ZIP_LFILE_COMPRESSEDSIZE(lfile1));
|
||
}
|
||
|
||
static bool HasZipAsset(unsigned char *lfile) {
|
||
int i;
|
||
for (i = 0; i < assets.n; ++i) {
|
||
if (IsZipFileNameEqual(lfile, assets.p[i].lfile)) {
|
||
if (IsZipFileContentEqual(lfile, assets.p[i].lfile)) {
|
||
return true;
|
||
} else {
|
||
Die(outpath, "multiple ELF files define assets at the same ZIP path, "
|
||
"but these duplicated assets can't be merged because they "
|
||
"don't have exactly the same content; perhaps the build "
|
||
"system is in an inconsistent state");
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static unsigned char *GetZipEndOfCentralDirectory(struct Input *in) {
|
||
unsigned char *img = in->umap;
|
||
unsigned char *eocd = img + in->size - kZipCdirHdrMinSize;
|
||
unsigned char *stop = MAX(eocd - 65536, img);
|
||
for (; eocd >= stop; --eocd) {
|
||
if (READ32LE(eocd) == kZipCdirHdrMagic) {
|
||
if (IsZipEocd32(img, in->size, eocd - img) != kZipOk) {
|
||
Die(in->path, "found corrupted ZIP EOCD header at the end of an "
|
||
"ELF file with a .zip section");
|
||
}
|
||
return eocd;
|
||
}
|
||
}
|
||
Die(in->path, "zip eocd not found in last 64kb of elf even though a .zip "
|
||
"section exists; you may need to run fixupobj");
|
||
}
|
||
|
||
static void IndexZipAssetsFromElf(struct Input *in) {
|
||
unsigned char *lfile;
|
||
unsigned char *img = in->umap;
|
||
unsigned char *eof = img + in->size;
|
||
unsigned char *eocd = GetZipEndOfCentralDirectory(in);
|
||
unsigned char *cdir = img + ZIP_CDIR_OFFSET(eocd);
|
||
unsigned char *cfile = cdir;
|
||
unsigned char *stop = cdir + ZIP_CDIR_SIZE(eocd);
|
||
if (stop > eof) {
|
||
Die(in->path, "zip central directory size overlaps image eof");
|
||
}
|
||
for (; cfile < stop; cfile += ZIP_CFILE_HDRSIZE(cfile)) {
|
||
if (cfile + kZipCfileHdrMinSize > eof || //
|
||
cfile + ZIP_CFILE_HDRSIZE(cfile) > eof) {
|
||
Die(in->path, "zip central directory entry overlaps image eof");
|
||
}
|
||
if (READ32LE(cfile) != kZipCfileHdrMagic) {
|
||
Die(in->path, "zip central directory entry magic corrupted");
|
||
}
|
||
if (ZIP_CFILE_OFFSET(cfile) == 0xFFFFFFFF) {
|
||
Die(in->path, "zip64 file format not supported at link time");
|
||
}
|
||
if ((lfile = img + ZIP_CFILE_OFFSET(cfile)) > eof) {
|
||
Die(in->path, "zip offset to local file points past image eof");
|
||
}
|
||
if (lfile + kZipLfileHdrMinSize > eof || //
|
||
lfile + ZIP_LFILE_HDRSIZE(lfile) > eof) {
|
||
Die(in->path, "zip local file header overlaps image eof");
|
||
}
|
||
if (READ32LE(lfile) != kZipLfileHdrMagic) {
|
||
Die(in->path, "zip local file entry magic corrupted");
|
||
}
|
||
if (ZIP_LFILE_COMPRESSEDSIZE(lfile) == 0xFFFFFFFF ||
|
||
ZIP_LFILE_UNCOMPRESSEDSIZE(lfile) == 0xFFFFFFFF) {
|
||
Die(in->path, "zip64 file format not supported at link time");
|
||
}
|
||
if (lfile + ZIP_LFILE_SIZE(lfile) > eof) {
|
||
Die(in->path, "zip local file content overlaps image eof");
|
||
}
|
||
if (!IsZipFileNamed(lfile, ".symtab") && !HasZipAsset(lfile)) {
|
||
AppendZipAsset(lfile, cfile);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void CopyZips(Elf64_Off offset) {
|
||
int i;
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
struct Input *in = inputs.p + i;
|
||
if (IsElfCarryingZipAssets(in)) {
|
||
IndexZipAssetsFromElf(in);
|
||
}
|
||
}
|
||
if (!assets.n) {
|
||
return; // nothing to do
|
||
}
|
||
if (offset + assets.total_local_file_bytes + assets.total_centraldir_bytes +
|
||
kZipCdirHdrMinSize >
|
||
INT_MAX) {
|
||
Die(outpath, "more than 2gb of zip files not supported yet");
|
||
}
|
||
Elf64_Off lp = offset;
|
||
Elf64_Off midpoint = offset + assets.total_local_file_bytes;
|
||
Elf64_Off cp = midpoint;
|
||
for (i = 0; i < assets.n; ++i) {
|
||
unsigned char *cfile = assets.p[i].cfile;
|
||
WRITE32LE(cfile + kZipCfileOffsetOffset, lp);
|
||
unsigned char *lfile = assets.p[i].lfile;
|
||
Pwrite(lfile, ZIP_LFILE_SIZE(lfile), lp);
|
||
lp += ZIP_LFILE_SIZE(lfile);
|
||
Pwrite(cfile, ZIP_CFILE_HDRSIZE(cfile), cp);
|
||
cp += ZIP_CFILE_HDRSIZE(cfile);
|
||
}
|
||
unassert(lp == midpoint);
|
||
unsigned char eocd[kZipCdirHdrMinSize] = {0};
|
||
WRITE32LE(eocd, kZipCdirHdrMagic);
|
||
WRITE32LE(eocd + kZipCdirRecordsOnDiskOffset, assets.n);
|
||
WRITE32LE(eocd + kZipCdirRecordsOffset, assets.n);
|
||
WRITE32LE(eocd + kZipCdirSizeOffset, assets.total_centraldir_bytes);
|
||
WRITE32LE(eocd + kZipCdirOffsetOffset,
|
||
offset + assets.total_local_file_bytes);
|
||
Pwrite(eocd, sizeof(eocd), cp);
|
||
}
|
||
|
||
int main(int argc, char *argv[]) {
|
||
char *p;
|
||
int i, j;
|
||
Elf64_Off offset;
|
||
Elf64_Xword prologue_bytes;
|
||
|
||
#ifdef MODE_DBG
|
||
ShowCrashReports();
|
||
#endif
|
||
|
||
prog = argv[0];
|
||
if (!prog)
|
||
prog = "apelink";
|
||
|
||
// process flags
|
||
GetOpts(argc, argv);
|
||
|
||
// determine strategy
|
||
//
|
||
// if we're only targeting a single architecture, and we're not
|
||
// targeting windows or macos then it's possible to make an elf
|
||
// executable without a shell script.
|
||
if (argc - optind == 1 && !(support_vector & (_HOSTWINDOWS | _HOSTXNU))) {
|
||
strategy = kElf;
|
||
loaders.n = 0;
|
||
} else if (argc - optind == 1 &&
|
||
!(support_vector & ~(_HOSTWINDOWS | _HOSTXNU))) {
|
||
strategy = kPe;
|
||
loaders.n = 0;
|
||
} else if (inputs.n == 1 && support_vector == _HOSTXNU) {
|
||
strategy = kMacho;
|
||
loaders.n = 0;
|
||
} else {
|
||
strategy = kApe;
|
||
}
|
||
|
||
// open loaders
|
||
for (i = 0; i < loaders.n; ++i) {
|
||
OpenLoader(loaders.p + i);
|
||
}
|
||
for (i = 0; i < loaders.n; ++i) {
|
||
for (j = i + 1; j < loaders.n; ++j) {
|
||
if (loaders.p[i].os == loaders.p[j].os &&
|
||
loaders.p[i].machine == loaders.p[j].machine) {
|
||
Die(prog, "multiple ape loaders specified for the same platform");
|
||
}
|
||
}
|
||
}
|
||
|
||
// open input files
|
||
for (i = optind; i < argc; ++i) {
|
||
OpenInput(argv[i]);
|
||
}
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
for (j = i + 1; j < inputs.n; ++j) {
|
||
if (inputs.p[i].elf->e_machine == inputs.p[j].elf->e_machine) {
|
||
Die(prog, "multiple executables passed for the same machine type");
|
||
}
|
||
}
|
||
}
|
||
|
||
// validate input files
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
struct Input *in = inputs.p + i;
|
||
ValidateElfImage(in->elf, in->size, in->path, false);
|
||
}
|
||
|
||
// load symbols
|
||
if (!want_stripped) {
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
struct Input *in = inputs.p + i;
|
||
if (GetElfSymbol(in, "__zipos_get")) {
|
||
LoadSymbols(in->elf, in->size, in->path);
|
||
}
|
||
}
|
||
}
|
||
|
||
// generate the executable header
|
||
p = prologue;
|
||
if (strategy == kElf) {
|
||
p = GenerateElf(p, inputs.p);
|
||
if (support_vector & _HOSTMETAL) {
|
||
p = CopyMasterBootRecord(p);
|
||
}
|
||
} else if (strategy == kMacho) {
|
||
p = GenerateMacho(p, inputs.p);
|
||
} else if (strategy == kPe) {
|
||
p[0] = 'M';
|
||
p[1] = 'Z';
|
||
p += 64;
|
||
r_off32_e_lfanew = p - 4;
|
||
} else {
|
||
assert(strategy == kApe);
|
||
if (force_bypass_binfmt_misc) {
|
||
p = stpcpy(p, "APEDBG='\n\n");
|
||
if (support_vector & (_HOSTWINDOWS | _HOSTMETAL)) {
|
||
p = FinishGeneratingDosHeader(p);
|
||
}
|
||
} else if (support_vector & _HOSTWINDOWS) {
|
||
p = stpcpy(p, "MZqFpD='\n\n");
|
||
p = FinishGeneratingDosHeader(p);
|
||
} else {
|
||
p = stpcpy(p, "jartsr='\n\n");
|
||
if (support_vector & _HOSTMETAL) {
|
||
p = FinishGeneratingDosHeader(p);
|
||
}
|
||
}
|
||
if (support_vector & _HOSTMETAL) {
|
||
p = CopyMasterBootRecord(p);
|
||
}
|
||
p = stpcpy(p, "\n");
|
||
p = stpcpy(p, ape_heredoc);
|
||
p = stpcpy(p, "\n");
|
||
p = stpcpy(p, "#'\"\n"); // longstanding convention (see mz notes)
|
||
p = stpcpy(p, "\n");
|
||
if (custom_sh_code) {
|
||
p = stpcpy(p, custom_sh_code);
|
||
*p++ = '\n';
|
||
}
|
||
p = stpcpy(p, "o=$(command -v \"$0\")\n");
|
||
|
||
// run this program using the systemwide ape loader if it exists
|
||
if (loaders.n) {
|
||
p = stpcpy(p, "[ x\"$1\" != x--assimilate ] && ");
|
||
}
|
||
if (!dont_path_lookup_ape_loader) {
|
||
p = stpcpy(p, "type ape >/dev/null 2>&1 && "
|
||
"exec ape \"$o\" \"$@\"\n");
|
||
}
|
||
|
||
// otherwise try to use the ad-hoc self-extracted loader, securely
|
||
if (loaders.n) {
|
||
p = stpcpy(p, "t=\"${TMPDIR:-${HOME:-.}}/.ape-" APE_VERSION_STR "\"\n"
|
||
"[ x\"$1\" != x--assimilate ] && "
|
||
"[ -x \"$t\" ] && "
|
||
"exec \"$t\" \"$o\" \"$@\"\n");
|
||
}
|
||
|
||
// otherwise this is a fresh install so consider the platform
|
||
p = stpcpy(p, "m=$(uname -m 2>/dev/null) || m=x86_64\n");
|
||
if (support_vector & _HOSTXNU) {
|
||
p = stpcpy(p, "if [ ! -d /Applications ]; then\n");
|
||
}
|
||
|
||
// generate shell script for elf operating systems
|
||
//
|
||
// 1. for classic ape binaries which assimilate, all we have to do
|
||
// is self-modify this executable file using the printf builtin
|
||
// and then we re-exec this file. we only need Elf64_Ehdr which
|
||
// is always exactly 64 bytes.
|
||
//
|
||
// 2. if an appropriate ape loader program was passed in the flags
|
||
// then we won't self-modify the executable, and we instead try
|
||
// to self-extract the loader binary to a safe location, and it
|
||
// is then used by re-launch this program. thanks to incredible
|
||
// greatness of the elf file format, our 12kb ape loader binary
|
||
// is able to run on all of our supported elf operating systems
|
||
// but, we do still need to embed multiple copies of ape loader
|
||
// when generating fat binaries that run on multiple cpu types.
|
||
//
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
struct Input *in = inputs.p + i;
|
||
if (GetLoader(in->elf->e_machine, ~_HOSTXNU)) {
|
||
// if an ape loader is available for this microprocessor for
|
||
// this operating system, then put assimilate behind a flag.
|
||
p = stpcpy(p, "if [ x\"$1\" = x--assimilate ]; then\n");
|
||
}
|
||
p = GenerateScriptIfMachine(p, in); // if [ arch ]
|
||
p = stpcpy(p, "exec 7<> \"$o\" || exit 121\n"
|
||
"printf '"
|
||
"\\177ELF" // 0x0: ⌂ELF
|
||
"\\2" // 4: long mode
|
||
"\\1" // 5: little endian
|
||
"\\1"); // 6: elf v1.o
|
||
p = EncodeWordAsPrintf(p, PickElfOsAbi(), 1);
|
||
p = stpcpy(p, "\\0" // 8: os/abi ver.
|
||
"\\0\\0\\0" // 9: padding 3/7
|
||
"\\0\\0\\0\\0" // padding 4/7
|
||
"\\2\\0"); // 10: εxεcµταblε
|
||
p = EncodeWordAsPrintf(p, in->elf->e_machine, 1);
|
||
p = stpcpy(p, "\\0\\1\\0\\0\\0"); // elf v1.o
|
||
p = EncodeWordAsPrintf(p, in->elf->e_entry, 8);
|
||
in->printf_phoff = p;
|
||
p = stpcpy(p, "\\000\\000\\000\\000\\0\\0\\0\\0");
|
||
p = stpcpy(p, "\\0\\0\\0\\0\\0\\0\\0\\0" // 28: e_shoff
|
||
"\\0\\0\\0\\0" // 30: e_flags
|
||
"\\100\\0" // 34: e_ehsize
|
||
"\\70\\0"); // 36: e_phentsize
|
||
in->printf_phnum = p;
|
||
p = stpcpy(p, "\\000\\000" // 38: e_phnum
|
||
"\\0\\0" // 3a: e_shentsize
|
||
"\\0\\0" // 3c: e_shnum
|
||
"\\0\\0" // 3e: e_shstrndx
|
||
"' >&7\n"
|
||
"exec 7<&-\n");
|
||
if (GetLoader(in->elf->e_machine, ~_HOSTXNU)) {
|
||
p = stpcpy(p, "fi\n"); // </GenerateScriptIfMachine>
|
||
p = stpcpy(p, "exit\n");
|
||
p = stpcpy(p, "fi\n"); // </--assimilate>
|
||
} else {
|
||
p = stpcpy(p, "exec \"$o\" \"$@\"\n");
|
||
p = stpcpy(p, "fi\n"); // </GenerateScriptIfMachine>
|
||
}
|
||
}
|
||
|
||
// generate shell script for xnu
|
||
//
|
||
// 1. for classic ape binaries which assimilate, ape will favor
|
||
// using `dd`, to memmove the embedded mach-o headers to the
|
||
// front of the this file, before re-exec'ing itself. that's
|
||
// because ape was originally designed using a linker script
|
||
// which couldn't generate printf statements outputting data
|
||
// of variable length. we are now stuck with the `dd` design
|
||
// because tools like assimilate.com expect it there. that's
|
||
// how it's able to determine the offset of the macho header
|
||
//
|
||
// 2. the x86 elf ape loader executable is able to runs on xnu,
|
||
// but we need to use an additional `dd` command after it is
|
||
// copied, which memmove's the embedded macho-o headers into
|
||
// the first bytes of the file.
|
||
//
|
||
// 3. xnu on arm64 has strict code signing requirements, and it
|
||
// means we can't embed a precompiled ape loader program :'(
|
||
// so what we do is embed the the ape loader source code and
|
||
// then hope the user has a c compiler installed, which will
|
||
// let our shell script compile the ape loader on first run.
|
||
//
|
||
if (support_vector & _HOSTXNU) {
|
||
bool gotsome = false;
|
||
p = stpcpy(p, "else\n"); // if [ -d /Applications ]; then
|
||
|
||
// output native mach-o morph
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
struct Input *in = inputs.p + i;
|
||
if (in->elf->e_machine != EM_NEXGEN32E)
|
||
continue;
|
||
if (GetLoader(in->elf->e_machine, _HOSTXNU)) {
|
||
p = stpcpy(p, "if [ x\"$1\" = x--assimilate ]; then\n");
|
||
}
|
||
p = GenerateScriptIfMachine(p, in);
|
||
p = stpcpy(p, "dd if=\"$o\" of=\"$o\" bs=1");
|
||
p = stpcpy(p, " skip=");
|
||
in->ddarg_macho_skip = p;
|
||
p = GenerateDecimalOffsetRelocation(p);
|
||
p = stpcpy(p, " count=");
|
||
in->ddarg_macho_count = p;
|
||
p = GenerateDecimalOffsetRelocation(p);
|
||
p = stpcpy(p, " conv=notrunc 2>/dev/null ||exit\n");
|
||
if (GetLoader(in->elf->e_machine, _HOSTXNU)) {
|
||
p = stpcpy(p, "exit\n"); // --assimilate
|
||
p = stpcpy(p, "fi\n");
|
||
} else {
|
||
p = stpcpy(p, "exec \"$o\" \"$@\"\n");
|
||
}
|
||
p = stpcpy(p, "fi\n");
|
||
gotsome = true;
|
||
break;
|
||
}
|
||
|
||
// output mach-o ape loader binary
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
struct Loader *loader;
|
||
struct Input *in = inputs.p + i;
|
||
if ((loader = GetLoader(in->elf->e_machine, _HOSTXNU))) {
|
||
loader->used = true;
|
||
p = GenerateScriptIfMachine(p, in); // <if-machine>
|
||
p = stpcpy(p, "mkdir -p \"${t%/*}\" ||exit\n"
|
||
"dd if=\"$o\"");
|
||
p = stpcpy(p, " skip=");
|
||
loader->ddarg_skip1 = p;
|
||
p = GenerateDecimalOffsetRelocation(p);
|
||
p = stpcpy(p, " count=");
|
||
loader->ddarg_size1 = p;
|
||
p = GenerateDecimalOffsetRelocation(p);
|
||
p = stpcpy(p, " bs=1 2>/dev/null | gzip -dc >\"$t.$$\" ||exit\n");
|
||
if (loader->macho_offset) {
|
||
p = stpcpy(p, "dd if=\"$t.$$\" of=\"$t.$$\"");
|
||
p = stpcpy(p, " skip=");
|
||
p = FormatInt32(p, loader->macho_offset / 64);
|
||
p = stpcpy(p, " count=");
|
||
p = FormatInt32(p, ROUNDUP(loader->macho_length, 64) / 64);
|
||
p = stpcpy(p, " bs=64 conv=notrunc 2>/dev/null ||exit\n");
|
||
}
|
||
p = stpcpy(p, "chmod 755 \"$t.$$\" ||exit\n"
|
||
"mv -f \"$t.$$\" \"$t\" ||exit\n");
|
||
p = stpcpy(p, "exec \"$t\" \"$o\" \"$@\"\n"
|
||
"fi\n"); // </if-machine>
|
||
gotsome = true;
|
||
}
|
||
}
|
||
|
||
if (macos_silicon_loader_source_path) {
|
||
struct Input *in;
|
||
if (!(in = GetInput(EM_AARCH64))) {
|
||
Die(macos_silicon_loader_source_path,
|
||
"won't embed macos arm64 ape loader source code because a native "
|
||
"build of your program for aarch64 wasn't passed as an argument");
|
||
}
|
||
p = GenerateScriptIfMachine(p, in); // <if-machine>
|
||
p = stpcpy(p, "if ! type cc >/dev/null 2>&1; then\n"
|
||
"echo \"$0: please run: xcode-select --install\" >&2\n"
|
||
"exit 1\n"
|
||
"fi\n"
|
||
"mkdir -p \"${t%/*}\" ||exit\n"
|
||
"dd if=\"$o\"");
|
||
p = stpcpy(p, " skip=");
|
||
macos_silicon_loader_source_ddarg_skip = p;
|
||
p = GenerateDecimalOffsetRelocation(p);
|
||
p = stpcpy(p, " count=");
|
||
macos_silicon_loader_source_ddarg_size = p;
|
||
p = GenerateDecimalOffsetRelocation(p);
|
||
p = stpcpy(p, " bs=1 2>/dev/null | gzip -dc >\"$t.c.$$\" ||exit\n"
|
||
"mv -f \"$t.c.$$\" \"$t.c\" ||exit\n"
|
||
"cc -w -O -o \"$t.$$\" \"$t.c\" ||exit\n"
|
||
"mv -f \"$t.$$\" \"$t\" ||exit\n"
|
||
"exec \"$t\" \"$o\" \"$@\"\n"
|
||
"fi\n"); // </if-machine>
|
||
gotsome = true;
|
||
}
|
||
|
||
if (!gotsome) {
|
||
p = stpcpy(p, "true\n");
|
||
}
|
||
p = stpcpy(p, "fi\n"); // if [ -d /Applications ]; then
|
||
} else {
|
||
macos_silicon_loader_source_path = 0;
|
||
}
|
||
|
||
// extract the ape loader for open platforms
|
||
bool gotsome = false;
|
||
if (inputs.n && (support_vector & _HOSTXNU)) {
|
||
p = stpcpy(p, "if [ ! -d /Applications ]; then\n");
|
||
}
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
struct Loader *loader;
|
||
struct Input *in = inputs.p + i;
|
||
if ((loader = GetLoader(in->elf->e_machine, ~_HOSTXNU))) {
|
||
loader->used = true;
|
||
p = GenerateScriptIfMachine(p, in);
|
||
p = stpcpy(p, "mkdir -p \"${t%/*}\" ||exit\n"
|
||
"dd if=\"$o\"");
|
||
p = stpcpy(p, " skip=");
|
||
loader->ddarg_skip2 = p;
|
||
p = GenerateDecimalOffsetRelocation(p);
|
||
p = stpcpy(p, " count=");
|
||
loader->ddarg_size2 = p;
|
||
p = GenerateDecimalOffsetRelocation(p);
|
||
p = stpcpy(p, " bs=1 2>/dev/null | gzip -dc >\"$t.$$\" ||exit\n"
|
||
"chmod 755 \"$t.$$\" ||exit\n"
|
||
"mv -f \"$t.$$\" \"$t\" ||exit\n");
|
||
p = stpcpy(p, "exec \"$t\" \"$o\" \"$@\"\n"
|
||
"fi\n");
|
||
gotsome = true;
|
||
}
|
||
}
|
||
if (inputs.n && (support_vector & _HOSTXNU)) {
|
||
if (!gotsome) {
|
||
p = stpcpy(p, "true\n");
|
||
}
|
||
p = stpcpy(p, "fi\n");
|
||
}
|
||
|
||
// the final failure
|
||
p = stpcpy(p, "echo \"$0: this ape program lacks $m support\" >&2\n");
|
||
p = stpcpy(p, "exit 127\n\n\n\n");
|
||
}
|
||
p = GenerateElfNotes(p);
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
p = SecondPass(p, inputs.p + i);
|
||
}
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
p = SecondPass2(p, inputs.p + i);
|
||
}
|
||
prologue_bytes = p - prologue;
|
||
|
||
// write the output file
|
||
if ((outfd = creat(outpath, 0755)) == -1) {
|
||
DieSys(outpath);
|
||
}
|
||
offset = prologue_bytes;
|
||
for (i = 0; i < inputs.n; ++i) {
|
||
offset = ThirdPass(offset, inputs.p + i);
|
||
}
|
||
|
||
// concatenate ape loader binaries
|
||
for (i = 0; i < loaders.n; ++i) {
|
||
char *compressed_data;
|
||
size_t compressed_size;
|
||
struct Loader *loader;
|
||
loader = loaders.p + i;
|
||
if (!loader->used)
|
||
continue;
|
||
compressed_data = Gzip(loader->map, loader->size, &compressed_size);
|
||
if (loader->ddarg_skip1) {
|
||
FixupWordAsDecimal(loader->ddarg_skip1, offset);
|
||
}
|
||
if (loader->ddarg_size1) {
|
||
FixupWordAsDecimal(loader->ddarg_size1, compressed_size);
|
||
}
|
||
if (loader->ddarg_skip2) {
|
||
FixupWordAsDecimal(loader->ddarg_skip2, offset);
|
||
}
|
||
if (loader->ddarg_size2) {
|
||
FixupWordAsDecimal(loader->ddarg_size2, compressed_size);
|
||
}
|
||
Pwrite(compressed_data, compressed_size, offset);
|
||
offset += compressed_size;
|
||
free(compressed_data);
|
||
}
|
||
|
||
// concatenate ape loader source code
|
||
if (macos_silicon_loader_source_path) {
|
||
char *compressed_data;
|
||
size_t compressed_size;
|
||
compressed_data =
|
||
Gzip(macos_silicon_loader_source_text,
|
||
strlen(macos_silicon_loader_source_text), &compressed_size);
|
||
FixupWordAsDecimal(macos_silicon_loader_source_ddarg_skip, offset);
|
||
FixupWordAsDecimal(macos_silicon_loader_source_ddarg_size, compressed_size);
|
||
Pwrite(compressed_data, compressed_size, offset);
|
||
offset += compressed_size;
|
||
free(compressed_data);
|
||
}
|
||
|
||
// add the zip files
|
||
CopyZips(offset);
|
||
|
||
// write the header
|
||
Pwrite(prologue, prologue_bytes, 0);
|
||
|
||
if (close(outfd)) {
|
||
DieSys(outpath);
|
||
}
|
||
}
|