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.
.ascii "o=\"$(command -v \"$0\")\"\n"
// Try to use a system-wide APE loader.
.ascii "type ape-loader >/dev/null 2>&1 && "
.ascii "exec ape-loader \"$o\" \"$@\"\n"
.ascii "type ape >/dev/null 2>&1 && "
.ascii "exec ape \"$o\" \"$@\"\n"
#ifdef APE_LOADER
// There is no system-wide APE loader, but there is one
// embedded inside the APE. So if the system is not MacOs,
// extract the loader into a temp folder, and use it to
// load the APE without modifying it.
.ascii "t=\"${TMPDIR:-/tmp}/ape\"\n"
.ascii "if [ ! -x \"$t\" ]; then\n"
.ascii "if [ ! -d /Applications ]; then\n"
.ascii "t=\"${TMPDIR:-/tmp}/ape-loader\"\n"
.ascii "[ -x \"$t\" ] || {\n"
.ascii "dd if=\"$o\" of=\"$t.$$\" skip=\""
.shstub ape_loader_dd_skip,2
.ascii "\" count=\""
.shstub ape_loader_dd_count,2
.ascii "\" bs=64 2>/dev/null &&\n"
.ascii "\" bs=64\n"
#if SupportsXnu() && defined(APE_LOADER_MACHO)
.ascii "else\n"
.ascii "dd if=\"$o\" of=\"$t.$$\" skip=\""
.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 "}\n"
.ascii "exec \"$t\" \"$o\" \"$@\"\n"
.ascii "fi\n"
#endif
.ascii "exec \"$t\" \"$o\" \"$@\"\n"
#endif /* APE_LOADER */
#ifndef APE_NO_MODIFY_SELF
// The default behavior is: to overwrite the header in place.
// 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
.previous
#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() */
#if SupportsSystemv() || SupportsMetal()
@ -856,7 +869,7 @@ ape_macho:
.long (520f-510f)/4 # count
510: .quad 0 # rax
.quad IMAGE_BASE_VIRTUAL # rbx
.quad 0 # rcx
.quad XNU # rcx
.quad 0 # rdx
.quad 0 # rdi
.quad 0 # rsi
@ -870,7 +883,7 @@ ape_macho:
.quad 0 # r13
.quad 0 # r14
.quad 0 # r15
.quad _xnu # rip
.quad _start # rip
.quad 0 # rflags
.quad 0 # cs
.quad 0 # fs

View file

