Support Linux binfmt_misc and APE loading on Apple

The "no modify self" variant of Actually Portable Executable is now
supported on all platforms. If you use `$(APE_NO_MODIFY_SELF)` then
ld.bfd will embed a 4096 byte ELF binary and a 4096 byte Macho file
which are installed on the fly to ${TMPDIR:-/tmp}, which enables us
launch the executable, without needing to copy the whole executable

To prevent it from copying a tiny executable to your temp directory
you need to install the `ape` command (renamed from ape-loader), to
a system path. For example:

    # FreeBSD / NetBSD / OpenBSD
    make -j8 o//ape/ape
    cp o//ape/ape /usr/bin/ape

    # Mac OS
    # make -j8 o//ape/ape.macho
    curl https://justine.lol/ape.macho >/usr/bin/ape
    chmod +x /usr/bin/ape

On Linux you can get even more performance with the new binfmt_misc
support which makes launching non-modifying APE binaries as fast as
launching ELF executables. Running the following command:

    # Linux
    ape/apeinstall.sh

Will copy APE loader to /usr/bin/ape and register with binfmt_misc
Lastly, this change also fixes a really interesting race condition
with OpenBSD thread joining.
This commit is contained in:
Justine Tunney 2022-05-21 07:52:58 -07:00
parent 7838edae88
commit db0d8dd806
31 changed files with 1089 additions and 305 deletions

View file

@ -542,27 +542,35 @@ apesh: .ascii "'\n#'\"\n" # sixth edition shebang
// present two choices. // present two choices.
.ascii "o=\"$(command -v \"$0\")\"\n" .ascii "o=\"$(command -v \"$0\")\"\n"
// Try to use a system-wide APE loader. // Try to use a system-wide APE loader.
.ascii "type ape-loader >/dev/null 2>&1 && " .ascii "type ape >/dev/null 2>&1 && "
.ascii "exec ape-loader \"$o\" \"$@\"\n" .ascii "exec ape \"$o\" \"$@\"\n"
#ifdef APE_LOADER #ifdef APE_LOADER
// There is no system-wide APE loader, but there is one // There is no system-wide APE loader, but there is one
// embedded inside the APE. So if the system is not MacOs, // embedded inside the APE. So if the system is not MacOs,
// extract the loader into a temp folder, and use it to // extract the loader into a temp folder, and use it to
// load the APE without modifying it. // load the APE without modifying it.
.ascii "if [ ! -d /Applications ]; then\n" .ascii "t=\"${TMPDIR:-/tmp}/ape\"\n"
.ascii "t=\"${TMPDIR:-/tmp}/ape-loader\"\n" .ascii "if [ ! -x \"$t\" ]; then\n"
.ascii "[ -x \"$t\" ] || {\n" .ascii "if [ ! -d /Applications ]; then\n"
.ascii "dd if=\"$o\" of=\"$t.$$\" skip=\"" .ascii "dd if=\"$o\" of=\"$t.$$\" skip=\""
.shstub ape_loader_dd_skip,2 .shstub ape_loader_dd_skip,2
.ascii "\" count=\"" .ascii "\" count=\""
.shstub ape_loader_dd_count,2 .shstub ape_loader_dd_count,2
.ascii "\" bs=64 2>/dev/null &&\n" .ascii "\" bs=64\n"
.ascii "chmod 755 \"$t.$$\" &&\n" #if SupportsXnu() && defined(APE_LOADER_MACHO)
.ascii "mv \"$t.$$\" \"$t\"\n" .ascii "else\n"
.ascii "}\n" .ascii "dd if=\"$o\" of=\"$t.$$\" skip=\""
.ascii "exec \"$t\" \"$o\" \"$@\"\n" .shstub ape_loader_macho_dd_skip,2
.ascii "\" count=\""
.shstub ape_loader_macho_dd_count,2
.ascii "\" bs=64\n"
#endif /* APE_LOADER_MACHO */
.ascii "fi 2>/dev/null &&\n"
.ascii "chmod 755 \"$t.$$\" &&\n"
.ascii "mv \"$t.$$\" \"$t\"\n"
.ascii "fi\n" .ascii "fi\n"
#endif .ascii "exec \"$t\" \"$o\" \"$@\"\n"
#endif /* APE_LOADER */
#ifndef APE_NO_MODIFY_SELF #ifndef APE_NO_MODIFY_SELF
// The default behavior is: to overwrite the header in place. // The default behavior is: to overwrite the header in place.
// We prefer this because it's a tiny constant one time cost. // We prefer this because it's a tiny constant one time cost.
@ -656,6 +664,11 @@ apesh: .ascii "'\n#'\"\n" # sixth edition shebang
.incbin APE_LOADER .incbin APE_LOADER
.previous .previous
#endif /* APE_LOADER */ #endif /* APE_LOADER */
#if SupportsXnu() && defined(APE_LOADER_MACHO)
.section .ape.loader-macho,"a",@progbits
.incbin APE_LOADER_MACHO
.previous
#endif /* APE_LOADER_MACHO */
#endif /* SupportsWindows() || SupportsMetal() || SupportsXnu() */ #endif /* SupportsWindows() || SupportsMetal() || SupportsXnu() */
#if SupportsSystemv() || SupportsMetal() #if SupportsSystemv() || SupportsMetal()
@ -856,7 +869,7 @@ ape_macho:
.long (520f-510f)/4 # count .long (520f-510f)/4 # count
510: .quad 0 # rax 510: .quad 0 # rax
.quad IMAGE_BASE_VIRTUAL # rbx .quad IMAGE_BASE_VIRTUAL # rbx
.quad 0 # rcx .quad XNU # rcx
.quad 0 # rdx .quad 0 # rdx
.quad 0 # rdi .quad 0 # rdi
.quad 0 # rsi .quad 0 # rsi
@ -870,7 +883,7 @@ ape_macho:
.quad 0 # r13 .quad 0 # r13
.quad 0 # r14 .quad 0 # r14
.quad 0 # r15 .quad 0 # r15
.quad _xnu # rip .quad _start # rip
.quad 0 # rflags .quad 0 # rflags
.quad 0 # cs .quad 0 # cs
.quad 0 # fs .quad 0 # fs

View file

@ -397,6 +397,12 @@ SECTIONS {
KEEP(*(.ape.loader)) KEEP(*(.ape.loader))
. = ALIGN(64); . = ALIGN(64);
HIDDEN(ape_loader_end = .); HIDDEN(ape_loader_end = .);
#if SupportsXnu()
HIDDEN(ape_loader_macho = .);
KEEP(*(.ape.loader-macho))
. = ALIGN(64);
HIDDEN(ape_loader_macho_end = .);
#endif
} }
/*END: payload */ /*END: payload */
/*BEGIN: bss memory void */ /*BEGIN: bss memory void */
@ -549,6 +555,11 @@ HIDDEN(ape_bss_align = PAGESIZE);
SHSTUB2(ape_loader_dd_skip, RVA(ape_loader) / 64); SHSTUB2(ape_loader_dd_skip, RVA(ape_loader) / 64);
SHSTUB2(ape_loader_dd_count, (ape_loader_end - ape_loader) / 64); SHSTUB2(ape_loader_dd_count, (ape_loader_end - ape_loader) / 64);
#if SupportsXnu()
SHSTUB2(ape_loader_macho_dd_skip, RVA(ape_loader_macho) / 64);
SHSTUB2(ape_loader_macho_dd_count, (ape_loader_macho_end - ape_loader_macho) / 64);
#endif
#if SupportsXnu() #if SupportsXnu()
SHSTUB2(ape_macho_dd_skip, RVA(ape_macho) / 8); SHSTUB2(ape_macho_dd_skip, RVA(ape_macho) / 8);
SHSTUB2(ape_macho_dd_count, (ape_macho_end - ape_macho) / 8); SHSTUB2(ape_macho_dd_count, (ape_macho_end - ape_macho) / 8);

View file

