/*-*- 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"); // p = stpcpy(p, "exit\n"); p = stpcpy(p, "fi\n"); // } else { p = stpcpy(p, "exec \"$o\" \"$@\"\n"); p = stpcpy(p, "fi\n"); // } } // 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); // 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"); // 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); // 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"); // 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); } }