diff --git a/Makefile b/Makefile index 86424d681..ceef06b66 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,6 @@ include libc/vga/vga.mk #─┘ include libc/calls/calls.mk #─┐ include libc/runtime/runtime.mk # ├──SYSTEMS RUNTIME include libc/crt/crt.mk # │ You can issue system calls -include tool/hello/hello.mk # │ include third_party/nsync/nsync.mk # │ include third_party/dlmalloc/dlmalloc.mk #─┘ include libc/mem/mem.mk #─┐ @@ -147,6 +146,7 @@ include third_party/gdtoa/gdtoa.mk # ├──DYNAMIC RUNTIME include third_party/nsync/mem/mem.mk # │ You can now use stdio include libc/thread/thread.mk # │ You can finally call malloc() include libc/zipos/zipos.mk # │ +include tool/hello/hello.mk # │ include libc/stdio/stdio.mk # │ include libc/time/time.mk # │ include net/net.mk # │ diff --git a/ape/ape.S b/ape/ape.S index f7052a275..054e4bf3e 100644 --- a/ape/ape.S +++ b/ape/ape.S @@ -1044,7 +1044,8 @@ DLLEXE = DLLSTD // ││││││││ │ │┌6:Contains Initialized Data // ││││││││ o │ ││┌5:Contains Code // ││││││││┌┴─┐rrrr│ ooror│││rorrr -PETEXT = 0b01100000000000000000000001100000 +PETEXT = 0b01100000000000000000000000100000 +PERDAT = 0b01000000000000000000000001000000 PEDATA = 0b11000000000000000000000011000000 PEIMPS = 0b11000000000000000000000001000000 @@ -1106,6 +1107,19 @@ ape_pe: .ascin "PE",4 .long PETEXT // Flags .previous + .section .pe.sections,"a",@progbits + .ascin ".rdata",8 // Section Name + .long ape_rom_memsz // Virtual Size or Physical Address + .long ape_rom_rva // Relative Virtual Address + .long ape_rom_filesz // Physical Size + .long ape_rom_offset // Physical Offset + .long 0 // Relocation Table Offset + .long 0 // Line Number Table Offset + .short 0 // Relocation Count + .short 0 // Line Number Count + .long PERDAT // Flags + .previous + .section .pe.sections,"a",@progbits .ascin ".data",8 // Section Name .long ape_ram_memsz // Virtual Size or Physical Address @@ -1128,6 +1142,7 @@ ape_pe: .ascin "PE",4 .type ape_idata_idt,@object .globl ape_idata_idt,ape_idata_idtend .hidden ape_idata_idt,ape_idata_idtend + .balign 4 ape_idata_idt: .previous/* ... @@ -1136,6 +1151,7 @@ ape_idata_idt: */.section .idata.ro.idt.3,"a",@progbits .long 0,0,0,0,0 ape_idata_idtend: + .byte 0 .previous .section .piro.data.sort.iat.1,"aw",@progbits @@ -1143,13 +1159,16 @@ ape_idata_idtend: .type ape_idata_iat,@object .globl ape_idata_iat,ape_idata_iatend .hidden ape_idata_iat,ape_idata_iatend + .balign 8 ape_idata_iat: .previous/* ... decentralized content ... */.section .piro.data.sort.iat.3,"aw",@progbits + .quad 0 ape_idata_iatend: + .byte 0 .previous #endif /* SupportsWindows() */ @@ -1259,8 +1278,6 @@ realmodeloader: call lhinit call rlinit call sinit4 - .optfn _start16 - call _start16 call longmodeloader .endfn realmodeloader diff --git a/ape/ape.lds b/ape/ape.lds index 0fa664d93..446cfa396 100644 --- a/ape/ape.lds +++ b/ape/ape.lds @@ -229,7 +229,7 @@ SECTIONS { KEEP(*(.text.head)) /* Executable & Linkable Format */ - . = ALIGN(__SIZEOF_POINTER__); + . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0); ape_phdrs = .; KEEP(*(.elf.phdrs)) ape_phdrs_end = .; @@ -239,7 +239,7 @@ SECTIONS { KEEP(*(.emushepilogue)) /* OpenBSD */ - . = ALIGN(__SIZEOF_POINTER__); + . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0); ape_note = .; KEEP(*(.note.openbsd.ident)) KEEP(*(.note.netbsd.ident)) @@ -253,15 +253,15 @@ SECTIONS { /* Mach-O */ KEEP(*(.macho)) - . = ALIGN(__SIZEOF_POINTER__); + . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0); ape_macho_end = .; /* APE loader */ KEEP(*(.ape.loader)) - . = ALIGN(CODE_GRANULE); + . = ALIGN(. != 0 ? CODE_GRANULE : 0); KEEP(*(.ape.pad.head)) - . = ALIGN(SupportsWindows() || SupportsMetal() ? CONSTANT(MAXPAGESIZE) : 16); + . = ALIGN(. != 0 ? (SupportsWindows() || SupportsMetal() ? CONSTANT(MAXPAGESIZE) : 16) : 0); _ehead = .; } :Head @@ -312,14 +312,14 @@ SECTIONS { /* Privileged code invulnerable to magic */ KEEP(*(.ape.pad.privileged)); - . = ALIGN(__privileged_end > __privileged_start ? CONSTANT(MAXPAGESIZE) : 1); + . = ALIGN(__privileged_end > __privileged_start ? CONSTANT(MAXPAGESIZE) : 0); /*END: morphable code */ __privileged_start = .; *(.privileged) __privileged_end = .; KEEP(*(.ape.pad.text)) - . = ALIGN(CONSTANT(MAXPAGESIZE)); + . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0); /*END: Read Only Data (only needed for initialization) */ } :Cod @@ -354,7 +354,7 @@ SECTIONS { KEEP(*(SORT_BY_NAME(.initro.*))) KEEP(*(.initroepilogue)) KEEP(*(SORT_BY_NAME(.sort.rodata.*))) - . = ALIGN(CONSTANT(MAXPAGESIZE)); + . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0); /* don't delete this line :o */ /*END: read-only data that's only needed for initialization */ @@ -368,14 +368,13 @@ SECTIONS { *(SORT_BY_ALIGNMENT(.tdata.*)) _tdata_end = .; KEEP(*(.ape.pad.rodata)) - . = ALIGN(CONSTANT(MAXPAGESIZE)); + . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0); _etext = .; PROVIDE(etext = .); } :Tls :Rom /*END: Read Only Data */ - . = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(MAXPAGESIZE)); - . = DATA_SEGMENT_RELRO_END(0, .); + . = ALIGN(CONSTANT(MAXPAGESIZE)); /* this only tells the linker about the layout of uninitialized */ /* TLS data, and does not advance the linker's location counter */ @@ -409,7 +408,7 @@ SECTIONS { *(.got.plt) KEEP(*(.gotpltepilogue)) - . = ALIGN(__SIZEOF_POINTER__); + . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0); __init_array_start = .; KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) @@ -418,7 +417,7 @@ SECTIONS { KEEP(*(.preinit_array)) __init_array_end = .; - . = ALIGN(__SIZEOF_POINTER__); + . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0); __fini_array_start = .; KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) @@ -427,19 +426,21 @@ SECTIONS { __fini_array_end = .; /*BEGIN: Post-Initialization Read-Only */ - . = ALIGN(__SIZEOF_POINTER__); + . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0); KEEP(*(SORT_BY_NAME(.piro.relo.sort.*))) - . = ALIGN(__SIZEOF_POINTER__); + . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0); KEEP(*(SORT_BY_NAME(.piro.data.sort.*))) KEEP(*(.piro.pad.data)) KEEP(*(.dataepilogue)) - . = ALIGN(CONSTANT(MAXPAGESIZE)); + . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0); /*END: NT FORK COPYING */ _edata = .; PROVIDE(edata = .); _ezip = .; /* <-- very deprecated */ } :Ram + . = ALIGN(CONSTANT(MAXPAGESIZE)); + /*END: file content that's loaded by o/s */ /*END: file content */ /*BEGIN: bss memory that's addressable */ @@ -463,14 +464,14 @@ SECTIONS { KEEP(*(.bssepilogue)) - . = ALIGN(CONSTANT(MAXPAGESIZE)); + . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0); /*END: NT FORK COPYING */ - _end = .; - PROVIDE(end = .); } :Ram - . = DATA_SEGMENT_END(.); + . = ALIGN(CONSTANT(MAXPAGESIZE)); + _end = .; + PROVIDE(end = .); /*END: nt addressability guarantee */ /*END: bsd addressability guarantee */ @@ -556,24 +557,24 @@ _tls_align = 1; ape_cod_offset = 0; ape_cod_vaddr = ADDR(.head); ape_cod_paddr = LOADADDR(.head); -ape_cod_filesz = SIZEOF(.head) + SIZEOF(.text); +ape_cod_filesz = ADDR(.rodata) - ADDR(.head); ape_cod_memsz = ape_cod_filesz; ape_cod_align = CONSTANT(MAXPAGESIZE); ape_cod_rva = RVA(ape_cod_vaddr); -ape_rom_offset = ape_cod_offset + ape_cod_filesz; ape_rom_vaddr = ADDR(.rodata); +ape_rom_offset = ape_rom_vaddr - __executable_start; ape_rom_paddr = LOADADDR(.rodata); -ape_rom_filesz = SIZEOF(.rodata) + SIZEOF(.tdata); +ape_rom_filesz = ADDR(.tbss) - ADDR(.rodata); ape_rom_memsz = ape_rom_filesz; ape_rom_align = CONSTANT(MAXPAGESIZE); ape_rom_rva = RVA(ape_rom_vaddr); -ape_ram_offset = ape_rom_offset + ape_rom_filesz; ape_ram_vaddr = ADDR(.data); +ape_ram_offset = ape_ram_vaddr - __executable_start; ape_ram_paddr = LOADADDR(.data); -ape_ram_filesz = SIZEOF(.data); -ape_ram_memsz = SIZEOF(.data) + SIZEOF(.bss); +ape_ram_filesz = ADDR(.bss) - ADDR(.data); +ape_ram_memsz = _end - ADDR(.data); ape_ram_align = CONSTANT(MAXPAGESIZE); ape_ram_rva = RVA(ape_ram_vaddr); @@ -591,10 +592,10 @@ ape_note_offset = ape_cod_offset + (ape_note - ape_cod_vaddr); ape_note_filesz = ape_note_end - ape_note; ape_note_memsz = ape_note_filesz; -ape_text_offset = ape_cod_offset + LOADADDR(.text) - ape_cod_paddr; -ape_text_paddr = LOADADDR(.text); ape_text_vaddr = ADDR(.text); -ape_text_filesz = SIZEOF(.text) + SIZEOF(.rodata) + SIZEOF(.tdata); +ape_text_offset = ape_text_vaddr - __executable_start; +ape_text_paddr = LOADADDR(.text); +ape_text_filesz = ADDR(.rodata) - ADDR(.text); ape_text_memsz = ape_text_filesz; ape_text_align = CONSTANT(MAXPAGESIZE); ape_text_rva = RVA(ape_text_vaddr); @@ -760,12 +761,6 @@ ASSERT(((DEFINED(__init_rodata_end) ? __init_rodata_end : 0) % ASSERT((!DEFINED(ape_grub) ? 1 : RVA(ape_grub) < 8192), "grub stub needs to be in first 8kb of image"); -ASSERT(DEFINED(_start) || DEFINED(_start16), - "please link a _start() or _start16() entrypoint"); - -ASSERT(!DEFINED(_start16) || REAL(_end) < 65536, - "ape won't support non-tiny real mode programs"); - ASSERT(IS2POW(ape_stack_memsz), "ape_stack_memsz must be a two power"); diff --git a/ape/idata.internal.h b/ape/idata.internal.h index 80c0ef892..c090229e1 100644 --- a/ape/idata.internal.h +++ b/ape/idata.internal.h @@ -43,10 +43,10 @@ .hidden \fn .previous .section ".idata.ro.ilt.\dll\().2.\actual","a",@progbits -".Lidata.ilt.\dll\().\actual": +"idata.ilt.\dll\().\actual": .quad RVA("\dll\().\actual") - .type ".Lidata.ilt.\dll\().\actual",@object - .size ".Lidata.ilt.\dll\().\actual",.-".Lidata.ilt.\dll\().\actual" + .type "idata.ilt.\dll\().\actual",@object + .size "idata.ilt.\dll\().\actual",.-"idata.ilt.\dll\().\actual" .previous .section ".idata.ro.hnt.\dll\().2.\actual","a",@progbits "\dll\().\actual": diff --git a/build/bootstrap/pecheck.com b/build/bootstrap/pecheck.com new file mode 100755 index 000000000..6536a79bf Binary files /dev/null and b/build/bootstrap/pecheck.com differ diff --git a/build/definitions.mk b/build/definitions.mk index aca2e1880..077fbacf4 100644 --- a/build/definitions.mk +++ b/build/definitions.mk @@ -75,6 +75,7 @@ PKG = build/bootstrap/package.com MKDEPS = build/bootstrap/mkdeps.com ZIPOBJ = build/bootstrap/zipobj.com ZIPCOPY = build/bootstrap/zipcopy.com +PECHECK = build/bootstrap/pecheck.com FIXUPOBJ = build/bootstrap/fixupobj.com MKDIR = build/bootstrap/mkdir.com -p COMPILE = build/bootstrap/compile.com -V9 -P4096 $(QUOTA) diff --git a/build/rules.mk b/build/rules.mk index 6d11c6da3..11fde207f 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -17,8 +17,9 @@ MAKEFLAGS += --no-builtin-rules MAKE_ZIPCOPY = $(COMPILE) -AZIPCOPY -wT$@ $(ZIPCOPY) $< $@ +MAKE_PECHECK = $(COMPILE) -APECHECK -wT$@ $(PECHECK) $@ ifneq ($(ARCH), aarch64) -MAKE_OBJCOPY = $(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ && $(MAKE_ZIPCOPY) +MAKE_OBJCOPY = $(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ && $(MAKE_ZIPCOPY) && $(MAKE_PECHECK) else MAKE_OBJCOPY = $(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S $< $@ && $(MAKE_ZIPCOPY) endif diff --git a/examples/hello2.c b/examples/hello2.c index c46a5794b..ecf749dee 100644 --- a/examples/hello2.c +++ b/examples/hello2.c @@ -8,9 +8,8 @@ ╚─────────────────────────────────────────────────────────────────*/ #endif #include "libc/calls/calls.h" -#include "libc/str/str.h" int main() { - // write(1, "hello world\n", 12); + write(1, "hello world\n", 12); return 0; } diff --git a/libc/runtime/abort.c b/libc/calls/abort.c similarity index 100% rename from libc/runtime/abort.c rename to libc/calls/abort.c diff --git a/libc/calls/kntsystemdirectory.S b/libc/calls/kntsystemdirectory.S index 870b4faf9..0139b92b1 100644 --- a/libc/calls/kntsystemdirectory.S +++ b/libc/calls/kntsystemdirectory.S @@ -32,10 +32,12 @@ kNtSystemDirectory: .init.start 300,_init_kNtSystemDirectory #if SupportsWindows() + testb IsWindows() + jz 1f pushpop BYTES,%rdx mov __imp_GetSystemDirectoryA(%rip),%rax call __getntsyspath -#else - add $BYTES,%rdi + jmp 2f #endif - .init.end 300,_init_kNtSystemDirectory +1: add $BYTES,%rdi +2: .init.end 300,_init_kNtSystemDirectory diff --git a/libc/calls/kntwindowsdirectory.S b/libc/calls/kntwindowsdirectory.S index 890cebfda..4f56b5a8f 100644 --- a/libc/calls/kntwindowsdirectory.S +++ b/libc/calls/kntwindowsdirectory.S @@ -32,10 +32,12 @@ kNtWindowsDirectory: .init.start 300,_init_kNtWindowsDirectory #if SupportsWindows() + testb IsWindows() + jz 1f pushpop BYTES,%rdx mov __imp_GetWindowsDirectoryA(%rip),%rax call __getntsyspath -#else - add $BYTES,%rdi + jmp 2f #endif - .init.end 300,_init_kNtWindowsDirectory +1: add $BYTES,%rdi +2: .init.end 300,_init_kNtWindowsDirectory diff --git a/libc/calls/onntconsoleevent_init.S b/libc/calls/onntconsoleevent_init.S index 2e8862be9..1f22620d9 100644 --- a/libc/calls/onntconsoleevent_init.S +++ b/libc/calls/onntconsoleevent_init.S @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" #include "libc/macros.internal.h" .text.windows @@ -25,7 +26,9 @@ __onntconsoleevent_nt: .endfn __onntconsoleevent_nt,globl,hidden .init.start 300,_init_onntconsoleevent + testb IsWindows() + jz 1f ezlea __onntconsoleevent_nt,cx pushpop 1,%rdx ntcall __imp_SetConsoleCtrlHandler - .init.end 300,_init_onntconsoleevent,globl,hidden +1: .init.end 300,_init_onntconsoleevent,globl,hidden diff --git a/libc/calls/wincrash_init.S b/libc/calls/wincrash_init.S index 33e0888fb..a4ab04618 100644 --- a/libc/calls/wincrash_init.S +++ b/libc/calls/wincrash_init.S @@ -20,6 +20,8 @@ #include "libc/macros.internal.h" .init.start 300,_init_wincrash + testb IsWindows() + jz 1f #if !IsTiny() mov __wincrashearly(%rip),%rcx ntcall __imp_RemoveVectoredExceptionHandler @@ -27,4 +29,4 @@ pushpop 1,%rcx ezlea __wincrash_nt,dx ntcall __imp_AddVectoredExceptionHandler - .init.end 300,_init_wincrash,globl,hidden +1: .init.end 300,_init_wincrash,globl,hidden diff --git a/libc/crt/crt.S b/libc/crt/crt.S index 678b50d2e..f26258264 100644 --- a/libc/crt/crt.S +++ b/libc/crt/crt.S @@ -83,7 +83,7 @@ _start: // make win32 imps noop .weak ape_idata_iat .weak ape_idata_iatend - ezlea _missingno,ax + ezlea __win32_oops,ax ezlea ape_idata_iat,di ezlea ape_idata_iatend,cx sub %rdi,%rcx diff --git a/libc/dce.h b/libc/dce.h index 0f946bf1d..db0df834c 100644 --- a/libc/dce.h +++ b/libc/dce.h @@ -66,6 +66,18 @@ #define IsXnuSilicon() 0 #endif +#if defined(__x86_64__) +#define _ARCH_NAME "amd64" +#elif defined(__aarch64__) +#define _ARCH_NAME "arm64" +#elif defined(__powerpc64__) +#define _ARCH_NAME "ppc64" +#elif defined(__s390x__) +#define _ARCH_NAME "s390x" +#elif defined(__riscv) +#define _ARCH_NAME "riscv" +#endif + #define SupportsLinux() ((SUPPORT_VECTOR & _HOSTLINUX) == _HOSTLINUX) #define SupportsMetal() ((SUPPORT_VECTOR & _HOSTMETAL) == _HOSTMETAL) #define SupportsWindows() ((SUPPORT_VECTOR & _HOSTWINDOWS) == _HOSTWINDOWS) diff --git a/libc/integral/c.inc b/libc/integral/c.inc index 167c057f6..dd917e9bd 100644 --- a/libc/integral/c.inc +++ b/libc/integral/c.inc @@ -611,8 +611,8 @@ void abort(void) wontreturn; } while (0) #ifndef __STRICT_ANSI__ -#define textstartup _Section(".text.startup") dontinstrument -#define textexit _Section(".text.exit") dontinstrument +#define textstartup _Section(".text.startup") +#define textexit _Section(".text.exit") #define textreal _Section(".text.real") #define texthead _Section(".text.head") #define textwindows _Section(".text.windows") diff --git a/libc/log/log.h b/libc/log/log.h index df18056a8..2a30c2f66 100644 --- a/libc/log/log.h +++ b/libc/log/log.h @@ -50,6 +50,7 @@ char *GetSymbolByAddr(int64_t); void PrintGarbage(void); void PrintGarbageNumeric(FILE *); void CheckForMemoryLeaks(void); +void PrintWindowsMemory(const char *, size_t); #ifndef __STRICT_ANSI__ diff --git a/libc/log/printwindowsmemory.c b/libc/log/printwindowsmemory.c new file mode 100644 index 000000000..b96e12ec8 --- /dev/null +++ b/libc/log/printwindowsmemory.c @@ -0,0 +1,83 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 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 "libc/fmt/conv.h" +#include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/kprintf.h" +#include "libc/log/log.h" +#include "libc/macros.internal.h" +#include "libc/nt/enum/memflags.h" +#include "libc/nt/memory.h" +#include "libc/nt/struct/memorybasicinformation.h" +#include "libc/str/str.h" + +static const struct DescribeFlags kNtMemState[] = { + {kNtMemCommit, "Commit"}, // + {kNtMemFree, "Free"}, // + {kNtMemReserve, "Reserve"}, // +}; + +static const char *DescribeNtMemState(char buf[64], uint32_t x) { + return DescribeFlags(buf, 64, kNtMemState, ARRAYLEN(kNtMemState), "kNtMem", + x); +} + +static const struct DescribeFlags kNtMemType[] = { + {kNtMemImage, "Image"}, // + {kNtMemMapped, "Mapped"}, // + {kNtMemPrivate, "Private"}, // +}; + +static const char *DescribeNtMemType(char buf[64], uint32_t x) { + return DescribeFlags(buf, 64, kNtMemType, ARRAYLEN(kNtMemType), "kNtMem", x); +} + +/** + * Prints to stderr all memory mappings that exist according to WIN32. + * + * The `high` and `size` parameters may optionally be specified so that + * memory mappings which overlap `[high,high+size)` will get printed in + * ANSI bold red text. + */ +void PrintWindowsMemory(const char *high, size_t size) { + char *p, b[5][64]; + const char *start, *stop; + struct NtMemoryBasicInformation mi; + kprintf("%-12s %-12s %10s %16s %16s %32s %32s\n", "Allocation", "BaseAddress", + "RegionSize", "State", "Type", "AllocationProtect", "Protect"); + for (p = 0;; p = (char *)mi.BaseAddress + mi.RegionSize) { + const char *start, *stop; + bzero(&mi, sizeof(mi)); + if (!VirtualQuery(p, &mi, sizeof(mi))) break; + sizefmt(b[0], mi.RegionSize, 1024); + if (MAX(high, (char *)mi.BaseAddress) < + MIN(high + size, (char *)mi.BaseAddress + mi.RegionSize)) { + start = "\e[1;31m"; + stop = "\e[0m"; + } else { + start = ""; + stop = ""; + } + kprintf("%s%.12lx %.12lx %10s %16s %16s %32s %32s%s\n", start, + mi.AllocationBase, mi.BaseAddress, b[0], + DescribeNtMemState(b[1], mi.State), + DescribeNtMemType(b[2], mi.Type), + (DescribeNtPageFlags)(b[3], mi.AllocationProtect), + (DescribeNtPageFlags)(b[4], mi.Protect), stop); + } +} diff --git a/libc/macros.internal.h b/libc/macros.internal.h index b9a1b62f7..ff18895cd 100644 --- a/libc/macros.internal.h +++ b/libc/macros.internal.h @@ -347,14 +347,6 @@ pop \dest .endm -// Declares optional function. -.macro .optfn fn:req - .globl "\fn" - .weak "\fn" - .equ "\fn",_missingno - .type "\fn",@function -.endm - // Embeds fixed-width zero-filled string table. // @note zero-padded ≠ nul-terminated .macro .fxstr width head rest:vararg diff --git a/libc/nexgen32e/missingno.S b/libc/nexgen32e/missingno.S deleted file mode 100644 index 4c535b9ed..000000000 --- a/libc/nexgen32e/missingno.S +++ /dev/null @@ -1,30 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 sw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.real - -// Optional function stub. -_missingno: -#ifdef __x86__ - xor %eax,%eax -#elif defined(__aarch64__) - mov x0,#0 -#endif - ret - .endfn _missingno,globl,hidden diff --git a/libc/nt/ntdllimport.S b/libc/nt/ntdllimport.S index ce72c4e8e..da5db5c60 100644 --- a/libc/nt/ntdllimport.S +++ b/libc/nt/ntdllimport.S @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/nt/enum/status.h" +#include "libc/dce.h" #include "libc/macros.internal.h" #ifdef __x86_64__ @@ -40,19 +41,25 @@ kNtdllProcRvas: .init.start 202,_init_ntdll push %r12 push %r13 - lea _ntdllmissingno(%rip),%r13 + lea __ntdll_not_found(%rip),%r13 sub $32,%rsp + xor %eax,%eax + testb IsWindows() + jz 7f loadstr "ntdll.dll",cx call *__imp_GetModuleHandleA(%rip) - mov %rax,%r12 +7: mov %rax,%r12 0: lodsq test %rax,%rax jz 1f .weak __executable_start lea __executable_start(%rax),%rdx + xor %eax,%eax + testb IsWindows() + jz 7f mov %r12,%rcx call *__imp_GetProcAddress(%rip) - test %rax,%rax +7: test %rax,%rax cmovz %r13,%rax stosq jmp 0b @@ -62,9 +69,13 @@ kNtdllProcRvas: .init.end 202,_init_ntdll,globl,hidden .text.windows -_ntdllmissingno: + .ftrace1 +__ntdll_not_found: + .ftrace2 mov $kNtStatusDllNotFound,%eax ret .previous + .weak __hostos + #endif /* __x86_64__ */ diff --git a/libc/nt/pedef.internal.h b/libc/nt/pedef.internal.h index 152dc1c11..58d71b2c8 100644 --- a/libc/nt/pedef.internal.h +++ b/libc/nt/pedef.internal.h @@ -32,10 +32,10 @@ #define kNtPe32bit 0x010b #define kNtPe64bit 0x020b -#define kNtPeSectionCntCode 0x000000020 -#define kNtPeSectionCntInitializedData 0x000000040 -#define kNtPeSectionCntUninitializedData 0x000000080 -#define kNtPeSectionGprel 0x000008000 +#define kNtPeSectionCntCode 0x00000020 +#define kNtPeSectionCntInitializedData 0x00000040 +#define kNtPeSectionCntUninitializedData 0x00000080 +#define kNtPeSectionGprel 0x00008000 #define kNtPeSectionMemDiscardable 0x02000000 #define kNtPeSectionMemNotCached 0x04000000 #define kNtPeSectionMemNotPaged 0x08000000 diff --git a/libc/runtime/getsymboltable.c b/libc/runtime/getsymboltable.c index 09cd4d9e3..7adbe492e 100644 --- a/libc/runtime/getsymboltable.c +++ b/libc/runtime/getsymboltable.c @@ -34,16 +34,6 @@ __static_yoink("__get_symbol"); -#ifdef __x86_64__ -#define SYMTAB_ARCH ".symtab.x86" -#elif defined(__aarch64__) -#define SYMTAB_ARCH ".symtab.aarch64" -#elif defined(__powerpc64__) -#define SYMTAB_ARCH ".symtab.powerpc64" -#else -#error "unsupported architecture" -#endif - static pthread_spinlock_t g_lock; struct SymbolTable *__symtab; // for kprintf @@ -69,7 +59,7 @@ static struct SymbolTable *GetSymbolTableFromZip(struct Zipos *zipos) { size_t size, size2; ssize_t rc, cf, lf; struct SymbolTable *res = 0; - if ((cf = GetZipFile(zipos, SYMTAB_ARCH)) != -1 || + if ((cf = GetZipFile(zipos, ".symtab." _ARCH_NAME)) != -1 || (cf = GetZipFile(zipos, ".symtab")) != -1) { lf = GetZipCfileOffset(zipos->map + cf); size = GetZipLfileUncompressedSize(zipos->map + lf); diff --git a/libc/runtime/runtime.h b/libc/runtime/runtime.h index 621f79bf9..01734f513 100644 --- a/libc/runtime/runtime.h +++ b/libc/runtime/runtime.h @@ -113,7 +113,6 @@ void _Exitr(int) libcesque wontreturn; void _Exit1(int) libcesque wontreturn; void _restorewintty(void); void __paginate(int, const char *); -long _missingno(); /* memory management */ void _weakfree(void *); void *_mapanon(size_t) attributeallocsize((1)) mallocesque; diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c index 3d543fe39..1f141a5e1 100644 --- a/libc/runtime/winmain.greg.c +++ b/libc/runtime/winmain.greg.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/state.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/dce.h" @@ -95,6 +96,12 @@ static const short kConsoleModes[3] = { kNtEnableVirtualTerminalProcessing, }; +// implements all win32 apis on non-windows hosts +__msabi long __win32_oops(void) { + assert(!"win32 api called on non-windows host"); + return 0; +} + // https://nullprogram.com/blog/2022/02/18/ __msabi static inline char16_t *MyCommandLine(void) { void *cmd; diff --git a/libc/stdio/fmt.c b/libc/stdio/fmt.c index a9697f05b..2c97ec76e 100644 --- a/libc/stdio/fmt.c +++ b/libc/stdio/fmt.c @@ -705,6 +705,10 @@ haveinc: return prec; } +static int __fmt_noop(const char *, void *, size_t) { + return 0; +} + /** * Implements {,v}{,s{,n},{,{,x}as},f,d}printf domain-specific language. * @@ -797,7 +801,7 @@ int __fmt(void *fn, void *arg, const char *format, va_list va) { x = 0; lasterr = errno; - out = fn ? fn : (void *)_missingno; + out = fn ? fn : __fmt_noop; while (*format) { if (*format != '%') { diff --git a/test/libc/runtime/mprotect_test.c b/test/libc/runtime/mprotect_test.c index 48f36af10..3d824e842 100644 --- a/test/libc/runtime/mprotect_test.c +++ b/test/libc/runtime/mprotect_test.c @@ -134,7 +134,7 @@ TEST(mprotect, testSegfault_writeToReadOnlyAnonymous) { EXPECT_FALSE(gotsegv); EXPECT_FALSE(gotbusted); EXPECT_NE(-1, mprotect(p, getauxval(AT_PAGESZ), PROT_READ)); - _missingno(p[0]); + __expropriate(p[0]); EXPECT_FALSE(gotsegv); EXPECT_FALSE(gotbusted); p[0] = 2; @@ -162,7 +162,7 @@ TEST(mprotect, testProtNone_cantEvenRead) { volatile char *p; p = gc(memalign(getauxval(AT_PAGESZ), getauxval(AT_PAGESZ))); EXPECT_NE(-1, mprotect(p, getauxval(AT_PAGESZ), PROT_NONE)); - _missingno(p[0]); + __expropriate(p[0]); EXPECT_TRUE(gotsegv | gotbusted); EXPECT_NE(-1, mprotect(p, getauxval(AT_PAGESZ), PROT_READ | PROT_WRITE)); } diff --git a/test/libc/runtime/munmap_test.c b/test/libc/runtime/munmap_test.c index 9cd713276..efb39f84e 100644 --- a/test/libc/runtime/munmap_test.c +++ b/test/libc/runtime/munmap_test.c @@ -19,6 +19,8 @@ #include "libc/calls/calls.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/kprintf.h" +#include "libc/log/log.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" @@ -200,12 +202,12 @@ TEST(munmap, tinyFile_preciseUnmapSize) { // clang-format off TEST(munmap, tinyFile_mapThriceUnmapOnce) { - char *p = (char *)0x02000000; + char *p = (char *)0x000063d646e20000; ASSERT_SYS(0, 3, open("doge", O_RDWR | O_CREAT | O_TRUNC, 0644)); ASSERT_SYS (0, 5, write(3, "hello", 5)); - ASSERT_NE(MAP_FAILED, mmap(p+FRAMESIZE*0, FRAMESIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0)); - ASSERT_NE(MAP_FAILED, mmap(p+FRAMESIZE*1, 5, PROT_READ, MAP_PRIVATE|MAP_FIXED, 3, 0)); - ASSERT_NE(MAP_FAILED, mmap(p+FRAMESIZE*3, 5, PROT_READ, MAP_PRIVATE|MAP_FIXED, 3, 0)); + ASSERT_EQ(p+FRAMESIZE*0, mmap(p+FRAMESIZE*0, FRAMESIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0)); + ASSERT_EQ(p+FRAMESIZE*1, mmap(p+FRAMESIZE*1, 5, PROT_READ, MAP_PRIVATE|MAP_FIXED, 3, 0)); + ASSERT_EQ(p+FRAMESIZE*3, mmap(p+FRAMESIZE*3, 5, PROT_READ, MAP_PRIVATE|MAP_FIXED, 3, 0)); ASSERT_SYS(0, 0, close(3)); EXPECT_TRUE(testlib_memoryexists(p+FRAMESIZE*0)); EXPECT_TRUE(testlib_memoryexists(p+FRAMESIZE*1)); diff --git a/test/libc/str/regex_test.c b/test/libc/str/regex_test.c index 563d8b92b..6b014acc2 100644 --- a/test/libc/str/regex_test.c +++ b/test/libc/str/regex_test.c @@ -16,12 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/mem/mem.h" +#include "third_party/regex/regex.h" #include "libc/mem/gc.internal.h" +#include "libc/mem/mem.h" #include "libc/str/str.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" -#include "third_party/regex/regex.h" TEST(regex, test) { regex_t rx; @@ -112,6 +112,63 @@ void D(regex_t *rx, regmatch_t *m) { regexec(rx, "127.0.0.1", rx->re_nsub + 1, m, 0); } +TEST(ape, testPeMachoDd) { + regex_t rx; + ASSERT_EQ(REG_OK, regcomp(&rx, + "bs=" // dd block size arg + "(['\"] *)?" // #1 optional quote w/ space + "(\\$\\(\\( *)?" // #2 optional math w/ space + "([[:digit:]]+)" // #3 + "( *\\)\\))?" // #4 optional math w/ space + "( *['\"])?" // #5 optional quote w/ space + " +" // + "skip=", + REG_EXTENDED)); + EXPECT_EQ(REG_OK, regexec(&rx, "bs=123 skip=", 0, NULL, 0)); + EXPECT_EQ(REG_OK, regexec(&rx, "bs=\"123\" skip=", 0, NULL, 0)); + EXPECT_EQ(REG_OK, regexec(&rx, "bs=$((123 skip=", 0, NULL, 0)); + EXPECT_EQ(REG_OK, regexec(&rx, "bs=\"$((123 skip=", 0, NULL, 0)); + EXPECT_EQ(REG_NOMATCH, regexec(&rx, "bs= skip=", 0, NULL, 0)); + EXPECT_EQ(REG_NOMATCH, regexec(&rx, "bs= 123 skip=", 0, NULL, 0)); + EXPECT_EQ(REG_NOMATCH, regexec(&rx, "bs= 123skip=", 0, NULL, 0)); + EXPECT_EQ(REG_OK, regexec(&rx, "bs=' 123' skip=", 0, NULL, 0)); + EXPECT_EQ(REG_OK, regexec(&rx, "bs=$(( 123)) skip=", 0, NULL, 0)); + EXPECT_EQ(REG_OK, regexec(&rx, "bs=\"$(( 123 ))\" skip=", 0, NULL, 0)); + regfree(&rx); +} + +TEST(ape, testPeMachoDd2) { + regex_t rx; + ASSERT_EQ(REG_OK, regcomp(&rx, + "bs=" // dd block size arg + "(['\"] *)?" // #1 optional quote w/ space + "(\\$\\(\\( *)?" // #2 optional math w/ space + "([[:digit:]]+)" // #3 + "( *\\)\\))?" // #4 optional math w/ space + "( *['\"])?" // #5 optional quote w/ space + " +" // + "skip=" // dd skip arg + "(['\"] *)?" // #6 optional quote w/ space + "(\\$\\(\\( *)?" // #7 optional math w/ space + "([[:digit:]]+)" // #8 + "( *\\)\\))?" // #9 optional math w/ space + "( *['\"])?" // #10 optional quote w/ space + " +" // + "count=" // dd count arg + "(['\"] *)?" // #11 optional quote w/ space + "(\\$\\(\\( *)?" // #12 optional math w/ space + "([[:digit:]]+)", // #13 + REG_EXTENDED)); + ASSERT_EQ(13, rx.re_nsub); + regmatch_t *m = gc(calloc(rx.re_nsub + 1, sizeof(regmatch_t))); + const char *s = "dd bs=123 skip=$(( 456)) count='7'"; + EXPECT_EQ(REG_OK, regexec(&rx, s, rx.re_nsub + 1, m, 0)); + EXPECT_STREQ("123", gc(strndup(s + m[3].rm_so, m[3].rm_eo - m[3].rm_so))); + EXPECT_STREQ("456", gc(strndup(s + m[8].rm_so, m[8].rm_eo - m[8].rm_so))); + EXPECT_STREQ("7", gc(strndup(s + m[13].rm_so, m[13].rm_eo - m[13].rm_so))); + regfree(&rx); +} + BENCH(regex, bench) { regex_t rx; regmatch_t *m; diff --git a/tool/build/apelink.c b/tool/build/apelink.c new file mode 100644 index 000000000..3c6957187 --- /dev/null +++ b/tool/build/apelink.c @@ -0,0 +1,2164 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 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 "libc/assert.h" +#include "libc/calls/calls.h" +#include "libc/dce.h" +#include "libc/dos.internal.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/intrin/bits.h" +#include "libc/intrin/kprintf.h" +#include "libc/limits.h" +#include "libc/macho.internal.h" +#include "libc/macros.internal.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/stdalign.internal.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.internal.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" \ + " -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" \ + " -s 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, `-s 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 -s flag is\n" \ + " also repeatable, e.g. `-s nt -s 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 int support_vector; +static int macholoadcount; +static const char *outpath; +static struct Assets assets; +static struct Inputs inputs; +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 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; + +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 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 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); + cfile[4] = kZipCosmopolitanVersion; + cfile[5] = kZipOsUnix; + cfile[6] = kZipEra1993; + 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); + cfile[4] = kZipEra1993; + cfile[5] = kZipOsDos; + 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 SizeOfStackReserve 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 SizeOfHeapReserve 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].SizeOfRawData) + 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)); + + Elf64_Sxword skew = rva_skew; + + // 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[]) { + char *endptr; + int opt, bits; + bool got_support_vector = false; + while ((opt = getopt(argc, argv, "hvgGBo:s:l:S:M:")) != -1) { + switch (opt) { + case 'o': + outpath = optarg; + break; + case 's': + HashInputString("-s"); + HashInputString(optarg); + if (ParseSupportVector(optarg, &bits)) { + support_vector |= bits; + } else { + Die(prog, "unrecognized token passed to -s support vector flag"); + exit(1); + } + got_support_vector = true; + break; + case 'l': + AddLoader(optarg); + break; + case 'S': + custom_sh_code = optarg; + break; + case 'B': + force_bypass_binfmt_misc = true; + break; + case 'g': + generate_debuggable_binary = true; + break; + case 'G': + dont_path_lookup_ape_loader = true; + break; + case 'M': + macos_silicon_loader_source_path = 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(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, 106000000); + 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); + in->we_must_skew_pe_vaspace = + 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; + char *data; + Elf64_Addr vaddr; + Elf64_Phdr *phdr; + 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 = stpcpy(p, "JT"); // 36: Justine Tunney + p = WRITE16LE(p, 0); // 38 + p = stpcpy(p, "' <<'@'\n"); // 40 + p = WRITE16LE(p, 0); // 48 + p = WRITE16LE(p, 0); // 50 + p = WRITE16LE(p, 0); // 52 + p = WRITE16LE(p, 0); // 54 + p = WRITE16LE(p, 0); // 56 + p = WRITE16LE(p, 0); // 58 + p = WRITE32LE(p, 0); // 60: portable executable + r_off32_e_lfanew = p - 4; + return p; +} + +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.com"); +} + +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, opt; + Elf64_Off offset; + char empty[64] = {0}; + Elf64_Xword prologue_bytes; +#ifndef NDEBUG + 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); + } + + // open input files + for (i = optind; i < argc; ++i) { + OpenInput(argv[i]); + } + + // 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 + 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); + } else { + tinyprint(2, in->path, + ": warning: won't generate symbol table unless " + "__static_yoink(\"zipos\") is linked\n", + NULL); + } + } + + // 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@\n" + "#'\"\n" + "\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-1.6\"\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)\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; + 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 { + if (macos_silicon_loader_source_path) { + Die(macos_silicon_loader_source_path, + "won't embed macos arm64 ape loader source code because xnu isn't " + "in the support vector"); + } + } + + // extract the ape loader for open platforms + 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"); + } + } + if (inputs.n && (support_vector & _HOSTXNU)) { + 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) { + int fd; + char *map; + ssize_t size; + char *compressed_data; + size_t compressed_size; + fd = open(macos_silicon_loader_source_path, O_RDONLY); + if (fd == -1) DieSys(macos_silicon_loader_source_path); + size = lseek(fd, 0, SEEK_END); + if (size == -1) DieSys(macos_silicon_loader_source_path); + map = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) DieSys(macos_silicon_loader_source_path); + compressed_data = Gzip(map, size, &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); + munmap(map, size); + close(fd); + } + + // add the zip files + CopyZips(offset); + + // write the header + Pwrite(prologue, prologue_bytes, 0); + + if (close(outfd)) { + DieSys(outpath); + } +} diff --git a/tool/build/assimilate.c b/tool/build/assimilate.c index 5aec3be3c..4c7048145 100644 --- a/tool/build/assimilate.c +++ b/tool/build/assimilate.c @@ -20,13 +20,16 @@ #include "libc/calls/calls.h" #include "libc/calls/struct/stat.h" #include "libc/dce.h" +#include "libc/elf/def.h" +#include "libc/elf/struct/ehdr.h" #include "libc/fmt/conv.h" #include "libc/intrin/bits.h" -#include "libc/intrin/kprintf.h" -#include "libc/log/check.h" +#include "libc/limits.h" +#include "libc/macho.internal.h" #include "libc/macros.internal.h" #include "libc/runtime/runtime.h" #include "libc/stdckdint.h" +#include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/msync.h" @@ -35,85 +38,129 @@ #include "third_party/getopt/getopt.internal.h" #include "third_party/regex/regex.h" -__static_yoink("strerror_wr"); +#define VERSION \ + "actually portable executable assimilate v1.6\n" \ + "copyright 2023 justine alexandra roberts tunney\n" -// options used: fhem -// letters not used: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdgijklnopqrstuvwxyz -// digits not used: 0123456789 -// puncts not used: !"#$%&'()*+,-./;<=>@[\]^_`{|}~ -// letters duplicated: none -#define GETOPTS "fem" +#define USAGE \ + "usage: assimilate.com [-hvfem] COMFILE...\n" \ + " -h show help\n" \ + " -v show version\n" \ + " -f ignore soft errors\n" \ + " -e convert to elf regardless of host os\n" \ + " -m convert to macho regardless of host os\n" \ + " -x convert to amd64 regardless of host cpu\n" \ + " -a convert to arm64 regardless of host cpu\n" -#define USAGE \ - "\ -Usage: assimilate.com [-hfem] COMFILE...\n\ - -h show help\n\ - -e force elf\n\ - -m force macho\n\ - -f continue on soft errors\n\ -\n\ -αcτµαlly pδrταblε εxεcµταblε assimilate v1.o\n\ -copyright 2022 justine alexandra roberts tunney\n\ -https://twitter.com/justinetunney\n\ -https://linkedin.com/in/jtunney\n\ -https://justine.lol/ape.html\n\ -https://github.com/jart\n\ -\n\ -This program converts Actually Portable Executable files so they're\n\ -in the platform-local executable format, rather than the multiplatform\n\ -APE shell script format. This is useful on UNIX operating systems when\n\ -you want to use your APE programs as script interpreter or for setuid.\n\ -" +#define ARCH_NATIVE 0 +#define ARCH_AMD64 1 +#define ARCH_ARM64 2 -#define MODE_NATIVE 0 -#define MODE_ELF 1 -#define MODE_MACHO 2 -#define MODE_PE 3 +#define FORMAT_NATIVE 0 +#define FORMAT_ELF 1 +#define FORMAT_MACHO 2 +#define FORMAT_PE 3 -int g_mode; -bool g_force; -int exitcode; -const char *prog; -char errstr[128]; +static int g_arch; +static int g_format; +static bool g_force; +static const char *prog; +static const char *path; +static char errstr[128]; +static bool got_format_flag; -void GetOpts(int argc, char *argv[]) { +static wontreturn void Die(const char *thing, const char *reason) { + const char *native_explainer; + if (got_format_flag) { + native_explainer = ""; + } else if (IsXnu()) { + native_explainer = " (the host os uses macho natively)"; + } else if (IsLinux() || IsFreebsd() || IsNetbsd() || IsOpenbsd()) { + native_explainer = " (the host os uses elf natively)"; + } else { + native_explainer = " (the host os uses pe natively)"; + } + tinyprint(2, thing, ": ", reason, native_explainer, "\n", NULL); + exit(1); +} + +static wontreturn void DieSys(const char *thing) { + perror(thing); + exit(1); +} + +static int Atoi(const char *s) { + int x; + if ((x = atoi(s)) == INT_MAX) { + Die(path, "integer overflow parsing ape macho dd argument"); + } + return x; +} + +static void GetOpts(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, GETOPTS)) != -1) { + while ((opt = getopt(argc, argv, "hvfemxa")) != -1) { switch (opt) { case 'f': g_force = true; break; case 'e': - g_mode = MODE_ELF; + g_format = FORMAT_ELF; + got_format_flag = true; break; case 'm': - g_mode = MODE_MACHO; + g_format = FORMAT_MACHO; + got_format_flag = true; break; + case 'x': + g_arch = ARCH_AMD64; + break; + case 'a': + g_arch = ARCH_ARM64; + break; + case 'v': + tinyprint(1, VERSION, NULL); + exit(0); case 'h': - write(1, USAGE, sizeof(USAGE) - 1); + tinyprint(1, VERSION, USAGE, NULL); exit(0); default: - write(2, USAGE, sizeof(USAGE) - 1); - exit(64); + tinyprint(2, VERSION, USAGE, NULL); + exit(1); } } - if (g_mode == MODE_NATIVE) { + if (optind == argc) { + Die(prog, "missing operand"); + } + if (g_format == FORMAT_NATIVE) { if (IsXnu()) { - g_mode = MODE_MACHO; + g_format = FORMAT_MACHO; } else if (IsLinux() || IsFreebsd() || IsNetbsd() || IsOpenbsd()) { - g_mode = MODE_ELF; + g_format = FORMAT_ELF; } else { - g_mode = MODE_PE; + g_format = FORMAT_PE; } } + if (g_arch == ARCH_NATIVE) { +#ifdef __aarch64__ + g_arch = ARCH_ARM64; +#else + g_arch = ARCH_AMD64; +#endif + } + if (g_format == FORMAT_PE && g_arch == ARCH_ARM64) { + Die(prog, "native arm64 on windows not supported yet"); + } } -void GetElfHeader(char ehdr[hasatleast 64], const char *image, size_t n) { - char *p; +static void GetElfHeader(char ehdr[hasatleast 64], const char *image, + size_t n) { int c, i; - for (p = image; p < image + MIN(n, 4096); ++p) { + char *p, *e; + for (p = image, e = p + MIN(n, 8192); p < e; ++p) { + TryAgain: if (READ64LE(p) != READ64LE("printf '")) continue; - for (i = 0, p += 8; p + 3 < image + MIN(n, 4096) && (c = *p++) != '\'';) { + for (i = 0, p += 8; p + 3 < e && (c = *p++) != '\'';) { if (c == '\\') { if ('0' <= *p && *p <= '7') { c = *p++ - '0'; @@ -130,29 +177,35 @@ void GetElfHeader(char ehdr[hasatleast 64], const char *image, size_t n) { if (i < 64) { ehdr[i++] = c; } else { - kprintf("%s: ape printf elf header too long\n", prog); - exit(1); + goto TryAgain; } } - if (i != 64) { - kprintf("%s: ape printf elf header too short\n", prog); - exit(2); - } - if (READ32LE(ehdr) != READ32LE("\177ELF")) { - kprintf("%s: ape printf elf header didn't have elf magic\n", prog); - exit(3); + if (i != 64 || // + READ32LE(ehdr) != READ32LE("\177ELF") || // + ehdr[EI_CLASS] == ELFCLASS32 || // + READ16LE(ehdr + 18) != + (g_arch == ARCH_AMD64 ? EM_NEXGEN32E : EM_AARCH64)) { + goto TryAgain; } return; } - kprintf("%s: printf statement not found in first 4096 bytes\n", prog); - exit(4); + switch (g_arch) { + case ARCH_AMD64: + Die(path, "printf statement not found in first 8192 bytes of image " + "containing elf64 ehdr for amd64"); + case ARCH_ARM64: + Die(path, "printf statement not found in first 8192 bytes of image " + "containing elf64 ehdr for arm64"); + default: + __builtin_unreachable(); + } } -void GetMachoPayload(const char *image, size_t imagesize, int *out_offset, - int *out_size) { +static void GetMachoPayload(const char *image, size_t imagesize, + int *out_offset, int *out_size) { regex_t rx; const char *script; - regmatch_t rm[1 + 3] = {0}; + regmatch_t rm[1 + 13] = {0}; int rc, skip, count, bs, offset, size; if ((script = memmem(image, imagesize, "'\n#'\"\n", 6))) { @@ -160,147 +213,239 @@ void GetMachoPayload(const char *image, size_t imagesize, int *out_offset, } else if ((script = memmem(image, imagesize, "#'\"\n", 4))) { script += 4; } else { - kprintf("%s: ape shell script not found\n", prog); - exit(5); + Die(path, "ape shell script not found"); } - DCHECK_EQ(REG_OK, regcomp(&rx, - "bs=\"?\\$?(*([ [:digit:]]+))*\"? " - "skip=\"?\\$?(*([ [:digit:]]+))*\"? " - "count=\"?\\$?(*([ [:digit:]]+))*\"?", - REG_EXTENDED)); - rc = regexec(&rx, script, 4, rm, 0); + // the ape shell script has always historically used `dd` to + // assimilate binaries to the mach-o file format but we have + // formatted the arguments in a variety of different ways eg + // + // - `arg=" 9293"` is how we originally had ape do it + // - `arg=$(( 9293))` b/c busybox sh disliked quoted space + // - `arg=9293 ` is generated by modern apelink.com program + // + unassert(regcomp(&rx, + "bs=" // dd block size arg + "(['\"] *)?" // #1 optional quote w/ space + "(\\$\\(\\( *)?" // #2 optional math w/ space + "([[:digit:]]+)" // #3 + "( *\\)\\))?" // #4 optional math w/ space + "( *['\"])?" // #5 optional quote w/ space + " +" // + "skip=" // dd skip arg + "(['\"] *)?" // #6 optional quote w/ space + "(\\$\\(\\( *)?" // #7 optional math w/ space + "([[:digit:]]+)" // #8 + "( *\\)\\))?" // #9 optional math w/ space + "( *['\"])?" // #10 optional quote w/ space + " +" // + "count=" // dd count arg + "(['\"] *)?" // #11 optional quote w/ space + "(\\$\\(\\( *)?" // #12 optional math w/ space + "([[:digit:]]+)", // #13 + REG_EXTENDED) == REG_OK); + + int i = 0; +TryAgain: + rc = regexec(&rx, script + i, 1 + 13, rm, 0); if (rc != REG_OK) { - if (rc == REG_NOMATCH) { - kprintf("%s: ape macho dd command not found\n", prog); - exit(6); + unassert(rc == REG_NOMATCH); + switch (g_arch) { + case ARCH_AMD64: + Die(path, "ape macho dd command for amd64 not found"); + case ARCH_ARM64: + Die(path, "ape macho dd command for arm64 not found"); + default: + __builtin_unreachable(); } - regerror(rc, &rx, errstr, sizeof(errstr)); - kprintf("%s: ape macho dd regex failed: %s\n", prog, errstr); - exit(7); } - bs = atoi(script + rm[1].rm_so); - skip = atoi(script + rm[2].rm_so); - count = atoi(script + rm[3].rm_so); - if (ckd_mul(&offset, skip, bs) || ckd_mul(&size, count, bs)) { - kprintf("%s: integer overflow parsing macho\n"); - exit(8); + i += rm[13].rm_eo; + + bs = Atoi(script + rm[3].rm_so); + skip = Atoi(script + rm[8].rm_so); + count = Atoi(script + rm[13].rm_so); + + if (ckd_mul(&offset, skip, bs)) { + Die(path, "integer overflow computing ape macho dd offset"); } + if (ckd_mul(&size, count, bs)) { + Die(path, "integer overflow computing ape macho dd size"); + } + if (offset < 64) { - kprintf("%s: ape macho dd offset should be ≥64: %d\n", prog, offset); - exit(9); + Die(path, "ape macho dd offset must be at least 64"); } if (offset >= imagesize) { - kprintf("%s: ape macho dd offset is outside file: %d\n", prog, offset); - exit(10); + Die(path, "ape macho dd offset points outside image"); } if (size < 32) { - kprintf("%s: ape macho dd size should be ≥32: %d\n", prog, size); - exit(11); + Die(path, "ape macho dd size must be at least 32"); } if (size > imagesize - offset) { - kprintf("%s: ape macho dd size is outside file: %d\n", prog, size); - exit(12); + Die(path, "ape macho dd size overlaps end of image"); + exit(1); } + + if (READ32LE(image + offset) != 0xFEEDFACE + 1 || + READ32LE(image + offset + 4) != + (g_arch == ARCH_AMD64 ? MAC_CPU_NEXGEN32E : MAC_CPU_ARM64)) { + goto TryAgain; + } + *out_offset = offset; *out_size = size; regfree(&rx); } -void AssimilateElf(char *p, size_t n) { +static void AssimilateElf(char *p, size_t n) { char ehdr[64]; GetElfHeader(ehdr, p, n); memcpy(p, ehdr, 64); msync(p, 4096, MS_SYNC); } -void AssimilateMacho(char *p, size_t n) { +static void AssimilateMacho(char *p, size_t n) { int offset, size; GetMachoPayload(p, n, &offset, &size); memmove(p, p + offset, size); msync(p, n, MS_SYNC); } -void Assimilate(void) { +static void Assimilate(void) { int fd; char *p; - struct stat st; - if ((fd = open(prog, O_RDWR)) == -1) { - kprintf("%s: open(O_RDWR) failed: %m\n", prog); - exit(13); - } - if (fstat(fd, &st) == -1) { - kprintf("%s: fstat() failed: %m\n", prog); - exit(14); - } - if (st.st_size < 64) { - kprintf("%s: ape binaries must be at least 64 bytes\n", prog); - exit(15); - } - if ((p = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == - MAP_FAILED) { - kprintf("%s: mmap failed: %m\n", prog); - exit(16); - } - if (g_mode == MODE_PE) { - if (READ16LE(p) == READ16LE("MZ")) { - if (!g_force) { - kprintf("%s: program is already an elf binary\n", prog); - if (g_mode != MODE_ELF) { - exitcode = 1; - } - } - goto Finish; - } else { - kprintf("%s: currently cannot back-convert to pe\n", prog); - exit(17); - } - } + ssize_t size; + if ((fd = open(path, O_RDWR)) == -1) DieSys(path); + if ((size = lseek(fd, 0, SEEK_END)) == -1) DieSys(path); + if (size < 64) Die(path, "ape executables must be at least 64 bytes"); + p = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) DieSys(path); + if (READ32LE(p) == READ32LE("\177ELF")) { - if (!g_force) { - kprintf("%s: program is already an elf binary\n", prog); - if (g_mode != MODE_ELF) { - exitcode = 1; - } + Elf64_Ehdr *ehdr; + switch (g_format) { + case FORMAT_ELF: + ehdr = (Elf64_Ehdr *)p; + if (ehdr->e_ident[EI_CLASS] == ELFCLASS32) { + Die(path, "32-bit elf not supported"); + } + switch (g_arch) { + case ARCH_AMD64: + switch (ehdr->e_machine) { + case EM_NEXGEN32E: + if (g_force) { + exit(0); + } else { + Die(path, "already an elf amd64 executable"); + } + case EM_AARCH64: + Die(path, "can't assimilate elf arm64 to elf amd64"); + default: + Die(path, "elf has unsupported architecture"); + } + case ARCH_ARM64: + switch (ehdr->e_machine) { + case EM_AARCH64: + if (g_force) { + exit(0); + } else { + Die(path, "already an elf arm64 executable"); + } + case EM_NEXGEN32E: + Die(path, "can't assimilate elf amd64 to elf arm64"); + default: + Die(path, "elf has unsupported architecture"); + } + default: + __builtin_unreachable(); + } + case FORMAT_MACHO: + Die(path, "can't assimilate elf to macho"); + case FORMAT_PE: + Die(path, "can't assimilate elf to pe (try elf2pe.com)"); + default: + __builtin_unreachable(); } - goto Finish; } + if (READ32LE(p) == 0xFEEDFACE + 1) { - if (!g_force) { - kprintf("%s: program is already a mach-o binary\n", prog); - if (g_mode != MODE_MACHO) { - exitcode = 1; - } + struct MachoHeader *macho; + switch (g_format) { + case FORMAT_MACHO: + macho = (struct MachoHeader *)p; + switch (g_arch) { + case ARCH_AMD64: + switch (macho->arch) { + case MAC_CPU_NEXGEN32E: + if (g_force) { + exit(0); + } else { + Die(path, "already a macho amd64 executable"); + } + case MAC_CPU_ARM64: + Die(path, "can't assimilate macho arm64 to macho amd64"); + default: + Die(path, "macho has unsupported architecture"); + } + case ARCH_ARM64: + switch (macho->arch) { + case MAC_CPU_ARM64: + if (g_force) { + exit(0); + } else { + Die(path, "already a macho arm64 executable"); + } + case MAC_CPU_NEXGEN32E: + Die(path, "can't assimilate macho amd64 to macho arm64"); + default: + Die(path, "macho has unsupported architecture"); + } + default: + __builtin_unreachable(); + } + case FORMAT_ELF: + Die(path, "can't assimilate macho to elf"); + case FORMAT_PE: + Die(path, "can't assimilate macho to pe"); + default: + __builtin_unreachable(); } - goto Finish; } - if (READ64LE(p) != READ64LE("MZqFpD='")) { - kprintf("%s: this file is not an actually portable executable\n", prog); - exit(17); + + if (READ64LE(p) != READ64LE("MZqFpD='") && // + READ64LE(p) != READ64LE("jartsr='") && // + READ64LE(p) != READ64LE("APEDBG='")) { + Die(path, "not an actually portable executable"); } - if (g_mode == MODE_ELF) { - AssimilateElf(p, st.st_size); - } else if (g_mode == MODE_MACHO) { - AssimilateMacho(p, st.st_size); + + if (g_format == FORMAT_PE) { + if (READ16LE(p) == READ16LE("MZ")) { + if (g_force) { + exit(0); + } else { + Die(path, "this ape file is already a pe file"); + } + } else { + Die(path, "this ape file was built without pe support"); + } } -Finish: - if (munmap(p, st.st_size) == -1) { - kprintf("%s: munmap() failed: %m\n", prog); - exit(18); + + if (g_format == FORMAT_ELF) { + AssimilateElf(p, size); + } else if (g_format == FORMAT_MACHO) { + AssimilateMacho(p, size); } + + if (munmap(p, size)) DieSys(path); + if (close(fd)) DieSys(path); } int main(int argc, char *argv[]) { - int i; + prog = argv[0]; + if (!prog) prog = "assimilate"; GetOpts(argc, argv); - if (optind == argc) { - kprintf("error: need at least one program path to assimilate\n"); - write(2, USAGE, sizeof(USAGE) - 1); - exit(64); - } - for (i = optind; i < argc; ++i) { - prog = argv[i]; + for (int i = optind; i < argc; ++i) { + path = argv[i]; Assimilate(); } - return exitcode; } diff --git a/tool/build/elf2pe.c b/tool/build/elf2pe.c index 416b05e0a..b97864b78 100644 --- a/tool/build/elf2pe.c +++ b/tool/build/elf2pe.c @@ -47,8 +47,8 @@ #include "libc/sysv/consts/prot.h" #include "third_party/getopt/getopt.internal.h" -// see tool/hello/hello.c for an example program this can link -// make -j8 m=tiny o/tiny/tool/hello/hello.com +// see tool/hello/hello-pe.c for an example program this can link +// make -j8 m=tiny o/tiny/tool/hello/hello-pe.com #define VERSION \ "elf2pe v0.1\n" \ @@ -571,6 +571,7 @@ static struct Section *LoadSection(struct Elf *elf, int index, static void LoadSectionsIntoSegments(struct Elf *elf) { int i; Elf64_Shdr *shdr; + bool hasdataseg = false; struct Segment *segment = 0; for (i = 0; i < elf->ehdr->e_shnum; ++i) { if ((shdr = GetElfSectionHeaderAddress(elf->ehdr, elf->size, i)) && @@ -590,6 +591,7 @@ static void LoadSectionsIntoSegments(struct Elf *elf) { segment->vaddr_min = section->shdr->sh_addr; if (shdr->sh_type == SHT_PROGBITS) segment->offset_min = section->shdr->sh_offset; + hasdataseg |= segment->prot == (PROT_READ | PROT_WRITE); } segment->hasnobits |= shdr->sh_type == SHT_NOBITS; segment->hasprogbits |= shdr->sh_type == SHT_PROGBITS; @@ -604,6 +606,16 @@ static void LoadSectionsIntoSegments(struct Elf *elf) { if (segment) { dll_make_last(&elf->segments, &segment->elem); } + if (elf->imports && !hasdataseg) { + // if the program we're linking is really tiny and it doesn't have + // either a .data or .bss section but it does import function from + // libraries, then create a synthetic .data segment for the pe iat + segment = NewSegment(); + segment->align = 8; + segment->hasprogbits = true; + segment->prot = PROT_READ | PROT_WRITE; + dll_make_last(&elf->segments, &segment->elem); + } } static bool ParseDllImportSymbol(const char *symbol_name, @@ -678,8 +690,8 @@ static struct Elf *OpenElf(const char *path) { if (!elf->strtab) Die(path, "elf doesn't have string table"); elf->secstrs = GetElfSectionNameStringTable(elf->ehdr, elf->size); if (!elf->strtab) Die(path, "elf doesn't have section string table"); - LoadSectionsIntoSegments(elf); LoadDllImports(elf); + LoadSectionsIntoSegments(elf); close(fd); return elf; } @@ -745,11 +757,20 @@ static void PickPeSectionName(char *p, struct Elf *elf, static uint32_t GetPeSectionCharacteristics(struct Segment *s) { uint32_t x = 0; - if (s->prot & PROT_EXEC) x |= kNtPeSectionCntCode | kNtPeSectionMemExecute; - if (s->prot & PROT_READ) x |= kNtPeSectionMemRead; - if (s->prot & PROT_WRITE) x |= kNtPeSectionMemWrite; - if (s->hasnobits) x |= kNtPeSectionCntUninitializedData; - if (s->hasprogbits) x |= kNtPeSectionCntInitializedData; + if (s->prot & PROT_EXEC) { + x |= kNtPeSectionCntCode | kNtPeSectionMemExecute; + } else if (s->hasprogbits) { + x |= kNtPeSectionCntInitializedData; + } + if (s->prot & PROT_READ) { + x |= kNtPeSectionMemRead; + } + if (s->prot & PROT_WRITE) { + x |= kNtPeSectionMemWrite; + } + if (s->hasnobits) { + x |= kNtPeSectionCntUninitializedData; + } return x; } @@ -780,9 +801,6 @@ static struct ImagePointer GeneratePe(struct Elf *elf, char *fp, int64_t vp) { mzhdr = (struct NtImageDosHeader *)fp; fp += sizeof(struct NtImageDosHeader); memcpy(mzhdr, "MZ", 2); - /* memcpy(mzhdr, "MZqFpD='\n\n", 10); */ - /* mzhdr->e_oemid = 'J' | 'T' << 8; */ - /* memcpy(mzhdr->e_res2, "' <<'@'\n", 8); */ // embed the ms-dos stub and/or bios bootloader if (stubpath) { @@ -797,10 +815,6 @@ static struct ImagePointer GeneratePe(struct Elf *elf, char *fp, int64_t vp) { if (close(fd)) DieSys(stubpath); } - // begin the shell script - /* fp = stpcpy(fp, "\n@\n" */ - /* "#'\"\n"); */ - // output portable executable magic fp = ALIGN_FILE(fp, 8); mzhdr->e_lfanew = fp - (char *)mzhdr; @@ -956,6 +970,7 @@ static struct ImagePointer GeneratePe(struct Elf *elf, char *fp, int64_t vp) { struct Library *library = LIBRARY_CONTAINER(e); library->idt->ImportAddressTable = vp - opthdr->ImageBase; fp = mempcpy(fp, library->ilt, library->iltbytes); + segment->hasprogbits = true; for (struct Dll *g = dll_first(library->funcs); g; g = dll_next(library->funcs, g)) { struct Func *func = FUNC_CONTAINER(g); @@ -1055,19 +1070,17 @@ int main(int argc, char *argv[]) { #ifndef NDEBUG ShowCrashReports(); #endif - // get program name prog = argv[0]; if (!prog) prog = "elf2pe"; - // process flags GetOpts(argc, argv); - // translate executable struct Elf *elf = OpenElf(argv[optind]); - char *buf = memalign(MAX_ALIGN, INT_MAX); + char *buf = memalign(MAX_ALIGN, 134217728); struct ImagePointer ip = GeneratePe(elf, buf, 0x00400000); if (creat(outpath, 0755) == -1) DieSys(elf->path); Pwrite(3, buf, ip.fp - buf, 0); if (close(3)) DieSys(elf->path); + // PrintElf(elf); } diff --git a/tool/build/elf2pe.h b/tool/build/elf2pe.h new file mode 100644 index 000000000..53312b1a2 --- /dev/null +++ b/tool/build/elf2pe.h @@ -0,0 +1,8 @@ +#ifndef COSMOPOLITAN_TOOL_BUILD_ELF2PE_H_ +#define COSMOPOLITAN_TOOL_BUILD_ELF2PE_H_ + +#define __dll_import(DLL, RET, FUNC, ARGS) \ + extern RET(*const __attribute__((__ms_abi__, __weak__)) FUNC) \ + ARGS __asm__("\"dll$" DLL "$" #FUNC "\"") + +#endif /* COSMOPOLITAN_TOOL_BUILD_ELF2PE_H_ */ diff --git a/tool/build/lib/lib.h b/tool/build/lib/lib.h new file mode 100644 index 000000000..3bb1e531c --- /dev/null +++ b/tool/build/lib/lib.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_LIB_H_ +#define COSMOPOLITAN_TOOL_BUILD_LIB_LIB_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +bool ParseSupportVector(char *, int *); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_LIB_H_ */ diff --git a/tool/build/lib/parsesupportvector.c b/tool/build/lib/parsesupportvector.c new file mode 100644 index 000000000..84d8b271c --- /dev/null +++ b/tool/build/lib/parsesupportvector.c @@ -0,0 +1,75 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 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 "libc/dce.h" +#include "libc/fmt/conv.h" +#include "libc/str/str.h" +#include "tool/build/lib/lib.h" + +bool ParseSupportVector(char *str, int *out_bits) { + // you can supply a number, e.g. 123, 0x123, etc. + char *endptr; + int bits = strtol(str, &endptr, 0); + if (!*endptr) { + *out_bits = bits; + return true; + } + // you can supply a string, e.g. -s linux+mac+bsd + bits = 0; + char *tok, *state; + const char *sep = " ,+:/|"; + while ((tok = strtok_r(str, sep, &state))) { + if (_startswithi(tok, "_HOST")) { + tok += 5; + } + if (!strcasecmp(tok, "linux")) { + bits |= _HOSTLINUX; + } else if (!strcasecmp(tok, "metal")) { + bits |= _HOSTMETAL; + } else if (!strcasecmp(tok, "windows") || // + !strcasecmp(tok, "win") || // + !strcasecmp(tok, "nt") || // + !strcasecmp(tok, "pe")) { + bits |= _HOSTWINDOWS; + } else if (!strcasecmp(tok, "xnu") || // + !strcasecmp(tok, "mac") || // + !strcasecmp(tok, "macos") || // + !strcasecmp(tok, "macho") || // + !strcasecmp(tok, "darwin")) { + bits |= _HOSTXNU; + } else if (!strcasecmp(tok, "freebsd")) { + bits |= _HOSTFREEBSD; + } else if (!strcasecmp(tok, "openbsd")) { + bits |= _HOSTOPENBSD; + } else if (!strcasecmp(tok, "netbsd")) { + bits |= _HOSTNETBSD; + } else if (!strcasecmp(tok, "bsd")) { + bits |= _HOSTFREEBSD | _HOSTOPENBSD | _HOSTNETBSD; + } else if (!strcasecmp(tok, "elf")) { + bits |= + _HOSTMETAL | _HOSTLINUX | _HOSTFREEBSD | _HOSTOPENBSD | _HOSTNETBSD; + } else if (!strcasecmp(tok, "unix")) { + bits |= _HOSTLINUX | _HOSTFREEBSD | _HOSTOPENBSD | _HOSTNETBSD | _HOSTXNU; + } else { + return false; + } + str = 0; + } + *out_bits = bits; + return true; +} diff --git a/tool/build/pecheck.c b/tool/build/pecheck.c index 6665ce0d0..7c3c7bc30 100644 --- a/tool/build/pecheck.c +++ b/tool/build/pecheck.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/intrin/bits.h" +#include "libc/intrin/kprintf.h" #include "libc/limits.h" #include "libc/nt/struct/imageimportbyname.internal.h" #include "libc/nt/struct/imageimportdescriptor.internal.h" @@ -27,10 +28,29 @@ #include "libc/runtime/runtime.h" #include "libc/stdckdint.h" #include "libc/stdio/stdio.h" +#include "libc/str/str.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" +/** + * @fileoverview Linter for PE static executable files. + * + * Generating PE files from scratch is tricky. There's numerous things + * that can go wrong, and operating systems don't explain what's wrong + * when they refuse to run a program. This program can help illuminate + * any issues with your generated binaries, with better error messages + */ + +struct Exe { + char *map; + size_t size; + const char *path; + struct NtImageNtHeaders *pe; + struct NtImageSectionHeader *sections; + uint32_t section_count; +}; + static wontreturn void Die(const char *thing, const char *reason) { tinyprint(2, thing, ": ", reason, "\n", NULL); exit(1); @@ -41,6 +61,68 @@ static wontreturn void DieSys(const char *thing) { exit(1); } +static void LogPeSections(FILE *f, struct NtImageSectionHeader *p, size_t n) { + size_t i; + fprintf(f, "Name Offset RelativeVirtAddr FileSiz MemSiz Flg\n"); + for (i = 0; i < n; ++i) { + fprintf(f, "%-8.8s 0x%06lx 0x%016lx 0x%06lx 0x%06lx %c%c%c\n", p[i].Name, + p[i].PointerToRawData, 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' : ' '); + } +} + +// resolves 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 *GetRva(struct Exe *exe, uint32_t rva, uint32_t size) { + int i; + for (i = 0; i < exe->section_count; ++i) { + if (exe->sections[i].VirtualAddress <= rva && + rva < exe->sections[i].VirtualAddress + + exe->sections[i].Misc.VirtualSize) { + if (rva + size <= + exe->sections[i].VirtualAddress + exe->sections[i].Misc.VirtualSize) { + return exe->map + exe->sections[i].PointerToRawData + + (rva - exe->sections[i].VirtualAddress); + } else { + break; + } + } + } + return 0; +} + +static bool HasControlCodes(const char *s) { + int c; + while ((c = *s++)) { + if (isascii(c) && iscntrl(c)) { + return true; + } + } + return false; +} + +static void CheckPeImportByName(struct Exe *exe, uint32_t rva) { + struct NtImageImportByName *hintname; + if (rva & 1) + Die(exe->path, "PE IMAGE_IMPORT_BY_NAME (hint name) structures must " + "be 2-byte aligned"); + if (!(hintname = GetRva(exe, rva, sizeof(struct NtImageImportByName)))) + Die(exe->path, "PE import table RVA entry didn't reslove"); + if (!*hintname->Name) + Die(exe->path, "PE imported function name is empty string"); + if (HasControlCodes(hintname->Name)) + Die(exe->path, "PE imported function name contains ascii control codes"); +} + static void CheckPe(const char *path, char *map, size_t size) { int pagesz = 4096; @@ -53,6 +135,8 @@ static void CheckPe(const char *path, char *map, size_t size) { uint32_t pe_offset; if ((pe_offset = READ32LE(map + 60)) >= size) Die(path, "PE header offset points past end of image"); + if (pe_offset & 7) + Die(path, "PE header offset must possess an 8-byte alignment"); if (pe_offset + sizeof(struct NtImageNtHeaders) > size) Die(path, "PE mandatory headers overlap end of image"); struct NtImageNtHeaders *pe = (struct NtImageNtHeaders *)(map + pe_offset); @@ -93,12 +177,14 @@ static void CheckPe(const char *path, char *map, size_t size) { Die(path, "PE FileHeader.Characteristics needs " "IMAGE_FILE_LARGE_ADDRESS_AWARE if ImageBase > INT_MAX"); - // fixup pe header + // validate the size of the pe optional headers 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 (ckd_mul(&len, pe->OptionalHeader.NumberOfRvaAndSizes, + sizeof(struct NtImageDataDirectory)) || + ckd_add(&len, len, sizeof(struct NtImageOptionalHeader))) + Die(path, "encountered overflow computing PE SizeOfOptionalHeader"); + if (pe->FileHeader.SizeOfOptionalHeader != len) + Die(path, "PE SizeOfOptionalHeader had incorrect value"); if (len > size || (char *)&pe->OptionalHeader + len > map + size) Die(path, "PE OptionalHeader overflows image"); @@ -167,34 +253,60 @@ static void CheckPe(const char *path, char *map, size_t size) { } } -#if 0 // broken + // create an object for our portable executable + struct Exe exe[1] = {{ + .pe = pe, + .path = path, + .map = map, + .size = size, + .sections = sections, + .section_count = pe->FileHeader.NumberOfSections, + }}; + // validate dll imports - if (pe->OptionalHeader.NumberOfRvaAndSizes >= 2 && - pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport] - .VirtualAddress) { - struct NtImageImportDescriptor *idt = - (struct NtImageImportDescriptor - *)(map + - pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport] - .VirtualAddress); - for (int i = 0;; ++i) { - if ((char *)(idt + i + sizeof(*idt)) > map + size) - Die(path, "PE IMAGE_DIRECTORY_ENTRY_IMPORT points outside image"); - if (!idt[i].ImportLookupTable) break; - uint64_t *ilt = (uint64_t *)(map + idt[i].ImportLookupTable); - for (int j = 0;; ++j) { - if ((char *)(ilt + j + sizeof(*ilt)) > map + size) - Die(path, "PE ImportLookupTable points outside image"); - if (!ilt[j]) break; - struct NtImageImportByName *func = - (struct NtImageImportByName *)(map + ilt[j]); + struct NtImageDataDirectory *ddImports = + exe->pe->OptionalHeader.DataDirectory + kNtImageDirectoryEntryImport; + if (exe->pe->OptionalHeader.NumberOfRvaAndSizes >= 2 && ddImports->Size) { + if (ddImports->Size % sizeof(struct NtImageImportDescriptor) != 0) + Die(exe->path, "PE Imports data directory entry Size should be a " + "multiple of sizeof(IMAGE_IMPORT_DESCRIPTOR)"); + if (ddImports->VirtualAddress & 3) + Die(exe->path, "PE IMAGE_IMPORT_DESCRIPTOR table must be 4-byte aligned"); + struct NtImageImportDescriptor *idt; + if (!(idt = GetRva(exe, ddImports->VirtualAddress, ddImports->Size))) + Die(exe->path, "couldn't resolve VirtualAddress/Size RVA of PE Import " + "Directory Table to within a defined PE section"); + if (idt->ImportLookupTable >= exe->size) + Die(exe->path, "Import Directory Table VirtualAddress/Size RVA resolved " + "to dense unrelated binary content"); + for (int i = 0; idt->ImportLookupTable; ++i, ++idt) { + char *dllname; + if (!(dllname = GetRva(exe, idt->DllNameRva, 2))) + Die(exe->path, "PE DllNameRva doesn't resolve to a PE section"); + if (!*dllname) + Die(exe->path, "PE import DllNameRva pointed to empty string"); + if (HasControlCodes(dllname)) + Die(exe->path, "PE import DllNameRva contained ascii control codes"); + if (idt->ImportLookupTable & 7) + Die(exe->path, "PE ImportLookupTable must be 8-byte aligned"); + if (idt->ImportAddressTable & 7) + Die(exe->path, "PE ImportAddressTable must be 8-byte aligned"); + uint64_t *ilt, *iat; + if (!(ilt = GetRva(exe, idt->ImportLookupTable, 8))) + Die(exe->path, "PE ImportLookupTable RVA didn't resolve to a section"); + if (!(iat = GetRva(exe, idt->ImportAddressTable, 8))) + Die(exe->path, "PE ImportAddressTable RVA didn't resolve to a section"); + for (int j = 0;; ++j, ++ilt, ++iat) { + if (*ilt != *iat) { + kprintf("i=%d j=%d ilt=%#x iat=%#x\n", i, j, *ilt, *iat); + Die(exe->path, "PE ImportLookupTable and ImportAddressTable should " + "have identical content"); + } + if (!*ilt) break; + CheckPeImportByName(exe, *ilt); } - uint64_t *iat = (uint64_t *)(map + idt[i].ImportAddressTable); - if ((char *)(iat + sizeof(*iat)) > map + size) - Die(path, "PE ImportAddressTable points outside image"); } } -#endif } int main(int argc, char *argv[]) { @@ -202,6 +314,9 @@ int main(int argc, char *argv[]) { void *map; ssize_t size; const char *path; +#ifndef NDEBUG + ShowCrashReports(); +#endif for (i = 1; i < argc; ++i) { path = argv[i]; if ((fd = open(path, O_RDONLY)) == -1) DieSys(path); diff --git a/tool/build/runitd.c b/tool/build/runitd.c index b5b596532..977bfc9cd 100644 --- a/tool/build/runitd.c +++ b/tool/build/runitd.c @@ -483,8 +483,8 @@ void HandleClient(void) { goto TerminateJob; } if (received > 0) { - WARNF("%s client sent %d unexpected bytes so killing job", exename, - received); + WARNF("%s client sent %d bytes unexpected bytes so killing job", + exename, received); goto TerminateJob; } if (received != MBEDTLS_ERR_SSL_WANT_READ) { diff --git a/tool/decode/pe2.c b/tool/decode/pe2.c index d2e706f1b..fb9d0b01e 100644 --- a/tool/decode/pe2.c +++ b/tool/decode/pe2.c @@ -226,13 +226,14 @@ static void showpeoptionalheader(struct NtImageOptionalHeader *opt) { } } -static void ShowIlt(int64_t *ilt) { - printf("\n"); - showtitle(basename(path), "windows", "import lookup table (ilt)", 0, 0); +static void ShowIlt(uint32_t rva) { + int64_t *ilt, *ilt0; + ilt = ilt0 = GetRva(rva); do { printf("\n"); show(".quad", format(b1, "%#lx", *ilt), - _gc(xasprintf("@%#lx", (intptr_t)ilt - (intptr_t)mz))); + _gc(xasprintf("rva=%#lx off=%#lx", (char *)ilt - (char *)ilt0 + rva, + (intptr_t)ilt - (intptr_t)mz))); if (*ilt) { char *hint = GetRva(*ilt); printf("/\t.short\t%d\t\t\t# @%#lx\n", READ16LE(hint), @@ -244,11 +245,11 @@ static void ShowIlt(int64_t *ilt) { } while (*ilt++); } -static void ShowIat(char *iat, size_t size) { +static void ShowIdt(char *idt, size_t size) { char *p, *e; printf("\n"); - showtitle(basename(path), "windows", "import address table (iat)", 0, 0); - for (p = iat, e = iat + size; p + 20 <= e; p += 20) { + showtitle(basename(path), "windows", "import descriptor table (idt)", 0, 0); + for (p = idt, e = idt + size; p + 20 <= e; p += 20) { printf("\n"); show(".long", format(b1, "%#x", READ32LE(p)), _gc(xasprintf("ImportLookupTable RVA @%#lx", @@ -262,9 +263,18 @@ static void ShowIat(char *iat, size_t size) { show(".long", format(b1, "%#x", READ32LE(p + 16)), "ImportAddressTable RVA"); } - for (p = iat, e = iat + size; p + 20 <= e; p += 20) { + for (p = idt, e = idt + size; p + 20 <= e; p += 20) { if (READ32LE(p)) { - ShowIlt(GetRva(READ32LE(p))); + printf("\n"); + showtitle(basename(path), "windows", "import lookup table (ilt)", 0, 0); + ShowIlt(READ32LE(p)); + } + } + for (p = idt, e = idt + size; p + 20 <= e; p += 20) { + if (READ32LE(p)) { + printf("\n"); + showtitle(basename(path), "windows", "import address table (iat)", 0, 0); + ShowIlt(READ32LE(p + 16)); } } } @@ -342,7 +352,7 @@ static void showpeheader(struct NtImageNtHeaders *pe) { pe->FileHeader.NumberOfSections * sizeof(struct NtImageSectionHeader)), pe->FileHeader.NumberOfSections); - ShowIat(GetRva(pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport] + ShowIdt(GetRva(pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport] .VirtualAddress), pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport].Size); } diff --git a/tool/emacs/cosmo-c-builtins.el b/tool/emacs/cosmo-c-builtins.el index f044b54ec..1a9a69330 100644 --- a/tool/emacs/cosmo-c-builtins.el +++ b/tool/emacs/cosmo-c-builtins.el @@ -206,6 +206,7 @@ "__conceal" "__expropriate" "__yoink" + "__dll_import" "__static_yoink" "PYTHON_YOINK" "PYTHON_PROVIDE" diff --git a/tool/hello/hello-pe.c b/tool/hello/hello-pe.c new file mode 100644 index 000000000..c54ce5e08 --- /dev/null +++ b/tool/hello/hello-pe.c @@ -0,0 +1,21 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "tool/build/elf2pe.h" + +#define STD_OUTPUT_HANDLE -11u + +__dll_import("kernel32.dll", long, GetStdHandle, (unsigned)); +__dll_import("kernel32.dll", int, WriteFile, + (long, const void *, unsigned, unsigned *, void *)); + +__attribute__((__ms_abi__)) long WinMain(void) { + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "hello world\n", 12, 0, 0); + return 0; +} diff --git a/tool/hello/hello.c b/tool/hello/hello.c index 83f952264..8c003121d 100644 --- a/tool/hello/hello.c +++ b/tool/hello/hello.c @@ -1,433 +1,18 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 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. │ -╚─────────────────────────────────────────────────────────────────────────────*/ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/calls/calls.h" -#define _HOSTLINUX 1 -#define _HOSTWINDOWS 4 -#define _HOSTXNU 8 -#define _HOSTOPENBSD 16 -#define _HOSTFREEBSD 32 -#define _HOSTNETBSD 64 - -#ifndef SUPPORT_VECTOR -#define SUPPORT_VECTOR -1 +#ifndef TINY +__static_yoink("zipos"); // so apelink embeds symbol table #endif -#ifdef __aarch64__ -#define IsAarch64() 1 -#else -#define IsAarch64() 0 -#endif - -#define SupportsLinux() (SUPPORT_VECTOR & _HOSTLINUX) -#define SupportsXnu() (SUPPORT_VECTOR & _HOSTXNU) -#define SupportsWindows() (SUPPORT_VECTOR & _HOSTWINDOWS) -#define SupportsFreebsd() (SUPPORT_VECTOR & _HOSTFREEBSD) -#define SupportsOpenbsd() (SUPPORT_VECTOR & _HOSTOPENBSD) -#define SupportsNetbsd() (SUPPORT_VECTOR & _HOSTNETBSD) - -#define IsLinux() (SupportsLinux() && __crt.os == _HOSTLINUX) -#define IsXnu() (SupportsXnu() && __crt.os == _HOSTXNU) -#define IsWindows() (SupportsWindows() && __crt.os == _HOSTWINDOWS) -#define IsFreebsd() (SupportsFreebsd() && __crt.os == _HOSTFREEBSD) -#define IsOpenbsd() (SupportsOpenbsd() && __crt.os == _HOSTOPENBSD) -#define IsNetbsd() (SupportsNetbsd() && __crt.os == _HOSTNETBSD) - -#define O_RDONLY 0 -#define PROT_NONE 0 -#define PROT_READ 1 -#define PROT_WRITE 2 -#define PROT_EXEC 4 -#define MAP_SHARED 1 -#define MAP_PRIVATE 2 -#define MAP_FIXED 16 -#define MAP_ANONYMOUS 32 -#define MAP_EXECUTABLE 4096 -#define MAP_NORESERVE 16384 -#define ELFCLASS32 1 -#define ELFDATA2LSB 1 -#define EM_NEXGEN32E 62 -#define EM_AARCH64 183 -#define ET_EXEC 2 -#define ET_DYN 3 -#define PT_LOAD 1 -#define PT_DYNAMIC 2 -#define PT_INTERP 3 -#define EI_CLASS 4 -#define EI_DATA 5 -#define PF_X 1 -#define PF_W 2 -#define PF_R 4 -#define AT_PHDR 3 -#define AT_PHENT 4 -#define AT_PHNUM 5 -#define AT_PAGESZ 6 -#define AT_EXECFN_LINUX 31 -#define AT_EXECFN_NETBSD 2014 -#define X_OK 1 -#define XCR0_SSE 2 -#define XCR0_AVX 4 -#define PR_SET_MM 35 -#define PR_SET_MM_EXE_FILE 13 - -#define EIO 5 -#define EBADF 9 - -#define kNtInvalidHandleValue -1L -#define kNtStdInputHandle -10u -#define kNtStdOutputHandle -11u -#define kNtStdErrorHandle -12u - -#define kNtFileTypeUnknown 0x0000 -#define kNtFileTypeDisk 0x0001 -#define kNtFileTypeChar 0x0002 -#define kNtFileTypePipe 0x0003 -#define kNtFileTypeRemote 0x8000 - -#define kNtGenericRead 0x80000000u -#define kNtGenericWrite 0x40000000u - -#define kNtFileShareRead 0x00000001u -#define kNtFileShareWrite 0x00000002u -#define kNtFileShareDelete 0x00000004u - -#define kNtCreateNew 1 -#define kNtCreateAlways 2 -#define kNtOpenExisting 3 -#define kNtOpenAlways 4 -#define kNtTruncateExisting 5 - -#define kNtFileFlagOverlapped 0x40000000u -#define kNtFileAttributeNotContentIndexed 0x00002000u -#define kNtFileFlagBackupSemantics 0x02000000u -#define kNtFileFlagOpenReparsePoint 0x00200000u -#define kNtFileAttributeCompressed 0x00000800u -#define kNtFileAttributeTemporary 0x00000100u -#define kNtFileAttributeDirectory 0x00000010u - -struct NtOverlapped { - unsigned long Internal; - unsigned long InternalHigh; - union { - struct { - unsigned Offset; - unsigned OffsetHigh; - }; - void *Pointer; - }; - long hEvent; -}; - -#define __dll_import(DLL, RET, FUNC, ARGS) \ - extern RET(*__attribute__((__ms_abi__, __weak__)) FUNC) \ - ARGS __asm__("dll$" DLL ".dll$" #FUNC) - -__dll_import("kernel32", void, ExitProcess, (unsigned)); -__dll_import("kernel32", int, CloseHandle, (long)); -__dll_import("kernel32", long, GetStdHandle, (unsigned)); -__dll_import("kernel32", int, ReadFile, - (long, void *, unsigned, unsigned *, struct NtOverlapped *)); -__dll_import("kernel32", int, WriteFile, - (long, const void *, unsigned, unsigned *, struct NtOverlapped *)); - -struct WinCrt { - long fd2handle[3]; -}; - -struct Crt { - int os; - int argc; - char **argv; - char **envp; - long *auxv; - int pagesz; - int gransz; - struct WinCrt *wincrt; -} __crt; - -long SystemCall(long, long, long, long, long, long, long, int); -long CallSystem(long a, long b, long c, long d, long e, long f, long g, int x) { - if (IsXnu() && !IsAarch64()) x |= 0x2000000; - return SystemCall(a, b, c, d, e, f, g, x); -} - -wontreturn void _Exit(int rc) { - int numba; - if (!IsWindows()) { - if (IsLinux()) { - if (IsAarch64()) { - numba = 94; - } else { - numba = 60; - } - } else { - numba = 1; - } - CallSystem(rc, 0, 0, 0, 0, 0, 0, numba); - } else { - ExitProcess((unsigned)rc << 8); - } - __builtin_unreachable(); -} - -static int ConvertFdToWin32Handle(int fd, long *out_handle) { - if (fd < 0 || fd > 2) return -EBADF; - *out_handle = __crt.wincrt->fd2handle[fd]; - return 0; -} - -int sys_close(int fd) { - if (!IsWindows()) { - int numba; - if (IsLinux()) { - if (IsAarch64()) { - numba = 57; - } else { - numba = 3; - } - } else { - numba = 6; - } - return CallSystem(fd, 0, 0, 0, 0, 0, 0, numba); - } else { - int rc; - long handle; - if (!(rc = ConvertFdToWin32Handle(fd, &handle))) { - CloseHandle(handle); - return 0; - } else { - return rc; - } - } -} - -ssize_t sys_write(int fd, const void *data, size_t size) { - if (!IsWindows()) { - int numba; - if (IsLinux()) { - if (IsAarch64()) { - numba = 64; - } else { - numba = 1; - } - } else { - numba = 4; - } - return CallSystem(fd, (long)data, size, 0, 0, 0, 0, numba); - } else { - ssize_t rc; - long handle; - uint32_t got; - if (!(rc = ConvertFdToWin32Handle(fd, &handle))) { - if (WriteFile(handle, data, size, &got, 0)) { - return got; - } else { - return -EIO; - } - } else { - return rc; - } - } -} - -ssize_t sys_pread(int fd, void *data, size_t size, long off) { - int numba; - if (IsLinux()) { - if (IsAarch64()) { - numba = 0x043; - } else { - numba = 0x011; - } - } else if (IsXnu()) { - numba = 0x099; - } else if (IsFreebsd()) { - numba = 0x1db; - } else if (IsOpenbsd()) { - numba = 0x0a9; - } else if (IsNetbsd()) { - numba = 0x0ad; - } else { - __builtin_unreachable(); - } - return SystemCall(fd, (long)data, size, off, off, 0, 0, numba); -} - -int sys_access(const char *path, int mode) { - if (IsLinux() && IsAarch64()) { - return SystemCall(-100, (long)path, mode, 0, 0, 0, 0, 48); - } else { - return CallSystem((long)path, mode, 0, 0, 0, 0, 0, IsLinux() ? 21 : 33); - } -} - -int sys_open(const char *path, int flags, int mode) { - if (IsLinux() && IsAarch64()) { - return SystemCall(-100, (long)path, flags, mode, 0, 0, 0, 56); - } else { - return CallSystem((long)path, flags, mode, 0, 0, 0, 0, IsLinux() ? 2 : 5); - } -} - -int sys_mprotect(void *addr, size_t size, int prot) { - int numba; - // all unix operating systems define the same values for prot - if (IsLinux()) { - if (IsAarch64()) { - numba = 226; - } else { - numba = 10; - } - } else { - numba = 74; - } - return CallSystem((long)addr, size, prot, 0, 0, 0, 0, numba); -} - -long sys_mmap(void *addr, size_t size, int prot, int flags, int fd, long off) { - long numba; - if (IsLinux()) { - if (IsAarch64()) { - numba = 222; - } else { - numba = 9; - } - } else { - // this flag isn't supported on non-Linux systems. since it's just - // hinting the kernel, it should be inconsequential to just ignore - flags &= ~MAP_NORESERVE; - // this flag is ignored by Linux and it overlaps with bsd map_anon - flags &= ~MAP_EXECUTABLE; - if (flags & MAP_ANONYMOUS) { - // all bsd-style operating systems share the same mag_anon magic - flags &= ~MAP_ANONYMOUS; - flags |= 4096; - } - if (IsFreebsd()) { - numba = 477; - } else if (IsOpenbsd()) { - numba = 49; - } else { - numba = 197; // xnu, netbsd - } - } - return CallSystem((long)addr, size, prot, flags, fd, off, off, numba); -} - -wontreturn void __unix_start(long di, long *sp, char os) { - - // detect freebsd - if (SupportsXnu() && os == _HOSTXNU) { - os = _HOSTXNU; - } else if (SupportsFreebsd() && di) { - os = _HOSTFREEBSD; - sp = (long *)di; - } - - // extract arguments - __crt.argc = *sp; - __crt.argv = (char **)(sp + 1); - __crt.envp = (char **)(sp + 1 + __crt.argc + 1); - __crt.auxv = (long *)(sp + 1 + __crt.argc + 1); - for (;;) { - if (!*__crt.auxv++) { - break; - } - } - - // detect openbsd - if (SupportsOpenbsd() && !os && !__crt.auxv[0]) { - os = _HOSTOPENBSD; - } - - // detect netbsd and find end of words - __crt.pagesz = IsAarch64() ? 16384 : 4096; - for (long *ap = __crt.auxv; ap[0]; ap += 2) { - if (ap[0] == AT_PAGESZ) { - __crt.pagesz = ap[1]; - } else if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) { - os = _HOSTNETBSD; - } - } - if (!os) { - os = _HOSTLINUX; - } - __crt.gransz = __crt.pagesz; - __crt.os = os; - - // call startup routines - typedef int init_f(int, char **, char **, long *); - extern init_f *__init_array_start[] __attribute__((__weak__)); - extern init_f *__init_array_end[] __attribute__((__weak__)); - for (init_f **fp = __init_array_end; fp-- > __init_array_start;) { - (*fp)(__crt.argc, __crt.argv, __crt.envp, __crt.auxv); - } - - // call program - int main(int, char **, char **); - _Exit(main(__crt.argc, __crt.argv, __crt.envp)); -} - -wontreturn void __win32_start(void) { - long sp[] = { - 0, // argc - 0, // empty argv - 0, // empty environ - 0, // empty auxv - }; - __crt.wincrt = &(struct WinCrt){ - .fd2handle = - { - GetStdHandle(kNtStdInputHandle), - GetStdHandle(kNtStdOutputHandle), - GetStdHandle(kNtStdErrorHandle), - }, - }; - __unix_start(0, sp, _HOSTWINDOWS); -} - -ssize_t print(int fd, const char *s, ...) { - int c; - unsigned n; - va_list va; - char b[512]; - va_start(va, s); - for (n = 0; s; s = va_arg(va, const char *)) { - while ((c = *s++)) { - if (n < sizeof(b)) { - b[n++] = c; - } - } - } - va_end(va); - return sys_write(fd, b, n); -} - -//////////////////////////////////////////////////////////////////////////////// - -char data[10] = "sup"; -char bss[0xf9]; -_Thread_local char tdata[10] = "hello"; -_Thread_local char tbss[0xf9]; - -_Section(".love") int main(int argc, char **argv, char **envp) { - if (argc == 666) { - bss[0] = data[0]; - tbss[0] = tdata[0]; - } - print(1, "hello world\n", NULL); +int main(int argc, char *argv[]) { + write(2, "hello world\n", 12); } diff --git a/tool/hello/hello.mk b/tool/hello/hello.mk index b40e978f5..5fc6889aa 100644 --- a/tool/hello/hello.mk +++ b/tool/hello/hello.mk @@ -1,6 +1,9 @@ #-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ #───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ +# qemu-user execve() is broken so we need to build/bootstrap/ commands +ifeq ($(ARCH), x86_64) + PKGS += TOOL_HELLO TOOL_HELLO_FILES := $(wildcard tool/hello/*) @@ -9,20 +12,77 @@ TOOL_HELLO_SRCS_C = $(filter %.c,$(TOOL_HELLO_FILES)) TOOL_HELLO_SRCS_S = $(filter %.S,$(TOOL_HELLO_FILES)) TOOL_HELLO_SRCS = $(TOOL_HELLO_SRCS_C) $(TOOL_HELLO_SRCS_S) TOOL_HELLO_OBJS = $(TOOL_HELLO_SRCS_C:%.c=o/$(MODE)/%.o) $(TOOL_HELLO_SRCS_S:%.S=o/$(MODE)/%.o) -TOOL_HELLO_BINS = o/$(MODE)/tool/hello/hello.com.dbg +TOOL_HELLO_BINS = $(TOOL_HELLO_COMS) $(TOOL_HELLO_COMS:%=%.dbg) -o/$(MODE)/tool/hello/hello.com.dbg: \ - o/$(MODE)/tool/hello/systemcall.o \ - o/$(MODE)/tool/hello/hello.o \ - o/$(MODE)/tool/hello/start.o - @$(COMPILE) -ALINK.elf $(LINK) $(LINKARGS) $(OUTPUT_OPTION) -q -zmax-page-size=4096 +TOOL_HELLO_COMS = \ + o/$(MODE)/tool/hello/hello.com \ + o/$(MODE)/tool/hello/hello-pe.com \ + o/$(MODE)/tool/hello/hello-elf.com \ + o/$(MODE)/tool/hello/hello-unix.com -o/$(MODE)/tool/hello/hello.com: \ - o/$(MODE)/tool/hello/hello.com.dbg \ +TOOL_HELLO_DIRECTDEPS = \ + LIBC_CALLS \ + LIBC_RUNTIME \ + LIBC_ZIPOS + +TOOL_HELLO_DEPS := \ + $(call uniq,$(foreach x,$(TOOL_HELLO_DIRECTDEPS),$($(x)))) + +o/$(MODE)/tool/hello/hello.pkg: \ + $(TOOL_HELLO_OBJS) \ + $(foreach x,$(TOOL_HELLO_DIRECTDEPS),$($(x)_A).pkg) + +# generates debuggable executable using gcc +o/$(MODE)/tool/hello/hello.com.dbg: \ + $(TOOL_HELLO_DEPS) \ + o/$(MODE)/tool/hello/hello.o \ + o/$(MODE)/tool/hello/hello.pkg \ + $(CRT) \ + $(APE) + @$(APELINK) + +# uses apelink to turn it into an ape executable +# support vector is set to all operating systems +o/$(MODE)/tool/hello/hello.com: \ + o/$(MODE)/tool/hello/hello.com.dbg \ + o/$(MODE)/tool/build/apelink.com \ + o/$(MODE)/tool/build/pecheck.com \ + o/$(MODE)/ape/ape.elf + @$(COMPILE) -ALINK.ape o/$(MODE)/tool/build/apelink.com -o $@ -l o/$(MODE)/ape/ape.elf $< + @$(COMPILE) -APECHECK -wT$@ o/$(MODE)/tool/build/pecheck.com $@ + +# uses apelink to generate elf-only executable +# support vector = linux/freebsd/openbsd/netbsd/metal +o/$(MODE)/tool/hello/hello-elf.com: \ + o/$(MODE)/tool/hello/hello.com.dbg \ + o/$(MODE)/tool/build/apelink.com \ + o/$(MODE)/ape/ape.elf + @$(COMPILE) -ALINK.ape o/$(MODE)/tool/build/apelink.com -s elf -o $@ -l o/$(MODE)/ape/ape.elf $< + +# uses apelink to generate non-pe ape executable +# support vector = macos/linux/freebsd/openbsd/netbsd +# - great way to avoid attention from bad virus scanners +# - creates tinier executable by reducing alignment requirement +o/$(MODE)/tool/hello/hello-unix.com: \ + o/$(MODE)/tool/hello/hello.com.dbg \ + o/$(MODE)/tool/build/apelink.com \ + o/$(MODE)/ape/ape.elf + @$(COMPILE) -ALINK.ape o/$(MODE)/tool/build/apelink.com -s unix -o $@ -l o/$(MODE)/ape/ape.elf $< + +# elf2pe generates optimal pe binaries +# windows is the only platform supported +# doesn't depend on ape or the cosmopolitan c library +o/$(MODE)/tool/hello/hello-pe.com.dbg: \ + o/$(MODE)/tool/hello/hello-pe.o + @$(COMPILE) -ALINK.elf $(LINK) $(LINKARGS) $(OUTPUT_OPTION) -q -e WinMain +o/$(MODE)/tool/hello/hello-pe.com: \ + o/$(MODE)/tool/hello/hello-pe.com.dbg \ o/$(MODE)/tool/build/elf2pe.com - o/$(MODE)/tool/build/elf2pe.com -o $@ $< + @$(COMPILE) -AELF2PE o/$(MODE)/tool/build/elf2pe.com -o $@ $< $(TOOL_HELLO_OBJS): tool/hello/hello.mk .PHONY: o/$(MODE)/tool/hello o/$(MODE)/tool/hello: $(TOOL_HELLO_BINS) + +endif diff --git a/tool/hello/start.S b/tool/hello/start.S deleted file mode 100644 index c4bb088ce..000000000 --- a/tool/hello/start.S +++ /dev/null @@ -1,27 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 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 "libc/macros.internal.h" - -_apple: mov $8,%dl -_start: mov %rsp,%rsi - call __unix_start - call __win32_start // prevent --gc-sections - .weak __win32_start - .endfn _start,globl - .endfn _apple,globl diff --git a/tool/hello/systemcall.S b/tool/hello/systemcall.S deleted file mode 100644 index 4f4829f2c..000000000 --- a/tool/hello/systemcall.S +++ /dev/null @@ -1,57 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 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 "libc/macros.internal.h" - -// Invokes system call. -// -// This function has eight parameters. The first seven are for -// arguments passed along to the system call. The eight is for -// the magic number that indicates which system call is called -// -// The return value follows the Linux kernel convention, where -// errors are returned as `-errno`. BSD systems are normalized -// to follow this convention automatically. -// -// It's important to use a function call wrapper when invoking -// syscall, because BSD kernels will unpredictably clobber any -// volatile registers (unlike Linux). There's no overhead with -// the extra call since a system call takes like a microsecond -// -// @return negative errno above -4096ul on error -SystemCall: -#ifdef __aarch64__ - mov x8,x7 - mov x16,x7 - mov x9,0 - adds x9,x9,0 - svc 0 - bcs 1f - ret -1: neg x0,x0 - ret -#else - mov %rcx,%r10 - mov 16(%rsp),%eax - clc - syscall - jnc 1f - neg %rax -1: ret -#endif - .endfn SystemCall,globl