@ -15,18 +15,18 @@
PKGS += APE PKGS += APE
APE = o/$(MODE)/ape/ape.o \ APE = o/$(MODE)/ape/ape.o \
o/$(MODE)/ape/ape.lds o/$(MODE)/ape/ape.lds
APE_NO_MODIFY_SELF = \ APE_NO_MODIFY_SELF = \
o/$(MODE)/ape/ape.lds \ o/$(MODE)/ape/ape.lds \
o/$(MODE)/ape/ape-no-modify-self.o o/$(MODE)/ape/ape-no-modify-self.o
APELINK = \ APELINK = \
$(COMPILE) \ $(COMPILE) \
-ALINK.ape \ -ALINK.ape \
$(LINK) \ $(LINK) \
$(LINKARGS) \ $(LINKARGS) \
$(OUTPUT_OPTION) $(OUTPUT_OPTION)
APE_FILES := $(wildcard ape/*.*) APE_FILES := $(wildcard ape/*.*)
@ -38,32 +38,43 @@ APE_SRCS = $(APE_SRCS_C) $(APE_SRCS_S)
APE_OBJS = $(APE_SRCS_S:%.S=o/$(MODE)/%.o) APE_OBJS = $(APE_SRCS_S:%.S=o/$(MODE)/%.o)
APE_CHECKS = $(APE_HDRS:%=o/%.ok) APE_CHECKS = $(APE_HDRS:%=o/%.ok)
o/$(MODE)/ape/ape.lds: \ o/$(MODE)/ape/ape.lds: \
ape/ape.lds \ ape/ape.lds \
ape/macros.internal.h \ ape/macros.internal.h \
libc/dce.h \ libc/dce.h \
libc/zip.h libc/zip.h
o/ape/idata.inc: \ o/ape/idata.inc: \
ape/idata.internal.h \ ape/idata.internal.h \
ape/relocations.h ape/relocations.h
o/$(MODE)/ape/ape-no-modify-self.o: ape/ape.S o/$(MODE)/ape/loader.elf o/$(MODE)/ape/ape-no-modify-self.o: \
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -DAPE_LOADER="\"o/$(MODE)/ape/loader.elf\"" -DAPE_NO_MODIFY_SELF $< ape/ape.S \
o/$(MODE)/ape/ape \
o/$(MODE)/ape/ape.macho
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -DAPE_LOADER="\"o/$(MODE)/ape/ape\"" -DAPE_LOADER_MACHO="\"o/$(MODE)/ape/ape.macho\"" $<
o/$(MODE)/ape/loader.o: ape/loader.c o/$(MODE)/ape/loader.o: ape/loader.c
@$(COMPILE) -AOBJECTIFY.c $(CC) $(cpp.flags) -fpie -Os -ffreestanding -mno-red-zone -fno-ident -fno-gnu-unique -c $(OUTPUT_OPTION) $< @$(COMPILE) -AOBJECTIFY.c $(CC) -DNDEBUG -iquote. -Wall -Wextra -fpie -Os -g -ffreestanding -mno-red-zone -fno-ident -fno-gnu-unique -c $(OUTPUT_OPTION) $<
o/$(MODE)/ape/loader-gcc.asm: ape/loader.c o/$(MODE)/ape/loader-gcc.asm: ape/loader.c
@$(COMPILE) -AOBJECTIFY.c $(CC) $(cpp.flags) -Os -ffreestanding -mno-red-zone -fno-ident -fno-gnu-unique -c -S $(OUTPUT_OPTION) $< @$(COMPILE) -AOBJECTIFY.c $(CC) -DNDEBUG -iquote. -Wall -Wextra -fpie -Os -g -ffreestanding -mno-red-zone -fno-ident -fno-gnu-unique -c -S $(OUTPUT_OPTION) $<
o/$(MODE)/ape/loader.elf: \ o/$(MODE)/ape/ape: \
o/$(MODE)/ape/loader.o \ o/$(MODE)/ape/loader.o \
o/$(MODE)/ape/loader1.o \ o/$(MODE)/ape/loader-elf.o \
ape/loader.lds ape/loader.lds
@$(ELFLINK) -s -z max-page-size=0x10 @$(ELFLINK) -s -z max-page-size=0x10
o/$(MODE)/ape/ape.macho: \
o/$(MODE)/ape/loader.o \
o/$(MODE)/ape/loader-macho.o \
ape/loader-macho.lds
@$(ELFLINK) -s -z max-page-size=0x10
.PHONY: o/$(MODE)/ape .PHONY: o/$(MODE)/ape
o/$(MODE)/ape: $(APE) \ o/$(MODE)/ape: $(APE) \
$(APE_CHECKS) \ $(APE_CHECKS) \
o/$(MODE)/ape/ape \
o/$(MODE)/ape/ape.macho \
o/$(MODE)/ape/ape-no-modify-self.o o/$(MODE)/ape/ape-no-modify-self.o

63
ape/apeinstall.sh Executable file
View file

@ -0,0 +1,63 @@
#!/bin/sh
if ! [ x"$(uname -s)" = xLinux ]; then
echo this script is intended for linux binfmt_misc >&2
echo freebsd/netbsd/openbsd users can use release binary >&2
exit 1
fi
if [ -f o/depend ]; then
# mkdeps.com build was successfully run so assume we can build
echo >&2
echo running: make -j8 o//ape/ape >&2
make -j8 o//ape/ape || exit
echo done >&2
else
# no evidence we can build, use prebuilt one
mkdir -p o//ape || exit
cp -af build/bootstrap/ape o//ape/ape
fi
echo >&2
echo installing o//ape/ape to /usr/bin/ape >&2
echo sudo mv -f o//ape/ape /usr/bin/ape >&2
sudo mv -f o//ape/ape /usr/bin/ape || exit
echo done >&2
if [ -e /proc/sys/fs/binfmt_misc/APE ]; then
echo >&2
echo it looks like APE is already registered with binfmt_misc >&2
echo please check that it is mapped to ape not /bin/sh >&2
echo cat /proc/sys/fs/binfmt_misc/APE >&2
cat /proc/sys/fs/binfmt_misc/APE >&2
# TODO: we need better uninstall recommendations
# the following works fine for justine
# but might remove unrelated software?
# sudo sh -c 'echo -1 >/proc/sys/fs/binfmt_misc/status'
exit
fi
if ! [ -e /proc/sys/fs/binfmt_misc ]; then
echo >&2
echo loading binfmt_misc into your kernel >&2
echo you may need to edit configs to persist across reboot >&2
echo sudo modprobe binfmt_misc >&2
sudo modprobe binfmt_misc || exit
echo done >&2
fi
if ! [ -e /proc/sys/fs/binfmt_misc/register ]; then
echo >&2
echo mounting binfmt_misc into your kernel >&2
echo you may need to edit configs to persist across reboot >&2
echo sudo mount -t binfmt_misc none /proc/sys/fs/binfmt_misc >&2
sudo mount -t binfmt_misc none /proc/sys/fs/binfmt_misc || exit
echo done >&2
fi
echo >&2
echo registering APE with binfmt_misc >&2
echo you may need to edit configs to persist across reboot >&2
echo 'sudo sh -c "echo '"'"':APE:M::MZqFpD::/usr/bin/ape:'"'"' >/proc/sys/fs/binfmt_misc/register"' >&2
sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register" || exit
echo done >&2

View file

@ -38,20 +38,54 @@ ehdr: .ascii "\177ELF"
.long 0 # e_flags .long 0 # e_flags
.word 64 # e_ehsize .word 64 # e_ehsize
.word 56 # e_phentsize .word 56 # e_phentsize
.word 3 # e_phnum .word 4 # e_phnum
.word 0 # e_shentsize .word 0 # e_shentsize
.word 0 # e_shnum .word 0 # e_shnum
.word 0 # e_shstrndx .word 0 # e_shstrndx
.endobj ehdr,globl .endobj ehdr,globl
// memcpy(0x200000, loader); xor %eax,%eax; jmp 0x200000 // Ape Loader Entrpoint
//
// This is normally called by the operating system. However it may
// be called by the Actually Portable Executables themselves, when
// re-executing a program. Just do this:
//
// memcpy(0x200000, loader)
// xor %eax,%eax
// inc %eax
// jmp 0x200000
//
// @see APE_LOADER_ENTRY
jg47h: .org 0x47 jg47h: .org 0x47
.endobj jg47h .endobj jg47h
_start: mov %rsp,%rsi _start: mov %rsp,%rsi
jmp loader jmp ApeLoader
.endfn _start,globl .endfn _start,globl
// System Call Entrpoint
//
// This function is used by the APE loader to make system calls.
// We also pass a reference to this function to the APE binary's
// _start() function. It's needed because on OpenBSD, msyscall()
// restricts which pages can issue system calls, and it can only
// be called once. Therefore if we want to be load and re-load a
// binary multiple times without calling the system execve(), we
// need to be able to handover the SYSCALL function. We hardcode
// this to a fixed address, but that shouldn't be used, since we
// would ideally want to move it to a random page in the future.
//
// @see APE_LOADER_SYSCALL
sc50h: .org 0x50
.endobj sc50h
__syscall_loader:
clc
syscall
jc 1f
ret
1: neg %rax
ret
.endfn __syscall_loader,globl
.align 8 .align 8
phdrs: .long PT_LOAD # p_type phdrs: .long PT_LOAD # p_type
.long PF_R|PF_X # p_flags .long PF_R|PF_X # p_flags
@ -61,6 +95,16 @@ phdrs: .long PT_LOAD # p_type
.quad filesz # p_filesz .quad filesz # p_filesz
.quad filesz # p_memsz .quad filesz # p_memsz
.quad PAGESIZE # p_align .quad PAGESIZE # p_align
.long PT_LOAD # p_type
.long PF_R|PF_W # p_flags
.quad 0 # p_offset
.quad bss # p_vaddr
.quad bss # p_paddr
.quad 0 # p_filesz
.quad bsssize # p_memsz
.quad PAGESIZE # p_align
.long PT_GNU_STACK # p_type .long PT_GNU_STACK # p_type
.long PF_R|PF_W # p_flags .long PF_R|PF_W # p_flags
.quad 0 # p_offset .quad 0 # p_offset
@ -69,6 +113,7 @@ phdrs: .long PT_LOAD # p_type
.quad 0 # p_filesz .quad 0 # p_filesz
.quad 0 # p_memsz .quad 0 # p_memsz
.quad 16 # p_align .quad 16 # p_align
.long PT_NOTE # p_type .long PT_NOTE # p_type
.long PF_R # p_flags .long PF_R # p_flags
.quad note - ehdr # p_offset .quad note - ehdr # p_offset

127
ape/loader-macho.S Normal file
View file

@ -0,0 +1,127 @@
/*-*- 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 2021 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/macho.internal.h"
#include "libc/sysv/consts/prot.h"
#include "libc/dce.h"
#include "libc/macros.internal.h"
// APE Loader Executable Structure for XNU
.align 4096
macho: .long 0xFEEDFACE+1
.long MAC_CPU_NEXGEN32E
.long MAC_CPU_NEXGEN32E_ALL
.long MAC_EXECUTE
.long 5 # number of load commands
.long 60f-10f # size of all load commands
.long MAC_NOUNDEFS # flags
.long 0 # reserved
10: .long MAC_LC_SEGMENT_64
.long 20f-10b # unmaps first page dir
.ascin "__PAGEZERO",16 # consistent with linux
.quad 0,0x200000,0,0 # which forbids mem <2m
.long 0,0,0,0
20: .long MAC_LC_SEGMENT_64
.long 30f-20b
.ascin "__TEXT",16
.quad macho # vaddr
.quad filesz # memsz
.quad 0 # file offset
.quad filesz # file size
.long PROT_EXEC|PROT_READ|PROT_WRITE # maxprot
.long PROT_EXEC|PROT_READ # initprot
.long 1 # segment section count
.long 0 # flags
210: .ascin "__text",16 # section name (.text)
.ascin "__TEXT",16
.quad _start # vaddr
.quad textsz # memsz
.long textoff # offset
.long 3 # align 2**3 = 8
.long 0 # reloc table offset
.long 0 # relocation count
.long MAC_S_ATTR_SOME_INSTRUCTIONS # section type & attributes
.long 0,0,0 # reserved
30: .long MAC_LC_SEGMENT_64
.long 40f-30b
.ascin "__DATA",16
.quad bss # vaddr
.quad bsssize # memsz
.quad 0 # offset
.quad 0 # file size
.long PROT_EXEC|PROT_READ|PROT_WRITE # maxprot
.long PROT_READ|PROT_WRITE # initprot
.long 1 # segment section count
.long 0 # flags
310: .ascin "__bss",16 # section name (.bss)
.ascin "__DATA",16
.quad bss # vaddr
.quad bsssize # memsz
.long 0 # offset
.long 12 # align 2**12 = 4096
.long 0 # reloc table offset
.long 0 # relocation count
.long MAC_S_ZEROFILL # section type & attributes
.long 0,0,0 # reserved
40: .long MAC_LC_UUID
.long 50f-40b
.quad 0x3fb29ee4ac6c87aa # uuid1
.quad 0xdd2c9bb866d9eef8 # uuid2
50: .long MAC_LC_UNIXTHREAD
.long 60f-50b # cmdsize
.long MAC_THREAD_NEXGEN32E # flavaflav
.long (520f-510f)/4 # count
510: .quad 0 # rax
.quad 0 # rbx
.quad 0 # rcx
.quad XNU # rdx
.quad 0 # rdi
.quad 0 # rsi
.quad 0 # rbp
.quad 0 # rsp
.quad 0 # r8
.quad 0 # r9
.quad 0 # r10
.quad 0 # r11
.quad 0 # r12
.quad 0 # r13
.quad 0 # r14
.quad 0 # r15
.quad _start # rip
.quad 0 # rflags
.quad 0 # cs
.quad 0 # fs
.quad 0 # gs
520:
60:
.endobj macho,globl
.align 8
_start: mov %rsp,%rsi
jmp ApeLoader
.endfn _start,globl
__syscall_loader:
clc
syscall
jc 1f
ret
1: neg %rax
ret
.endfn __syscall_loader,globl

43
ape/loader-macho.lds Normal file
View file

@ -0,0 +1,43 @@
/*-*- mode: ld-script; indent-tabs-mode: nil; tab-width: 2; coding: utf-8 -*-│
│vi: set et sts=2 tw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2021 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. │
╚─────────────────────────────────────────────────────────────────────────────*/
ENTRY(_start)
OUTPUT_FORMAT(binary)
SECTIONS {
. = 0x200000;
.text : {
*(.text)
*(.rodata .rodata.*)
. = ALIGN(4096);
}
filesz = . - macho;
textsz = . - _start;
.bss ALIGN(4096) : {
bss = .;
*(.bss)
. = ALIGN(4096);
}
memsz = . - macho;
/DISCARD/ : {
*(.*)
}
}
bsssize = SIZEOF(.bss);
textoff = _start - macho;

View file

@ -16,28 +16,74 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/bits/bits.h" #include "ape/loader.h"
#include "libc/calls/struct/metastat.internal.h"
#include "libc/calls/struct/stat.h" #define TROUBLESHOOT 0
#include "libc/elf/def.h" #define TROUBLESHOOT_OS LINUX
#include "libc/elf/struct/ehdr.h"
#include "libc/elf/struct/phdr.h"
#include "libc/sysv/consts/prot.h"
/** /**
* @fileoverview APE embeddable loader for Linux and BSD, e.g. * @fileoverview APE Loader for GNU/Systemd and FreeBSD/NetBSD/OpenBSD
*
* We recommend using the normal APE design, where binaries assimilate
* themselves once by self-modifying the first 64 bytes. If that can't
* meet your requirements then we provide an excellent alternative.
* *
* m=tiny * m=tiny
* make -j8 MODE=$m o/$m/ape o/$m/examples/printargs.com * make -j8 MODE=$m o/$m/ape o/$m/examples/printargs.com
* o/$m/ape/loader.elf o/$m/examples/printargs.com * o/$m/ape/ape o/$m/examples/printargs.com
*
* This is an embeddable Actually Portable Executable interpreter. The
* `ape/ape.S` bootloader embeds this binary inside each binary that's
* linked using `$(APE_NO_MODIFY_SELF)` so it is an automated seamless
* process. the shell script at the top of the .COM files will copy it
* to `${TMPDIR:-/tmp}/ape` and call execve(). It's a zero copy
* operation in praxis since this payload uses mmap() to load the rest
* of your executable the same way the kernel does, based on ELF phdrs
* which are located in accordance with the first sh printf statement.
*
* APE executables will look for this program on the system path first
* so your APE loader may be installed to your system as follows:
*
* m=tiny
* make -j8 MODE=$m o/$m/ape/ape
* sudo cp o/$m/ape/ape /usr/bin/ape
*
* Your APE loader may be used as a shebang interpreter by doing this:
*
* #!/usr/bin/ape python.com
* # -*- python -*-
* print("hello world")
*
* However you won't need to do that, if your APE Loader is registered
* as a binfmt_misc interpreter. You can do that as follows with root:
*
* sudo cp -f o/$m/ape/ape /usr/bin
* f=/proc/sys/fs/binfmt_misc/register
* sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >$f"
*
* If the register file doesn't exist on your Linux machine then you'd
* load it using the following commands:
*
* sudo modprobe binfmt_misc
* sudo mount -t binfmt_misc none /proc/sys/fs/binfmt_misc
*
* You should now experience a performance boost, and you can also now
* use the regular shebang form:
*
* #!/usr/bin/python.com
* # -*- python -*-
* print("hello world")
* *
* @note this can probably be used as a binfmt_misc interpreter * @note this can probably be used as a binfmt_misc interpreter
*/ */
#define LINUX 0 #define LINUX 1
#define FREEBSD 1 #define METAL 2
#define NETBSD 2 #define WINDOWS 4
#define OPENBSD 3 #define XNU 8
#define OPENBSD 16
#define FREEBSD 32
#define NETBSD 64
#define O_RDONLY 0 #define O_RDONLY 0
#define PROT_READ 1 #define PROT_READ 1
@ -49,170 +95,394 @@
#define MAP_ANONYMOUS (os == LINUX ? 32 : 4096) #define MAP_ANONYMOUS (os == LINUX ? 32 : 4096)
#define AT_EXECFN_LINUX 31 #define AT_EXECFN_LINUX 31
#define AT_EXECFN_NETBSD 2014 #define AT_EXECFN_NETBSD 2014
#define ELFCLASS64 2
#define ELFDATA2LSB 1
#define EM_NEXGEN32E 62
#define ET_EXEC 2
#define PT_LOAD 1
#define PT_DYNAMIC 2
#define EI_CLASS 4
#define EI_DATA 5
#define PF_X 1
#define PF_W 2
#define PF_R 4
#define X_OK 1
#define XCR0_SSE 2
#define XCR0_AVX 4
#define __NR_read (os == LINUX ? 0 : 3) #define Read32(S) \
#define __NR_write (os == LINUX ? 1 : 4) ((unsigned)(255 & (S)[3]) << 030 | (unsigned)(255 & (S)[2]) << 020 | \
#define __NR_open (os == LINUX ? 2 : 5) (unsigned)(255 & (S)[1]) << 010 | (unsigned)(255 & (S)[0]) << 000)
#define __NR_close (os == LINUX ? 3 : 6)
#define __NR_exit (os == LINUX ? 60 : 1)
#define __NR_mmap (os == LINUX ? 9 : os == FREEBSD ? 477 : 197)
#define __NR_fstat \
(os == LINUX ? 5 : os == FREEBSD ? 551 : os == OPENBSD ? 53 : 440)
static wontreturn void Exit(int os, long rc) { #define Read64(S) \
asm volatile("syscall" ((unsigned long)(255 & (S)[7]) << 070 | \
: /* no outputs */ (unsigned long)(255 & (S)[6]) << 060 | \
: "a"(__NR_exit), "D"(rc) (unsigned long)(255 & (S)[5]) << 050 | \
: "memory"); (unsigned long)(255 & (S)[4]) << 040 | \
unreachable; (unsigned long)(255 & (S)[3]) << 030 | \
(unsigned long)(255 & (S)[2]) << 020 | \
(unsigned long)(255 & (S)[1]) << 010 | \
(unsigned long)(255 & (S)[0]) << 000)
struct PathSearcher {
char os;
unsigned long namelen;
const char *name;
const char *syspath;
char path[1024];
};
struct ElfEhdr {
unsigned char e_ident[16];
unsigned short e_type;
unsigned short e_machine;
unsigned e_version;
unsigned long e_entry;
unsigned long e_phoff;
unsigned long e_shoff;
unsigned e_flags;
unsigned short e_ehsize;
unsigned short e_phentsize;
unsigned short e_phnum;
unsigned short e_shentsize;
unsigned short e_shnum;
unsigned short e_shstrndx;
};
struct ElfPhdr {
unsigned p_type;
unsigned p_flags;
unsigned long p_offset;
unsigned long p_vaddr;
unsigned long p_paddr;
unsigned long p_filesz;
unsigned long p_memsz;
unsigned long p_align;
};
static void *syscall;
static struct PathSearcher ps;
extern char __syscall_loader[];
static int ToLower(int c) {
return 'A' <= c && c <= 'Z' ? c + ('a' - 'A') : c;
} }
static void Close(int os, long fd) { static char *MemCpy(char *d, const char *s, unsigned long n) {
unsigned long i = 0;
for (; i < n; ++i) d[i] = s[i];
return d + n;
}
static unsigned long StrLen(const char *s) {
unsigned long n = 0;
while (*s++) ++n;
return n;
}
static const char *MemChr(const char *s, unsigned char c, unsigned long n) {
for (; n; --n, ++s) {
if ((*s & 255) == c) {
return s;
}
}
return 0;
}
static char *GetEnv(char **p, const char *s) {
unsigned long i, j;
if (p) {
for (i = 0; p[i]; ++i) {
for (j = 0;; ++j) {
if (!s[j]) {
if (p[i][j] == '=') {
return p[i] + j + 1;
}
break;
}
if (s[j] != p[i][j]) {
break;
}
}
}
}
return 0;
}
static char *Utoa(char p[21], unsigned long x) {
char t;
unsigned long i, a, b;
i = 0;
do {
p[i++] = x % 10 + '0';
x = x / 10;
} while (x > 0);
p[i] = '\0';
if (i) {
for (a = 0, b = i - 1; a < b; ++a, --b) {
t = p[a];
p[a] = p[b];
p[b] = t;
}
}
return p + i;
}
static char *Itoa(char p[21], long x) {
if (x < 0) *p++ = '-', x = -(unsigned long)x;
return Utoa(p, x);
}
#if TROUBLESHOOT
const char *DescribeOs(int os) {
if (os == LINUX) {
return "GNU/SYSTEMD";
} else if (os == XNU) {
return "XNU";
} else if (os == FREEBSD) {
return "FREEBSD";
} else if (os == OPENBSD) {
return "OPENBSD";
} else if (os == NETBSD) {
return "NETBSD";
} else {
return "WUT";
}
}
#endif
__attribute__((__noreturn__)) static void Exit(long rc, int os) {
asm volatile("call\t*%2"
: /* no outputs */
: "a"((os == LINUX ? 60 : 1) | (os == XNU ? 0x2000000 : 0)),
"D"(rc), "m"(syscall)
: "memory");
__builtin_unreachable();
}
static void Close(long fd, int os) {
long ax, di; long ax, di;
asm volatile("syscall" asm volatile("call\t*%4"
: "=a"(ax), "=D"(di) : "=a"(ax), "=D"(di)
: "0"(__NR_close), "1"(fd) : "0"((os == LINUX ? 3 : 6) | (os == XNU ? 0x2000000 : 0)),
"1"(fd), "m"(syscall)
: "rcx", "rdx", "rsi", "r8", "r9", "r10", "r11", "memory", "cc"); : "rcx", "rdx", "rsi", "r8", "r9", "r10", "r11", "memory", "cc");
} }
static long Read(int os, long fd, void *data, unsigned long size) { static long Read(long fd, void *data, unsigned long size, int os) {
bool cf;
long ax, di, si, dx; long ax, di, si, dx;
asm volatile("clc\n\t" asm volatile("call\t*%8"
"syscall" : "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "=@ccc"(cf), "=a"(ax), "=D"(di), "=S"(si), "=d"(dx) : "0"((os == LINUX ? 0 : 3) | (os == XNU ? 0x2000000 : 0)),
: "1"(__NR_read), "2"(fd), "3"(data), "4"(size) "1"(fd), "2"(data), "3"(size), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory"); : "rcx", "r8", "r9", "r10", "r11", "memory");
if (cf) ax = -ax;
return ax; return ax;
} }
static void Write(int os, long fd, const void *data, unsigned long size) { static void Write(long fd, const void *data, unsigned long size, int os) {
long ax, di, si, dx; long ax, di, si, dx;
asm volatile("syscall" asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx) : "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"(__NR_write), "1"(fd), "2"(data), "3"(size) : "0"((os == LINUX ? 1 : 4) | (os == XNU ? 0x2000000 : 0)),
"1"(fd), "2"(data), "3"(size), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc"); : "rcx", "r8", "r9", "r10", "r11", "memory", "cc");
} }
static long Fstat(int os, long fd, union metastat *st) { static void Execve(const char *prog, char **argv, char **envp, int os) {
long ax, di, si; long ax, di, si, dx;
asm volatile("syscall" asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si) : "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"(__NR_fstat), "1"(fd), "2"(st) : "0"((59) | (os == XNU ? 0x2000000 : 0)), "1"(prog), "2"(argv),
: "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc"); "3"(envp), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc");
}
static long Access(const char *path, int mode, int os) {
long ax, dx, di, si;
asm volatile("call\t*%7"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"((os == LINUX ? 21 : 33) | (os == XNU ? 0x2000000 : 0)),
"1"(path), "2"(mode), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc");
return ax; return ax;
} }
static void Msyscall(int os, long p, long n) { static void Msyscall(long p, long n, int os) {
long ax, di, si; long ax, di, si;
if (os == OPENBSD) { if (os == OPENBSD) {
asm volatile("syscall" asm volatile("call\t*%6"
: "=a"(ax), "=D"(di), "=S"(si) : "=a"(ax), "=D"(di), "=S"(si)
: "0"(37), "1"(p), "2"(n) : "0"(37), "1"(p), "2"(n), "m"(syscall)
: "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc"); : "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc");
} }
} }
static long Open(int os, const char *path, long flags, long mode) { static long Open(const char *path, long flags, long mode, int os) {
bool cf;
long ax, di, si, dx; long ax, di, si, dx;
asm volatile("clc\n\t" asm volatile("call\t*%8"
"syscall" : "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "=@ccc"(cf), "=a"(ax), "=D"(di), "=S"(si), "=d"(dx) : "0"((os == LINUX ? 2 : 5) | (os == XNU ? 0x2000000 : 0)),
: "1"(__NR_open), "2"(path), "3"(flags), "4"(mode) "1"(path), "2"(flags), "3"(mode), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory"); : "rcx", "r8", "r9", "r10", "r11", "memory");
if (cf) ax = -ax;
return ax; return ax;
} }
static long Mmap(int os, long addr, long size, long prot, long flags, long fd, __attribute__((__noinline__)) long Mmap(long addr, long size, long prot,
long off) { long flags, long fd, long off, int os) {
bool cf;
long ax; long ax;
register long flags_ asm("r10") = flags; register long flags_ asm("r10") = flags;
register long fd_ asm("r8") = fd; register long fd_ asm("r8") = fd;
register long off_ asm("r9") = off; register long off_ asm("r9") = off;
asm volatile("push\t%%r9\n\t" asm volatile("push\t%%r9\n\t"
"push\t%%r9\n\t" "call\t*%8\n\t"
"clc\n\t"
"syscall\n\t"
"pop\t%%r9\n\t"
"pop\t%%r9" "pop\t%%r9"
: "=@ccc"(cf), "=a"(ax) : "=a"(ax)
: "1"(__NR_mmap), "D"(addr), "S"(size), "d"(prot), "r"(flags_), : "0"((os == LINUX ? 9
"r"(fd_), "r"(off_) : os == FREEBSD ? 477
: 197) |
(os == XNU ? 0x2000000 : 0)),
"D"(addr), "S"(size), "d"(prot), "r"(flags_), "r"(fd_),
"r"(off_), "m"(syscall)
: "rcx", "r11", "memory"); : "rcx", "r11", "memory");
if (cf) ax = -ax;
return ax; return ax;
} }
static size_t GetFdSize(int os, int fd) { static void Emit(int os, const char *s) {
union metastat st; Write(2, s, StrLen(s), os);
if (!Fstat(os, fd, &st)) { }
if (os == LINUX) {
return st.linux.st_size; static void Perror(int os, const char *c, int rc, const char *s) {
} else if (os == FREEBSD) { char ibuf[21];
return st.freebsd.st_size; Emit(os, "ape error: ");
} else if (os == OPENBSD) { Emit(os, c);
return st.openbsd.st_size; Emit(os, ": ");
} else { Emit(os, s);
return st.netbsd.st_size; if (rc) {
Emit(os, " failed errno=");
Itoa(ibuf, -rc);
Emit(os, ibuf);
}
Emit(os, "\n");
}
__attribute__((__noreturn__)) static void Pexit(int os, const char *c, int rc,
const char *s) {
Perror(os, c, rc, s);
Exit(127, os);
}
static int StrCmp(const char *l, const char *r) {
unsigned long i = 0;
while (l[i] == r[i] && r[i]) ++i;
return (l[i] & 255) - (r[i] & 255);
}
static char EndsWithIgnoreCase(const char *p, unsigned long n, const char *s) {
unsigned long i, m;
if (n >= (m = StrLen(s))) {
for (i = n - m; i < n; ++i) {
if (ToLower(p[i]) != *s++) {
return 0;
}
} }
return 1;
} else { } else {
return 0; return 0;
} }
} }
static size_t Length(const char *s) { static char IsComPath(struct PathSearcher *ps) {
size_t n = 0; return EndsWithIgnoreCase(ps->name, ps->namelen, ".com") ||
while (*s++) ++n; EndsWithIgnoreCase(ps->name, ps->namelen, ".exe") ||
return n; EndsWithIgnoreCase(ps->name, ps->namelen, ".com.dbg");
} }
static void Emit(int os, const char *s) { static char AccessCommand(struct PathSearcher *ps, const char *suffix,
Write(os, 2, s, Length(s)); unsigned long pathlen) {
unsigned long suffixlen;
suffixlen = StrLen(suffix);
if (pathlen + 1 + ps->namelen + suffixlen + 1 > sizeof(ps->path)) return 0;
if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/';
MemCpy(ps->path + pathlen, ps->name, ps->namelen);
MemCpy(ps->path + pathlen + ps->namelen, suffix, suffixlen + 1);
return !Access(ps->path, X_OK, ps->os);
} }
static void Log(int os, const char *s) { static char SearchPath(struct PathSearcher *ps, const char *suffix) {
#ifndef NDEBUG const char *p;
Emit(os, "ape loader error: "); unsigned long i;
Emit(os, s); for (p = ps->syspath;;) {
#endif for (i = 0; p[i] && p[i] != ':'; ++i) {
} if (i < sizeof(ps->path)) {
ps->path[i] = p[i];
static void Spawn(int os, int fd, long *sp, char *b, struct Elf64_Ehdr *e) { }
size_t i; }
int prot, flags; if (AccessCommand(ps, suffix, i)) {
long code, codesize; return 1;
struct Elf64_Phdr *p; } else if (p[i] == ':') {
if (e->e_ident[EI_CLASS] != ELFCLASS64) { p += i + 1;
Log(os, "EI_CLASS != ELFCLASS64\n"); } else {
return; return 0;
}
} }
if (e->e_ident[EI_DATA] != ELFDATA2LSB) { }
Log(os, "EI_CLASS != ELFCLASS64\n");
return; static char FindCommand(struct PathSearcher *ps, const char *suffix) {
if (MemChr(ps->name, '/', ps->namelen) ||
MemChr(ps->name, '\\', ps->namelen)) {
ps->path[0] = 0;
return AccessCommand(ps, suffix, 0);
} else {
if (AccessCommand(ps, suffix, 0)) return 1;
}
return SearchPath(ps, suffix);
}
static char *Commandv(struct PathSearcher *ps, int os, const char *name,
const char *syspath) {
ps->os = os;
ps->syspath = syspath ? syspath : "/bin:/usr/local/bin:/usr/bin";
if (!(ps->namelen = StrLen((ps->name = name)))) return 0;
if (ps->namelen + 1 > sizeof(ps->path)) return 0;
if (FindCommand(ps, "") || (!IsComPath(ps) && FindCommand(ps, ".com"))) {
return ps->path;
} else {
return 0;
}
}
__attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd,
long *sp, char *page,
struct ElfEhdr *e) {
long rc;
unsigned long i;
int prot, flags;
struct ElfPhdr *p;
long code, codesize;
if (e->e_type != ET_EXEC) {
Pexit(os, exe, 0, "ELF e_type != ET_EXEC");
} }
if (e->e_machine != EM_NEXGEN32E) { if (e->e_machine != EM_NEXGEN32E) {
Log(os, "e_machine != EM_NEXGEN32E\n"); Pexit(os, exe, 0, "ELF e_machine != EM_NEXGEN32E");
return;
} }
if (e->e_type != ET_EXEC) { if (e->e_ident[EI_CLASS] != ELFCLASS64) {
Log(os, "e_type != ET_EXEC\n"); Pexit(os, exe, 0, "ELF e_ident[EI_CLASS] != ELFCLASS64");
return; }
if (e->e_ident[EI_DATA] != ELFDATA2LSB) {
Pexit(os, exe, 0, "ELF e_ident[EI_DATA] != ELFDATA2LSB");
} }
if (e->e_phoff + e->e_phnum * sizeof(*p) > 0x1000) { if (e->e_phoff + e->e_phnum * sizeof(*p) > 0x1000) {
Log(os, "phnum out of bounds\n"); Pexit(os, exe, 0, "ELF phdrs need to be in first page");
return;
} }
code = 0; code = 0;
codesize = 0; codesize = 0;
for (p = (struct Elf64_Phdr *)(b + e->e_phoff), i = e->e_phnum; i--;) { for (p = (struct ElfPhdr *)(page + e->e_phoff), i = e->e_phnum; i--;) {
if (p[i].p_type == PT_DYNAMIC) {
Pexit(os, exe, 0, "not a real executable");
}
if (p[i].p_type != PT_LOAD) continue; if (p[i].p_type != PT_LOAD) continue;
if ((p[i].p_vaddr | p[i].p_filesz | p[i].p_memsz | p[i].p_offset) & 0xfff) { if ((p[i].p_vaddr | p[i].p_filesz | p[i].p_memsz | p[i].p_offset) & 0xfff) {
Log(os, "ape requires strict page size padding and alignment\n"); Pexit(os, exe, 0, "APE phdrs must be 4096-aligned and 4096-padded");
return;
} }
prot = 0; prot = 0;
flags = MAP_FIXED | MAP_PRIVATE; flags = MAP_FIXED | MAP_PRIVATE;
@ -228,107 +498,189 @@ static void Spawn(int os, int fd, long *sp, char *b, struct Elf64_Ehdr *e) {
codesize = p[i].p_filesz; codesize = p[i].p_filesz;
} }
if (p[i].p_memsz > p[i].p_filesz) { if (p[i].p_memsz > p[i].p_filesz) {
if (Mmap(os, p[i].p_vaddr + p[i].p_filesz, p[i].p_memsz - p[i].p_filesz, if ((rc = Mmap(p[i].p_vaddr + p[i].p_filesz, p[i].p_memsz - p[i].p_filesz,
prot, flags | MAP_ANONYMOUS, -1, 0) < 0) { prot, flags | MAP_ANONYMOUS, -1, 0, os)) < 0) {
Log(os, "bss mmap failed\n"); Pexit(os, exe, rc, "bss mmap()");
return;
} }
} }
if (p[i].p_filesz) { if (p[i].p_filesz) {
if (Mmap(os, p[i].p_vaddr, p[i].p_filesz, prot, flags, fd, if ((rc = Mmap(p[i].p_vaddr, p[i].p_filesz, prot, flags, fd,
p[i].p_offset) < 0) { p[i].p_offset, os)) < 0) {
Log(os, "image mmap failed\n"); Pexit(os, exe, rc, "image mmap()");
return;
} }
} }
} }
Close(os, fd); if (!code) {
Msyscall(os, code, codesize); Pexit(os, exe, 0, "ELF needs PT_LOAD phdr w/ PF_X");
sp[1] = sp[0] - 1; }
++sp; Close(fd, os);
asm volatile("mov\t%2,%%rsp\n\t" Msyscall(code, codesize, os);
"jmpq\t*%1" #if TROUBLESHOOT
Emit(TROUBLESHOOT_OS, "preparing to jump\n");
#endif
register long r8 asm("r8") = syscall;
asm volatile("xor\t%%eax,%%eax\n\t"
"xor\t%%ebx,%%ebx\n\t"
"xor\t%%r9d,%%r9d\n\t"
"xor\t%%r10d,%%r10d\n\t"
"xor\t%%r11d,%%r11d\n\t"
"xor\t%%r12d,%%r12d\n\t"
"xor\t%%r13d,%%r13d\n\t"
"xor\t%%r14d,%%r14d\n\t"
"xor\t%%r15d,%%r15d\n\t"
"mov\t%%rdx,%%rsp\n\t"
"xor\t%%edx,%%edx\n\t"
"push\t%%rsi\n\t"
"xor\t%%esi,%%esi\n\t"
"xor\t%%ebp,%%ebp\n\t"
"ret"
: /* no outputs */ : /* no outputs */
: "D"(os == FREEBSD ? sp : 0), "S"(e->e_entry), "d"(sp) : "D"(os == FREEBSD ? sp : 0), "S"(e->e_entry), "d"(sp), "c"(os),
"r"(r8)
: "memory"); : "memory");
unreachable; __builtin_unreachable();
} }
void loader(long di, long *sp) { __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl,
size_t size; struct ApeLoader *handoff) {
long rc, *auxv; long rc, *auxv;
char *p, **argv; struct ElfEhdr *ehdr;
int c, i, fd, os, argc; int c, i, fd, os, argc;
union { char *p, *exe, *prog, **argv, **envp, *page;
struct Elf64_Ehdr ehdr; static union {
struct ElfEhdr ehdr;
char p[0x1000]; char p[0x1000];
} u; } u;
os = 0;
// detect freebsd
if (di) { if (di) {
os = FREEBSD; os = FREEBSD;
sp = (long *)di; sp = (long *)di;
} else if (dl == XNU) {
os = XNU;
} else {
os = LINUX;
} }
// extract arguments
argc = *sp; argc = *sp;
argv = (char **)(sp + 1); argv = (char **)(sp + 1);
auxv = (long *)(argv + argc + 1); envp = (char **)(sp + 1 + argc + 1);
auxv = (long *)(sp + 1 + argc + 1);
for (;;) { for (;;) {
if (!*auxv++) { if (!*auxv++) {
break; break;
} }
} }
if (!auxv[0]) {
os = OPENBSD; // get syscall function pointer
if (handoff && handoff->syscall) {
syscall = handoff->syscall;
} else {
syscall = __syscall_loader;
} }
for (; auxv[0]; auxv += 2) {
if (!os) { if (handoff) {
if (auxv[0] == AT_EXECFN_NETBSD) { // we were called by ape_execve()
os = NETBSD; // no argument parsing is needed
if (argc > 1) { // no path searching is needed
auxv[1] = (long)argv[1]; exe = handoff->prog;
} fd = handoff->fd;
} else if (auxv[0] == AT_EXECFN_LINUX) { os = handoff->os;
if (argc > 1) { exe = handoff->prog;
auxv[1] = (long)argv[1]; page = handoff->page;
ehdr = (struct ElfEhdr *)handoff->page;
} else {
// detect openbsd
if (!auxv[0]) {
os = OPENBSD;
}
// detect netbsd
if (os == LINUX) {
for (; auxv[0]; auxv += 2) {
if (auxv[0] == AT_EXECFN_NETBSD) {
os = NETBSD;
break;
} }
} }
} }
// we can load via shell, shebang, or binfmt_misc
if (argc >= 3 && !StrCmp(argv[1], "-")) {
// if the first argument is a hyphen then we give the user the
// power to change argv[0] or omit it entirely. most operating
// systems don't permit the omission of argv[0] but we do, b/c
// it's specified by ANSI X3.159-1988.
prog = (char *)sp[3];
argc = sp[3] = sp[0] - 3;
argv = (char **)((sp += 3) + 1);
} else if (argc < 2) {
Emit(os, "usage: ape PROG [ARGV1,ARGV2,...]\n"
" ape - PROG [ARGV0,ARGV1,...]\n"
"αcτµαlly pδrταblε εxεcµταblε loader v1.o\n"
"copyright 2022 justine alexandra roberts tunney\n"
"https://justine.lol/ape.html\n");
Exit(1, os);
} else {
prog = (char *)sp[2];
argc = sp[1] = sp[0] - 1;
argv = (char **)((sp += 1) + 1);
}
if (!(exe = Commandv(&ps, os, prog, GetEnv(envp, "PATH")))) {
Pexit(os, prog, 0, "not found (maybe chmod +x)");
} else if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) {
Pexit(os, exe, fd, "open");
} else if ((rc = Read(fd, u.p, sizeof(u.p), os)) < 0) {
Pexit(os, exe, rc, "read");
} else if (rc != sizeof(u.p) && Read32(u.p) != Read32("\177ELF")) {
Pexit(os, exe, 0, "too small");
}
page = u.p;
ehdr = &u.ehdr;
} }
if (argc < 2) {
Emit(os, "usage: loader PROG [ARGS...]\n"); #if TROUBLESHOOT
} else if ((fd = Open(os, argv[1], O_RDONLY, 0)) < 0) { Emit(TROUBLESHOOT_OS, "os = ");
Log(os, "open failed\n"); Emit(TROUBLESHOOT_OS, DescribeOs(os));
} else if ((rc = Read(os, fd, u.p, sizeof(u.p))) < 0) { Emit(TROUBLESHOOT_OS, "\n");
Log(os, "read failed\n"); for (i = 0; i < argc; ++i) {
} else if (rc != sizeof(u.p)) { Emit(TROUBLESHOOT_OS, "argv = ");
Log(os, "file too small\n"); Emit(TROUBLESHOOT_OS, argv[i]);
} else if (READ32LE(u.p) == READ32LE("\177ELF")) { Emit(TROUBLESHOOT_OS, "\n");
Spawn(os, fd, sp, u.p, &u.ehdr); }
} else { #endif
for (p = u.p; p < u.p + sizeof(u.p); ++p) {
if (READ64LE(p) == READ64LE("printf '")) { if (Read32(page) == Read32("\177ELF") || Read32(page) == 0xFEEDFACE + 1) {
for (i = 0, p += 8; p + 3 < u.p + sizeof(u.p) && (c = *p++) != '\'';) { Close(fd, os);
if (c == '\\') { Execve(exe, argv, envp, os);
}
for (p = page; p < page + sizeof(u.p); ++p) {
if (Read64(p) != Read64("printf '")) continue;
for (i = 0, p += 8; p + 3 < page + sizeof(u.p) && (c = *p++) != '\'';) {
if (c == '\\') {
if ('0' <= *p && *p <= '7') {
c = *p++ - '0';
if ('0' <= *p && *p <= '7') {
c *= 8;
c += *p++ - '0';
if ('0' <= *p && *p <= '7') { if ('0' <= *p && *p <= '7') {
c = *p++ - '0'; c *= 8;
if ('0' <= *p && *p <= '7') { c += *p++ - '0';
c *= 8;
c += *p++ - '0';
if ('0' <= *p && *p <= '7') {
c *= 8;
c += *p++ - '0';
}
}
} }
} }
u.p[i++] = c;
}
if (i >= 64 && READ32LE(u.p) == READ32LE("\177ELF")) {
Spawn(os, fd, sp, u.p, &u.ehdr);
Exit(os, 127);
} }
} }
page[i++] = c;
}
if (i >= 64 && Read32(page) == Read32("\177ELF")) {
Spawn(os, exe, fd, sp, page, ehdr);
} }
Log(os, "could not find printf elf in first page\n");
} }
Exit(os, 127);
Pexit(os, exe, 0, "could not find printf elf in first page");
} }

