From 0105e3e2b64f03262c522162a9b9f3159edd5648 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 11 Aug 2023 04:37:23 -0700 Subject: [PATCH] Introduce new linker for fat ape binaries --- Makefile | 2 +- ape/ape.S | 23 +- ape/ape.lds | 65 +- ape/idata.internal.h | 6 +- build/bootstrap/pecheck.com | Bin 0 -> 65536 bytes build/definitions.mk | 1 + build/rules.mk | 3 +- examples/hello2.c | 3 +- libc/{runtime => calls}/abort.c | 0 libc/calls/kntsystemdirectory.S | 8 +- libc/calls/kntwindowsdirectory.S | 8 +- libc/calls/onntconsoleevent_init.S | 5 +- libc/calls/wincrash_init.S | 4 +- libc/crt/crt.S | 2 +- libc/dce.h | 12 + libc/integral/c.inc | 4 +- libc/log/log.h | 1 + libc/log/printwindowsmemory.c | 83 + libc/macros.internal.h | 8 - libc/nexgen32e/missingno.S | 30 - libc/nt/ntdllimport.S | 19 +- libc/nt/pedef.internal.h | 8 +- libc/runtime/getsymboltable.c | 12 +- libc/runtime/runtime.h | 1 - libc/runtime/winmain.greg.c | 7 + libc/stdio/fmt.c | 6 +- test/libc/runtime/mprotect_test.c | 4 +- test/libc/runtime/munmap_test.c | 10 +- test/libc/str/regex_test.c | 61 +- tool/build/apelink.c | 2164 +++++++++++++++++++++++++++ tool/build/assimilate.c | 469 ++++-- tool/build/elf2pe.c | 51 +- tool/build/elf2pe.h | 8 + tool/build/lib/lib.h | 10 + tool/build/lib/parsesupportvector.c | 75 + tool/build/pecheck.c | 173 ++- tool/build/runitd.c | 4 +- tool/decode/pe2.c | 30 +- tool/emacs/cosmo-c-builtins.el | 1 + tool/hello/hello-pe.c | 21 + tool/hello/hello.c | 443 +----- tool/hello/hello.mk | 78 +- tool/hello/start.S | 27 - tool/hello/systemcall.S | 57 - 44 files changed, 3140 insertions(+), 867 deletions(-) create mode 100755 build/bootstrap/pecheck.com rename libc/{runtime => calls}/abort.c (100%) create mode 100644 libc/log/printwindowsmemory.c delete mode 100644 libc/nexgen32e/missingno.S create mode 100644 tool/build/apelink.c create mode 100644 tool/build/elf2pe.h create mode 100644 tool/build/lib/lib.h create mode 100644 tool/build/lib/parsesupportvector.c create mode 100644 tool/hello/hello-pe.c delete mode 100644 tool/hello/start.S delete mode 100644 tool/hello/systemcall.S 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 0000000000000000000000000000000000000000..6536a79bf3ae09b594bcc9777311bc2baebd1187 GIT binary patch literal 65536 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 literal 0 HcmV?d00001 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