diff --git a/Makefile b/Makefile
index 86424d681..ceef06b66 100644
--- a/Makefile
+++ b/Makefile
@@ -139,7 +139,6 @@ include libc/vga/vga.mk #─┘
include libc/calls/calls.mk #─┐
include libc/runtime/runtime.mk # ├──SYSTEMS RUNTIME
include libc/crt/crt.mk # │ You can issue system calls
-include tool/hello/hello.mk # │
include third_party/nsync/nsync.mk # │
include third_party/dlmalloc/dlmalloc.mk #─┘
include libc/mem/mem.mk #─┐
@@ -147,6 +146,7 @@ include third_party/gdtoa/gdtoa.mk # ├──DYNAMIC RUNTIME
include third_party/nsync/mem/mem.mk # │ You can now use stdio
include libc/thread/thread.mk # │ You can finally call malloc()
include libc/zipos/zipos.mk # │
+include tool/hello/hello.mk # │
include libc/stdio/stdio.mk # │
include libc/time/time.mk # │
include net/net.mk # │
diff --git a/ape/ape.S b/ape/ape.S
index f7052a275..054e4bf3e 100644
--- a/ape/ape.S
+++ b/ape/ape.S
@@ -1044,7 +1044,8 @@ DLLEXE = DLLSTD
// ││││││││ │ │┌6:Contains Initialized Data
// ││││││││ o │ ││┌5:Contains Code
// ││││││││┌┴─┐rrrr│ ooror│││rorrr
-PETEXT = 0b01100000000000000000000001100000
+PETEXT = 0b01100000000000000000000000100000
+PERDAT = 0b01000000000000000000000001000000
PEDATA = 0b11000000000000000000000011000000
PEIMPS = 0b11000000000000000000000001000000
@@ -1106,6 +1107,19 @@ ape_pe: .ascin "PE",4
.long PETEXT // Flags
.previous
+ .section .pe.sections,"a",@progbits
+ .ascin ".rdata",8 // Section Name
+ .long ape_rom_memsz // Virtual Size or Physical Address
+ .long ape_rom_rva // Relative Virtual Address
+ .long ape_rom_filesz // Physical Size
+ .long ape_rom_offset // Physical Offset
+ .long 0 // Relocation Table Offset
+ .long 0 // Line Number Table Offset
+ .short 0 // Relocation Count
+ .short 0 // Line Number Count
+ .long PERDAT // Flags
+ .previous
+
.section .pe.sections,"a",@progbits
.ascin ".data",8 // Section Name
.long ape_ram_memsz // Virtual Size or Physical Address
@@ -1128,6 +1142,7 @@ ape_pe: .ascin "PE",4
.type ape_idata_idt,@object
.globl ape_idata_idt,ape_idata_idtend
.hidden ape_idata_idt,ape_idata_idtend
+ .balign 4
ape_idata_idt:
.previous/*
...
@@ -1136,6 +1151,7 @@ ape_idata_idt:
*/.section .idata.ro.idt.3,"a",@progbits
.long 0,0,0,0,0
ape_idata_idtend:
+ .byte 0
.previous
.section .piro.data.sort.iat.1,"aw",@progbits
@@ -1143,13 +1159,16 @@ ape_idata_idtend:
.type ape_idata_iat,@object
.globl ape_idata_iat,ape_idata_iatend
.hidden ape_idata_iat,ape_idata_iatend
+ .balign 8
ape_idata_iat:
.previous/*
...
decentralized content
...
*/.section .piro.data.sort.iat.3,"aw",@progbits
+ .quad 0
ape_idata_iatend:
+ .byte 0
.previous
#endif /* SupportsWindows() */
@@ -1259,8 +1278,6 @@ realmodeloader:
call lhinit
call rlinit
call sinit4
- .optfn _start16
- call _start16
call longmodeloader
.endfn realmodeloader
diff --git a/ape/ape.lds b/ape/ape.lds
index 0fa664d93..446cfa396 100644
--- a/ape/ape.lds
+++ b/ape/ape.lds
@@ -229,7 +229,7 @@ SECTIONS {
KEEP(*(.text.head))
/* Executable & Linkable Format */
- . = ALIGN(__SIZEOF_POINTER__);
+ . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0);
ape_phdrs = .;
KEEP(*(.elf.phdrs))
ape_phdrs_end = .;
@@ -239,7 +239,7 @@ SECTIONS {
KEEP(*(.emushepilogue))
/* OpenBSD */
- . = ALIGN(__SIZEOF_POINTER__);
+ . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0);
ape_note = .;
KEEP(*(.note.openbsd.ident))
KEEP(*(.note.netbsd.ident))
@@ -253,15 +253,15 @@ SECTIONS {
/* Mach-O */
KEEP(*(.macho))
- . = ALIGN(__SIZEOF_POINTER__);
+ . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0);
ape_macho_end = .;
/* APE loader */
KEEP(*(.ape.loader))
- . = ALIGN(CODE_GRANULE);
+ . = ALIGN(. != 0 ? CODE_GRANULE : 0);
KEEP(*(.ape.pad.head))
- . = ALIGN(SupportsWindows() || SupportsMetal() ? CONSTANT(MAXPAGESIZE) : 16);
+ . = ALIGN(. != 0 ? (SupportsWindows() || SupportsMetal() ? CONSTANT(MAXPAGESIZE) : 16) : 0);
_ehead = .;
} :Head
@@ -312,14 +312,14 @@ SECTIONS {
/* Privileged code invulnerable to magic */
KEEP(*(.ape.pad.privileged));
- . = ALIGN(__privileged_end > __privileged_start ? CONSTANT(MAXPAGESIZE) : 1);
+ . = ALIGN(__privileged_end > __privileged_start ? CONSTANT(MAXPAGESIZE) : 0);
/*END: morphable code */
__privileged_start = .;
*(.privileged)
__privileged_end = .;
KEEP(*(.ape.pad.text))
- . = ALIGN(CONSTANT(MAXPAGESIZE));
+ . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0);
/*END: Read Only Data (only needed for initialization) */
} :Cod
@@ -354,7 +354,7 @@ SECTIONS {
KEEP(*(SORT_BY_NAME(.initro.*)))
KEEP(*(.initroepilogue))
KEEP(*(SORT_BY_NAME(.sort.rodata.*)))
- . = ALIGN(CONSTANT(MAXPAGESIZE));
+ . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0); /* don't delete this line :o */
/*END: read-only data that's only needed for initialization */
@@ -368,14 +368,13 @@ SECTIONS {
*(SORT_BY_ALIGNMENT(.tdata.*))
_tdata_end = .;
KEEP(*(.ape.pad.rodata))
- . = ALIGN(CONSTANT(MAXPAGESIZE));
+ . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0);
_etext = .;
PROVIDE(etext = .);
} :Tls :Rom
/*END: Read Only Data */
- . = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(MAXPAGESIZE));
- . = DATA_SEGMENT_RELRO_END(0, .);
+ . = ALIGN(CONSTANT(MAXPAGESIZE));
/* this only tells the linker about the layout of uninitialized */
/* TLS data, and does not advance the linker's location counter */
@@ -409,7 +408,7 @@ SECTIONS {
*(.got.plt)
KEEP(*(.gotpltepilogue))
- . = ALIGN(__SIZEOF_POINTER__);
+ . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0);
__init_array_start = .;
KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*)
SORT_BY_INIT_PRIORITY(.ctors.*)))
@@ -418,7 +417,7 @@ SECTIONS {
KEEP(*(.preinit_array))
__init_array_end = .;
- . = ALIGN(__SIZEOF_POINTER__);
+ . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0);
__fini_array_start = .;
KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*)
SORT_BY_INIT_PRIORITY(.dtors.*)))
@@ -427,19 +426,21 @@ SECTIONS {
__fini_array_end = .;
/*BEGIN: Post-Initialization Read-Only */
- . = ALIGN(__SIZEOF_POINTER__);
+ . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0);
KEEP(*(SORT_BY_NAME(.piro.relo.sort.*)))
- . = ALIGN(__SIZEOF_POINTER__);
+ . = ALIGN(. != 0 ? __SIZEOF_POINTER__ : 0);
KEEP(*(SORT_BY_NAME(.piro.data.sort.*)))
KEEP(*(.piro.pad.data))
KEEP(*(.dataepilogue))
- . = ALIGN(CONSTANT(MAXPAGESIZE));
+ . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0);
/*END: NT FORK COPYING */
_edata = .;
PROVIDE(edata = .);
_ezip = .; /* <-- very deprecated */
} :Ram
+ . = ALIGN(CONSTANT(MAXPAGESIZE));
+
/*END: file content that's loaded by o/s */
/*END: file content */
/*BEGIN: bss memory that's addressable */
@@ -463,14 +464,14 @@ SECTIONS {
KEEP(*(.bssepilogue))
- . = ALIGN(CONSTANT(MAXPAGESIZE));
+ . = ALIGN(. != 0 ? CONSTANT(MAXPAGESIZE) : 0);
/*END: NT FORK COPYING */
- _end = .;
- PROVIDE(end = .);
} :Ram
- . = DATA_SEGMENT_END(.);
+ . = ALIGN(CONSTANT(MAXPAGESIZE));
+ _end = .;
+ PROVIDE(end = .);
/*END: nt addressability guarantee */
/*END: bsd addressability guarantee */
@@ -556,24 +557,24 @@ _tls_align = 1;
ape_cod_offset = 0;
ape_cod_vaddr = ADDR(.head);
ape_cod_paddr = LOADADDR(.head);
-ape_cod_filesz = SIZEOF(.head) + SIZEOF(.text);
+ape_cod_filesz = ADDR(.rodata) - ADDR(.head);
ape_cod_memsz = ape_cod_filesz;
ape_cod_align = CONSTANT(MAXPAGESIZE);
ape_cod_rva = RVA(ape_cod_vaddr);
-ape_rom_offset = ape_cod_offset + ape_cod_filesz;
ape_rom_vaddr = ADDR(.rodata);
+ape_rom_offset = ape_rom_vaddr - __executable_start;
ape_rom_paddr = LOADADDR(.rodata);
-ape_rom_filesz = SIZEOF(.rodata) + SIZEOF(.tdata);
+ape_rom_filesz = ADDR(.tbss) - ADDR(.rodata);
ape_rom_memsz = ape_rom_filesz;
ape_rom_align = CONSTANT(MAXPAGESIZE);
ape_rom_rva = RVA(ape_rom_vaddr);
-ape_ram_offset = ape_rom_offset + ape_rom_filesz;
ape_ram_vaddr = ADDR(.data);
+ape_ram_offset = ape_ram_vaddr - __executable_start;
ape_ram_paddr = LOADADDR(.data);
-ape_ram_filesz = SIZEOF(.data);
-ape_ram_memsz = SIZEOF(.data) + SIZEOF(.bss);
+ape_ram_filesz = ADDR(.bss) - ADDR(.data);
+ape_ram_memsz = _end - ADDR(.data);
ape_ram_align = CONSTANT(MAXPAGESIZE);
ape_ram_rva = RVA(ape_ram_vaddr);
@@ -591,10 +592,10 @@ ape_note_offset = ape_cod_offset + (ape_note - ape_cod_vaddr);
ape_note_filesz = ape_note_end - ape_note;
ape_note_memsz = ape_note_filesz;
-ape_text_offset = ape_cod_offset + LOADADDR(.text) - ape_cod_paddr;
-ape_text_paddr = LOADADDR(.text);
ape_text_vaddr = ADDR(.text);
-ape_text_filesz = SIZEOF(.text) + SIZEOF(.rodata) + SIZEOF(.tdata);
+ape_text_offset = ape_text_vaddr - __executable_start;
+ape_text_paddr = LOADADDR(.text);
+ape_text_filesz = ADDR(.rodata) - ADDR(.text);
ape_text_memsz = ape_text_filesz;
ape_text_align = CONSTANT(MAXPAGESIZE);
ape_text_rva = RVA(ape_text_vaddr);
@@ -760,12 +761,6 @@ ASSERT(((DEFINED(__init_rodata_end) ? __init_rodata_end : 0) %
ASSERT((!DEFINED(ape_grub) ? 1 : RVA(ape_grub) < 8192),
"grub stub needs to be in first 8kb of image");
-ASSERT(DEFINED(_start) || DEFINED(_start16),
- "please link a _start() or _start16() entrypoint");
-
-ASSERT(!DEFINED(_start16) || REAL(_end) < 65536,
- "ape won't support non-tiny real mode programs");
-
ASSERT(IS2POW(ape_stack_memsz),
"ape_stack_memsz must be a two power");
diff --git a/ape/idata.internal.h b/ape/idata.internal.h
index 80c0ef892..c090229e1 100644
--- a/ape/idata.internal.h
+++ b/ape/idata.internal.h
@@ -43,10 +43,10 @@
.hidden \fn
.previous
.section ".idata.ro.ilt.\dll\().2.\actual","a",@progbits
-".Lidata.ilt.\dll\().\actual":
+"idata.ilt.\dll\().\actual":
.quad RVA("\dll\().\actual")
- .type ".Lidata.ilt.\dll\().\actual",@object
- .size ".Lidata.ilt.\dll\().\actual",.-".Lidata.ilt.\dll\().\actual"
+ .type "idata.ilt.\dll\().\actual",@object
+ .size "idata.ilt.\dll\().\actual",.-"idata.ilt.\dll\().\actual"
.previous
.section ".idata.ro.hnt.\dll\().2.\actual","a",@progbits
"\dll\().\actual":
diff --git a/build/bootstrap/pecheck.com b/build/bootstrap/pecheck.com
new file mode 100755
index 000000000..6536a79bf
Binary files /dev/null and b/build/bootstrap/pecheck.com differ
diff --git a/build/definitions.mk b/build/definitions.mk
index aca2e1880..077fbacf4 100644
--- a/build/definitions.mk
+++ b/build/definitions.mk
@@ -75,6 +75,7 @@ PKG = build/bootstrap/package.com
MKDEPS = build/bootstrap/mkdeps.com
ZIPOBJ = build/bootstrap/zipobj.com
ZIPCOPY = build/bootstrap/zipcopy.com
+PECHECK = build/bootstrap/pecheck.com
FIXUPOBJ = build/bootstrap/fixupobj.com
MKDIR = build/bootstrap/mkdir.com -p
COMPILE = build/bootstrap/compile.com -V9 -P4096 $(QUOTA)
diff --git a/build/rules.mk b/build/rules.mk
index 6d11c6da3..11fde207f 100644
--- a/build/rules.mk
+++ b/build/rules.mk
@@ -17,8 +17,9 @@
MAKEFLAGS += --no-builtin-rules
MAKE_ZIPCOPY = $(COMPILE) -AZIPCOPY -wT$@ $(ZIPCOPY) $< $@
+MAKE_PECHECK = $(COMPILE) -APECHECK -wT$@ $(PECHECK) $@
ifneq ($(ARCH), aarch64)
-MAKE_OBJCOPY = $(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ && $(MAKE_ZIPCOPY)
+MAKE_OBJCOPY = $(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ && $(MAKE_ZIPCOPY) && $(MAKE_PECHECK)
else
MAKE_OBJCOPY = $(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S $< $@ && $(MAKE_ZIPCOPY)
endif
diff --git a/examples/hello2.c b/examples/hello2.c
index c46a5794b..ecf749dee 100644
--- a/examples/hello2.c
+++ b/examples/hello2.c
@@ -8,9 +8,8 @@
╚─────────────────────────────────────────────────────────────────*/
#endif
#include "libc/calls/calls.h"
-#include "libc/str/str.h"
int main() {
- // write(1, "hello world\n", 12);
+ write(1, "hello world\n", 12);
return 0;
}
diff --git a/libc/runtime/abort.c b/libc/calls/abort.c
similarity index 100%
rename from libc/runtime/abort.c
rename to libc/calls/abort.c
diff --git a/libc/calls/kntsystemdirectory.S b/libc/calls/kntsystemdirectory.S
index 870b4faf9..0139b92b1 100644
--- a/libc/calls/kntsystemdirectory.S
+++ b/libc/calls/kntsystemdirectory.S
@@ -32,10 +32,12 @@ kNtSystemDirectory:
.init.start 300,_init_kNtSystemDirectory
#if SupportsWindows()
+ testb IsWindows()
+ jz 1f
pushpop BYTES,%rdx
mov __imp_GetSystemDirectoryA(%rip),%rax
call __getntsyspath
-#else
- add $BYTES,%rdi
+ jmp 2f
#endif
- .init.end 300,_init_kNtSystemDirectory
+1: add $BYTES,%rdi
+2: .init.end 300,_init_kNtSystemDirectory
diff --git a/libc/calls/kntwindowsdirectory.S b/libc/calls/kntwindowsdirectory.S
index 890cebfda..4f56b5a8f 100644
--- a/libc/calls/kntwindowsdirectory.S
+++ b/libc/calls/kntwindowsdirectory.S
@@ -32,10 +32,12 @@ kNtWindowsDirectory:
.init.start 300,_init_kNtWindowsDirectory
#if SupportsWindows()
+ testb IsWindows()
+ jz 1f
pushpop BYTES,%rdx
mov __imp_GetWindowsDirectoryA(%rip),%rax
call __getntsyspath
-#else
- add $BYTES,%rdi
+ jmp 2f
#endif
- .init.end 300,_init_kNtWindowsDirectory
+1: add $BYTES,%rdi
+2: .init.end 300,_init_kNtWindowsDirectory
diff --git a/libc/calls/onntconsoleevent_init.S b/libc/calls/onntconsoleevent_init.S
index 2e8862be9..1f22620d9 100644
--- a/libc/calls/onntconsoleevent_init.S
+++ b/libc/calls/onntconsoleevent_init.S
@@ -16,6 +16,7 @@
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/dce.h"
#include "libc/macros.internal.h"
.text.windows
@@ -25,7 +26,9 @@ __onntconsoleevent_nt:
.endfn __onntconsoleevent_nt,globl,hidden
.init.start 300,_init_onntconsoleevent
+ testb IsWindows()
+ jz 1f
ezlea __onntconsoleevent_nt,cx
pushpop 1,%rdx
ntcall __imp_SetConsoleCtrlHandler
- .init.end 300,_init_onntconsoleevent,globl,hidden
+1: .init.end 300,_init_onntconsoleevent,globl,hidden
diff --git a/libc/calls/wincrash_init.S b/libc/calls/wincrash_init.S
index 33e0888fb..a4ab04618 100644
--- a/libc/calls/wincrash_init.S
+++ b/libc/calls/wincrash_init.S
@@ -20,6 +20,8 @@
#include "libc/macros.internal.h"
.init.start 300,_init_wincrash
+ testb IsWindows()
+ jz 1f
#if !IsTiny()
mov __wincrashearly(%rip),%rcx
ntcall __imp_RemoveVectoredExceptionHandler
@@ -27,4 +29,4 @@
pushpop 1,%rcx
ezlea __wincrash_nt,dx
ntcall __imp_AddVectoredExceptionHandler
- .init.end 300,_init_wincrash,globl,hidden
+1: .init.end 300,_init_wincrash,globl,hidden
diff --git a/libc/crt/crt.S b/libc/crt/crt.S
index 678b50d2e..f26258264 100644
--- a/libc/crt/crt.S
+++ b/libc/crt/crt.S
@@ -83,7 +83,7 @@ _start:
// make win32 imps noop
.weak ape_idata_iat
.weak ape_idata_iatend
- ezlea _missingno,ax
+ ezlea __win32_oops,ax
ezlea ape_idata_iat,di
ezlea ape_idata_iatend,cx
sub %rdi,%rcx
diff --git a/libc/dce.h b/libc/dce.h
index 0f946bf1d..db0df834c 100644
--- a/libc/dce.h
+++ b/libc/dce.h
@@ -66,6 +66,18 @@
#define IsXnuSilicon() 0
#endif
+#if defined(__x86_64__)
+#define _ARCH_NAME "amd64"
+#elif defined(__aarch64__)
+#define _ARCH_NAME "arm64"
+#elif defined(__powerpc64__)
+#define _ARCH_NAME "ppc64"
+#elif defined(__s390x__)
+#define _ARCH_NAME "s390x"
+#elif defined(__riscv)
+#define _ARCH_NAME "riscv"
+#endif
+
#define SupportsLinux() ((SUPPORT_VECTOR & _HOSTLINUX) == _HOSTLINUX)
#define SupportsMetal() ((SUPPORT_VECTOR & _HOSTMETAL) == _HOSTMETAL)
#define SupportsWindows() ((SUPPORT_VECTOR & _HOSTWINDOWS) == _HOSTWINDOWS)
diff --git a/libc/integral/c.inc b/libc/integral/c.inc
index 167c057f6..dd917e9bd 100644
--- a/libc/integral/c.inc
+++ b/libc/integral/c.inc
@@ -611,8 +611,8 @@ void abort(void) wontreturn;
} while (0)
#ifndef __STRICT_ANSI__
-#define textstartup _Section(".text.startup") dontinstrument
-#define textexit _Section(".text.exit") dontinstrument
+#define textstartup _Section(".text.startup")
+#define textexit _Section(".text.exit")
#define textreal _Section(".text.real")
#define texthead _Section(".text.head")
#define textwindows _Section(".text.windows")
diff --git a/libc/log/log.h b/libc/log/log.h
index df18056a8..2a30c2f66 100644
--- a/libc/log/log.h
+++ b/libc/log/log.h
@@ -50,6 +50,7 @@ char *GetSymbolByAddr(int64_t);
void PrintGarbage(void);
void PrintGarbageNumeric(FILE *);
void CheckForMemoryLeaks(void);
+void PrintWindowsMemory(const char *, size_t);
#ifndef __STRICT_ANSI__
diff --git a/libc/log/printwindowsmemory.c b/libc/log/printwindowsmemory.c
new file mode 100644
index 000000000..b96e12ec8
--- /dev/null
+++ b/libc/log/printwindowsmemory.c
@@ -0,0 +1,83 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2023 Justine Alexandra Roberts Tunney │
+│ │
+│ Permission to use, copy, modify, and/or distribute this software for │
+│ any purpose with or without fee is hereby granted, provided that the │
+│ above copyright notice and this permission notice appear in all copies. │
+│ │
+│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
+│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
+│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
+│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
+│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
+│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
+│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
+│ PERFORMANCE OF THIS SOFTWARE. │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/fmt/conv.h"
+#include "libc/intrin/describeflags.internal.h"
+#include "libc/intrin/kprintf.h"
+#include "libc/log/log.h"
+#include "libc/macros.internal.h"
+#include "libc/nt/enum/memflags.h"
+#include "libc/nt/memory.h"
+#include "libc/nt/struct/memorybasicinformation.h"
+#include "libc/str/str.h"
+
+static const struct DescribeFlags kNtMemState[] = {
+ {kNtMemCommit, "Commit"}, //
+ {kNtMemFree, "Free"}, //
+ {kNtMemReserve, "Reserve"}, //
+};
+
+static const char *DescribeNtMemState(char buf[64], uint32_t x) {
+ return DescribeFlags(buf, 64, kNtMemState, ARRAYLEN(kNtMemState), "kNtMem",
+ x);
+}
+
+static const struct DescribeFlags kNtMemType[] = {
+ {kNtMemImage, "Image"}, //
+ {kNtMemMapped, "Mapped"}, //
+ {kNtMemPrivate, "Private"}, //
+};
+
+static const char *DescribeNtMemType(char buf[64], uint32_t x) {
+ return DescribeFlags(buf, 64, kNtMemType, ARRAYLEN(kNtMemType), "kNtMem", x);
+}
+
+/**
+ * Prints to stderr all memory mappings that exist according to WIN32.
+ *
+ * The `high` and `size` parameters may optionally be specified so that
+ * memory mappings which overlap `[high,high+size)` will get printed in
+ * ANSI bold red text.
+ */
+void PrintWindowsMemory(const char *high, size_t size) {
+ char *p, b[5][64];
+ const char *start, *stop;
+ struct NtMemoryBasicInformation mi;
+ kprintf("%-12s %-12s %10s %16s %16s %32s %32s\n", "Allocation", "BaseAddress",
+ "RegionSize", "State", "Type", "AllocationProtect", "Protect");
+ for (p = 0;; p = (char *)mi.BaseAddress + mi.RegionSize) {
+ const char *start, *stop;
+ bzero(&mi, sizeof(mi));
+ if (!VirtualQuery(p, &mi, sizeof(mi))) break;
+ sizefmt(b[0], mi.RegionSize, 1024);
+ if (MAX(high, (char *)mi.BaseAddress) <
+ MIN(high + size, (char *)mi.BaseAddress + mi.RegionSize)) {
+ start = "\e[1;31m";
+ stop = "\e[0m";
+ } else {
+ start = "";
+ stop = "";
+ }
+ kprintf("%s%.12lx %.12lx %10s %16s %16s %32s %32s%s\n", start,
+ mi.AllocationBase, mi.BaseAddress, b[0],
+ DescribeNtMemState(b[1], mi.State),
+ DescribeNtMemType(b[2], mi.Type),
+ (DescribeNtPageFlags)(b[3], mi.AllocationProtect),
+ (DescribeNtPageFlags)(b[4], mi.Protect), stop);
+ }
+}
diff --git a/libc/macros.internal.h b/libc/macros.internal.h
index b9a1b62f7..ff18895cd 100644
--- a/libc/macros.internal.h
+++ b/libc/macros.internal.h
@@ -347,14 +347,6 @@
pop \dest
.endm
-// Declares optional function.
-.macro .optfn fn:req
- .globl "\fn"
- .weak "\fn"
- .equ "\fn",_missingno
- .type "\fn",@function
-.endm
-
// Embeds fixed-width zero-filled string table.
// @note zero-padded ≠ nul-terminated
.macro .fxstr width head rest:vararg
diff --git a/libc/nexgen32e/missingno.S b/libc/nexgen32e/missingno.S
deleted file mode 100644
index 4c535b9ed..000000000
--- a/libc/nexgen32e/missingno.S
+++ /dev/null
@@ -1,30 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 sw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ Permission to use, copy, modify, and/or distribute this software for │
-│ any purpose with or without fee is hereby granted, provided that the │
-│ above copyright notice and this permission notice appear in all copies. │
-│ │
-│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
-│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
-│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
-│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
-│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
-│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
-│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
-│ PERFORMANCE OF THIS SOFTWARE. │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/macros.internal.h"
-.real
-
-// Optional function stub.
-_missingno:
-#ifdef __x86__
- xor %eax,%eax
-#elif defined(__aarch64__)
- mov x0,#0
-#endif
- ret
- .endfn _missingno,globl,hidden
diff --git a/libc/nt/ntdllimport.S b/libc/nt/ntdllimport.S
index ce72c4e8e..da5db5c60 100644
--- a/libc/nt/ntdllimport.S
+++ b/libc/nt/ntdllimport.S
@@ -17,6 +17,7 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/nt/enum/status.h"
+#include "libc/dce.h"
#include "libc/macros.internal.h"
#ifdef __x86_64__
@@ -40,19 +41,25 @@ kNtdllProcRvas:
.init.start 202,_init_ntdll
push %r12
push %r13
- lea _ntdllmissingno(%rip),%r13
+ lea __ntdll_not_found(%rip),%r13
sub $32,%rsp
+ xor %eax,%eax
+ testb IsWindows()
+ jz 7f
loadstr "ntdll.dll",cx
call *__imp_GetModuleHandleA(%rip)
- mov %rax,%r12
+7: mov %rax,%r12
0: lodsq
test %rax,%rax
jz 1f
.weak __executable_start
lea __executable_start(%rax),%rdx
+ xor %eax,%eax
+ testb IsWindows()
+ jz 7f
mov %r12,%rcx
call *__imp_GetProcAddress(%rip)
- test %rax,%rax
+7: test %rax,%rax
cmovz %r13,%rax
stosq
jmp 0b
@@ -62,9 +69,13 @@ kNtdllProcRvas:
.init.end 202,_init_ntdll,globl,hidden
.text.windows
-_ntdllmissingno:
+ .ftrace1
+__ntdll_not_found:
+ .ftrace2
mov $kNtStatusDllNotFound,%eax
ret
.previous
+ .weak __hostos
+
#endif /* __x86_64__ */
diff --git a/libc/nt/pedef.internal.h b/libc/nt/pedef.internal.h
index 152dc1c11..58d71b2c8 100644
--- a/libc/nt/pedef.internal.h
+++ b/libc/nt/pedef.internal.h
@@ -32,10 +32,10 @@
#define kNtPe32bit 0x010b
#define kNtPe64bit 0x020b
-#define kNtPeSectionCntCode 0x000000020
-#define kNtPeSectionCntInitializedData 0x000000040
-#define kNtPeSectionCntUninitializedData 0x000000080
-#define kNtPeSectionGprel 0x000008000
+#define kNtPeSectionCntCode 0x00000020
+#define kNtPeSectionCntInitializedData 0x00000040
+#define kNtPeSectionCntUninitializedData 0x00000080
+#define kNtPeSectionGprel 0x00008000
#define kNtPeSectionMemDiscardable 0x02000000
#define kNtPeSectionMemNotCached 0x04000000
#define kNtPeSectionMemNotPaged 0x08000000
diff --git a/libc/runtime/getsymboltable.c b/libc/runtime/getsymboltable.c
index 09cd4d9e3..7adbe492e 100644
--- a/libc/runtime/getsymboltable.c
+++ b/libc/runtime/getsymboltable.c
@@ -34,16 +34,6 @@
__static_yoink("__get_symbol");
-#ifdef __x86_64__
-#define SYMTAB_ARCH ".symtab.x86"
-#elif defined(__aarch64__)
-#define SYMTAB_ARCH ".symtab.aarch64"
-#elif defined(__powerpc64__)
-#define SYMTAB_ARCH ".symtab.powerpc64"
-#else
-#error "unsupported architecture"
-#endif
-
static pthread_spinlock_t g_lock;
struct SymbolTable *__symtab; // for kprintf
@@ -69,7 +59,7 @@ static struct SymbolTable *GetSymbolTableFromZip(struct Zipos *zipos) {
size_t size, size2;
ssize_t rc, cf, lf;
struct SymbolTable *res = 0;
- if ((cf = GetZipFile(zipos, SYMTAB_ARCH)) != -1 ||
+ if ((cf = GetZipFile(zipos, ".symtab." _ARCH_NAME)) != -1 ||
(cf = GetZipFile(zipos, ".symtab")) != -1) {
lf = GetZipCfileOffset(zipos->map + cf);
size = GetZipLfileUncompressedSize(zipos->map + lf);
diff --git a/libc/runtime/runtime.h b/libc/runtime/runtime.h
index 621f79bf9..01734f513 100644
--- a/libc/runtime/runtime.h
+++ b/libc/runtime/runtime.h
@@ -113,7 +113,6 @@ void _Exitr(int) libcesque wontreturn;
void _Exit1(int) libcesque wontreturn;
void _restorewintty(void);
void __paginate(int, const char *);
-long _missingno();
/* memory management */
void _weakfree(void *);
void *_mapanon(size_t) attributeallocsize((1)) mallocesque;
diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c
index 3d543fe39..1f141a5e1 100644
--- a/libc/runtime/winmain.greg.c
+++ b/libc/runtime/winmain.greg.c
@@ -16,6 +16,7 @@
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/assert.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
@@ -95,6 +96,12 @@ static const short kConsoleModes[3] = {
kNtEnableVirtualTerminalProcessing,
};
+// implements all win32 apis on non-windows hosts
+__msabi long __win32_oops(void) {
+ assert(!"win32 api called on non-windows host");
+ return 0;
+}
+
// https://nullprogram.com/blog/2022/02/18/
__msabi static inline char16_t *MyCommandLine(void) {
void *cmd;
diff --git a/libc/stdio/fmt.c b/libc/stdio/fmt.c
index a9697f05b..2c97ec76e 100644
--- a/libc/stdio/fmt.c
+++ b/libc/stdio/fmt.c
@@ -705,6 +705,10 @@ haveinc:
return prec;
}
+static int __fmt_noop(const char *, void *, size_t) {
+ return 0;
+}
+
/**
* Implements {,v}{,s{,n},{,{,x}as},f,d}printf domain-specific language.
*
@@ -797,7 +801,7 @@ int __fmt(void *fn, void *arg, const char *format, va_list va) {
x = 0;
lasterr = errno;
- out = fn ? fn : (void *)_missingno;
+ out = fn ? fn : __fmt_noop;
while (*format) {
if (*format != '%') {
diff --git a/test/libc/runtime/mprotect_test.c b/test/libc/runtime/mprotect_test.c
index 48f36af10..3d824e842 100644
--- a/test/libc/runtime/mprotect_test.c
+++ b/test/libc/runtime/mprotect_test.c
@@ -134,7 +134,7 @@ TEST(mprotect, testSegfault_writeToReadOnlyAnonymous) {
EXPECT_FALSE(gotsegv);
EXPECT_FALSE(gotbusted);
EXPECT_NE(-1, mprotect(p, getauxval(AT_PAGESZ), PROT_READ));
- _missingno(p[0]);
+ __expropriate(p[0]);
EXPECT_FALSE(gotsegv);
EXPECT_FALSE(gotbusted);
p[0] = 2;
@@ -162,7 +162,7 @@ TEST(mprotect, testProtNone_cantEvenRead) {
volatile char *p;
p = gc(memalign(getauxval(AT_PAGESZ), getauxval(AT_PAGESZ)));
EXPECT_NE(-1, mprotect(p, getauxval(AT_PAGESZ), PROT_NONE));
- _missingno(p[0]);
+ __expropriate(p[0]);
EXPECT_TRUE(gotsegv | gotbusted);
EXPECT_NE(-1, mprotect(p, getauxval(AT_PAGESZ), PROT_READ | PROT_WRITE));
}
diff --git a/test/libc/runtime/munmap_test.c b/test/libc/runtime/munmap_test.c
index 9cd713276..efb39f84e 100644
--- a/test/libc/runtime/munmap_test.c
+++ b/test/libc/runtime/munmap_test.c
@@ -19,6 +19,8 @@
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/errno.h"
+#include "libc/intrin/kprintf.h"
+#include "libc/log/log.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
@@ -200,12 +202,12 @@ TEST(munmap, tinyFile_preciseUnmapSize) {
// clang-format off
TEST(munmap, tinyFile_mapThriceUnmapOnce) {
- char *p = (char *)0x02000000;
+ char *p = (char *)0x000063d646e20000;
ASSERT_SYS(0, 3, open("doge", O_RDWR | O_CREAT | O_TRUNC, 0644));
ASSERT_SYS (0, 5, write(3, "hello", 5));
- ASSERT_NE(MAP_FAILED, mmap(p+FRAMESIZE*0, FRAMESIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0));
- ASSERT_NE(MAP_FAILED, mmap(p+FRAMESIZE*1, 5, PROT_READ, MAP_PRIVATE|MAP_FIXED, 3, 0));
- ASSERT_NE(MAP_FAILED, mmap(p+FRAMESIZE*3, 5, PROT_READ, MAP_PRIVATE|MAP_FIXED, 3, 0));
+ ASSERT_EQ(p+FRAMESIZE*0, mmap(p+FRAMESIZE*0, FRAMESIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0));
+ ASSERT_EQ(p+FRAMESIZE*1, mmap(p+FRAMESIZE*1, 5, PROT_READ, MAP_PRIVATE|MAP_FIXED, 3, 0));
+ ASSERT_EQ(p+FRAMESIZE*3, mmap(p+FRAMESIZE*3, 5, PROT_READ, MAP_PRIVATE|MAP_FIXED, 3, 0));
ASSERT_SYS(0, 0, close(3));
EXPECT_TRUE(testlib_memoryexists(p+FRAMESIZE*0));
EXPECT_TRUE(testlib_memoryexists(p+FRAMESIZE*1));
diff --git a/test/libc/str/regex_test.c b/test/libc/str/regex_test.c
index 563d8b92b..6b014acc2 100644
--- a/test/libc/str/regex_test.c
+++ b/test/libc/str/regex_test.c
@@ -16,12 +16,12 @@
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/mem/mem.h"
+#include "third_party/regex/regex.h"
#include "libc/mem/gc.internal.h"
+#include "libc/mem/mem.h"
#include "libc/str/str.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
-#include "third_party/regex/regex.h"
TEST(regex, test) {
regex_t rx;
@@ -112,6 +112,63 @@ void D(regex_t *rx, regmatch_t *m) {
regexec(rx, "127.0.0.1", rx->re_nsub + 1, m, 0);
}
+TEST(ape, testPeMachoDd) {
+ regex_t rx;
+ ASSERT_EQ(REG_OK, regcomp(&rx,
+ "bs=" // dd block size arg
+ "(['\"] *)?" // #1 optional quote w/ space
+ "(\\$\\(\\( *)?" // #2 optional math w/ space
+ "([[:digit:]]+)" // #3
+ "( *\\)\\))?" // #4 optional math w/ space
+ "( *['\"])?" // #5 optional quote w/ space
+ " +" //
+ "skip=",
+ REG_EXTENDED));
+ EXPECT_EQ(REG_OK, regexec(&rx, "bs=123 skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_OK, regexec(&rx, "bs=\"123\" skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_OK, regexec(&rx, "bs=$((123 skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_OK, regexec(&rx, "bs=\"$((123 skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_NOMATCH, regexec(&rx, "bs= skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_NOMATCH, regexec(&rx, "bs= 123 skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_NOMATCH, regexec(&rx, "bs= 123skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_OK, regexec(&rx, "bs=' 123' skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_OK, regexec(&rx, "bs=$(( 123)) skip=", 0, NULL, 0));
+ EXPECT_EQ(REG_OK, regexec(&rx, "bs=\"$(( 123 ))\" skip=", 0, NULL, 0));
+ regfree(&rx);
+}
+
+TEST(ape, testPeMachoDd2) {
+ regex_t rx;
+ ASSERT_EQ(REG_OK, regcomp(&rx,
+ "bs=" // dd block size arg
+ "(['\"] *)?" // #1 optional quote w/ space
+ "(\\$\\(\\( *)?" // #2 optional math w/ space
+ "([[:digit:]]+)" // #3
+ "( *\\)\\))?" // #4 optional math w/ space
+ "( *['\"])?" // #5 optional quote w/ space
+ " +" //
+ "skip=" // dd skip arg
+ "(['\"] *)?" // #6 optional quote w/ space
+ "(\\$\\(\\( *)?" // #7 optional math w/ space
+ "([[:digit:]]+)" // #8
+ "( *\\)\\))?" // #9 optional math w/ space
+ "( *['\"])?" // #10 optional quote w/ space
+ " +" //
+ "count=" // dd count arg
+ "(['\"] *)?" // #11 optional quote w/ space
+ "(\\$\\(\\( *)?" // #12 optional math w/ space
+ "([[:digit:]]+)", // #13
+ REG_EXTENDED));
+ ASSERT_EQ(13, rx.re_nsub);
+ regmatch_t *m = gc(calloc(rx.re_nsub + 1, sizeof(regmatch_t)));
+ const char *s = "dd bs=123 skip=$(( 456)) count='7'";
+ EXPECT_EQ(REG_OK, regexec(&rx, s, rx.re_nsub + 1, m, 0));
+ EXPECT_STREQ("123", gc(strndup(s + m[3].rm_so, m[3].rm_eo - m[3].rm_so)));
+ EXPECT_STREQ("456", gc(strndup(s + m[8].rm_so, m[8].rm_eo - m[8].rm_so)));
+ EXPECT_STREQ("7", gc(strndup(s + m[13].rm_so, m[13].rm_eo - m[13].rm_so)));
+ regfree(&rx);
+}
+
BENCH(regex, bench) {
regex_t rx;
regmatch_t *m;
diff --git a/tool/build/apelink.c b/tool/build/apelink.c
new file mode 100644
index 000000000..3c6957187
--- /dev/null
+++ b/tool/build/apelink.c
@@ -0,0 +1,2164 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2023 Justine Alexandra Roberts Tunney │
+│ │
+│ Permission to use, copy, modify, and/or distribute this software for │
+│ any purpose with or without fee is hereby granted, provided that the │
+│ above copyright notice and this permission notice appear in all copies. │
+│ │
+│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
+│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
+│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
+│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
+│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
+│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
+│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
+│ PERFORMANCE OF THIS SOFTWARE. │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/assert.h"
+#include "libc/calls/calls.h"
+#include "libc/dce.h"
+#include "libc/dos.internal.h"
+#include "libc/elf/def.h"
+#include "libc/elf/elf.h"
+#include "libc/elf/scalar.h"
+#include "libc/elf/struct/ehdr.h"
+#include "libc/elf/struct/phdr.h"
+#include "libc/fmt/conv.h"
+#include "libc/fmt/itoa.h"
+#include "libc/intrin/bits.h"
+#include "libc/intrin/kprintf.h"
+#include "libc/limits.h"
+#include "libc/macho.internal.h"
+#include "libc/macros.internal.h"
+#include "libc/mem/mem.h"
+#include "libc/nt/pedef.internal.h"
+#include "libc/nt/struct/imageimportbyname.internal.h"
+#include "libc/nt/struct/imageimportdescriptor.internal.h"
+#include "libc/nt/struct/imagentheaders.internal.h"
+#include "libc/nt/struct/imagesectionheader.internal.h"
+#include "libc/runtime/runtime.h"
+#include "libc/runtime/symbols.internal.h"
+#include "libc/stdalign.internal.h"
+#include "libc/stdckdint.h"
+#include "libc/stdio/stdio.h"
+#include "libc/str/blake2.h"
+#include "libc/str/str.h"
+#include "libc/sysv/consts/map.h"
+#include "libc/sysv/consts/o.h"
+#include "libc/sysv/consts/prot.h"
+#include "libc/zip.internal.h"
+#include "third_party/getopt/getopt.internal.h"
+#include "third_party/zlib/zlib.h"
+#include "tool/build/lib/lib.h"
+
+#define VERSION \
+ "apelink v0.1\n" \
+ "copyright 2023 justine tunney\n" \
+ "https://github.com/jart/cosmopolitan\n"
+
+#define MANUAL \
+ " -o OUTPUT INPUT...\n" \
+ "\n" \
+ "DESCRIPTION\n" \
+ "\n" \
+ " actually portable executable linker\n" \
+ "\n" \
+ " this program may be used to turn elf executables into\n" \
+ " ape executables. it's useful for creating fat binaries\n" \
+ " that run on multiple architectures.\n" \
+ "\n" \
+ "FLAGS\n" \
+ "\n" \
+ " -h show usage\n" \
+ "\n" \
+ " -v show version\n" \
+ "\n" \
+ " -o OUTPUT set output path\n" \
+ "\n" \
+ " -l PATH bundle ape loader executable [repeatable]\n" \
+ " if no ape loaders are specified then your\n" \
+ " executable will self-modify its header on\n" \
+ " the first run, to use the platform format\n" \
+ "\n" \
+ " -M PATH bundle ape loader source code file for m1\n" \
+ " processors running the xnu kernel so that\n" \
+ " it can be compiled on the fly by xcode\n" \
+ "\n" \
+ " -s BITS set OS support vector\n" \
+ "\n" \
+ " the default value is -1 which sets the bits\n" \
+ " for all supported operating systems to true\n" \
+ " of which the following are defined:\n" \
+ "\n" \
+ " - 1: linux\n" \
+ " - 2: metal\n" \
+ " - 4: windows\n" \
+ " - 8: xnu\n" \
+ " - 16: openbsd\n" \
+ " - 32: freebsd\n" \
+ " - 64: netbsd\n" \
+ "\n" \
+ " for example, `-s 0b1110001` may be used to\n" \
+ " produce ELF binaries that only support the\n" \
+ " truly open unix systems. in this case when\n" \
+ " a single input executable is supplied, the\n" \
+ " output file will end up being portable elf\n" \
+ "\n" \
+ " when the default of -1 is supplied, certain\n" \
+ " operating systems can have support detected\n" \
+ " by examining your symbols and sections. for\n" \
+ " example, xnu is detected by looking for the\n" \
+ " _apple() entrypoint which is defined on elf\n" \
+ " platforms as being e_entry. if only the XNU\n" \
+ " platform is supported then e_entry is used.\n" \
+ "\n" \
+ " in addition to accepting numbers, you could\n" \
+ " also pass strings in a variety of intuitive\n" \
+ " supported representations. for example, bsd\n" \
+ " will enable freebsd+netbsd+openbsd and that\n" \
+ " string too is a legal input. the -s flag is\n" \
+ " also repeatable, e.g. `-s nt -s xnu` to use\n" \
+ " the union of the two.\n" \
+ "\n" \
+ " since the support vector controls the file\n" \
+ " format that's outputted too you can supply\n" \
+ " the keywords `pe`, `elf`, & 'macho' to use\n" \
+ " a support vector targetting whichever OSes\n" \
+ " use it as a native format.\n" \
+ "\n" \
+ " -g generate a debugging friendly ape binary\n" \
+ "\n" \
+ " -G don't $PATH lookup systemwide ape loader\n" \
+ "\n" \
+ " -S CODE add custom code to start of shell script\n" \
+ " for example you can use `-S 'set -ex' to\n" \
+ " troubleshoot any issues with your script\n" \
+ "\n" \
+ " -B force bypassing of any binfmt_misc loader\n" \
+ " by using alternative 'APEDBG=' file magic\n" \
+ "\n" \
+ "ARGUMENTS\n" \
+ "\n" \
+ " OUTPUT is your ape executable\n" \
+ " INPUT specifies multiple ELF builds of the same\n" \
+ " program, for different architectures that\n" \
+ " shall be merged into a single output file\n" \
+ "\n"
+
+#define ALIGN(p, a) (char *)ROUNDUP((uintptr_t)(p), (a))
+
+enum Strategy {
+ kElf,
+ kApe,
+ kMacho,
+ kPe,
+};
+
+struct MachoLoadThread64 {
+ struct MachoLoadThreadCommand thread;
+ uint64_t regs[21];
+};
+
+struct OffsetRelocs {
+ int n;
+ uint64_t *p[64];
+};
+
+struct Input {
+ union {
+ char *map;
+ Elf64_Ehdr *elf;
+ unsigned char *umap;
+ };
+ size_t size;
+ const char *path;
+ Elf64_Off machoff;
+ Elf64_Off minload;
+ Elf64_Off maxload;
+ char *printf_phoff;
+ char *printf_phnum;
+ char *ddarg_macho_skip;
+ char *ddarg_macho_count;
+ int pe_offset;
+ int pe_e_lfanew;
+ int pe_SizeOfHeaders;
+ int size_of_pe_headers;
+ bool we_are_generating_pe;
+ bool we_must_skew_pe_vaspace;
+ struct NtImageNtHeaders *pe;
+ struct OffsetRelocs offsetrelocs;
+ struct MachoLoadSegment *first_macho_load;
+};
+
+struct Inputs {
+ int n;
+ struct Input p[16];
+};
+
+struct Loader {
+ int os;
+ int machine;
+ bool used;
+ void *map;
+ size_t size;
+ const char *path;
+ int macho_offset;
+ int macho_length;
+ char *ddarg_skip1;
+ char *ddarg_size1;
+ char *ddarg_skip2;
+ char *ddarg_size2;
+};
+
+struct Loaders {
+ int n;
+ struct Loader p[16];
+};
+
+struct Asset {
+ unsigned char *cfile;
+ unsigned char *lfile;
+};
+
+struct Assets {
+ int n;
+ struct Asset *p;
+ size_t total_centraldir_bytes;
+ size_t total_local_file_bytes;
+};
+
+static int outfd;
+static int hashes;
+static const char *prog;
+static int support_vector;
+static int macholoadcount;
+static const char *outpath;
+static struct Assets assets;
+static struct Inputs inputs;
+static enum Strategy strategy;
+static struct Loaders loaders;
+static const char *custom_sh_code;
+static bool force_bypass_binfmt_misc;
+static bool generate_debuggable_binary;
+static bool dont_path_lookup_ape_loader;
+_Alignas(4096) static char prologue[1048576];
+static uint8_t hashpool[BLAKE2B256_DIGEST_LENGTH];
+static const char *macos_silicon_loader_source_path;
+static char *macos_silicon_loader_source_ddarg_skip;
+static char *macos_silicon_loader_source_ddarg_size;
+
+static Elf64_Off noteoff;
+static Elf64_Xword notesize;
+
+static char *r_off32_e_lfanew;
+
+static wontreturn void Die(const char *thing, const char *reason) {
+ tinyprint(2, thing, ": ", reason, "\n", NULL);
+ exit(1);
+}
+
+static wontreturn void DieSys(const char *thing) {
+ perror(thing);
+ exit(1);
+}
+
+static wontreturn void ShowUsage(int rc, int fd) {
+ tinyprint(fd, "USAGE\n\n ", prog, MANUAL, NULL);
+ exit(rc);
+}
+
+static wontreturn void DieOom(void) {
+ Die("apelink", "out of memory");
+}
+
+static void *Malloc(size_t n) {
+ void *p;
+ if (!(p = malloc(n))) DieOom();
+ return p;
+}
+
+static void *Realloc(void *p, size_t n) {
+ if (!(p = realloc(p, n))) DieOom();
+ return p;
+}
+
+// By convention, Cosmopolitan's GetSymbolTable() function will inspect
+// its own executable image to see if it's also a zip archive, in which
+// case it tries to load the symbol table from an architecture-specific
+// filename below, and falls back to the ".symtab" when it isn't found.
+// the file format for these symbol files is unique to cosmopiltan libc
+static const char *ConvertElfMachineToSymtabName(Elf64_Ehdr *e) {
+ switch (e->e_machine) {
+ case EM_NEXGEN32E:
+ return ".symtab.amd64";
+ case EM_AARCH64:
+ return ".symtab.arm64";
+ case EM_PPC64:
+ return ".symtab.ppc64";
+ case EM_S390:
+ return ".symtab.s390x";
+ case EM_RISCV:
+ return ".symtab.riscv";
+ default:
+ Die(prog, "unsupported architecture");
+ }
+}
+
+static const char *DescribePhdrType(Elf64_Word p_type) {
+ static char buf[12];
+ switch (p_type) {
+ case PT_LOAD:
+ return "LOAD";
+ case PT_TLS:
+ return "TLS";
+ case PT_PHDR:
+ return "PHDR";
+ case PT_NOTE:
+ return "NOTE";
+ case PT_INTERP:
+ return "INTERP";
+ case PT_DYNAMIC:
+ return "DYNAMIC";
+ case PT_GNU_STACK:
+ return "GNU_STACK";
+ default:
+ FormatInt32(buf, p_type);
+ return buf;
+ }
+}
+
+static void BlendHashes(uint8_t out[static BLAKE2B256_DIGEST_LENGTH],
+ uint8_t inp[static BLAKE2B256_DIGEST_LENGTH]) {
+ int i;
+ for (i = 0; i < BLAKE2B256_DIGEST_LENGTH; ++i) {
+ out[i] ^= inp[i];
+ }
+}
+
+static void HashInput(const void *data, size_t size) {
+ uint8_t digest[BLAKE2B256_DIGEST_LENGTH];
+ uint32_t hash = crc32_z(hashes, data, size); // 30 GB/s
+ BLAKE2B256(&hash, sizeof(hash), digest); // .6 GB/s
+ BlendHashes(hashpool, digest);
+ ++hashes;
+}
+
+static void HashInputString(const char *str) {
+ HashInput(str, strlen(str));
+}
+
+static void Pwrite(const void *data, size_t size, uint64_t offset) {
+ ssize_t rc;
+ const char *p, *e;
+ for (p = data, e = p + size; p < e; p += (size_t)rc, offset += (size_t)rc) {
+ if ((rc = pwrite(outfd, p, e - p, offset)) == -1) {
+ DieSys(outpath);
+ }
+ }
+}
+
+static void LogElfPhdrs(FILE *f, Elf64_Phdr *p, size_t n) {
+ size_t i;
+ fprintf(f, "Type Offset VirtAddr PhysAddr "
+ "FileSiz MemSiz Flg Align\n");
+ for (i = 0; i < n; ++i) {
+ fprintf(f,
+ "%-14s 0x%06lx 0x%016lx 0x%016lx 0x%06lx 0x%06lx %c%c%c 0x%04lx\n",
+ DescribePhdrType(p[i].p_type), p[i].p_offset, p[i].p_vaddr,
+ p[i].p_paddr, p[i].p_filesz, p[i].p_memsz,
+ p[i].p_flags & PF_R ? 'R' : ' ', p[i].p_flags & PF_W ? 'W' : ' ',
+ p[i].p_flags & PF_X ? 'E' : ' ', p[i].p_align);
+ }
+}
+
+static void LogPeSections(FILE *f, struct NtImageSectionHeader *p, size_t n) {
+ size_t i;
+ fprintf(f, "Name Offset RelativeVirtAddr PhysAddr "
+ "FileSiz MemSiz Flg\n");
+ for (i = 0; i < n; ++i) {
+ fprintf(f, "%-14.8s 0x%06lx 0x%016lx 0x%016lx 0x%06lx 0x%06lx %c%c%c\n",
+ p[i].Name, p[i].PointerToRawData, p[i].VirtualAddress,
+ p[i].VirtualAddress, p[i].SizeOfRawData, p[i].Misc.VirtualSize,
+ p[i].Characteristics & kNtPeSectionMemRead ? 'R' : ' ',
+ p[i].Characteristics & kNtPeSectionMemWrite ? 'W' : ' ',
+ p[i].Characteristics & kNtPeSectionMemExecute ? 'E' : ' ');
+ }
+}
+
+static void ValidateElfImage(Elf64_Ehdr *e, Elf64_Off esize, //
+ const char *epath, bool isloader) {
+
+ // validate elf header
+ if (e->e_type != ET_EXEC && e->e_type != ET_DYN)
+ Die(epath, "elf binary isn't an executable");
+ if (!e->e_phnum)
+ Die(epath, "elf executable needs at least one program header");
+ if (e->e_phnum > 65534)
+ Die(epath, "elf with more than 65534 phdrs not supported");
+ if (e->e_phentsize != sizeof(Elf64_Phdr))
+ Die(epath, "elf e_phentsize isn't sizeof(Elf64_Phdr)");
+ if (e->e_phoff > esize)
+ Die(epath, "elf program header offset points past image eof");
+ if (e->e_phoff & 7)
+ Die(epath, "elf e_phoff must be aligned on an 8-byte boundary");
+ if (e->e_phoff + e->e_phoff + e->e_phnum * sizeof(Elf64_Phdr) > esize)
+ Die(epath, "elf program header array overlaps image eof");
+
+ // determine microprocessor page size requirement
+ //
+ // even though operating systems (windows) and c libraries (cosmo)
+ // sometimes impose a larger page size requirement than the actual
+ // microprocessor itself, the cpu page size is still the only page
+ // size that actually matters when it comes to executable loading.
+ unsigned long pagesz;
+ if (e->e_machine == EM_AARCH64) {
+ pagesz = 16384; // apple m1 (xnu, linux)
+ } else { //
+ pagesz = 4096; // x86-64, raspberry pi
+ }
+
+ // remove empty segment loads
+ // empty loads should be inconsequential; linux ignores them
+ // however they tend to trip up many systems such as openbsd
+ int i, j;
+ Elf64_Phdr *p = (Elf64_Phdr *)((char *)e + e->e_phoff);
+
+ for (i = 0; i < e->e_phnum;) {
+ if (p[i].p_type == PT_LOAD && !p[i].p_memsz) {
+ if (i + 1 < e->e_phnum) {
+ memmove(p + i, p + i + 1, (e->e_phnum - (i + 1)) * sizeof(*p));
+ }
+ --e->e_phnum;
+ } else {
+ ++i;
+ }
+ }
+
+ // oracle says loadable segment entries in the program header table
+ // appear in ascending order, sorted on the p_vaddr member.
+ int found_load = 0;
+ Elf64_Addr last_vaddr = 0;
+ for (i = 0; i < e->e_phnum; ++i) {
+ if (p[i].p_type != PT_LOAD) continue;
+ if (found_load && p[i].p_vaddr <= last_vaddr) {
+ Die(epath, "ELF PT_LOAD segments must be ordered by p_vaddr");
+ }
+ last_vaddr = p[i].p_vaddr;
+ found_load = 1;
+ }
+ if (!found_load) {
+ Die(epath, "ELF must have at least one PT_LOAD segment");
+ }
+
+ // merge adjacent loads that are contiguous with equal protection
+ for (i = 0; i + 1 < e->e_phnum;) {
+ if (p[i].p_type == PT_LOAD && p[i + 1].p_type == PT_LOAD &&
+ ((p[i].p_flags & (PF_R | PF_W | PF_X)) ==
+ (p[i + 1].p_flags & (PF_R | PF_W | PF_X))) &&
+ ((p[i].p_offset + p[i].p_filesz + (pagesz - 1)) & -pagesz) -
+ (p[i + 1].p_offset & -pagesz) <=
+ pagesz &&
+ ((p[i].p_vaddr + p[i].p_memsz + (pagesz - 1)) & -pagesz) -
+ (p[i + 1].p_vaddr & -pagesz) <=
+ pagesz) {
+ p[i].p_memsz = (p[i + 1].p_vaddr + p[i + 1].p_memsz) - p[i].p_vaddr;
+ p[i].p_filesz = (p[i + 1].p_offset + p[i + 1].p_filesz) - p[i].p_offset;
+ if (i + 2 < e->e_phnum) {
+ memmove(p + i + 1, p + i + 2, (e->e_phnum - (i + 2)) * sizeof(*p));
+ }
+ --e->e_phnum;
+ } else {
+ ++i;
+ }
+ }
+
+ // validate program headers
+ bool found_entry = false;
+ for (i = 0; i < e->e_phnum; ++i) {
+ if (!isloader && p[i].p_type == PT_INTERP) {
+ Die(epath, "ELF has PT_INTERP which isn't supported");
+ }
+ if (!isloader && p[i].p_type == PT_DYNAMIC) {
+ Die(epath, "ELF has PT_DYNAMIC which isn't supported");
+ }
+ if (p[i].p_type != PT_LOAD) {
+ continue;
+ }
+ if (!p[i].p_memsz) {
+ Die(epath, "ELF PT_LOAD p_memsz was zero");
+ }
+ if (p[i].p_offset > esize) {
+ Die(epath, "ELF PT_LOAD p_offset points past EOF");
+ }
+ if (p[i].p_filesz > p[i].p_memsz) {
+ Die(epath, "ELF PT_LOAD p_filesz exceeds p_memsz");
+ }
+ if (p[i].p_align > 1 && (p[i].p_align & (p[i].p_align - 1))) {
+ Die(epath, "ELF PT_LOAD p_align must be two power");
+ }
+ if (p[i].p_vaddr + p[i].p_memsz < p[i].p_vaddr ||
+ p[i].p_vaddr + p[i].p_memsz + (pagesz - 1) < p[i].p_vaddr) {
+ Die(epath, "ELF PT_LOAD p_vaddr + p_memsz overflow");
+ }
+ if (p[i].p_offset + p[i].p_filesz < p[i].p_offset ||
+ p[i].p_offset + p[i].p_filesz + (pagesz - 1) < p[i].p_offset) {
+ Die(epath, "ELF PT_LOAD p_offset + p_filesz overflow");
+ }
+ if (p[i].p_align > 1 && ((p[i].p_vaddr & (p[i].p_align - 1)) !=
+ (p[i].p_offset & (p[i].p_align - 1)))) {
+ Die(epath, "ELF p_vaddr incongruent w/ p_offset modulo p_align");
+ }
+ if ((p[i].p_vaddr & (pagesz - 1)) != (p[i].p_offset & (pagesz - 1))) {
+ Die(epath, "ELF p_vaddr incongruent w/ p_offset modulo AT_PAGESZ");
+ }
+ if (p[i].p_offset + p[i].p_filesz > esize) {
+ Die(epath, "ELF PT_LOAD p_offset and p_filesz overlaps image EOF");
+ }
+ Elf64_Off a = p[i].p_vaddr & -pagesz;
+ Elf64_Off b = (p[i].p_vaddr + p[i].p_memsz + (pagesz - 1)) & -pagesz;
+ for (j = i + 1; j < e->e_phnum; ++j) {
+ if (p[j].p_type != PT_LOAD) continue;
+ Elf64_Off c = p[j].p_vaddr & -pagesz;
+ Elf64_Off d = (p[j].p_vaddr + p[j].p_memsz + (pagesz - 1)) & -pagesz;
+ if (MAX(a, c) < MIN(b, d)) {
+ Die(epath,
+ "ELF PT_LOAD phdrs %d and %d overlap each others virtual memory");
+ }
+ }
+ if (e->e_machine == EM_AARCH64 &&
+ p[i].p_vaddr + p[i].p_memsz > 0x0001000000000000) {
+ Die(epath, "this is an ARM ELF program but it loads to virtual"
+ " memory addresses outside the legal range [0,2^48)");
+ }
+ if (e->e_machine == EM_NEXGEN32E &&
+ !(-140737488355328 <= (Elf64_Sxword)p[i].p_vaddr &&
+ (Elf64_Sxword)(p[i].p_vaddr + p[i].p_memsz) <= 140737488355328)) {
+ Die(epath, "this is an x86-64 ELF program, but it loads to virtual"
+ " memory addresses outside the legal range [-2^47,2^47)");
+ }
+ if (e->e_type == ET_EXEC && //
+ (support_vector & _HOSTMETAL) && //
+ (Elf64_Sxword)p[i].p_vaddr < 0x200000) {
+ Die(epath, "ELF program header loads to address less than 2mb even"
+ " though you have userspace OSes in your support vector");
+ }
+ if (!isloader && //
+ e->e_type == ET_EXEC && //
+ !(p[i].p_vaddr >> 32) && //
+ e->e_machine == EM_AARCH64 && //
+ (support_vector & _HOSTXNU)) {
+ Die(epath, "ELF program header loads to address less than 4gb even"
+ " though you have XNU on AARCH64 in your support vector"
+ " which forbids 32-bit pointers without any compromises");
+ }
+ if ((p[i].p_flags & (PF_W | PF_X)) == (PF_W | PF_X)) {
+ if (support_vector & _HOSTOPENBSD) {
+ Die(epath, "found RWX ELF program segment, but you have OpenBSD in"
+ " your support vector, which totally forbids RWX memory");
+ }
+ if (e->e_machine == EM_AARCH64 && (support_vector & _HOSTXNU)) {
+ Die(epath, "found RWX ELF program segment on an AARCH64 executable"
+ " with XNU support, which completely forbids RWX memory");
+ }
+ }
+ if ((p[i].p_flags & PF_X) && //
+ p[i].p_vaddr <= e->e_entry &&
+ e->e_entry < p[i].p_vaddr + p[i].p_memsz) {
+ found_entry = 1;
+ }
+ }
+ if (!found_entry) {
+ Die(epath, "ELF entrypoint not found in PT_LOAD with PF_X");
+ }
+}
+
+static bool IsSymbolWorthyOfSymtab(Elf64_Sym *sym) {
+ return sym->st_size > 0 && (ELF64_ST_TYPE(sym->st_info) == STT_FUNC ||
+ ELF64_ST_TYPE(sym->st_info) == STT_OBJECT);
+}
+
+static void AppendZipAsset(unsigned char *lfile, unsigned char *cfile) {
+ if (assets.n == 65534) Die(outpath, "fat binary has >65534 zip assets");
+ assets.p = Realloc(assets.p, (assets.n + 1) * sizeof(*assets.p));
+ assets.p[assets.n].cfile = cfile;
+ assets.p[assets.n].lfile = lfile;
+ assets.total_local_file_bytes += ZIP_LFILE_SIZE(lfile);
+ assets.total_centraldir_bytes += ZIP_CFILE_HDRSIZE(cfile);
+ ++assets.n;
+}
+
+static void *Compress(const void *data, size_t size, size_t *out_size, int wb) {
+ void *res;
+ z_stream zs;
+ zs.zfree = 0;
+ zs.zalloc = 0;
+ zs.opaque = 0;
+ unassert(deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, wb, DEF_MEM_LEVEL,
+ Z_DEFAULT_STRATEGY) == Z_OK);
+ zs.next_in = data;
+ zs.avail_in = size;
+ zs.avail_out = compressBound(size);
+ zs.next_out = res = Malloc(zs.avail_out);
+ unassert(deflate(&zs, Z_FINISH) == Z_STREAM_END);
+ unassert(deflateEnd(&zs) == Z_OK);
+ *out_size = zs.total_out;
+ return res;
+}
+
+static void *Deflate(const void *data, size_t size, size_t *out_size) {
+ return Compress(data, size, out_size, -MAX_WBITS);
+}
+
+static void *Gzip(const void *data, size_t size, size_t *out_size) {
+ return Compress(data, size, out_size, MAX_WBITS + 16);
+}
+
+static void LoadSymbols(Elf64_Ehdr *e, Elf64_Off size, const char *path) {
+ const char *name = ConvertElfMachineToSymtabName(e);
+ size_t name_size = strlen(name);
+ struct SymbolTable *st = OpenSymbolTable(path);
+ if (!st) Die(path, "could not load elf symbol table");
+ size_t data_size;
+ void *data = Deflate(st, st->size, &data_size);
+ uint32_t crc = crc32_z(0, st, st->size);
+ size_t cfile_size = kZipCfileHdrMinSize + name_size;
+ unsigned char *cfile = Malloc(cfile_size);
+ bzero(cfile, cfile_size);
+ WRITE32LE(cfile, kZipCfileHdrMagic);
+ cfile[4] = kZipCosmopolitanVersion;
+ cfile[5] = kZipOsUnix;
+ cfile[6] = kZipEra1993;
+ WRITE16LE(cfile + kZipCfileOffsetCompressionmethod, kZipCompressionDeflate);
+ WRITE16LE(cfile + kZipCfileOffsetLastmodifieddate, DOS_DATE(2023, 7, 29));
+ WRITE16LE(cfile + kZipCfileOffsetLastmodifiedtime, DOS_TIME(0, 0, 0));
+ WRITE32LE(cfile + kZipCfileOffsetCompressedsize, data_size);
+ WRITE32LE(cfile + kZipCfileOffsetUncompressedsize, st->size);
+ WRITE32LE(cfile + kZipCfileOffsetCrc32, crc);
+ WRITE16LE(cfile + kZipCfileOffsetNamesize, name_size);
+ memcpy(cfile + kZipCfileHdrMinSize, name, name_size);
+ unassert(ZIP_CFILE_HDRSIZE(cfile) == cfile_size);
+ size_t lfile_size = kZipLfileHdrMinSize + name_size + data_size;
+ unsigned char *lfile = Malloc(lfile_size);
+ bzero(lfile, lfile_size);
+ WRITE32LE(lfile, kZipLfileHdrMagic);
+ cfile[4] = kZipEra1993;
+ cfile[5] = kZipOsDos;
+ WRITE16LE(lfile + kZipLfileOffsetCompressionmethod, kZipCompressionDeflate);
+ WRITE16LE(lfile + kZipLfileOffsetLastmodifieddate, DOS_DATE(2023, 7, 29));
+ WRITE16LE(lfile + kZipLfileOffsetLastmodifiedtime, DOS_TIME(0, 0, 0));
+ WRITE32LE(lfile + kZipLfileOffsetCompressedsize, data_size);
+ WRITE32LE(lfile + kZipLfileOffsetUncompressedsize, st->size);
+ WRITE32LE(lfile + kZipLfileOffsetCrc32, crc);
+ WRITE16LE(lfile + kZipLfileOffsetNamesize, name_size);
+ memcpy(lfile + kZipLfileHdrMinSize, name, name_size);
+ memcpy(lfile + kZipLfileHdrMinSize + name_size, data, data_size);
+ unassert(ZIP_LFILE_SIZE(lfile) == lfile_size);
+ free(data);
+ AppendZipAsset(lfile, cfile);
+}
+
+// resolves portable executable relative virtual address
+//
+// this is a trivial process when an executable has been loaded properly
+// i.e. a separate mmap() call was made for each individual section; but
+// we've only mapped the executable file itself into memory; thus, we'll
+// need to remap a virtual address into a file offset to get the pointer
+//
+// returns pointer to image data, or null on error
+static void *GetPeRva(char *map, struct NtImageNtHeaders *pe, uint32_t rva) {
+ struct NtImageSectionHeader *sections =
+ (struct NtImageSectionHeader *)((char *)&pe->OptionalHeader +
+ pe->FileHeader.SizeOfOptionalHeader);
+ for (int i = 0; i < pe->FileHeader.NumberOfSections; ++i) {
+ if (sections[i].VirtualAddress <= rva &&
+ rva < sections[i].VirtualAddress + sections[i].Misc.VirtualSize) {
+ return map + sections[i].PointerToRawData +
+ (rva - sections[i].VirtualAddress);
+ }
+ }
+ return 0;
+}
+
+static int ValidatePeImage(char *img, size_t imgsize, //
+ struct NtImageNtHeaders *pe, //
+ const char *path) {
+
+ int pagesz = 4096;
+
+ // sanity check pe header
+ if (pe->Signature != ('P' | 'E' << 8))
+ Die(path, "PE Signature must be 0x00004550");
+ if (!(pe->FileHeader.Characteristics & kNtPeFileExecutableImage))
+ Die(path, "PE Characteristics must have executable bit set");
+ if (pe->FileHeader.Characteristics & kNtPeFileDll)
+ Die(path, "PE Characteristics can't have DLL bit set");
+ if (pe->FileHeader.NumberOfSections < 1)
+ Die(path, "PE NumberOfSections >= 1 must be the case");
+ if (pe->OptionalHeader.Magic != kNtPe64bit)
+ Die(path, "PE OptionalHeader Magic must be 0x020b");
+ if (pe->OptionalHeader.FileAlignment < 512)
+ Die(path, "PE FileAlignment must be at least 512");
+ if (pe->OptionalHeader.FileAlignment > 65536)
+ Die(path, "PE FileAlignment can't exceed 65536");
+ if (pe->OptionalHeader.FileAlignment & (pe->OptionalHeader.FileAlignment - 1))
+ Die(path, "PE FileAlignment must be a two power");
+ if (pe->OptionalHeader.SectionAlignment &
+ (pe->OptionalHeader.SectionAlignment - 1))
+ Die(path, "PE SectionAlignment must be a two power");
+ if (pe->OptionalHeader.SectionAlignment < pe->OptionalHeader.FileAlignment)
+ Die(path, "PE SectionAlignment >= FileAlignment must be the case");
+ if (pe->OptionalHeader.SectionAlignment < pagesz &&
+ pe->OptionalHeader.SectionAlignment != pe->OptionalHeader.FileAlignment)
+ Die(path, "PE SectionAlignment must equal FileAlignment if it's less than "
+ "the microprocessor architecture's page size");
+ if (pe->OptionalHeader.ImageBase & 65535)
+ Die(path, "PE ImageBase must be multiple of 65536");
+ if (pe->OptionalHeader.ImageBase > INT_MAX &&
+ !(pe->FileHeader.Characteristics & kNtImageFileLargeAddressAware))
+ Die(path, "PE FileHeader.Characteristics needs "
+ "IMAGE_FILE_LARGE_ADDRESS_AWARE if ImageBase > INT_MAX");
+ if (!(support_vector & _HOSTMETAL) &&
+ pe->OptionalHeader.Subsystem == kNtImageSubsystemEfiApplication)
+ Die(path, "PE Subsystem is EFI but Metal wasn't in support_vector");
+ if (!(support_vector & _HOSTWINDOWS) &&
+ (pe->OptionalHeader.Subsystem == kNtImageSubsystemWindowsCui ||
+ pe->OptionalHeader.Subsystem == kNtImageSubsystemWindowsGui))
+ Die(path, "PE Subsystem is Windows but Windows wasn't in support_vector");
+
+ // fixup pe header
+ int len;
+ if (ckd_mul(&len, pe->OptionalHeader.NumberOfRvaAndSizes, 8) ||
+ ckd_add(&len, len, sizeof(struct NtImageOptionalHeader)) ||
+ pe->FileHeader.SizeOfOptionalHeader < len)
+ Die(path, "PE SizeOfOptionalHeader too small");
+ if (len > imgsize || (char *)&pe->OptionalHeader + len > img + imgsize)
+ Die(path, "PE OptionalHeader overflows image");
+
+ // perform even more pe validation
+ if (pe->OptionalHeader.SizeOfImage &
+ (pe->OptionalHeader.SectionAlignment - 1))
+ Die(path, "PE SizeOfImage must be multiple of SectionAlignment");
+ if (pe->OptionalHeader.SizeOfHeaders & (pe->OptionalHeader.FileAlignment - 1))
+ Die(path, "PE SizeOfHeaders must be multiple of FileAlignment");
+ if (pe->OptionalHeader.SizeOfHeaders > pe->OptionalHeader.AddressOfEntryPoint)
+ Die(path, "PE SizeOfHeaders <= AddressOfEntryPoint must be the case");
+ if (pe->OptionalHeader.SizeOfHeaders >= pe->OptionalHeader.SizeOfImage)
+ Die(path, "PE SizeOfHeaders < SizeOfImage must be the case");
+ if (pe->OptionalHeader.SizeOfStackCommit >> 32)
+ Die(path, "PE SizeOfStackReserve can't exceed 4GB");
+ if (pe->OptionalHeader.SizeOfStackReserve >> 32)
+ Die(path, "PE SizeOfStackReserve can't exceed 4GB");
+ if (pe->OptionalHeader.SizeOfHeapCommit >> 32)
+ Die(path, "PE SizeOfHeapReserve can't exceed 4GB");
+ if (pe->OptionalHeader.SizeOfHeapReserve >> 32)
+ Die(path, "PE SizeOfHeapReserve can't exceed 4GB");
+
+ // check pe section headers
+ struct NtImageSectionHeader *sections =
+ (struct NtImageSectionHeader *)((char *)&pe->OptionalHeader +
+ pe->FileHeader.SizeOfOptionalHeader);
+ for (int i = 0; i < pe->FileHeader.NumberOfSections; ++i) {
+ if (sections[i].SizeOfRawData & (pe->OptionalHeader.FileAlignment - 1))
+ Die(path, "PE SizeOfRawData should be multiple of FileAlignment");
+ if (sections[i].PointerToRawData & (pe->OptionalHeader.FileAlignment - 1))
+ Die(path, "PE PointerToRawData must be multiple of FileAlignment");
+ if (img + sections[i].PointerToRawData >= img + imgsize)
+ Die(path, "PE PointerToRawData points outside image");
+ if (img + sections[i].PointerToRawData + sections[i].SizeOfRawData >
+ img + imgsize)
+ Die(path, "PE SizeOfRawData overlaps end of image");
+ if (!sections[i].VirtualAddress)
+ Die(path, "PE VirtualAddress shouldn't be zero");
+ if (sections[i].VirtualAddress & (pe->OptionalHeader.SectionAlignment - 1))
+ Die(path, "PE VirtualAddress must be multiple of SectionAlignment");
+ if ((sections[i].Characteristics &
+ (kNtPeSectionCntCode | kNtPeSectionCntInitializedData |
+ kNtPeSectionCntUninitializedData)) ==
+ kNtPeSectionCntUninitializedData) {
+ if (sections[i].SizeOfRawData)
+ Die(path, "PE SizeOfRawData should be zero for pure BSS section");
+ if (sections[i].SizeOfRawData)
+ Die(path, "PE PointerToRawData should be zero for pure BSS section");
+ }
+ if (!i) {
+ if (sections[i].VirtualAddress !=
+ ((pe->OptionalHeader.SizeOfHeaders +
+ (pe->OptionalHeader.SectionAlignment - 1)) &
+ -pe->OptionalHeader.SectionAlignment))
+ Die(path, "PE VirtualAddress of first section must be SizeOfHeaders "
+ "rounded up to SectionAlignment");
+ } else {
+ if (sections[i].VirtualAddress !=
+ sections[i - 1].VirtualAddress +
+ ((sections[i - 1].Misc.VirtualSize +
+ (pe->OptionalHeader.SectionAlignment - 1)) &
+ -pe->OptionalHeader.SectionAlignment))
+ Die(path, "PE sections must be in ascending order and the virtual "
+ "memory they define must be adjacent after VirtualSize is "
+ "rounded up to the SectionAlignment");
+ }
+ }
+
+ int size_of_pe_headers =
+ (char *)(sections + pe->FileHeader.NumberOfSections) -
+ (char *)&pe->Signature;
+ return size_of_pe_headers;
+}
+
+void FixupPeImage(char *map, size_t size, //
+ struct NtImageNtHeaders *pe, //
+ const char *path, //
+ Elf64_Sxword rva_skew, //
+ Elf64_Sxword off_skew) {
+ assert(!(rva_skew & 65535));
+
+ Elf64_Sxword skew = rva_skew;
+
+ // fixup pe header
+ if (ckd_sub(&pe->OptionalHeader.ImageBase, //
+ pe->OptionalHeader.ImageBase, rva_skew))
+ Die(path, "skewing PE ImageBase VA overflowed");
+ if (ckd_add(&pe->OptionalHeader.AddressOfEntryPoint,
+ pe->OptionalHeader.AddressOfEntryPoint, rva_skew))
+ Die(path, "skewing PE AddressOfEntryPoint RVA overflowed");
+ if (ckd_add(&pe->OptionalHeader.SizeOfImage, //
+ pe->OptionalHeader.SizeOfImage, rva_skew))
+ Die(path, "skewing PE SizeOfImage VSZ overflowed");
+ if (ckd_add(&pe->OptionalHeader.SizeOfHeaders, //
+ pe->OptionalHeader.SizeOfHeaders, off_skew))
+ Die(path, "skewing PE SizeOfHeaders FSZ overflowed");
+
+ // fixup dll imports
+ //
+ // this implementation currently only works on simple pe file layouts
+ // where relative virtual addresses are equivalent to file offsets
+ struct NtImageDataDirectory *ddImports =
+ pe->OptionalHeader.DataDirectory + kNtImageDirectoryEntryImport;
+ if (pe->OptionalHeader.NumberOfRvaAndSizes >= 2 && ddImports->Size) {
+ struct NtImageImportDescriptor *idt;
+ if (!(idt = GetPeRva(map, pe, ddImports->VirtualAddress)))
+ Die(path, "couldn't resolve VirtualAddress/Size RVA of PE Import "
+ "Directory Table to within a defined PE section");
+ for (int i = 0; idt[i].ImportLookupTable; ++i) {
+ uint64_t *ilt, *iat;
+ if (!(ilt = GetPeRva(map, pe, idt[i].ImportLookupTable)))
+ Die(path, "PE ImportLookupTable RVA didn't resolve to a section");
+ for (int j = 0; ilt[j]; ++j) {
+ if (ckd_add(&ilt[j], ilt[j], rva_skew))
+ Die(path, "skewing PE Import Lookup Table RVA overflowed");
+ }
+ if (!(iat = GetPeRva(map, pe, idt[i].ImportAddressTable)))
+ Die(path, "PE ImportAddressTable RVA didn't resolve to a section");
+ for (int j = 0; iat[j]; ++j) {
+ if (ckd_add(&iat[j], iat[j], rva_skew))
+ Die(path, "skewing PE Import Lookup Table RVA overflowed");
+ }
+ if (ckd_add(&idt[i].DllNameRva, idt[i].DllNameRva, rva_skew))
+ Die(path, "skewing PE IDT DllNameRva RVA overflowed");
+ if (ckd_add(&idt[i].ImportLookupTable, idt[i].ImportLookupTable,
+ rva_skew))
+ Die(path, "skewing PE IDT ImportLookupTable RVA overflowed");
+ if (ckd_add(&idt[i].ImportAddressTable, idt[i].ImportAddressTable,
+ rva_skew))
+ Die(path, "skewing PE IDT ImportAddressTable RVA overflowed");
+ }
+ if (ckd_add(&ddImports->VirtualAddress, ddImports->VirtualAddress,
+ rva_skew))
+ Die(path, "skewing PE IMAGE_DIRECTORY_ENTRY_IMPORT RVA overflowed");
+ }
+
+ // fixup pe segments table
+ struct NtImageSectionHeader *sections =
+ (struct NtImageSectionHeader *)((char *)&pe->OptionalHeader +
+ pe->FileHeader.SizeOfOptionalHeader);
+ for (int i = 0; i < pe->FileHeader.NumberOfSections; ++i) {
+ if (ckd_add(§ions[i].VirtualAddress, sections[i].VirtualAddress,
+ rva_skew))
+ Die(path, "skewing PE section VirtualAddress overflowed");
+ if (ckd_add(§ions[i].PointerToRawData, sections[i].PointerToRawData,
+ off_skew))
+ Die(path, "skewing PE section PointerToRawData overflowed");
+ }
+}
+
+static bool FindLoaderEmbeddedMachoHeader(struct Loader *ape) {
+ size_t i;
+ struct MachoHeader *macho;
+ for (i = 0; i + sizeof(struct MachoHeader) < ape->size; i += 64) {
+ if (READ32LE((char *)ape->map + i) == 0xFEEDFACE + 1) {
+ macho = (struct MachoHeader *)((char *)ape->map + i);
+ ape->macho_offset = i;
+ ape->macho_length = sizeof(*macho) + macho->loadsize;
+ return true;
+ }
+ }
+ return false;
+}
+
+static struct Input *GetInput(int machine) {
+ int i;
+ for (i = 0; i < inputs.n; ++i) {
+ if (inputs.p[i].elf->e_machine == machine) {
+ return inputs.p + i;
+ }
+ }
+ return 0;
+}
+
+static struct Loader *GetLoader(int machine, int os) {
+ int i;
+ for (i = 0; i < loaders.n; ++i) {
+ if ((loaders.p[i].os & os) && loaders.p[i].machine == machine) {
+ return loaders.p + i;
+ }
+ }
+ return 0;
+}
+
+static void AddLoader(const char *path) {
+ if (loaders.n == ARRAYLEN(loaders.p)) {
+ Die(prog, "too many loaders");
+ }
+ loaders.p[loaders.n++].path = path;
+}
+
+static void GetOpts(int argc, char *argv[]) {
+ char *endptr;
+ int opt, bits;
+ bool got_support_vector = false;
+ while ((opt = getopt(argc, argv, "hvgGBo:s:l:S:M:")) != -1) {
+ switch (opt) {
+ case 'o':
+ outpath = optarg;
+ break;
+ case 's':
+ HashInputString("-s");
+ HashInputString(optarg);
+ if (ParseSupportVector(optarg, &bits)) {
+ support_vector |= bits;
+ } else {
+ Die(prog, "unrecognized token passed to -s support vector flag");
+ exit(1);
+ }
+ got_support_vector = true;
+ break;
+ case 'l':
+ AddLoader(optarg);
+ break;
+ case 'S':
+ custom_sh_code = optarg;
+ break;
+ case 'B':
+ force_bypass_binfmt_misc = true;
+ break;
+ case 'g':
+ generate_debuggable_binary = true;
+ break;
+ case 'G':
+ dont_path_lookup_ape_loader = true;
+ break;
+ case 'M':
+ macos_silicon_loader_source_path = optarg;
+ break;
+ case 'v':
+ tinyprint(0, VERSION, NULL);
+ exit(0);
+ case 'h':
+ ShowUsage(0, 1);
+ default:
+ ShowUsage(1, 2);
+ }
+ }
+ if (!outpath) {
+ Die(prog, "need output path");
+ }
+ if (optind == argc) {
+ Die(prog, "missing input argument");
+ }
+ if (!got_support_vector) {
+ support_vector = -1;
+ }
+}
+
+static void OpenLoader(struct Loader *ldr) {
+ int fd;
+ Elf64_Ehdr *elf;
+ struct MachoHeader *macho;
+ if ((fd = open(ldr->path, O_RDONLY)) == -1) {
+ DieSys(ldr->path);
+ }
+ if ((ldr->size = lseek(fd, 0, SEEK_END)) == -1) {
+ DieSys(ldr->path);
+ }
+ ldr->map = mmap(0, ldr->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (ldr->map == MAP_FAILED) {
+ DieSys(ldr->path);
+ }
+ HashInput(ldr->map, ldr->size);
+ close(fd);
+ if (IsElf64Binary((elf = ldr->map), ldr->size)) {
+ ValidateElfImage(ldr->map, ldr->size, ldr->path, true);
+ ldr->os = _HOSTLINUX | _HOSTFREEBSD | _HOSTNETBSD | _HOSTOPENBSD;
+ ldr->machine = elf->e_machine;
+ if (ldr->machine == EM_NEXGEN32E && FindLoaderEmbeddedMachoHeader(ldr)) {
+ ldr->os |= _HOSTXNU;
+ }
+ } else if (READ32LE(ldr->map) == 0xFEEDFACE + 1) {
+ macho = (struct MachoHeader *)ldr->map;
+ if (macho->arch == MAC_CPU_NEXGEN32E) {
+ Die(ldr->path, "there's no reason to embed the x86 ape.macho loader, "
+ "because ape.elf for x86-64 works on x86-64 macos too");
+ } else if (macho->arch == MAC_CPU_ARM64) {
+ ldr->os = _HOSTXNU;
+ ldr->machine = EM_AARCH64;
+ if (macos_silicon_loader_source_path) {
+ Die(ldr->path,
+ "it doesn't make sense to embed a prebuilt ape loader executable "
+ "for macos silicon when its source code was already passed so it "
+ "can be built by the ape shell script on the fly");
+ }
+ } else {
+ Die(ldr->path, "unrecognized mach-o ape loader architecture");
+ }
+ } else {
+ Die(ldr->path, "ape loader file format must be elf or mach-o");
+ }
+}
+
+static int PhdrFlagsToProt(Elf64_Word flags) {
+ int prot = PROT_NONE;
+ if (flags & PF_R) prot |= PROT_READ;
+ if (flags & PF_W) prot |= PROT_WRITE;
+ if (flags & PF_X) prot |= PROT_EXEC;
+ return prot;
+}
+
+static char *EncodeWordAsPrintf(char *p, uint64_t w, int bytes) {
+ int c;
+ while (bytes-- > 0) {
+ c = w & 255;
+ w >>= 8;
+ if (isascii(c) && isprint(c) && !isdigit(c) && c != '\\' && c != '%') {
+ *p++ = c;
+ } else {
+ *p++ = '\\';
+ if ((c & 0700)) *p++ = '0' + ((c & 0700) >> 6);
+ if ((c & 0770)) *p++ = '0' + ((c & 070) >> 3);
+ *p++ = '0' + (c & 7);
+ }
+ }
+ return p;
+}
+
+static char *FixupWordAsBinary(char *p, uint64_t w) {
+ do {
+ *p++ = w;
+ } while ((w >>= 8));
+ return p;
+}
+
+static char *FixupWordAsDecimal(char *p, uint64_t w) {
+ char buf[21];
+ return mempcpy(p, buf, FormatInt64(buf, w) - buf);
+}
+
+static char *FixupWordAsPrintf(char *p, uint64_t w) {
+ unassert(READ32LE(p) == READ32LE("\\000"));
+ do {
+ *p++ = '\\';
+ *p++ = '0' + ((w & 0700) >> 6);
+ *p++ = '0' + ((w & 070) >> 3);
+ *p++ = '0' + ((w & 07));
+ } while ((w >>= 8));
+ return p;
+}
+
+static char *FixupPrintf(char *p, uint64_t w) {
+ switch (strategy) {
+ case kPe:
+ return p;
+ case kElf:
+ case kMacho:
+ return FixupWordAsBinary(p, w);
+ case kApe:
+ return FixupWordAsPrintf(p, w);
+ default:
+ __builtin_unreachable();
+ }
+}
+
+static int PickElfOsAbi(void) {
+ switch (support_vector) {
+ case _HOSTLINUX:
+ return ELFOSABI_LINUX;
+ case _HOSTOPENBSD:
+ return ELFOSABI_OPENBSD;
+ case _HOSTNETBSD:
+ return ELFOSABI_NETBSD;
+ default:
+ // If we're supporting multiple operating systems, then FreeBSD
+ // has the only kernel that actually checks this field.
+ return ELFOSABI_FREEBSD;
+ }
+}
+
+static void AddOffsetReloc(struct Input *in, uint64_t *r) {
+ if (in->offsetrelocs.n == ARRAYLEN(in->offsetrelocs.p)) {
+ Die(in->path, "ran out of offset relocations");
+ }
+ in->offsetrelocs.p[in->offsetrelocs.n++] = r;
+}
+
+static char *GenerateDecimalOffsetRelocation(char *p) {
+ size_t i, n = 10;
+ for (i = 0; i < n; ++i) {
+ *p++ = ' ';
+ }
+ return p;
+}
+
+static char *GenerateElf(char *p, struct Input *in) {
+ Elf64_Ehdr *e;
+ p = ALIGN(p, alignof(Elf64_Ehdr));
+ e = (Elf64_Ehdr *)p;
+ e->e_ident[EI_MAG0] = ELFMAG0;
+ e->e_ident[EI_MAG1] = ELFMAG1;
+ e->e_ident[EI_MAG2] = ELFMAG2;
+ e->e_ident[EI_MAG3] = ELFMAG3;
+ e->e_ident[EI_CLASS] = ELFCLASS64;
+ e->e_ident[EI_DATA] = ELFDATA2LSB;
+ e->e_ident[EI_VERSION] = 1;
+ e->e_ident[EI_OSABI] = PickElfOsAbi();
+ e->e_ident[EI_ABIVERSION] = 0;
+ e->e_type = ET_EXEC;
+ e->e_machine = in->elf->e_machine;
+ e->e_version = 1;
+ e->e_entry = in->elf->e_entry;
+ e->e_ehsize = sizeof(Elf64_Ehdr);
+ e->e_phentsize = sizeof(Elf64_Phdr);
+ in->printf_phoff = (char *)&e->e_phoff;
+ in->printf_phnum = (char *)&e->e_phnum;
+ return p + sizeof(*e);
+}
+
+static bool IsPhdrAllocated(struct Input *in, Elf64_Phdr *phdr) {
+ int i;
+ Elf64_Shdr *shdr;
+ if (1) return true;
+ for (i = 0; i < in->elf->e_shnum; ++i) {
+ if (!(shdr = GetElfSectionHeaderAddress(in->elf, in->size, i))) {
+ Die(in->path, "elf section header overflow");
+ }
+ if ((shdr->sh_flags & SHF_ALLOC) &&
+ MAX(shdr->sh_addr, phdr->p_vaddr) <
+ MIN(shdr->sh_addr + shdr->sh_size, phdr->p_vaddr + phdr->p_memsz)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// finds non-local elf symbol by name
+static struct Elf64_Sym *GetElfSymbol(struct Input *in, const char *name) {
+ char *ss;
+ Elf64_Sym *st;
+ Elf64_Shdr *sh;
+ Elf64_Xword i, n;
+ ss = GetElfStringTable(in->elf, in->size, ".strtab");
+ sh = GetElfSymbolTable(in->elf, in->size, SHT_SYMTAB, &n);
+ st = GetElfSectionAddress(in->elf, in->size, sh);
+ if (!st || !ss) Die(in->path, "missing elf symbol table");
+ for (i = sh->sh_info; i < n; ++i) {
+ if (st[i].st_name && !strcmp(ss + st[i].st_name, name)) {
+ return st + i;
+ }
+ }
+ return 0;
+}
+
+static char *DefineMachoNull(char *p) {
+ struct MachoLoadSegment *load;
+ load = (struct MachoLoadSegment *)p;
+ ++macholoadcount;
+ load->command = MAC_LC_SEGMENT_64;
+ load->size = sizeof(*load);
+ strcpy(load->name, "__PAGEZERO");
+ load->memsz = 0x200000;
+ return p + sizeof(*load);
+}
+
+static char *DefineMachoUuid(char *p) {
+ struct MachoLoadUuid *load;
+ load = (struct MachoLoadUuid *)p;
+ ++macholoadcount;
+ load->command = MAC_LC_UUID;
+ load->size = sizeof(*load);
+ if (!hashes) Die(outpath, "won't generate macho uuid");
+ memcpy(load->uuid, hashpool, sizeof(load->uuid));
+ return p + sizeof(*load);
+}
+
+static char *DefineMachoEntrypoint(char *p, struct Input *in) {
+ struct Elf64_Sym *start;
+ struct MachoLoadThread64 *load;
+ if (!(start = GetElfSymbol(in, "_apple")) &&
+ !(start = GetElfSymbol(in, "_start"))) {
+ Die(in->path, "couldn't find _apple() or _start() function in elf "
+ "executable which can serve as the macho entrypoint");
+ }
+ load = (struct MachoLoadThread64 *)p;
+ ++macholoadcount;
+ load->thread.command = MAC_LC_UNIXTHREAD;
+ load->thread.size = sizeof(*load);
+ load->thread.flavor = MAC_THREAD_NEXGEN32E;
+ load->thread.count = sizeof(load->regs) / 4;
+ load->regs[16] = start->st_value;
+ return p + sizeof(*load);
+}
+
+static char *GenerateMachoSegment(char *p, struct Input *in, Elf64_Phdr *phdr) {
+ struct MachoLoadSegment *load;
+ load = (struct MachoLoadSegment *)p;
+ load->command = MAC_LC_SEGMENT_64;
+ load->size = sizeof(*load);
+ FormatInt32(stpcpy(load->name, "__APE"), macholoadcount);
+ ++macholoadcount;
+ load->vaddr = phdr->p_vaddr;
+ load->memsz = phdr->p_memsz;
+ load->offset = phdr->p_offset;
+ load->filesz = phdr->p_filesz;
+ load->maxprot = PROT_EXEC | PROT_READ | PROT_WRITE;
+ load->initprot = PhdrFlagsToProt(phdr->p_flags);
+ if (load->initprot & PROT_EXEC) {
+ load->initprot |= PROT_READ; // xnu wants it
+ }
+ AddOffsetReloc(in, &load->offset);
+ if (!in->first_macho_load) {
+ in->first_macho_load = load;
+ }
+ return p + sizeof(*load);
+}
+
+static char *GenerateMachoLoads(char *p, struct Input *in) {
+ int i;
+ struct Elf64_Phdr *phdr;
+ p = DefineMachoNull(p);
+ for (i = 0; i < in->elf->e_phnum; ++i) {
+ phdr = GetElfProgramHeaderAddress(in->elf, in->size, i);
+ if (phdr->p_type == PT_LOAD) {
+ if (!IsPhdrAllocated(in, phdr)) {
+ continue;
+ }
+ p = GenerateMachoSegment(p, in, phdr);
+ }
+ }
+ p = DefineMachoUuid(p);
+ p = DefineMachoEntrypoint(p, in);
+ return p;
+}
+
+static char *GenerateMacho(char *p, struct Input *in) {
+ char *pMacho, *pLoads;
+ struct MachoHeader *macho;
+ pMacho = p = ALIGN(p, 8);
+ macho = (struct MachoHeader *)p;
+ macho->magic = 0xFEEDFACE + 1;
+ macho->arch = MAC_CPU_NEXGEN32E;
+ macho->arch2 = MAC_CPU_NEXGEN32E_ALL;
+ macho->filetype = MAC_EXECUTE;
+ macho->flags = MAC_NOUNDEFS;
+ macho->__reserved = 0;
+ pLoads = p += sizeof(struct MachoHeader);
+ p = GenerateMachoLoads(p, in);
+ macho->loadcount = macholoadcount;
+ macho->loadsize = p - pLoads;
+ if (in->ddarg_macho_skip)
+ FixupWordAsDecimal(in->ddarg_macho_skip, pMacho - prologue);
+ if (in->ddarg_macho_count)
+ FixupWordAsDecimal(in->ddarg_macho_count, p - pMacho);
+ return p;
+}
+
+static void ValidatePortableExecutable(struct Input *in) {
+ unassert(r_off32_e_lfanew);
+ unassert(in->elf->e_machine == EM_NEXGEN32E);
+ Elf64_Sym *ape_pe;
+ if (!(ape_pe = GetElfSymbol(in, "ape_pe"))) {
+ if (support_vector & _HOSTWINDOWS) {
+ Die(in->path, "elf image needs to define `ape_pe` when windows is "
+ "in the support vector");
+ }
+ return;
+ }
+ int off;
+ Elf64_Shdr *shdr =
+ GetElfSectionHeaderAddress(in->elf, in->size, ape_pe->st_shndx);
+ if (!shdr || ckd_sub(&off, ape_pe->st_value, shdr->sh_addr) ||
+ ckd_add(&off, off, shdr->sh_offset) || off >= in->size ||
+ off + sizeof(struct NtImageNtHeaders) > in->size)
+ Die(in->path, "ape_pe symbol headers corrupted");
+ in->pe_offset = off;
+ in->pe = (struct NtImageNtHeaders *)(in->map + off);
+ in->size_of_pe_headers = ValidatePeImage(
+ in->map + in->minload, in->maxload - in->minload, in->pe, in->path);
+}
+
+static void LinkPortableExecutableHeader(struct Input *in, //
+ Elf64_Sxword rva_skew, //
+ Elf64_Sxword off_skew) {
+ if (!in->pe) {
+ // this can happen if (1) strategy is ape, (2) metal is in the
+ // support vector and (3) cosmo's efi entrypoint wasn't linked
+ return;
+ }
+ WRITE32LE(r_off32_e_lfanew, in->pe_offset - in->minload + off_skew);
+ FixupPeImage(in->map + in->minload, in->maxload - in->minload, in->pe,
+ in->path, rva_skew, off_skew);
+}
+
+static char *GenerateElfNote(char *p, const char *name, int type, int desc) {
+ p = WRITE32LE(p, strlen(name) + 1);
+ p = WRITE32LE(p, 4);
+ p = WRITE32LE(p, type);
+ p = stpcpy(p, name);
+ p = ALIGN(p, 4);
+ p = WRITE32LE(p, desc);
+ return p;
+}
+
+static char *GenerateElfNotes(char *p) {
+ char *save;
+ save = p = ALIGN(p, 4);
+ noteoff = p - prologue;
+ p = GenerateElfNote(p, "APE", 1, 106000000);
+ if (support_vector & _HOSTOPENBSD) {
+ p = GenerateElfNote(p, "OpenBSD", 1, 0);
+ }
+ if (support_vector & _HOSTNETBSD) {
+ p = GenerateElfNote(p, "NetBSD", 1, 901000000);
+ }
+ notesize = p - save;
+ return p;
+}
+
+static char *SecondPass(char *p, struct Input *in) {
+ int i, phnum;
+ Elf64_Phdr *phdr, *phdr2;
+
+ // plan elf program headers
+ phnum = 0;
+ in->minload = -1;
+ p = ALIGN(p, alignof(Elf64_Phdr));
+ FixupPrintf(in->printf_phoff, p - prologue);
+ for (i = 0; i < in->elf->e_phnum; ++i) {
+ phdr = GetElfProgramHeaderAddress(in->elf, in->size, i);
+ if (phdr->p_type == PT_LOAD && !IsPhdrAllocated(in, phdr)) continue;
+ *(phdr2 = (Elf64_Phdr *)p) = *phdr;
+ p += sizeof(Elf64_Phdr);
+ if (phdr->p_filesz) {
+ in->minload = MIN(in->minload, phdr->p_offset);
+ in->maxload = MAX(in->maxload, phdr->p_offset + phdr->p_filesz);
+ AddOffsetReloc(in, &phdr2->p_offset);
+ }
+ ++phnum;
+ }
+ if (noteoff && notesize) {
+ phdr = (Elf64_Phdr *)p;
+ p += sizeof(Elf64_Phdr);
+ phdr->p_type = PT_NOTE;
+ phdr->p_flags = PF_R;
+ phdr->p_offset = noteoff;
+ phdr->p_vaddr = 0;
+ phdr->p_paddr = 0;
+ phdr->p_filesz = notesize;
+ phdr->p_memsz = notesize;
+ phdr->p_align = 4;
+ ++phnum;
+ }
+ FixupPrintf(in->printf_phnum, phnum);
+
+ // plan embedded mach-o executable
+ if (strategy == kApe && //
+ (support_vector & _HOSTXNU) && //
+ in->elf->e_machine == EM_NEXGEN32E) {
+ p = GenerateMacho(p, in);
+ }
+
+ return p;
+}
+
+static char *SecondPass2(char *p, struct Input *in) {
+
+ // plan embedded portable executable
+ in->we_are_generating_pe = (strategy == kApe || strategy == kPe) &&
+ in->elf->e_machine == EM_NEXGEN32E &&
+ (support_vector & (_HOSTWINDOWS | _HOSTMETAL));
+ if (in->we_are_generating_pe) {
+ ValidatePortableExecutable(in);
+ // if the elf image has a non-alloc prologue that's being stripped
+ // away then there's a chance we can avoid adding 64kb of bloat to
+ // the new file size. that's only possible if all the fat ape hdrs
+ // we generate are able to fit inside the prologue.
+ p = ALIGN(p, 8);
+ in->we_must_skew_pe_vaspace =
+ ROUNDUP(p - prologue + in->size_of_pe_headers,
+ (int)in->pe->OptionalHeader.FileAlignment) > in->minload;
+ if (!in->we_must_skew_pe_vaspace) {
+ in->pe_e_lfanew = p - prologue;
+ in->pe_SizeOfHeaders = in->pe->OptionalHeader.SizeOfHeaders;
+ struct NtImageNtHeaders *pe2 = (struct NtImageNtHeaders *)p;
+ p += in->size_of_pe_headers;
+ memcpy(pe2, in->pe, in->size_of_pe_headers);
+ in->pe = pe2;
+ p = ALIGN(p, (int)in->pe->OptionalHeader.FileAlignment);
+ }
+ }
+
+ return p;
+}
+
+// on the third pass, we stop generating prologue content and begin
+// focusing on embedding the executable files passed via the flags.
+static Elf64_Off ThirdPass(Elf64_Off offset, struct Input *in) {
+ int i;
+ char *data;
+ Elf64_Addr vaddr;
+ Elf64_Phdr *phdr;
+ Elf64_Xword image_align;
+
+ // determine microprocessor page size
+ unsigned long pagesz;
+ if (in->elf->e_machine == EM_AARCH64) {
+ pagesz = 16384; // apple m1 (xnu, linux)
+ } else { //
+ pagesz = 4096; // x86-64, raspberry pi
+ }
+
+ // determine file alignment of image
+ //
+ // 1. elf requires that file offsets be congruent with virtual
+ // addresses modulo the cpu page size. so when adding stuff
+ // to the beginning of the file, we need to round up to the
+ // page size in order to maintain elf's invariant, although
+ // no such roundup is required on the program segments once
+ // the invariant is restored. elf loaders will happily mmap
+ // program headers from arbitrary file intervals (which may
+ // overlap) onto arbitrarily virtual intervals (which don't
+ // need to be contiguous). in order to do that, the loaders
+ // will generally use unix's mmap() function which needs to
+ // have page aligned addresses. since program headers start
+ // and stop at potentially any byte, elf loaders shall work
+ // around the mmap() requirements by rounding out intervals
+ // as necessary in order to ensure both the mmap() size and
+ // offset parameters are page size aligned. this means with
+ // elf, we never need to insert any empty space into a file
+ // when we don't want to. we can simply allow the offset to
+ // to drift apart from the virtual offset.
+ //
+ // 2. pe doesn't care about congruency and instead specifies a
+ // second kind of alignment. the minimum alignment of files
+ // is 512 because that's what dos used. where it gets hairy
+ // with pe is SizeOfHeaders which has complex and confusing
+ // requirements we're still figuring out. in cases where we
+ // determine that it's necessary to skew the pe image base,
+ // windows imposes a separate 64kb alignment requirement on
+ // the image base.
+ //
+ // 3. macho is the strictest executable format. xnu won't even
+ // load executables that do anything special with alignment
+ // which must conform to the cpu page size. it applies with
+ // both the image base and the segments. xnu also wants the
+ // segments to be contiguous similar to portable executable
+ // except that applies to both the file and virtual spaces.
+ //
+ vaddr = 0;
+ if (in->we_are_generating_pe) {
+ if (in->we_must_skew_pe_vaspace) {
+ image_align = 65535;
+ } else {
+ image_align = in->pe->OptionalHeader.FileAlignment;
+ }
+ image_align = MAX(image_align, pagesz);
+ } else {
+ image_align = pagesz;
+ }
+ while ((offset & (image_align - 1)) != (vaddr & (image_align - 1))) {
+ ++offset;
+ }
+
+ // fixup the program header offsets
+ for (i = 0; i < in->offsetrelocs.n; ++i) {
+ *in->offsetrelocs.p[i] += offset - in->minload;
+ }
+
+ // fixup macho loads to account for skew
+ if (in->first_macho_load) {
+ if (in->first_macho_load->offset) {
+ in->first_macho_load->vaddr -= in->first_macho_load->offset;
+ in->first_macho_load->memsz += in->first_macho_load->offset;
+ in->first_macho_load->filesz += in->first_macho_load->offset;
+ in->first_macho_load->offset = 0;
+ }
+ }
+
+ // fixup and link assembler generated portable executable headers
+ if (in->we_are_generating_pe) {
+ if (in->we_must_skew_pe_vaspace) {
+ LinkPortableExecutableHeader(in, offset, offset);
+ } else {
+ LinkPortableExecutableHeader(in, 0, offset);
+ WRITE32LE(r_off32_e_lfanew, in->pe_e_lfanew);
+ in->pe->OptionalHeader.SizeOfHeaders = in->pe_SizeOfHeaders;
+ }
+ }
+
+ // copy the stripped executable into the output
+ Pwrite(in->map + in->minload, in->maxload - in->minload, offset);
+ offset += in->maxload - in->minload;
+
+ return offset;
+}
+
+static void OpenInput(const char *path) {
+ int fd;
+ struct Input *in;
+ if (inputs.n == ARRAYLEN(inputs.p)) Die(prog, "too many input files");
+ in = inputs.p + inputs.n++;
+ in->path = path;
+ if ((fd = open(path, O_RDONLY)) == -1) DieSys(path);
+ if ((in->size = lseek(fd, 0, SEEK_END)) == -1) DieSys(path);
+ in->map = mmap(0, in->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (in->map == MAP_FAILED) DieSys(path);
+ if (!IsElf64Binary(in->elf, in->size)) Die(path, "not an elf64 binary");
+ HashInput(in->map, in->size);
+ close(fd);
+}
+
+static char *GenerateScriptIfMachine(char *p, struct Input *in) {
+ if (in->elf->e_machine == EM_NEXGEN32E) {
+ return stpcpy(p, "if [ \"$m\" = x86_64 ] || [ \"$m\" = amd64 ]; then\n");
+ } else if (in->elf->e_machine == EM_AARCH64) {
+ return stpcpy(p, "if [ \"$m\" = aarch64 ] || [ \"$m\" = arm64 ]; then\n");
+ } else if (in->elf->e_machine == EM_PPC64) {
+ return stpcpy(p, "if [ \"$m\" = ppc64le ]; then\n");
+ } else {
+ Die(in->path, "unsupported cpu architecture");
+ }
+}
+
+static char *FinishGeneratingDosHeader(char *p) {
+ p = WRITE16LE(p, 0x1000); // 10: MZ: lowers upper bound load / 16
+ p = WRITE16LE(p, 0xf800); // 12: MZ: roll greed on bss
+ p = WRITE16LE(p, 0); // 14: MZ: lower bound on stack segment
+ p = WRITE16LE(p, 0); // 16: MZ: initialize stack pointer
+ p = WRITE16LE(p, 0); // 18: MZ: ∑bₙ checksum don't bother
+ p = WRITE16LE(p, 0x0100); // 20: MZ: initial ip value
+ p = WRITE16LE(p, 0x0800); // 22: MZ: increases cs load lower bound
+ p = WRITE16LE(p, 0x0040); // 24: MZ: reloc table offset
+ p = WRITE16LE(p, 0); // 26: MZ: overlay number
+ p = WRITE16LE(p, 0); // 28: MZ: overlay information
+ p = WRITE16LE(p, 0); // 30
+ p = WRITE16LE(p, 0); // 32
+ p = WRITE16LE(p, 0); // 34
+ p = stpcpy(p, "JT"); // 36: Justine Tunney
+ p = WRITE16LE(p, 0); // 38
+ p = stpcpy(p, "' <<'@'\n"); // 40
+ p = WRITE16LE(p, 0); // 48
+ p = WRITE16LE(p, 0); // 50
+ p = WRITE16LE(p, 0); // 52
+ p = WRITE16LE(p, 0); // 54
+ p = WRITE16LE(p, 0); // 56
+ p = WRITE16LE(p, 0); // 58
+ p = WRITE32LE(p, 0); // 60: portable executable
+ r_off32_e_lfanew = p - 4;
+ return p;
+}
+
+static char *CopyMasterBootRecord(char *p) {
+ Elf64_Off off;
+ Elf64_Shdr *shdr;
+ struct Input *in;
+ struct Elf64_Sym *stub;
+ unassert(p == prologue + 64);
+ if ((in = GetInput(EM_NEXGEN32E)) && (stub = GetElfSymbol(in, "stub"))) {
+ shdr = GetElfSectionHeaderAddress(in->elf, in->size, stub->st_shndx);
+ if (!shdr || ckd_sub(&off, stub->st_value, shdr->sh_addr) ||
+ ckd_add(&off, off, shdr->sh_offset) || off >= in->size ||
+ off + (512 - 64) > in->size) {
+ Die(in->path, "corrupt symbol: stub");
+ }
+ p = mempcpy(p, in->map + off, 512 - 64);
+ }
+ return p;
+}
+
+static bool IsElfCarryingZipAssets(struct Input *in) {
+ return !!FindElfSectionByName(in->elf, in->size,
+ GetElfSectionNameStringTable(in->elf, in->size),
+ ".zip");
+}
+
+static bool IsZipFileNamed(unsigned char *lfile, const char *name) {
+ return ZIP_LFILE_NAMESIZE(lfile) == strlen(name) &&
+ !memcmp(ZIP_LFILE_NAME(lfile), name, ZIP_LFILE_NAMESIZE(lfile));
+}
+
+static bool IsZipFileNameEqual(unsigned char *lfile1, unsigned char *lfile2) {
+ return ZIP_LFILE_NAMESIZE(lfile1) == ZIP_LFILE_NAMESIZE(lfile2) &&
+ !memcmp(ZIP_LFILE_NAME(lfile1), ZIP_LFILE_NAME(lfile2),
+ ZIP_LFILE_NAMESIZE(lfile1));
+}
+
+static bool IsZipFileContentEqual(unsigned char *lfile1,
+ unsigned char *lfile2) {
+ return ZIP_LFILE_CRC32(lfile1) == ZIP_LFILE_CRC32(lfile2) &&
+ ZIP_LFILE_COMPRESSEDSIZE(lfile1) == ZIP_LFILE_COMPRESSEDSIZE(lfile2) &&
+ !memcmp(lfile1 + ZIP_LFILE_HDRSIZE(lfile1),
+ lfile2 + ZIP_LFILE_HDRSIZE(lfile2),
+ ZIP_LFILE_COMPRESSEDSIZE(lfile1));
+}
+
+static bool HasZipAsset(unsigned char *lfile) {
+ int i;
+ for (i = 0; i < assets.n; ++i) {
+ if (IsZipFileNameEqual(lfile, assets.p[i].lfile)) {
+ if (IsZipFileContentEqual(lfile, assets.p[i].lfile)) {
+ return true;
+ } else {
+ Die(outpath, "multiple ELF files define assets at the same ZIP path, "
+ "but these duplicated assets can't be merged because they "
+ "don't have exactly the same content; perhaps the build "
+ "system is in an inconsistent state");
+ }
+ }
+ }
+ return false;
+}
+
+static unsigned char *GetZipEndOfCentralDirectory(struct Input *in) {
+ unsigned char *img = in->umap;
+ unsigned char *eocd = img + in->size - kZipCdirHdrMinSize;
+ unsigned char *stop = MAX(eocd - 65536, img);
+ for (; eocd >= stop; --eocd) {
+ if (READ32LE(eocd) == kZipCdirHdrMagic) {
+ if (IsZipEocd32(img, in->size, eocd - img) != kZipOk) {
+ Die(in->path, "found corrupted ZIP EOCD header at the end of an "
+ "ELF file with a .zip section");
+ }
+ return eocd;
+ }
+ }
+ Die(in->path, "zip eocd not found in last 64kb of elf even though a .zip "
+ "section exists; you may need to run fixupobj.com");
+}
+
+static void IndexZipAssetsFromElf(struct Input *in) {
+ unsigned char *lfile;
+ unsigned char *img = in->umap;
+ unsigned char *eof = img + in->size;
+ unsigned char *eocd = GetZipEndOfCentralDirectory(in);
+ unsigned char *cdir = img + ZIP_CDIR_OFFSET(eocd);
+ unsigned char *cfile = cdir;
+ unsigned char *stop = cdir + ZIP_CDIR_SIZE(eocd);
+ if (stop > eof) {
+ Die(in->path, "zip central directory size overlaps image eof");
+ }
+ for (; cfile < stop; cfile += ZIP_CFILE_HDRSIZE(cfile)) {
+ if (cfile + kZipCfileHdrMinSize > eof || //
+ cfile + ZIP_CFILE_HDRSIZE(cfile) > eof) {
+ Die(in->path, "zip central directory entry overlaps image eof");
+ }
+ if (READ32LE(cfile) != kZipCfileHdrMagic) {
+ Die(in->path, "zip central directory entry magic corrupted");
+ }
+ if (ZIP_CFILE_OFFSET(cfile) == 0xFFFFFFFF) {
+ Die(in->path, "zip64 file format not supported at link time");
+ }
+ if ((lfile = img + ZIP_CFILE_OFFSET(cfile)) > eof) {
+ Die(in->path, "zip offset to local file points past image eof");
+ }
+ if (lfile + kZipLfileHdrMinSize > eof || //
+ lfile + ZIP_LFILE_HDRSIZE(lfile) > eof) {
+ Die(in->path, "zip local file header overlaps image eof");
+ }
+ if (READ32LE(lfile) != kZipLfileHdrMagic) {
+ Die(in->path, "zip local file entry magic corrupted");
+ }
+ if (ZIP_LFILE_COMPRESSEDSIZE(lfile) == 0xFFFFFFFF ||
+ ZIP_LFILE_UNCOMPRESSEDSIZE(lfile) == 0xFFFFFFFF) {
+ Die(in->path, "zip64 file format not supported at link time");
+ }
+ if (lfile + ZIP_LFILE_SIZE(lfile) > eof) {
+ Die(in->path, "zip local file content overlaps image eof");
+ }
+ if (!IsZipFileNamed(lfile, ".symtab") && !HasZipAsset(lfile)) {
+ AppendZipAsset(lfile, cfile);
+ }
+ }
+}
+
+static void CopyZips(Elf64_Off offset) {
+ int i;
+ for (i = 0; i < inputs.n; ++i) {
+ struct Input *in = inputs.p + i;
+ if (IsElfCarryingZipAssets(in)) {
+ IndexZipAssetsFromElf(in);
+ }
+ }
+ if (!assets.n) {
+ return; // nothing to do
+ }
+ if (offset + assets.total_local_file_bytes + assets.total_centraldir_bytes +
+ kZipCdirHdrMinSize >
+ INT_MAX) {
+ Die(outpath, "more than 2gb of zip files not supported yet");
+ }
+ Elf64_Off lp = offset;
+ Elf64_Off midpoint = offset + assets.total_local_file_bytes;
+ Elf64_Off cp = midpoint;
+ for (i = 0; i < assets.n; ++i) {
+ unsigned char *cfile = assets.p[i].cfile;
+ WRITE32LE(cfile + kZipCfileOffsetOffset, lp);
+ unsigned char *lfile = assets.p[i].lfile;
+ Pwrite(lfile, ZIP_LFILE_SIZE(lfile), lp);
+ lp += ZIP_LFILE_SIZE(lfile);
+ Pwrite(cfile, ZIP_CFILE_HDRSIZE(cfile), cp);
+ cp += ZIP_CFILE_HDRSIZE(cfile);
+ }
+ unassert(lp == midpoint);
+ unsigned char eocd[kZipCdirHdrMinSize] = {0};
+ WRITE32LE(eocd, kZipCdirHdrMagic);
+ WRITE32LE(eocd + kZipCdirRecordsOnDiskOffset, assets.n);
+ WRITE32LE(eocd + kZipCdirRecordsOffset, assets.n);
+ WRITE32LE(eocd + kZipCdirSizeOffset, assets.total_centraldir_bytes);
+ WRITE32LE(eocd + kZipCdirOffsetOffset,
+ offset + assets.total_local_file_bytes);
+ Pwrite(eocd, sizeof(eocd), cp);
+}
+
+int main(int argc, char *argv[]) {
+ char *p;
+ int i, opt;
+ Elf64_Off offset;
+ char empty[64] = {0};
+ Elf64_Xword prologue_bytes;
+#ifndef NDEBUG
+ ShowCrashReports();
+#endif
+ prog = argv[0];
+ if (!prog) prog = "apelink";
+
+ // process flags
+ GetOpts(argc, argv);
+
+ // determine strategy
+ //
+ // if we're only targeting a single architecture, and we're not
+ // targeting windows or macos then it's possible to make an elf
+ // executable without a shell script.
+ if (argc - optind == 1 && !(support_vector & (_HOSTWINDOWS | _HOSTXNU))) {
+ strategy = kElf;
+ loaders.n = 0;
+ } else if (argc - optind == 1 &&
+ !(support_vector & ~(_HOSTWINDOWS | _HOSTXNU))) {
+ strategy = kPe;
+ loaders.n = 0;
+ } else if (inputs.n == 1 && support_vector == _HOSTXNU) {
+ strategy = kMacho;
+ loaders.n = 0;
+ } else {
+ strategy = kApe;
+ }
+
+ // open loaders
+ for (i = 0; i < loaders.n; ++i) {
+ OpenLoader(loaders.p + i);
+ }
+
+ // open input files
+ for (i = optind; i < argc; ++i) {
+ OpenInput(argv[i]);
+ }
+
+ // validate input files
+ for (i = 0; i < inputs.n; ++i) {
+ struct Input *in = inputs.p + i;
+ ValidateElfImage(in->elf, in->size, in->path, false);
+ }
+
+ // load symbols
+ for (i = 0; i < inputs.n; ++i) {
+ struct Input *in = inputs.p + i;
+ if (GetElfSymbol(in, "__zipos_get")) {
+ LoadSymbols(in->elf, in->size, in->path);
+ } else {
+ tinyprint(2, in->path,
+ ": warning: won't generate symbol table unless "
+ "__static_yoink(\"zipos\") is linked\n",
+ NULL);
+ }
+ }
+
+ // generate the executable header
+ p = prologue;
+ if (strategy == kElf) {
+ p = GenerateElf(p, inputs.p);
+ if (support_vector & _HOSTMETAL) {
+ p = CopyMasterBootRecord(p);
+ }
+ } else if (strategy == kMacho) {
+ p = GenerateMacho(p, inputs.p);
+ } else if (strategy == kPe) {
+ p[0] = 'M';
+ p[1] = 'Z';
+ p += 64;
+ r_off32_e_lfanew = p - 4;
+ } else {
+ assert(strategy == kApe);
+ if (force_bypass_binfmt_misc) {
+ p = stpcpy(p, "APEDBG='\n\n");
+ if (support_vector & (_HOSTWINDOWS | _HOSTMETAL)) {
+ p = FinishGeneratingDosHeader(p);
+ }
+ } else if (support_vector & _HOSTWINDOWS) {
+ p = stpcpy(p, "MZqFpD='\n\n");
+ p = FinishGeneratingDosHeader(p);
+ } else {
+ p = stpcpy(p, "jartsr='\n\n");
+ if (support_vector & _HOSTMETAL) {
+ p = FinishGeneratingDosHeader(p);
+ }
+ }
+ if (support_vector & _HOSTMETAL) {
+ p = CopyMasterBootRecord(p);
+ }
+ p = stpcpy(p, "\n@\n"
+ "#'\"\n"
+ "\n");
+ if (custom_sh_code) {
+ p = stpcpy(p, custom_sh_code);
+ *p++ = '\n';
+ }
+ p = stpcpy(p, "o=$(command -v \"$0\")\n");
+
+ // run this program using the systemwide ape loader if it exists
+ if (loaders.n) {
+ p = stpcpy(p, "[ x\"$1\" != x--assimilate ] && ");
+ }
+ if (!dont_path_lookup_ape_loader) {
+ p = stpcpy(p, "type ape >/dev/null 2>&1 && "
+ "exec ape \"$o\" \"$@\"\n");
+ }
+
+ // otherwise try to use the ad-hoc self-extracted loader, securely
+ if (loaders.n) {
+ p = stpcpy(p, "t=\"${TMPDIR:-${HOME:-.}}/.ape-1.6\"\n"
+ "[ x\"$1\" != x--assimilate ] && "
+ "[ -x \"$t\" ] && "
+ "exec \"$t\" \"$o\" \"$@\"\n");
+ }
+
+ // otherwise this is a fresh install so consider the platform
+ p = stpcpy(p, "m=$(uname -m)\n");
+ if (support_vector & _HOSTXNU) {
+ p = stpcpy(p, "if [ ! -d /Applications ]; then\n");
+ }
+
+ // generate shell script for elf operating systems
+ //
+ // 1. for classic ape binaries which assimilate, all we have to do
+ // is self-modify this executable file using the printf builtin
+ // and then we re-exec this file. we only need Elf64_Ehdr which
+ // is always exactly 64 bytes.
+ //
+ // 2. if an appropriate ape loader program was passed in the flags
+ // then we won't self-modify the executable, and we instead try
+ // to self-extract the loader binary to a safe location, and it
+ // is then used by re-launch this program. thanks to incredible
+ // greatness of the elf file format, our 12kb ape loader binary
+ // is able to run on all of our supported elf operating systems
+ // but, we do still need to embed multiple copies of ape loader
+ // when generating fat binaries that run on multiple cpu types.
+ //
+ for (i = 0; i < inputs.n; ++i) {
+ struct Input *in = inputs.p + i;
+ if (GetLoader(in->elf->e_machine, ~_HOSTXNU)) {
+ // if an ape loader is available for this microprocessor for
+ // this operating system, then put assimilate behind a flag.
+ p = stpcpy(p, "if [ x\"$1\" = x--assimilate ]; then\n");
+ }
+ p = GenerateScriptIfMachine(p, in); // if [ arch ]
+ p = stpcpy(p, "exec 7<> \"$o\" || exit 121\n"
+ "printf '"
+ "\\177ELF" // 0x0: ⌂ELF
+ "\\2" // 4: long mode
+ "\\1" // 5: little endian
+ "\\1"); // 6: elf v1.o
+ p = EncodeWordAsPrintf(p, PickElfOsAbi(), 1);
+ p = stpcpy(p, "\\0" // 8: os/abi ver.
+ "\\0\\0\\0" // 9: padding 3/7
+ "\\0\\0\\0\\0" // padding 4/7
+ "\\2\\0"); // 10: εxεcµταblε
+ p = EncodeWordAsPrintf(p, in->elf->e_machine, 1);
+ p = stpcpy(p, "\\0\\1\\0\\0\\0"); // elf v1.o
+ p = EncodeWordAsPrintf(p, in->elf->e_entry, 8);
+ in->printf_phoff = p;
+ p = stpcpy(p, "\\000\\000\\000\\000\\0\\0\\0\\0");
+ p = stpcpy(p, "\\0\\0\\0\\0\\0\\0\\0\\0" // 28: e_shoff
+ "\\0\\0\\0\\0" // 30: e_flags
+ "\\100\\0" // 34: e_ehsize
+ "\\70\\0"); // 36: e_phentsize
+ in->printf_phnum = p;
+ p = stpcpy(p, "\\000\\000" // 38: e_phnum
+ "\\0\\0" // 3a: e_shentsize
+ "\\0\\0" // 3c: e_shnum
+ "\\0\\0" // 3e: e_shstrndx
+ "' >&7\n"
+ "exec 7<&-\n");
+ if (GetLoader(in->elf->e_machine, ~_HOSTXNU)) {
+ p = stpcpy(p, "fi\n"); //
+ p = stpcpy(p, "exit\n");
+ p = stpcpy(p, "fi\n"); // --assimilate>
+ } 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