21
ape/loader.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef COSMOPOLITAN_APE_LOADER_H_
#define COSMOPOLITAN_APE_LOADER_H_
#define APE_LOADER_BASE 0x200000
#define APE_LOADER_SIZE 0x200000
#define APE_LOADER_BSS (PAGESIZE * 2)
#define APE_LOADER_STACK 0x7f0000000000
#define APE_LOADER_ENTRY (APE_LOADER_BASE + 0x47)
#define APE_LOADER_SYSCALL (APE_LOADER_BASE + 0x50)
#define APE_BLOCK_BASE 0x7e0000000000
#define APE_BLOCK_SIZE 0x000200000000
struct ApeLoader {
int fd;
int os;
char *prog;
char *page;
void *syscall;
};
#endif /* COSMOPOLITAN_APE_LOADER_H_ */

View file

@ -24,9 +24,18 @@ SECTIONS {
.text : { .text : {
*(.text) *(.text)
*(.rodata .rodata.*) *(.rodata .rodata.*)
. = ALIGN(4096);
} }
filesz = . - ehdr; filesz = . - ehdr;
.bss ALIGN(4096) : {
bss = .;
*(.bss)
. = ALIGN(4096);
}
memsz = . - ehdr;
/DISCARD/ : { /DISCARD/ : {
*(.*) *(.*)
} }
} }
bsssize = SIZEOF(.bss);