@ -397,6 +397,12 @@ SECTIONS {
KEEP(*(.ape.loader))
. = ALIGN(64);
HIDDEN(ape_loader_end = .);
#if SupportsXnu()
HIDDEN(ape_loader_macho = .);
KEEP(*(.ape.loader-macho))
. = ALIGN(64);
HIDDEN(ape_loader_macho_end = .);
#endif
}
/*END: payload */
/*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_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()
SHSTUB2(ape_macho_dd_skip, RVA(ape_macho) / 8);
SHSTUB2(ape_macho_dd_count, (ape_macho_end - ape_macho) / 8);

View file

@ -48,22 +48,33 @@ o/ape/idata.inc: \
ape/idata.internal.h \
ape/relocations.h
o/$(MODE)/ape/ape-no-modify-self.o: ape/ape.S o/$(MODE)/ape/loader.elf
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -DAPE_LOADER="\"o/$(MODE)/ape/loader.elf\"" -DAPE_NO_MODIFY_SELF $<
o/$(MODE)/ape/ape-no-modify-self.o: \
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
@$(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
@$(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/loader1.o \
o/$(MODE)/ape/loader-elf.o \
ape/loader.lds
@$(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
o/$(MODE)/ape: $(APE) \
$(APE_CHECKS) \
o/$(MODE)/ape/ape \
o/$(MODE)/ape/ape.macho \
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
.word 64 # e_ehsize
.word 56 # e_phentsize
.word 3 # e_phnum
.word 4 # e_phnum
.word 0 # e_shentsize
.word 0 # e_shnum
.word 0 # e_shstrndx
.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
.endobj jg47h
_start: mov %rsp,%rsi
jmp loader
jmp ApeLoader
.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
phdrs: .long PT_LOAD # p_type
.long PF_R|PF_X # p_flags
@ -61,6 +95,16 @@ phdrs: .long PT_LOAD # p_type
.quad filesz # p_filesz
.quad filesz # p_memsz
.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 PF_R|PF_W # p_flags
.quad 0 # p_offset
@ -69,6 +113,7 @@ phdrs: .long PT_LOAD # p_type
.quad 0 # p_filesz
.quad 0 # p_memsz
.quad 16 # p_align
.long PT_NOTE # p_type
.long PF_R # p_flags
.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
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/bits.h"
#include "libc/calls/struct/metastat.internal.h"
#include "libc/calls/struct/stat.h"
#include "libc/elf/def.h"
#include "libc/elf/struct/ehdr.h"
#include "libc/elf/struct/phdr.h"
#include "libc/sysv/consts/prot.h"
#include "ape/loader.h"
#define TROUBLESHOOT 0
#define TROUBLESHOOT_OS LINUX
/**
* @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
* 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
*/
#define LINUX 0
#define FREEBSD 1
#define NETBSD 2
#define OPENBSD 3
#define LINUX 1
#define METAL 2
#define WINDOWS 4
#define XNU 8
#define OPENBSD 16
#define FREEBSD 32
#define NETBSD 64
#define O_RDONLY 0
#define PROT_READ 1
@ -49,170 +95,394 @@
#define MAP_ANONYMOUS (os == LINUX ? 32 : 4096)
#define AT_EXECFN_LINUX 31
#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 __NR_write (os == LINUX ? 1 : 4)
#define __NR_open (os == LINUX ? 2 : 5)
#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)
#define Read32(S) \
((unsigned)(255 & (S)[3]) << 030 | (unsigned)(255 & (S)[2]) << 020 | \
(unsigned)(255 & (S)[1]) << 010 | (unsigned)(255 & (S)[0]) << 000)
static wontreturn void Exit(int os, long rc) {
asm volatile("syscall"
: /* no outputs */
: "a"(__NR_exit), "D"(rc)
: "memory");
unreachable;
#define Read64(S) \
((unsigned long)(255 & (S)[7]) << 070 | \
(unsigned long)(255 & (S)[6]) << 060 | \
(unsigned long)(255 & (S)[5]) << 050 | \
(unsigned long)(255 & (S)[4]) << 040 | \
(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;
asm volatile("syscall"
asm volatile("call\t*%4"
: "=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");
}
static long Read(int os, long fd, void *data, unsigned long size) {
bool cf;
static long Read(long fd, void *data, unsigned long size, int os) {
long ax, di, si, dx;
asm volatile("clc\n\t"
"syscall"
: "=@ccc"(cf), "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "1"(__NR_read), "2"(fd), "3"(data), "4"(size)
asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"((os == LINUX ? 0 : 3) | (os == XNU ? 0x2000000 : 0)),
"1"(fd), "2"(data), "3"(size), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory");
if (cf) ax = -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;
asm volatile("syscall"
asm volatile("call\t*%8"
: "=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");
}
static long Fstat(int os, long fd, union metastat *st) {
long ax, di, si;
asm volatile("syscall"
: "=a"(ax), "=D"(di), "=S"(si)
: "0"(__NR_fstat), "1"(fd), "2"(st)
: "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc");
static void Execve(const char *prog, char **argv, char **envp, int os) {
long ax, di, si, dx;
asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"((59) | (os == XNU ? 0x2000000 : 0)), "1"(prog), "2"(argv),
"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;
}
static void Msyscall(int os, long p, long n) {
static void Msyscall(long p, long n, int os) {
long ax, di, si;
if (os == OPENBSD) {
asm volatile("syscall"
asm volatile("call\t*%6"
: "=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");
}
}
static long Open(int os, const char *path, long flags, long mode) {
bool cf;
static long Open(const char *path, long flags, long mode, int os) {
long ax, di, si, dx;
asm volatile("clc\n\t"
"syscall"
: "=@ccc"(cf), "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "1"(__NR_open), "2"(path), "3"(flags), "4"(mode)
asm volatile("call\t*%8"
: "=a"(ax), "=D"(di), "=S"(si), "=d"(dx)
: "0"((os == LINUX ? 2 : 5) | (os == XNU ? 0x2000000 : 0)),
"1"(path), "2"(flags), "3"(mode), "m"(syscall)
: "rcx", "r8", "r9", "r10", "r11", "memory");
if (cf) ax = -ax;
return ax;
}
static long Mmap(int os, long addr, long size, long prot, long flags, long fd,
long off) {
bool cf;
__attribute__((__noinline__)) long Mmap(long addr, long size, long prot,
long flags, long fd, long off, int os) {
long ax;
register long flags_ asm("r10") = flags;
register long fd_ asm("r8") = fd;
register long off_ asm("r9") = off;
asm volatile("push\t%%r9\n\t"
"push\t%%r9\n\t"
"clc\n\t"
"syscall\n\t"
"pop\t%%r9\n\t"
"call\t*%8\n\t"
"pop\t%%r9"
: "=@ccc"(cf), "=a"(ax)
: "1"(__NR_mmap), "D"(addr), "S"(size), "d"(prot), "r"(flags_),
"r"(fd_), "r"(off_)
: "=a"(ax)
: "0"((os == LINUX ? 9
: 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");
if (cf) ax = -ax;
return ax;
}
static size_t GetFdSize(int os, int fd) {
union metastat st;
if (!Fstat(os, fd, &st)) {
if (os == LINUX) {
return st.linux.st_size;
} else if (os == FREEBSD) {
return st.freebsd.st_size;
} else if (os == OPENBSD) {
return st.openbsd.st_size;
} else {
return st.netbsd.st_size;
static void Emit(int os, const char *s) {
Write(2, s, StrLen(s), os);
}
static void Perror(int os, const char *c, int rc, const char *s) {
char ibuf[21];
Emit(os, "ape error: ");
Emit(os, c);
Emit(os, ": ");
Emit(os, s);
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 {
return 0;
}
}
static size_t Length(const char *s) {
size_t n = 0;
while (*s++) ++n;
return n;
static char IsComPath(struct PathSearcher *ps) {
return EndsWithIgnoreCase(ps->name, ps->namelen, ".com") ||
EndsWithIgnoreCase(ps->name, ps->namelen, ".exe") ||
EndsWithIgnoreCase(ps->name, ps->namelen, ".com.dbg");
}
static void Emit(int os, const char *s) {
Write(os, 2, s, Length(s));
static char AccessCommand(struct PathSearcher *ps, const char *suffix,
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) {
#ifndef NDEBUG
Emit(os, "ape loader error: ");
Emit(os, s);
#endif
static char SearchPath(struct PathSearcher *ps, const char *suffix) {
const char *p;
unsigned long i;
for (p = ps->syspath;;) {
for (i = 0; p[i] && p[i] != ':'; ++i) {
if (i < sizeof(ps->path)) {
ps->path[i] = p[i];
}
}
if (AccessCommand(ps, suffix, i)) {
return 1;
} else if (p[i] == ':') {
p += i + 1;
} else {
return 0;
}
}
}
static void Spawn(int os, int fd, long *sp, char *b, struct Elf64_Ehdr *e) {
size_t i;
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;
struct Elf64_Phdr *p;
if (e->e_ident[EI_CLASS] != ELFCLASS64) {
Log(os, "EI_CLASS != ELFCLASS64\n");
return;
}
if (e->e_ident[EI_DATA] != ELFDATA2LSB) {
Log(os, "EI_CLASS != ELFCLASS64\n");
return;
if (e->e_type != ET_EXEC) {
Pexit(os, exe, 0, "ELF e_type != ET_EXEC");
}
if (e->e_machine != EM_NEXGEN32E) {
Log(os, "e_machine != EM_NEXGEN32E\n");
return;
Pexit(os, exe, 0, "ELF e_machine != EM_NEXGEN32E");
}
if (e->e_type != ET_EXEC) {
Log(os, "e_type != ET_EXEC\n");
return;
if (e->e_ident[EI_CLASS] != ELFCLASS64) {
Pexit(os, exe, 0, "ELF e_ident[EI_CLASS] != ELFCLASS64");
}
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) {
Log(os, "phnum out of bounds\n");
return;
Pexit(os, exe, 0, "ELF phdrs need to be in first page");
}
code = 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_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");
return;
Pexit(os, exe, 0, "APE phdrs must be 4096-aligned and 4096-padded");
}
prot = 0;
flags = MAP_FIXED | MAP_PRIVATE;
@ -228,85 +498,170 @@ static void Spawn(int os, int fd, long *sp, char *b, struct Elf64_Ehdr *e) {
codesize = 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,
prot, flags | MAP_ANONYMOUS, -1, 0) < 0) {
Log(os, "bss mmap failed\n");
return;
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, os)) < 0) {
Pexit(os, exe, rc, "bss mmap()");
}
}
if (p[i].p_filesz) {
if (Mmap(os, p[i].p_vaddr, p[i].p_filesz, prot, flags, fd,
p[i].p_offset) < 0) {
Log(os, "image mmap failed\n");
return;
if ((rc = Mmap(p[i].p_vaddr, p[i].p_filesz, prot, flags, fd,
p[i].p_offset, os)) < 0) {
Pexit(os, exe, rc, "image mmap()");
}
}
}
Close(os, fd);
Msyscall(os, code, codesize);
sp[1] = sp[0] - 1;
++sp;
asm volatile("mov\t%2,%%rsp\n\t"
"jmpq\t*%1"
if (!code) {
Pexit(os, exe, 0, "ELF needs PT_LOAD phdr w/ PF_X");
}
Close(fd, os);
Msyscall(code, codesize, os);
#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 */
: "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");
unreachable;
__builtin_unreachable();
}
void loader(long di, long *sp) {
size_t size;
__attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl,
struct ApeLoader *handoff) {
long rc, *auxv;
char *p, **argv;
struct ElfEhdr *ehdr;
int c, i, fd, os, argc;
union {
struct Elf64_Ehdr ehdr;
char *p, *exe, *prog, **argv, **envp, *page;
static union {
struct ElfEhdr ehdr;
char p[0x1000];
} u;
os = 0;
// detect freebsd
if (di) {
os = FREEBSD;
sp = (long *)di;
} else if (dl == XNU) {
os = XNU;
} else {
os = LINUX;
}
// extract arguments
argc = *sp;
argv = (char **)(sp + 1);
auxv = (long *)(argv + argc + 1);
envp = (char **)(sp + 1 + argc + 1);
auxv = (long *)(sp + 1 + argc + 1);
for (;;) {
if (!*auxv++) {
break;
}
}
// get syscall function pointer
if (handoff && handoff->syscall) {
syscall = handoff->syscall;
} else {
syscall = __syscall_loader;
}
if (handoff) {
// we were called by ape_execve()
// no argument parsing is needed
// no path searching is needed
exe = handoff->prog;
fd = handoff->fd;
os = handoff->os;
exe = handoff->prog;
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 (!os) {
if (auxv[0] == AT_EXECFN_NETBSD) {
os = NETBSD;
if (argc > 1) {
auxv[1] = (long)argv[1];
}
} else if (auxv[0] == AT_EXECFN_LINUX) {
if (argc > 1) {
auxv[1] = (long)argv[1];
break;
}
}
}
}
if (argc < 2) {
Emit(os, "usage: loader PROG [ARGS...]\n");
} else if ((fd = Open(os, argv[1], O_RDONLY, 0)) < 0) {
Log(os, "open failed\n");
} else if ((rc = Read(os, fd, u.p, sizeof(u.p))) < 0) {
Log(os, "read failed\n");
} else if (rc != sizeof(u.p)) {
Log(os, "file too small\n");
} else if (READ32LE(u.p) == READ32LE("\177ELF")) {
Spawn(os, fd, sp, u.p, &u.ehdr);
// 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 {
for (p = u.p; p < u.p + sizeof(u.p); ++p) {
if (READ64LE(p) == READ64LE("printf '")) {
for (i = 0, p += 8; p + 3 < u.p + sizeof(u.p) && (c = *p++) != '\'';) {
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 TROUBLESHOOT
Emit(TROUBLESHOOT_OS, "os = ");
Emit(TROUBLESHOOT_OS, DescribeOs(os));
Emit(TROUBLESHOOT_OS, "\n");
for (i = 0; i < argc; ++i) {
Emit(TROUBLESHOOT_OS, "argv = ");
Emit(TROUBLESHOOT_OS, argv[i]);
Emit(TROUBLESHOOT_OS, "\n");
}
#endif
if (Read32(page) == Read32("\177ELF") || Read32(page) == 0xFEEDFACE + 1) {
Close(fd, os);
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';
@ -320,15 +675,12 @@ void loader(long di, long *sp) {
}
}
}
u.p[i++] = c;
page[i++] = c;
}
if (i >= 64 && READ32LE(u.p) == READ32LE("\177ELF")) {
Spawn(os, fd, sp, u.p, &u.ehdr);
Exit(os, 127);
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)
*(.rodata .rodata.*)
. = ALIGN(4096);
}
filesz = . - ehdr;
.bss ALIGN(4096) : {
bss = .;
*(.bss)
. = ALIGN(4096);
}
memsz = . - ehdr;
/DISCARD/ : {
*(.*)
}
}
bsssize = SIZEOF(.bss);

BIN
build/bootstrap/ape Normal file

Binary file not shown.

View file

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

View file

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

View file

@ -104,6 +104,14 @@ o/$(MODE)/examples/%.com.dbg: \
$(APE)
@$(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: \
$(EXAMPLES_DEPS) \
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.
* 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) {
uint64_t *e, p;
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/strace.internal.h"
#include "libc/intrin/cmpxchg.h"
#include "libc/intrin/lockcmpxchg.h"
#include "libc/intrin/spinlock.h"
#include "libc/log/libfatal.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
// 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.
_spinlock(&__sig_lock);
_cmpxchg(__sighandrvas + sig, (int32_t)(intptr_t)SIG_DFL, rva);
_spunlock(&__sig_lock);
_lockcmpxchg(__sighandrvas + sig, (int32_t)(intptr_t)SIG_DFL, rva);
}
if (!restartable) {

View file

@ -26,6 +26,8 @@
//
// @param rsp is [n,argv₀..argvₙ₋₁,0,envp₀..,0,auxv₀..,0,..]
// @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
_start:
@ -34,12 +36,16 @@ _start:
test %rdi,%rdi
cmovnz %rdi,%rsp
jz 0f
movb $FREEBSD,__hostos(%rip)
movb $FREEBSD,%cl
0:
#endif
// set operating system when already detected
mov %cl,__hostos(%rip)
// get startup timestamp as early as possible
// its used by --strace flag and kprintf() %T
0: rdtsc
rdtsc
ezlea kStartTsc,bx
mov %eax,(%rbx)
mov %edx,4(%rbx)
@ -85,14 +91,3 @@ _start:
call cosmo
9: .unreachable
.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_
char *sleb64(char *, int64_t);
char *zleb64(char *, int64_t);
char *zleb64(char[hasatleast 10], int64_t);
char *uleb64(char[hasatleast 10], uint64_t);
int unzleb64(const char *, size_t, int64_t *);
int unuleb64(char *, size_t, uint64_t *);

View file

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

View file

@ -58,7 +58,10 @@ struct CloneArgs {
uint32_t utid;
int64_t tid64;
};
union {
int lock;
void *pstack;
};
int *ctid;
int *ztid;
char *tls;
@ -287,12 +290,18 @@ __attribute__((__used__, __no_reorder__))
static privileged wontreturn void
OpenbsdThreadMain(struct CloneArgs *wt) {
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);
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()
: "=m"(*wt->ztid)
: "a"(302), "D"(0)
: "a"(302), "D"(0), "r"(wt->pstack)
: "rcx", "r11", "memory");
unreachable;
}
@ -307,6 +316,7 @@ static int CloneOpenbsd(int (*func)(void *), char *stk, size_t stksz, int flags,
-alignof(struct CloneArgs));
wt->ctid = flags & CLONE_CHILD_SETTID ? ctid : &wt->tid;
wt->ztid = flags & CLONE_CHILD_CLEARTID ? ctid : &wt->tid;
wt->pstack = __builtin_frame_address(0);
wt->func = func;
wt->arg = arg;
params.tf_stack = wt;

View file

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

View file

@ -11,7 +11,7 @@
#define __TEST_PROTOTYPE(S, N, A, K) \
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)
#define __TEST_SECTION(NAME, CONTENT) \

View file

@ -23,13 +23,7 @@
#include "libc/testlib/testlib.h"
void SetUp(void) {
if (getenv("_SUBPROCESS")) {
if (!__argv[0]) {
exit(0);
} else {
exit(7);
}
} else if (getenv("_WEIRDENV")) {
if (getenv("_WEIRDENV")) {
for (char **e = environ; *e; ++e) {
if (!strcmp(*e, "WEIRD")) {
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) {
char *prog;
int pid, ws;

View file

@ -3,24 +3,32 @@
PKGS += TEST_LIBC_STR
TEST_LIBC_STR_SRCS := $(wildcard test/libc/str/*.c)
TEST_LIBC_STR_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_STR_SRCS))
TEST_LIBC_STR_FILES := $(wildcard test/libc/str/*)
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_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_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_COMS) \
$(TEST_LIBC_STR_COMS:%=%.dbg)
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_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 = \
LIBC_ALG \
@ -43,7 +51,9 @@ TEST_LIBC_STR_DIRECTDEPS = \
LIBC_ZIPOS \
THIRD_PARTY_MBEDTLS \
THIRD_PARTY_REGEX \
THIRD_PARTY_ZLIB
THIRD_PARTY_ZLIB \
THIRD_PARTY_LIBCXX \
THIRD_PARTY_SMALLZ4
TEST_LIBC_STR_DEPS := \
$(call uniq,$(foreach x,$(TEST_LIBC_STR_DIRECTDEPS),$($(x))))

View file

@ -620,7 +620,7 @@
/* #undef HAVE__SET_INVALID_PARAMETER_HANDLER */
/* 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. */
/* TODO(jart): make it work */

View file

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

View file

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