BIN
build/bootstrap/ape Normal file

Binary file not shown.

View file

@ -37,15 +37,14 @@ ERROR
DETAILS DETAILS
Actually Portable Executable assumes stock Linux configuration. Your system has likely been configured to use binfmt_misc and wine.
Normal behavior is non-ELF files with x bit are run by /bin/sh. You need to run the command below which will install a /usr/bin/ape
Linux lets people globally define arbitrary magic interpreters. program and then register it with binfmt_misc. See ape/loader.c for
Your computer couldve been tuned to run MZ scripts inside WINE. source code and technical details.
So if you use binfmt_misc you need to explicitly register this.
WORKAROUND WORKAROUND
sudo sh -c "echo ':APE:M::MZqFpD::/bin/sh:' >/proc/sys/fs/binfmt_misc/register" ape/apeinstall.sh
SEE ALSO SEE ALSO

View file

@ -1,5 +1,12 @@
MZqFpD=123 MZboop=123
exit $MZqFpD exit $MZboop

View file

@ -104,6 +104,14 @@ o/$(MODE)/examples/%.com.dbg: \
$(APE) $(APE)
@$(APELINK) @$(APELINK)
o/$(MODE)/examples/nomodifyself.com.dbg: \
$(EXAMPLES_DEPS) \
o/$(MODE)/examples/nomodifyself.o \
o/$(MODE)/examples/examples.pkg \
$(CRT) \
$(APE_NO_MODIFY_SELF)
@$(APELINK)
o/$(MODE)/examples/hellolua.com.dbg: \ o/$(MODE)/examples/hellolua.com.dbg: \
$(EXAMPLES_DEPS) \ $(EXAMPLES_DEPS) \
o/$(MODE)/examples/hellolua.o \ o/$(MODE)/examples/hellolua.o \

17
examples/mkhello.c Normal file
View file

@ -0,0 +1,17 @@
#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"
int main(int argc, char *argv[]) {
creat("hello.txt", 0644);
write(3, "hello\n", 6);
close(3);
return 0;
}

36
examples/nomodifyself.c Normal file
View file

@ -0,0 +1,36 @@
#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/dce.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
/**
* @fileoverview Non-Self-Modifying APE Binary Demo
*
* See examples/examples.mk for the build config, which uses the
* alternative APE runtime.
*/
int main(int argc, char *argv[]) {
if (_base[0] == 'M' && _base[1] == 'Z') {
printf("success: %s spawned without needing to modify its "
"executable header, thanks to APE loader\n",
argv[0]);
if (!IsWindows()) {
printf(", thanks to APE loader!\n");
} else {
printf(", because you ran it on Windows :P\n");
}
return 0;
} else {
printf("error: %s doesn't have an MZ file header!\n", argv[0]);
return 1;
}
}

View file

@ -64,7 +64,7 @@ noasan texthead uint64_t __new_page(struct mman *mm) {
* Returns pointer to page table entry for page at virtual address. * Returns pointer to page table entry for page at virtual address.
* Additional page tables are allocated if needed as a side-effect. * Additional page tables are allocated if needed as a side-effect.
*/ */
noasan texthead uint64_t *__get_virtual(struct mman *mm, uint64_t *t, noasan textreal uint64_t *__get_virtual(struct mman *mm, uint64_t *t,
int64_t vaddr, bool maketables) { int64_t vaddr, bool maketables) {
uint64_t *e, p; uint64_t *e, p;
unsigned char h; unsigned char h;

34
libc/calls/nanos.c Normal file
View file

@ -0,0 +1,34 @@
/*-*- 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 2022 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/calls/calls.h"
#include "libc/calls/struct/timespec.h"
#include "libc/time/time.h"
/**
* Returns nanoseconds since UNIX epoch.
*/
int128_t _nanos(int timer) {
int128_t nanos;
struct timespec ts;
clock_gettime(timer, &ts);
nanos = ts.tv_sec;
nanos *= 1000000000;
nanos += ts.tv_nsec;
return nanos;
}

10
libc/calls/nanos.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_NANOS_H_
#define COSMOPOLITAN_LIBC_CALLS_NANOS_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int128_t _nanos(int);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_NANOS_H_ */

View file

@ -21,6 +21,7 @@
#include "libc/calls/sigbits.h" #include "libc/calls/sigbits.h"
#include "libc/calls/strace.internal.h" #include "libc/calls/strace.internal.h"
#include "libc/intrin/cmpxchg.h" #include "libc/intrin/cmpxchg.h"
#include "libc/intrin/lockcmpxchg.h"
#include "libc/intrin/spinlock.h" #include "libc/intrin/spinlock.h"
#include "libc/log/libfatal.internal.h" #include "libc/log/libfatal.internal.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
@ -131,9 +132,7 @@ static privileged bool __sig_deliver(bool restartable, int sig, int si_code,
// since sigaction() is @asyncsignalsafe we only restore it if the // since sigaction() is @asyncsignalsafe we only restore it if the
// user didn't change it during the signal handler. we also don't // user didn't change it during the signal handler. we also don't
// need to do anything if this was a oneshot signal or nodefer. // need to do anything if this was a oneshot signal or nodefer.
_spinlock(&__sig_lock); _lockcmpxchg(__sighandrvas + sig, (int32_t)(intptr_t)SIG_DFL, rva);
_cmpxchg(__sighandrvas + sig, (int32_t)(intptr_t)SIG_DFL, rva);
_spunlock(&__sig_lock);
} }
if (!restartable) { if (!restartable) {

View file

@ -26,6 +26,8 @@
// //
// @param rsp is [n,argv₀..argvₙ₋₁,0,envp₀..,0,auxv₀..,0,..] // @param rsp is [n,argv₀..argvₙ₋₁,0,envp₀..,0,auxv₀..,0,..]
// @note FreeBSD is special (see freebsd/lib/csu/amd64/...) // @note FreeBSD is special (see freebsd/lib/csu/amd64/...)
// @note NetBSD will only zero the call-clobbered registers
// @note ape.S and ape-loader both set RCX to XNU on Darwin
// @noreturn // @noreturn
_start: _start:
@ -34,12 +36,16 @@ _start:
test %rdi,%rdi test %rdi,%rdi
cmovnz %rdi,%rsp cmovnz %rdi,%rsp
jz 0f jz 0f
movb $FREEBSD,__hostos(%rip) movb $FREEBSD,%cl
0:
#endif #endif
// set operating system when already detected
mov %cl,__hostos(%rip)
// get startup timestamp as early as possible // get startup timestamp as early as possible
// its used by --strace flag and kprintf() %T // its used by --strace flag and kprintf() %T
0: rdtsc rdtsc
ezlea kStartTsc,bx ezlea kStartTsc,bx
mov %eax,(%rbx) mov %eax,(%rbx)
mov %edx,4(%rbx) mov %edx,4(%rbx)
@ -85,14 +91,3 @@ _start:
call cosmo call cosmo
9: .unreachable 9: .unreachable
.endfn _start,weak,hidden .endfn _start,weak,hidden
#if SupportsXnu()
// Macintosh userspace program entrypoint.
//
// @param rsp is [n,argv₀..argvₙ₋₁,0,envp₀..,0,auxv₀..,0,..]
// @note FreeBSD is special (see freebsd/lib/csu/amd64/...)
// @noreturn
_xnu: movb $XNU,__hostos(%rip)
jmp 0b
.endfn _xnu,weak,hidden
#endif

View file

@ -4,7 +4,7 @@
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
char *sleb64(char *, int64_t); char *sleb64(char *, int64_t);
char *zleb64(char *, int64_t); char *zleb64(char[hasatleast 10], int64_t);
char *uleb64(char[hasatleast 10], uint64_t); char *uleb64(char[hasatleast 10], uint64_t);
int unzleb64(const char *, size_t, int64_t *); int unzleb64(const char *, size_t, int64_t *);
int unuleb64(char *, size_t, uint64_t *); int unuleb64(char *, size_t, uint64_t *);

View file

@ -44,7 +44,7 @@
* @return p + i * @return p + i
* @see unzleb64() * @see unzleb64()
*/ */
char *zleb64(char *p, int64_t x) { char *zleb64(char p[hasatleast 10], int64_t x) {
int c; int c;
uint64_t u; uint64_t u;
u = x; u = x;

View file

@ -58,7 +58,10 @@ struct CloneArgs {
uint32_t utid; uint32_t utid;
int64_t tid64; int64_t tid64;
}; };
int lock; union {
int lock;
void *pstack;
};
int *ctid; int *ctid;
int *ztid; int *ztid;
char *tls; char *tls;
@ -287,12 +290,18 @@ __attribute__((__used__, __no_reorder__))
static privileged wontreturn void static privileged wontreturn void
OpenbsdThreadMain(struct CloneArgs *wt) { OpenbsdThreadMain(struct CloneArgs *wt) {
wt->func(wt->arg); wt->func(wt->arg);
// we no longer use the stack after this point // we no longer use the stack after this point. however openbsd
// validates the rsp register too so a race condition can still
// happen if the parent tries to free the stack. we'll solve it
// by simply changing rsp back to the old value before exiting!
// although ideally there should be a better solution.
//
// void __threxit(%rdi = int32_t *notdead); // void __threxit(%rdi = int32_t *notdead);
asm volatile("movl\t$0,%0\n\t" // *wt->ztid = 0 asm volatile("mov\t%3,%%rsp\n\t"
"movl\t$0,%0\n\t" // *wt->ztid = 0
"syscall" // _Exit1() "syscall" // _Exit1()
: "=m"(*wt->ztid) : "=m"(*wt->ztid)
: "a"(302), "D"(0) : "a"(302), "D"(0), "r"(wt->pstack)
: "rcx", "r11", "memory"); : "rcx", "r11", "memory");
unreachable; unreachable;
} }
@ -307,6 +316,7 @@ static int CloneOpenbsd(int (*func)(void *), char *stk, size_t stksz, int flags,
-alignof(struct CloneArgs)); -alignof(struct CloneArgs));
wt->ctid = flags & CLONE_CHILD_SETTID ? ctid : &wt->tid; wt->ctid = flags & CLONE_CHILD_SETTID ? ctid : &wt->tid;
wt->ztid = flags & CLONE_CHILD_CLEARTID ? ctid : &wt->tid; wt->ztid = flags & CLONE_CHILD_CLEARTID ? ctid : &wt->tid;
wt->pstack = __builtin_frame_address(0);
wt->func = func; wt->func = func;
wt->arg = arg; wt->arg = arg;
params.tf_stack = wt; params.tf_stack = wt;

View file

@ -47,7 +47,7 @@ static struct Ftrace {
int64_t lastaddr; int64_t lastaddr;
} g_ftrace; } g_ftrace;
static privileged int GetNestingLevelImpl(struct StackFrame *frame) { static privileged inline int GetNestingLevelImpl(struct StackFrame *frame) {
int nesting = -2; int nesting = -2;
while (frame) { while (frame) {
++nesting; ++nesting;
@ -56,7 +56,7 @@ static privileged int GetNestingLevelImpl(struct StackFrame *frame) {
return MAX(0, nesting); return MAX(0, nesting);
} }
static privileged int GetNestingLevel(struct StackFrame *frame) { static privileged inline int GetNestingLevel(struct StackFrame *frame) {
int nesting; int nesting;
nesting = GetNestingLevelImpl(frame); nesting = GetNestingLevelImpl(frame);
if (nesting < g_ftrace.skew) g_ftrace.skew = nesting; if (nesting < g_ftrace.skew) g_ftrace.skew = nesting;

View file

@ -9,9 +9,9 @@
#define __BENCH_ARRAY(S) \ #define __BENCH_ARRAY(S) \
_Section(".piro.relo.sort.bench.2." #S ",\"aw\",@init_array #") _Section(".piro.relo.sort.bench.2." #S ",\"aw\",@init_array #")
#define __TEST_PROTOTYPE(S, N, A, K) \ #define __TEST_PROTOTYPE(S, N, A, K) \
void S##_##N(void); \ void S##_##N(void); \
const void *const S##_##N##_ptr[] A(S##_##N) = {S##_##N}; \ testfn_t S##_##N##_ptr[] A(S##_##N) = {S##_##N}; \
testonly K void S##_##N(void) testonly K void S##_##N(void)
#define __TEST_SECTION(NAME, CONTENT) \ #define __TEST_SECTION(NAME, CONTENT) \

View file

@ -23,13 +23,7 @@
#include "libc/testlib/testlib.h" #include "libc/testlib/testlib.h"
void SetUp(void) { void SetUp(void) {
if (getenv("_SUBPROCESS")) { if (getenv("_WEIRDENV")) {
if (!__argv[0]) {
exit(0);
} else {
exit(7);
}
} else if (getenv("_WEIRDENV")) {
for (char **e = environ; *e; ++e) { for (char **e = environ; *e; ++e) {
if (!strcmp(*e, "WEIRD")) { if (!strcmp(*e, "WEIRD")) {
exit(0); exit(0);
@ -39,22 +33,6 @@ void SetUp(void) {
} }
} }
TEST(execve, testWeirdAnsiC89emptyArgv) {
char *prog;
int pid, ws;
if (IsWindows()) return;
if (IsOpenbsd()) return;
prog = GetProgramExecutableName();
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
execve(prog, (char *const[]){0}, (char *const[]){"_SUBPROCESS=1", 0});
_Exit(127);
}
ASSERT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(0, WEXITSTATUS(ws));
}
TEST(execve, testWeirdEnvironmentVariable) { TEST(execve, testWeirdEnvironmentVariable) {
char *prog; char *prog;
int pid, ws; int pid, ws;

View file

@ -3,24 +3,32 @@
PKGS += TEST_LIBC_STR PKGS += TEST_LIBC_STR
TEST_LIBC_STR_SRCS := $(wildcard test/libc/str/*.c) TEST_LIBC_STR_FILES := $(wildcard test/libc/str/*)
TEST_LIBC_STR_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_STR_SRCS)) TEST_LIBC_STR_SRCS_C = $(filter %.c,$(TEST_LIBC_STR_FILES))
TEST_LIBC_STR_SRCS_CC = $(filter %.cc,$(TEST_LIBC_STR_FILES))
TEST_LIBC_STR_SRCS = $(TEST_LIBC_STR_SRCS_C) $(TEST_LIBC_STR_SRCS_CC)
TEST_LIBC_STR_SRCS_TEST_C = $(filter %_test.c,$(TEST_LIBC_STR_FILES))
TEST_LIBC_STR_SRCS_TEST_CC = $(filter %_test.cc,$(TEST_LIBC_STR_FILES))
TEST_LIBC_STR_OBJS = \ TEST_LIBC_STR_OBJS = \
$(TEST_LIBC_STR_SRCS:%.c=o/$(MODE)/%.o) $(TEST_LIBC_STR_SRCS_C:%.c=o/$(MODE)/%.o) \
$(TEST_LIBC_STR_SRCS_CC:%.cc=o/$(MODE)/%.o)
TEST_LIBC_STR_COMS = \ TEST_LIBC_STR_COMS = \
$(TEST_LIBC_STR_SRCS:%.c=o/$(MODE)/%.com) $(TEST_LIBC_STR_SRCS_TEST_C:%.c=o/$(MODE)/%.com) \
$(TEST_LIBC_STR_SRCS_TEST_CC:%.cc=o/$(MODE)/%.com)
TEST_LIBC_STR_BINS = \ TEST_LIBC_STR_BINS = \
$(TEST_LIBC_STR_COMS) \ $(TEST_LIBC_STR_COMS) \
$(TEST_LIBC_STR_COMS:%=%.dbg) $(TEST_LIBC_STR_COMS:%=%.dbg)
TEST_LIBC_STR_TESTS = \ TEST_LIBC_STR_TESTS = \
$(TEST_LIBC_STR_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) $(TEST_LIBC_STR_SRCS_TEST_C:%.c=o/$(MODE)/%.com.ok) \
$(TEST_LIBC_STR_SRCS_TEST_CC:%.cc=o/$(MODE)/%.com.ok)
TEST_LIBC_STR_CHECKS = \ TEST_LIBC_STR_CHECKS = \
$(TEST_LIBC_STR_SRCS_TEST:%.c=o/$(MODE)/%.com.runs) $(TEST_LIBC_STR_SRCS_TEST_C:%.c=o/$(MODE)/%.com.runs) \
$(TEST_LIBC_STR_SRCS_TEST_CC:%.cc=o/$(MODE)/%.com.runs)
TEST_LIBC_STR_DIRECTDEPS = \ TEST_LIBC_STR_DIRECTDEPS = \
LIBC_ALG \ LIBC_ALG \
@ -43,7 +51,9 @@ TEST_LIBC_STR_DIRECTDEPS = \
LIBC_ZIPOS \ LIBC_ZIPOS \
THIRD_PARTY_MBEDTLS \ THIRD_PARTY_MBEDTLS \
THIRD_PARTY_REGEX \ THIRD_PARTY_REGEX \
THIRD_PARTY_ZLIB THIRD_PARTY_ZLIB \
THIRD_PARTY_LIBCXX \
THIRD_PARTY_SMALLZ4
TEST_LIBC_STR_DEPS := \ TEST_LIBC_STR_DEPS := \
$(call uniq,$(foreach x,$(TEST_LIBC_STR_DIRECTDEPS),$($(x)))) $(call uniq,$(foreach x,$(TEST_LIBC_STR_DIRECTDEPS),$($(x))))

View file

@ -620,7 +620,7 @@
/* #undef HAVE__SET_INVALID_PARAMETER_HANDLER */ /* #undef HAVE__SET_INVALID_PARAMETER_HANDLER */
/* Build host information. */ /* Build host information. */
#define MAKE_HOST "x86_64-pc-linux-gnu" #define MAKE_HOST "x86_64-pc-cosmopolitan"
/* Define to 1 to enable job server support in GNU make. */ /* Define to 1 to enable job server support in GNU make. */
/* TODO(jart): make it work */ /* TODO(jart): make it work */

View file

@ -689,15 +689,9 @@ void print_dir_data_base(void) {
if (dir->contents == 0) if (dir->contents == 0)
printf(_("# %s: could not be stat'd.\n"), dir->name); printf(_("# %s: could not be stat'd.\n"), dir->name);
else if (dir->contents->dirfiles.ht_vec == 0) { else if (dir->contents->dirfiles.ht_vec == 0) {
#ifdef WINDOWS32
printf(_("# %s (key %s, mtime %I64u): could not be opened.\n"),
dir->name, dir->contents->path_key,
(unsigned long long)dir->contents->mtime);
#else /* WINDOWS32 */
printf(_("# %s (device %ld, inode %ld): could not be opened.\n"), printf(_("# %s (device %ld, inode %ld): could not be opened.\n"),
dir->name, (long int)dir->contents->dev, dir->name, (long int)dir->contents->dev,
(long int)dir->contents->ino); (long int)dir->contents->ino);
#endif /* WINDOWS32 */
} else { } else {
unsigned int f = 0; unsigned int f = 0;
unsigned int im = 0; unsigned int im = 0;
@ -715,14 +709,8 @@ void print_dir_data_base(void) {
++f; ++f;
} }
} }
#ifdef WINDOWS32
printf(_("# %s (key %s, mtime %I64u): "), dir->name,
dir->contents->path_key,
(unsigned long long)dir->contents->mtime);
#else /* WINDOWS32 */
printf(_("# %s (device %ld, inode %ld): "), dir->name, printf(_("# %s (device %ld, inode %ld): "), dir->name,
(long)dir->contents->dev, (long)dir->contents->ino); (long)dir->contents->dev, (long)dir->contents->ino);
#endif /* WINDOWS32 */
if (f == 0) if (f == 0)
fputs(_("No"), stdout); fputs(_("No"), stdout);
else else
@ -822,9 +810,7 @@ static struct dirent *read_dirstream(__ptr_t stream) {
#ifdef _DIRENT_HAVE_D_NAMLEN #ifdef _DIRENT_HAVE_D_NAMLEN
d->d_namlen = len - 1; d->d_namlen = len - 1;
#endif #endif
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
d->d_type = df->type; d->d_type = df->type;
#endif
memcpy(d->d_name, df->name, len); memcpy(d->d_name, df->name, len);
return d; return d;
} }

View file

@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_THIRD_PARTY_SMALLZ4_SMALLZ4_H_ #ifndef COSMOPOLITAN_THIRD_PARTY_SMALLZ4_SMALLZ4_H_
#define COSMOPOLITAN_THIRD_PARTY_SMALLZ4_SMALLZ4_H_ #define COSMOPOLITAN_THIRD_PARTY_SMALLZ4_SMALLZ4_H_
#include "libc/bits/bits.h"
#include "third_party/libcxx/vector" #include "third_party/libcxx/vector"
/** /**
@ -138,7 +139,7 @@ class smallz4 {
/// return true, if the four bytes at *a and *b match /// return true, if the four bytes at *a and *b match
inline static bool match4(const void* const a, const void* const b) { inline static bool match4(const void* const a, const void* const b) {
return *(const uint32_t*)a == *(const uint32_t*)b; return READ32LE(a) == READ32LE(b);
} }
/// simple hash function, input: 32 bits, output: HashBits bits (by default: /// simple hash function, input: 32 bits, output: HashBits bits (by default:
@ -636,7 +637,7 @@ class smallz4 {
} }
// read next four bytes // read next four bytes
const uint32_t four = *(uint32_t*)(dataBlock + i); const uint32_t four = READ32LE(dataBlock + i);
// convert to a shorter hash // convert to a shorter hash
const uint32_t hash = getHash32(four); const uint32_t hash = getHash32(four);
@ -674,10 +675,9 @@ class smallz4 {
// check the hash chain // check the hash chain
while (true) { while (true) {
// read four bytes // read four bytes
currentFour = currentFour = READ32LE(
*(uint32_t*)(&data[lastHashMatch - &data[lastHashMatch - dataZero]); // match may be found in the
dataZero]); // match may be found in the // previous block, too
// previous block, too
// match chain found, first 4 bytes are identical // match chain found, first 4 bytes are identical
if (currentFour == four) break; if (currentFour == four) break;