Improve AARCH64 execution

This change fixes bugs in the APE loader. The execve() unit tests are
now enabled for MODE=aarch64. See the README for how you need to have
binfmt_misc configured with Qemu to run them. Apple Silicon bugs have
been fixed too, e.g. tkill() now works.
This commit is contained in:
Justine Tunney 2023-09-11 13:51:37 -07:00
parent 1965d7488e
commit 77a7873057
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
31 changed files with 599 additions and 195 deletions

View file

@ -97,21 +97,23 @@ endif
endif
.PLEDGE = stdio rpath wpath cpath fattr proc
.UNVEIL = \
libc/integral \
libc/stdbool.h \
libc/disclaimer.inc \
rwc:/dev/shm \
rx:build/bootstrap \
rx:o/third_party/gcc \
r:build/portcosmo.h \
/proc/stat \
rw:/dev/null \
rw:/dev/full \
w:o/stack.log \
/etc/hosts \
~/.runit.psk \
/proc/self/status \
.UNVEIL = \
libc/integral \
libc/stdbool.h \
libc/disclaimer.inc \
rwc:/dev/shm \
rx:build/bootstrap \
rx:o/third_party/gcc \
r:build/portcosmo.h \
/proc/stat \
rw:/dev/null \
rw:/dev/full \
w:o/stack.log \
/etc/hosts \
~/.runit.psk \
/proc/self/status \
rx:/usr/bin/qemu-aarch64 \
rx:o/third_party/qemu/qemu-aarch64 \
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
PKGS =

211
README.md
View file

@ -128,25 +128,7 @@ cosmocc -Os -o hello2.com hello2.c
## ARM
Cosmo supports cross-compiling binaries for machines with ARM
microprocessors. There are two options available for doing this.
The first option is to embed the [blink virtual
machine](https://github.com/jart/blink) by adding the following to the
top of your main.c file:
```c
__static_yoink("blink_linux_aarch64"); // for raspberry pi
__static_yoink("blink_xnu_aarch64"); // is apple silicon
```
The benefit is you'll have single file executables that'll run on both
x86_64 and arm64 platforms. The tradeoff is Blink's JIT is slower than
running natively, but tends to go fast enough, unless you're doing
scientific computing (e.g. running LLMs with
`o//third_party/ggml/llama.com`).
Therefore, the second option is to cross compile aarch64 executables,
by using build modes like the following:
microprocessors. For example:
```sh
make -j8 m=aarch64 o/aarch64/third_party/ggml/llama.com
@ -175,7 +157,196 @@ You can run your ELF AARCH64 executable on Apple Silicon as follows:
ape ./llama.com
```
## Source Builds
If you want to run the `MODE=aarch64` unit tests, you need to have
qemu-aarch64 installed as a binfmt_misc interpreter. It needs to be a
static binary if you want it to work with Landlock Make's security. You
can use the build included in our `third_party/qemu/` folder.
```
doas cp o/third_party/qemu/qemu-aarch64 /usr/bin/qemu-aarch64
doas sh -c "echo ':qemu-aarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-aarch64:CF' > /proc/sys/fs/binfmt_misc/register"
make -j8 m=aarch64
```
Please note that the qemu-aarch64 binfmt_misc interpreter installation
process is *essential* for being able to use the `aarch64-unknown-cosmo`
toolchain to build fat APE binaries on your x86-64 machine.
## AMD64 + ARM64 fat APE binaries
If you've setup the qemu binfmt_misc interpreter, then you can can use
cosmo's toolchains to build fat ape binaries. It works by compiling your
program twice, so you can have a native build for both architectures in
the same file. The two programs are merged together by apelink.com which
also embeds multiple copies of APE loader and multiple symbols tables.
The easiest way to build fat APE is using `fatcosmocc`. This compiler
works by creating a concomitant `.aarch64/foo.o` for every `foo.o` you
compile. The only exception is the C preprocessor mode, which actually
runs x86-64 GCC except with macros like `__x86_64__` undefined.
This toolchain works great for C projects that are written in a portable
way and don't produce architecturue-specific artifacts. One example of a
large project that can be easily built is GNU coreutils.
```sh
cd coreutils
fatcosmocc --update ||exit
./configure CC=fatcosmocc \
AR=fatcosmoar \
INSTALL=$(command -v fatcosmoinstall) \
--prefix=/opt/cosmos \
--disable-nls \
--disable-dependency-tracking \
--disable-silent-rules
make -j8
```
You'll then have a bunch of files like `src/ls` which are fat ape
binaries. If you want to run them on Windows, then you simply need to
rename the file so that it has the `.com` suffix. Better yet, consider
making that a symlink (a.k.a. reparse point). The biggest gotcha with
`fatcosmocc` though is ensuring builds don't strip binaries. For
example, Linux's `install -s` command actually understands Windows'
Portable Executable format well enough to remove the MS-DOS stub, which
is where the APE shell script is stored. You need to ensure that
`fatcosmoinstall` is used instead. Especially if your project needs to
install the libraries built by `fatacosmoar` into `/opt/cosmos`.
## Advanced Fat APE Builds
Once you get seriously involved in creating fat APE builds of software
you're going to eventually outgrow `fatcosmocc`. One example is Emacs
which is trickier to build, because it produces architecture-specific
files, and it also depends on shared files, e.g. zoneinfo. Since we like
having everything in a neat little single-file executable container that
doesn't need an "installation wizard", this tutorial will explain how we
manage to accomplish that.
What you're going to do is, instead of using `fatcosmocc`, you're going
to use both the `x86_64-unknown-cosmo-cc` and `aarch64-unknown-cosmo-cc`
toolchains independently, and then run `apelink` and `zip` to manually
build the final files. But there's a few tricks to learn first.
The first trick is to create a symlink on your system called `/zip`.
Cosmopolitan Libc normally uses that as a synthetic folder that lets you
access the assets in your zip executable. But since that's a read-only
file system, your build system should use the normal one.
```sh
doas ln -sf /opt/cosmos /zip
```
Now create a file named `rebuild-fat.sh` which runs the build twice:
```sh
#!/bin/sh
set -ex
export MODE=aarch64
export COSMOS=/opt/cosmos/aarch64
rebuild-cosmos.sh aarch64
export MODE=
export COSMOS=/opt/cosmos/x86_64
rebuild-cosmos.sh x86_64
wall.com 'finished building'
```
Then create a second file `rebuild-cosmos.sh` which runs your build:
```sh
#!/bin/bash
set -ex
ARCH=${1:-x86_64}
export COSMO=${COSMO:-/opt/cosmo}
export COSMOS=${COSMOS:-/opt/cosmos/$ARCH}
export AS=$(command -v $ARCH-unknown-cosmo-as) || exit
export CC=$(command -v $ARCH-unknown-cosmo-cc) || exit
export CXX=$(command -v $ARCH-unknown-cosmo-c++) || exit
export AR=$(command -v $ARCH-unknown-cosmo-ar) || exit
export STRIP=$(command -v $ARCH-unknown-cosmo-strip) || exit
export INSTALL=$(command -v $ARCH-unknown-cosmo-install) || exit
export OBJCOPY=$(command -v $ARCH-unknown-cosmo-objcopy) || exit
export OBJDUMP=$(command -v $ARCH-unknown-cosmo-objdump) || exit
export ADDR2LINE=$(command -v $ARCH-unknown-cosmo-addr2line) || exit
$CC --update
export COSMOPOLITAN_DISABLE_ZIPOS=1
cd ~/vendor/zlib
./configure --prefix=$COSMOS --static
make clean
make -j
make install
cd ~/vendor/ncurses-6.4
./configure --prefix=$COSMOS --sysconfdir=/zip --datarootdir=/zip/share --exec-prefix=/zip/$ARCH --disable-shared
make clean
make -j
make install
cd ~/vendor/readline-8.2
./configure --prefix=$COSMOS --sysconfdir=/zip --datarootdir=/zip/share --exec-prefix=/zip/$ARCH --disable-shared
make uninstall || true
make clean
make -j
make install
# NOTES:
# 1. You'll need to patch enum { FOO = x } that fails to build into a #define FOO
# 2. You'll need to patch configure.ac so it DOES NOT define USABLE_FIONREAD to 1
# 2. You'll need to patch configure.ac so it DOES NOT define INTERRUPT_INPUT to 1
cd ~/vendor/emacs-28.2
./configure --prefix=$COSMOS --sysconfdir=/zip --datarootdir=/zip/share --exec-prefix=/zip/$ARCH \
--without-x --with-threads --without-gnutls --disable-silent-rules --with-file-notification=no
make uninstall || true
make clean
make -j
make install
```
Once you've completed this build process, you'll have the ELF files
`/opt/cosmos/x86_64/bin/emacs` and `/opt/cosmos/aarch64/bin/emacs`. Your
next move is to combine them into a single pristine `emacs.com` file.
```sh
cd /zip
COSMO=${COSMO:-/opt/cosmo}
mkdir -p /opt/cosmos/bin
apelink \
-o /opt/cosmos/bin/emacs.com \
-l "$COSMO/o//ape/ape.elf" \
-l "$COSMO/o/aarch64/ape/ape.elf" \
-M "$COSMO/ape/ape-m1.c" \
/opt/cosmos/x86_64/bin/emacs \
/opt/cosmos/aarch64/bin/emacs
cd /zip
zip -r /opt/cosmos/bin/emacs.com \
aarch64/libexec \
x86_64/libexec \
share/terminfo \
$(find share/emacs -type f |
grep -v '\.el.gz$' |
grep -v refcards |
grep -v images)
```
You can now scp your `emacs.com` build to seven operating systems for
two distinct kinds of microprocessors without any dependencies. All the
LISP, zoneinfo, and termcap files it needs are stored inside the ZIP
structure of the binary, which has performance that's equivalent to the
Linux filesystem (even though it decompresses artifacts on the fly!) For
this reason, you might actually find that fat APE Emacs goes faster if
you're using an operating system like Windows where files are go slow.
If you like to use Vim instead of Emacs, then you can build that too.
However Vim's build system makes it a bit harder, since it's configured
to always strip binaries. The `apelink` program needs the symbol tables
to still be there when it creates the fat version. Otherwise tools like
`--ftrace` won't work.
## Monolithic Source Builds
Cosmopolitan can be compiled from source on any Linux distro. First, you
need to download or clone the repository.

View file

@ -151,6 +151,7 @@ union ElfPhdrBuf {
};
struct PathSearcher {
int literally;
unsigned long namelen;
const char *name;
const char *syspath;
@ -377,10 +378,27 @@ static char SearchPath(struct PathSearcher *ps, const char *suffix) {
}
static char FindCommand(struct PathSearcher *ps, const char *suffix) {
ps->path[0] = 0;
/* paths are always 100% taken literally when a slash exists
$ ape foo/bar.com arg1 arg2 */
if (MemChr(ps->name, '/', ps->namelen)) {
ps->path[0] = 0;
return AccessCommand(ps, suffix, 0);
}
/* we don't run files in the current directory
$ ape foo.com arg1 arg2
unless $PATH has an empty string entry, e.g.
$ expert PATH=":/bin"
$ ape foo.com arg1 arg2
however we will execute this
$ ape - foo.com foo.com arg1 arg2
because cosmo's execve needs it */
if (ps->literally && AccessCommand(ps, suffix, 0)) {
return 1;
}
/* otherwise search for name on $PATH */
return SearchPath(ps, suffix);
}
@ -825,7 +843,7 @@ int main(int argc, char **argv, char **envp) {
auxv = (long *)(envp + i + 1);
/* interpret command line arguments */
if (argc >= 3 && !StrCmp(argv[1], "-")) {
if ((M->ps.literally = 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
@ -836,7 +854,7 @@ int main(int argc, char **argv, char **envp) {
} else if (argc < 2) {
Emit("usage: ape PROG [ARGV1,ARGV2,...]\n"
" ape - PROG [ARGV0,ARGV1,...]\n"
"actually portable executable loader silicon 1.7\n"
"actually portable executable loader silicon 1.8\n"
"copyright 2023 justine alexandra roberts tunney\n"
"https://justine.lol/ape.html\n");
_exit(1);
@ -881,8 +899,8 @@ int main(int argc, char **argv, char **envp) {
pe = ebuf->buf + rc;
/* resolve argv[0] to reflect path search */
if ((argc > 0 && *prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) ||
!StrCmp(BaseName(prog), argv[0])) {
if (argc > 0 && ((*prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) ||
!StrCmp(BaseName(prog), argv[0]))) {
argv[0] = exe;
}

View file

@ -208,6 +208,7 @@ union ElfPhdrBuf {
struct PathSearcher {
int os;
int literally;
const char *name;
const char *syspath;
unsigned long namelen;
@ -588,10 +589,27 @@ static char SearchPath(struct PathSearcher *ps, const char *suffix) {
}
static char FindCommand(struct PathSearcher *ps, const char *suffix) {
ps->path[0] = 0;
/* paths are always 100% taken literally when a slash exists
$ ape foo/bar.com arg1 arg2 */
if (MemChr(ps->name, '/', ps->namelen)) {
ps->path[0] = 0;
return AccessCommand(ps, suffix, 0);
}
/* we don't run files in the current directory
$ ape foo.com arg1 arg2
unless $PATH has an empty string entry, e.g.
$ expert PATH=":/bin"
$ ape foo.com arg1 arg2
however we will execute this
$ ape - foo.com foo.com arg1 arg2
because cosmo's execve needs it */
if (ps->literally && AccessCommand(ps, suffix, 0)) {
return 1;
}
/* otherwise search for name on $PATH */
return SearchPath(ps, suffix);
}
@ -915,6 +933,7 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
char dl) {
int rc, n;
unsigned i;
char literally;
const char *ape;
int c, fd, os, argc;
struct ApeLoader *M;
@ -989,7 +1008,7 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
}
/* we can load via shell, shebang, or binfmt_misc */
if (argc >= 3 && !StrCmp(argv[1], "-")) {
if ((literally = 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
@ -1009,6 +1028,7 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
/* allocate loader memory in program's arg block */
n = sizeof(struct ApeLoader);
M = (struct ApeLoader *)__builtin_alloca(n);
M->ps.literally = literally;
/* create new bottom of stack for spawned program
system v abi aligns this on a 16-byte boundary
@ -1045,8 +1065,8 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
pe = ebuf->buf + rc;
/* change argv[0] to resolved path if it's ambiguous */
if ((argc > 0 && *prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) ||
!StrCmp(BaseName(prog), argv[0])) {
if (argc > 0 && ((*prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) ||
!StrCmp(BaseName(prog), argv[0]))) {
argv[0] = exe;
}

View file

@ -49,6 +49,7 @@ for x in .ape \
.ape-1.5 \
.ape-1.6 \
.ape-1.7 \
.ape-1.8 \
.ape-blink-0.9.2 \
.ape-blink-1.0.0; do
rm -f \

View file

@ -59,6 +59,7 @@
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#ifdef __x86_64__
#define keywords textwindows dontasan dontubsan dontinstrument
@ -275,3 +276,5 @@ static keywords void sys_execve_nt_relay(intptr_t h, long b, long c, long d) {
__imp_ExitProcess(dwExitCode);
__builtin_unreachable();
}
#endif /* __x86_64__ */

View file

@ -17,15 +17,21 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "ape/ape.h"
#include "libc/atomic.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/execve-sysv.internal.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/execve.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/magnumstrs.internal.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/limits.h"
#include "libc/mem/alloca.h"
#include "libc/paths.h"
@ -36,77 +42,100 @@
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/errfuns.h"
static bool CanExecute(const char *path) {
return !sys_faccessat(AT_FDCWD, path, X_OK, 0);
}
#define ELIBBAD_LINUX 80
#define EBADEXEC_XNU 85
#define EBADARCH_XNU 86
bool IsAPEMagic(char buf[8]) {
return READ64LE(buf) == READ64LE("MZqFpD='") ||
READ64LE(buf) == READ64LE("JTqFpD='");
}
static struct {
atomic_uint once;
const char *home;
const char *tmpdir;
} g_execve;
static bool IsApeBinary(const char *path) {
int fd;
char buf[8];
bool res = false;
// TODO(jart): Should we block signals too?
BLOCK_CANCELLATIONS;
if ((fd = sys_openat(AT_FDCWD, path, O_RDONLY, 0)) != -1) {
res = sys_read(fd, buf, 8) == 8 && IsAPEMagic(buf);
sys_close(fd);
static bool IsApeFile(const char *path) {
if (!endswith(path, ".com")) {
return true;
} else {
bool res = false;
BLOCK_CANCELLATIONS;
BEGIN_CANCELLATION_POINT;
int fd;
char buf[8];
int flags = O_RDONLY | O_NOCTTY | O_NONBLOCK | O_CLOEXEC;
if ((fd = sys_openat(AT_FDCWD, path, flags, 0)) != -1) {
res = sys_pread(fd, buf, 8, 0, 0) == 8 && IsApeLoadable(buf);
sys_close(fd);
}
END_CANCELLATION_POINT;
ALLOW_CANCELLATIONS;
return res;
}
ALLOW_CANCELLATIONS;
return res;
}
static const char *Join(const char *a, const char *b, char buf[PATH_MAX]) {
size_t n, m;
n = strlen(a);
m = strlen(b);
if (n + 1 + m + 1 < PATH_MAX) {
stpcpy(stpcpy(stpcpy(buf, a), "/"), b);
return buf;
} else {
return "";
if (a && *a) {
n = strlen(a);
m = strlen(b);
if (n + m + 1 < PATH_MAX) {
stpcpy(stpcpy(buf, a), b);
return buf;
}
}
return 0;
}
static void RetryExecve(const char *prog, char **argv, char *const *envp) {
if ((argv[0] = (char *)prog)) {
STRACE("execve(%#s, %s) due to %s", prog, DescribeStringList(argv),
_strerrno(errno));
__sys_execve(prog, argv, envp);
}
}
int sys_execve(const char *prog, char *const argv[], char *const envp[]) {
size_t i;
int e, rc;
char *buf;
char **shargs;
const char *ape;
e = errno;
__sys_execve(prog, argv, envp);
if (errno == ENOEXEC) {
for (i = 0; argv[i];) ++i;
buf = alloca(PATH_MAX);
shargs = alloca((i + 4) * sizeof(char *));
if (IsApeBinary(prog) &&
(CanExecute((ape = "/usr/bin/ape")) ||
CanExecute((ape = Join(firstnonnull(getenv("TMPDIR"),
firstnonnull(getenv("HOME"), ".")),
".ape-" APE_VERSION_STR, buf))) ||
CanExecute((ape = Join(firstnonnull(getenv("HOME"), "."),
".ape-" APE_VERSION_STR, buf))))) {
shargs[0] = (char *)ape;
shargs[1] = (char *)"-";
shargs[2] = (char *)prog;
memcpy(shargs + 3, argv, (i + 1) * sizeof(char *));
errno = e;
rc = __sys_execve(shargs[0], shargs, envp);
} else if (CanExecute(prog)) {
shargs[0] = _PATH_BSHELL;
shargs[1] = (char *)prog;
memcpy(shargs + 2, argv + 1, i * sizeof(char *));
errno = e;
rc = __sys_execve(shargs[0], shargs, envp);
} else {
rc = enoexec();
}
} else {
rc = -1;
}
return rc;
static void SetupExecve(void) {
g_execve.home = getenv("HOME");
g_execve.tmpdir = getenv("TMPDIR");
}
__attribute__((__constructor__)) static void InitExecve(void) {
cosmo_once(&g_execve.once, SetupExecve);
}
int sys_execve(const char *prog, char *const argv[], char *const envp[]) {
// try kernel
// this also checks execute bit
__sys_execve(prog, argv, envp);
if (!(errno == ENOEXEC || (IsLinux() && errno == ELIBBAD_LINUX))) {
return -1;
}
// allocate memory
int argc;
for (argc = 0; argv[argc];) ++argc;
char **shargs = alloca((argc + 4) * sizeof(char *));
// try ape loader
if (IsApeFile(prog)) {
shargs[1] = (char *)"-";
shargs[2] = (char *)prog;
memcpy(shargs + 3, argv, (argc + 1) * sizeof(char *));
RetryExecve("/usr/bin/ape", shargs, envp);
char *buf = alloca(PATH_MAX);
const char *name = "/.ape-" APE_VERSION_STR;
InitExecve();
RetryExecve(Join(g_execve.tmpdir, name, buf), shargs, envp);
RetryExecve(Join(g_execve.home, name, buf), shargs, envp);
RetryExecve(Join(".", name, buf), shargs, envp);
}
// try bourne shell
shargs[0] = _PATH_BSHELL;
shargs[1] = (char *)prog;
memcpy(shargs + 2, argv + 1, argc * sizeof(char *));
RetryExecve(shargs[0], shargs, envp);
enoexec();
return -1;
}

View file

@ -64,7 +64,7 @@ int execve(const char *prog, char *const argv[], char *const envp[]) {
!__asan_is_valid_strlist(envp)))) {
rc = efault();
} else {
STRACE("execve(%#s, %s, %s) → ...", prog, DescribeStringList(argv),
STRACE("execve(%#s, %s, %s)", prog, DescribeStringList(argv),
DescribeStringList(envp));
rc = 0;
if (IsLinux() && __execpromises && _weaken(sys_pledge_linux)) {

View file

@ -3,7 +3,9 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
bool IsAPEMagic(char[8]);
void __execve_lock(void);
void __execve_unlock(void);
bool IsApeLoadable(char[8]);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -21,7 +21,7 @@
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/execve-sysv.internal.h"
#include "libc/calls/execve.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/stat.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
@ -30,6 +30,7 @@
#include "libc/fmt/itoa.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
@ -46,7 +47,7 @@
static bool IsAPEFd(const int fd) {
char buf[8];
return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsAPEMagic(buf);
return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsApeLoadable(buf);
}
static int fexecve_impl(const int fd, char *const argv[], char *const envp[]) {
@ -141,7 +142,7 @@ static int fd_to_mem_fd(const int infd, char *path) {
ssize_t readRc;
readRc = pread(infd, space, st.st_size, 0);
bool success = readRc != -1;
if (success && (st.st_size > 8) && IsAPEMagic(space)) {
if (success && (st.st_size > 8) && IsApeLoadable(space)) {
int flags = fcntl(fd, F_GETFD);
if ((success = (flags != -1) &&
(fcntl(fd, F_SETFD, flags & (~FD_CLOEXEC)) != -1) &&

View file

@ -93,9 +93,9 @@ static inline void GetProgramExecutableNameImpl(char *p, char *e) {
// if argv[0] exists then turn it into an absolute path. we also try
// adding a .com suffix since the ape auto-appends it when resolving
if (((q = __argv[0]) && !sys_faccessat(AT_FDCWD, q, F_OK, 0)) ||
((q = StrCat(u.path, __argv[0], ".com")) &&
!sys_faccessat(AT_FDCWD, q, F_OK, 0))) {
if ((q = __argv[0]) && ((!sys_faccessat(AT_FDCWD, q, F_OK, 0)) ||
((q = StrCat(u.path, __argv[0], ".com")) &&
!sys_faccessat(AT_FDCWD, q, F_OK, 0)))) {
if (*q != '/') {
if (q[0] == '.' && q[1] == '/') {
q += 2;

29
libc/calls/isapemagic.c Normal file
View file

@ -0,0 +1,29 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/intrin/bits.h"
/**
* Returns true if executable image is supported by APE Loader.
*/
bool IsApeLoadable(char buf[8]) {
return READ32LE(buf) == READ32LE("\177ELF") ||
READ64LE(buf) == READ64LE("MZqFpD='") ||
READ64LE(buf) == READ64LE("JTqFpD='");
}

View file

@ -34,6 +34,7 @@
#include "libc/nt/struct/securityattributes.h"
#include "libc/nt/struct/startupinfo.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
struct SpawnBlock {
union {
@ -108,3 +109,5 @@ textwindows int ntspawn(
if (handle) CloseHandle(handle);
return __fix_enotdir(rc, prog16);
}
#endif /* __x86_64__ */

View file

@ -32,6 +32,7 @@
#include "libc/nt/runtime.h"
#include "libc/nt/struct/context.h"
#include "libc/nt/thread.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
@ -97,10 +98,17 @@ static dontinline textwindows int __tkill_nt(int tid, int sig,
}
}
static int __tkill_m1(int tid, int sig, struct CosmoTib *tib) {
struct PosixThread *pt = (struct PosixThread *)__get_tls()->tib_pthread;
return __syslib->pthread_kill(pt->next, sig);
}
// OpenBSD has an optional `tib` parameter for extra safety.
int __tkill(int tid, int sig, void *tib) {
int rc;
if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) {
if (IsXnuSilicon()) {
return __tkill_m1(tid, sig, tib);
} else if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) {
rc = sys_tkill(tid, sig, tib);
} else if (IsWindows()) {
rc = __tkill_nt(tid, sig, tib);

View file

@ -406,11 +406,9 @@ static bool __asan_is_mapped(int x) {
// xxx: we can't lock because no reentrant locks yet
int i;
bool res;
struct MemoryIntervals *m;
__mmi_lock();
m = _weaken(_mmi);
i = __find_memory(m, x);
res = i < m->i && x >= m->p[i].x;
i = __find_memory(&_mmi, x);
res = i < _mmi.i && x >= _mmi.p[i].x;
__mmi_unlock();
return res;
}
@ -902,7 +900,7 @@ static __wur __asan_die_f *__asan_report(const void *addr, int size,
p = __asan_format_section(p, _etext, _edata, ".data", addr);
p = __asan_format_section(p, _end, _edata, ".bss", addr);
__mmi_lock();
for (m = _weaken(_mmi), i = 0; i < m->i; ++i) {
for (m = &_mmi, i = 0; i < m->i; ++i) {
x = m->p[i].x;
y = m->p[i].y;
p = __asan_format_interval(p, x << 16, (y << 16) + (FRAMESIZE - 1));
@ -1396,7 +1394,7 @@ void __asan_map_shadow(uintptr_t p, size_t n) {
kprintf("error: %p size %'zu overlaps shadow space\n", p, n);
_Exit(1);
}
m = _weaken(_mmi);
m = &_mmi;
a = (0x7fff8000 + (p >> 3)) >> 16;
b = (0x7fff8000 + (p >> 3) + (n >> 3) + 0xffff) >> 16;
for (; a <= b; a += i) {
@ -1413,12 +1411,11 @@ void __asan_map_shadow(uintptr_t p, size_t n) {
addr = (void *)ADDR_32_TO_48(a);
prot = PROT_READ | PROT_WRITE;
flag = MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS;
sm = _weaken(sys_mmap)(addr, size, prot, flag, -1, 0);
sm = sys_mmap(addr, size, prot, flag, -1, 0);
if (sm.addr == MAP_FAILED ||
_weaken(__track_memory)(m, a, a + i - 1, sm.maphandle,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, false,
false, 0, size) == -1) {
__track_memory(m, a, a + i - 1, sm.maphandle, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, false, false, 0,
size) == -1) {
kprintf("error: could not map asan shadow memory\n");
__asan_die()();
__asan_unreachable();
@ -1539,9 +1536,6 @@ void __asan_init(int argc, char **argv, char **envp, unsigned long *auxv) {
__write_str("error: asan binaries require windows10\r\n");
_Exit(0); /* So `make MODE=dbg test` passes w/ Windows7 */
}
REQUIRE(_mmi);
REQUIRE(sys_mmap);
REQUIRE(__track_memory);
if (_weaken(hook_malloc) || _weaken(hook_calloc) || _weaken(hook_realloc) ||
_weaken(hook_realloc_in_place) || _weaken(hook_free) ||
_weaken(hook_malloc_usable_size)) {

View file

@ -18,7 +18,6 @@
*/
#include "libc/intrin/describeflags.internal.h"
// TODO(jart): Fork this function into ASAN and non-ASAN versions.
const char *DescribeFlags(char *p, size_t n, const struct DescribeFlags *d,
size_t m, const char *prefix, unsigned x) {
bool t;

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/runtime/memtrack.internal.h"
dontasan unsigned __find_memory(const struct MemoryIntervals *mm, int x) {
@ -31,6 +30,5 @@ dontasan unsigned __find_memory(const struct MemoryIntervals *mm, int x) {
r = m;
}
}
unassert(l == mm->i || x <= mm->p[l].y);
return l;
}

View file

@ -487,6 +487,10 @@ static errno_t CloneSilicon(int (*fn)(void *, int), char *stk, size_t stksz,
if (!(res = __syslib->pthread_create(&th, 0, SiliconThreadMain, wt)) &&
(flags & CLONE_PARENT_SETTID)) {
*ptid = tid;
if (flags & CLONE_SETTLS) {
struct CosmoTib *tib = tls;
tib[-1].tib_pthread = th;
}
}
return res;
}

View file

@ -16,7 +16,7 @@ COSMOPOLITAN_C_START_
*/
#define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24)
#define SYSLIB_VERSION 1
#define SYSLIB_VERSION 2
typedef uint64_t dispatch_time_t;
typedef uint64_t dispatch_semaphore_t;
@ -42,6 +42,8 @@ struct Syslib {
long (*dispatch_semaphore_signal)(dispatch_semaphore_t);
long (*dispatch_semaphore_wait)(dispatch_semaphore_t, dispatch_time_t);
dispatch_time_t (*dispatch_walltime)(const struct timespec *, int64_t);
/* v2 (2023-09-10) */
long (*pthread_self)(void);
};
extern struct Syslib *__syslib;

View file

@ -89,6 +89,8 @@ dos kNtErrorUnexpNetErr ECONNABORTED
dos kNtErrorWorkingSetQuota ENOMEM
dos kNtErrorWriteProtect EACCES
dos kNtErrorWrongDisk EACCES
dos kNtErrorExeMarkedInvalid ENOEXEC
dos kNtErrorExeMachineTypeMismatch ENOEXEC
dos WSAEACCES EACCES
dos WSAEDISCON EPIPE
dos WSAEFAULT EFAULT

View file

@ -0,0 +1,15 @@
// generated by libc/sysv/dos2errno.sh
#include "libc/nt/errors.h"
#ifndef __x86_64__
.end
#endif
.macro .e doscode systemv
.short \doscode
.long \systemv
.endm
.section .sort.rodata.dos2errno.2,"a",@progbits
.globl kDos2Errno.ENOEXEC
.type kDos2Errno.ENOEXEC,@object
kDos2Errno.ENOEXEC:
.e kNtErrorExeMarkedInvalid,ENOEXEC
.e kNtErrorExeMachineTypeMismatch,ENOEXEC

View file

@ -17,6 +17,8 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/o.h"
#include "libc/testlib/testlib.h"
@ -31,9 +33,24 @@ __static_yoink("zipos");
*/
void testlib_extract(const char *zip, const char *to, int mode) {
int fdin, fdout;
ASSERT_NE(-1, (fdin = open(zip, O_RDONLY)));
ASSERT_NE(-1, (fdout = creat(to, mode)));
ASSERT_NE(-1, copyfd(fdin, fdout, -1));
ASSERT_NE(-1, close(fdout));
ASSERT_NE(-1, close(fdin));
if ((fdin = open(zip, O_RDONLY)) == -1) {
perror(zip);
exit(1);
}
if ((fdout = creat(to, mode)) == -1) {
perror(to);
exit(1);
}
if (copyfd(fdin, fdout, -1) == -1) {
perror(zip);
exit(1);
}
if (close(fdout)) {
perror(to);
exit(1);
}
if (close(fdin)) {
perror(zip);
exit(1);
}
}

View file

@ -79,6 +79,7 @@ struct PosixThread {
char *tls; // bottom of tls allocation
struct CosmoTib *tib; // middle of tls allocation
struct Dll list; // list of threads
pthread_t next; // for xnu silicon
jmp_buf exiter; // for pthread_exit
pthread_attr_t attr;
struct _pthread_cleanup_buffer *cleanup;

View file

@ -78,6 +78,7 @@ static int PosixThread(void *arg, int tid) {
}
// set long jump handler so pthread_exit can bring control back here
if (!setjmp(pt->exiter)) {
pt->next = __get_tls()->tib_pthread;
__get_tls()->tib_pthread = (pthread_t)pt;
unassert(!sigprocmask(SIG_SETMASK, (sigset_t *)pt->attr.__sigmask, 0));
rc = pt->start(pt->arg);

View file

@ -31,8 +31,6 @@
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"
#ifdef __x86_64__
#define N 16
char *GenBuf(char buf[8], int x) {
@ -69,6 +67,7 @@ TEST(execve, testArgPassing) {
}
TEST(execve, ziposELF) {
if (1) return; // TODO: rewrite
if (IsFreebsd()) return; // TODO: fixme on freebsd
if (IsLinux() && !__is_linux_2_6_23()) return; // TODO: fixme on old linux
if (!IsLinux() && !IsFreebsd()) {
@ -83,6 +82,7 @@ TEST(execve, ziposELF) {
}
TEST(execve, ziposAPE) {
if (1) return; // TODO: rewrite
if (IsFreebsd()) return; // TODO: fixme on freebsd
if (IsLinux() && !__is_linux_2_6_23()) return; // TODO: fixme on old linux
if (!IsLinux() && !IsFreebsd()) {
@ -150,5 +150,3 @@ BENCH(execve, bench) {
EZBENCH2("execve", donothing, ExecveTinyElf(path));
unlink(path);
}
#endif

View file

@ -72,7 +72,6 @@ TEST(siocgifconf, test) {
ASSERT_NE(-1, close(socketfd));
}
#ifdef __x86_64__
TEST(siocgifconf, mkntenvblock_systemroot) {
if (__argc != 1) return;
SPAWN(fork);
@ -81,7 +80,6 @@ TEST(siocgifconf, mkntenvblock_systemroot) {
abort();
EXITS(0);
}
#endif
TEST(fionread, pipe) {
int pfds[2];

View file

@ -89,7 +89,6 @@ __attribute__((__constructor__)) static void init(void) {
}
}
#ifdef __x86_64__
TEST(sched_setaffinity, isInheritedAcrossExecve) {
cpu_set_t x;
CPU_ZERO(&x);
@ -104,7 +103,6 @@ TEST(sched_setaffinity, isInheritedAcrossExecve) {
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(42, WEXITSTATUS(ws));
}
#endif /* __x86_64__ */
TEST(sched_getaffinity, getpid) {
cpu_set_t x, y;

View file

@ -90,7 +90,7 @@ o/$(MODE)/test/libc/mem/prog/life.elf: \
o/$(MODE)/test/libc/mem/prog/life.elf
@$(COMPILE) -wAASSIMILATE -T$@ \
$(VM) \
o/$(MODE)/tool/build/assimilate.com -cf \
o/$(MODE)/tool/build/assimilate.com -bcef \
o/$(MODE)/test/libc/mem/prog/life.elf
o/$(MODE)/test/libc/mem/prog/life.elf.zip.o: private \
@ -99,6 +99,12 @@ o/$(MODE)/test/libc/mem/prog/life.elf.zip.o: private \
################################################################################
o/$(MODE)/test/libc/mem/prog/life.com.zip.o: private \
ZIPOBJ_FLAGS += \
-B
################################################################################
o/$(MODE)/test/libc/mem/prog/sock.com.dbg: \
$(LIBC_RUNTIME) \
$(LIBC_SOCK) \
@ -117,7 +123,7 @@ o/$(MODE)/test/libc/mem/prog/sock.elf: \
o/$(MODE)/test/libc/mem/prog/sock.elf
@$(COMPILE) -wAASSIMILATE -T$@ \
$(VM) \
o/$(MODE)/tool/build/assimilate.com -cf \
o/$(MODE)/tool/build/assimilate.com -cef \
o/$(MODE)/test/libc/mem/prog/sock.elf
o/$(MODE)/test/libc/mem/prog/sock.elf.zip.o: private \

View file

@ -17,14 +17,20 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/stdio/posix_spawn.h"
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/stat.h"
#include "libc/dce.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/limits.h"
#include "libc/mem/gc.h"
#include "libc/mem/gc.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/memtrack.internal.h"
@ -33,15 +39,52 @@
#include "libc/str/str.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/rusage.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/thread.h"
#include "third_party/nsync/mu.h"
#ifdef __x86_64__
const char kTinyLinuxExit[128] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, // ⌂ELF☻☺☺ 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, // ☻ > ☺   
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // x @     
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @       
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, //     @ 8 
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ☺       
0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // ☺   ♣   
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, //   @     
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, //   @     
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Ç       
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Ç       
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  ►      
0x6a, 0x2a, 0x5f, 0x6a, 0x3c, 0x58, 0x0f, 0x05, // j*_j<X☼♣
};
char testlib_enable_tmp_setup_teardown;
char *GetHost(void) {
static char host[256];
unassert(!gethostname(host, sizeof(host)));
return host;
}
long GetRss(void) {
struct rusage ru;
unassert(!getrusage(RUSAGE_SELF, &ru));
return ru.ru_maxrss * 1024;
}
long GetSize(const char *path) {
struct stat st;
unassert(!stat(path, &st));
return st.st_size;
}
__attribute__((__constructor__)) static void init(void) {
switch (atoi(nulltoempty(getenv("THE_DOGE")))) {
case 42:
@ -51,7 +94,7 @@ __attribute__((__constructor__)) static void init(void) {
}
}
TEST(posix_spawn, test) {
TEST(posix_spawn, self) {
int ws, pid;
char *prog = GetProgramExecutableName();
char *args[] = {prog, NULL};
@ -62,6 +105,31 @@ TEST(posix_spawn, test) {
EXPECT_EQ(42, WEXITSTATUS(ws));
}
TEST(posix_spawn, ape) {
int ws, pid;
char *prog = "./life.com";
char *args[] = {prog, 0};
char *envs[] = {0};
testlib_extract("/zip/life.com", prog, 0755);
ASSERT_EQ(0, posix_spawn(&pid, prog, NULL, NULL, args, envs));
ASSERT_NE(-1, waitpid(pid, &ws, 0));
ASSERT_TRUE(WIFEXITED(ws));
ASSERT_EQ(42, WEXITSTATUS(ws));
}
TEST(posix_spawn, elf) {
if (IsXnu() || IsWindows() || IsMetal()) return;
int ws, pid;
char *prog = "./life.elf"; // assimilate -bcef
char *args[] = {prog, 0};
char *envs[] = {0};
testlib_extract("/zip/life.elf", prog, 0755);
ASSERT_EQ(0, posix_spawn(&pid, prog, NULL, NULL, args, envs));
ASSERT_NE(-1, waitpid(pid, &ws, 0));
ASSERT_TRUE(WIFEXITED(ws));
ASSERT_EQ(42, WEXITSTATUS(ws));
}
TEST(posix_spawn, pipe) {
char buf[10];
int p[2], pid, status;
@ -151,51 +219,62 @@ TEST(posix_spawn, agony) {
}
}
/*
* BEST LINUX FORK+EXEC+EXIT+WAIT PERFORMANCE
* The fastest it can go with fork() is 40µs
* The fastest it can go with vfork() is 26µs
*/
////////////////////////////////////////////////////////////////////////////////
void BenchmarkProcessLifecycle(void) {
void ForkExecveWait(const char *prog) {
int ws;
if (!fork()) {
execve(prog, (char *[]){(char *)prog, 0}, (char *[]){0});
_Exit(127);
}
ASSERT_NE(-1, wait(&ws));
ASSERT_EQ(42, WEXITSTATUS(ws));
}
void VforkExecveWait(const char *prog) {
int ws;
if (!vfork()) {
execve(prog, (char *[]){(char *)prog, 0}, (char *[]){0});
_Exit(127);
}
ASSERT_NE(-1, wait(&ws));
ASSERT_EQ(42, WEXITSTATUS(ws));
}
void PosixSpawnWait(const char *prog) {
int ws, pid;
char *prog = "tiny64";
char *args[] = {"tiny64", NULL};
char *envs[] = {NULL};
char *args[] = {(char *)prog, 0};
char *envs[] = {0};
ASSERT_EQ(0, posix_spawn(&pid, prog, NULL, NULL, args, envs));
ASSERT_NE(-1, waitpid(pid, &ws, 0));
ASSERT_TRUE(WIFEXITED(ws));
ASSERT_EQ(42, WEXITSTATUS(ws));
}
const char kTinyLinuxExit[128] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, // ⌂ELF☻☺☺ 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, // ☻ > ☺   
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // x @     
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @       
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, //     @ 8 
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ☺       
0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // ☺   ♣   
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, //   @     
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, //   @     
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Ç       
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Ç       
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  ►      
0x6a, 0x2a, 0x5f, 0x6a, 0x3c, 0x58, 0x0f, 0x05, // j*_j<X☼♣
};
/* BENCH(spawn, bench) { */
/* int fd; */
/* if (IsLinux()) { */
/* fd = open("tiny64", O_CREAT | O_TRUNC | O_WRONLY, 0755); */
/* write(fd, kTinyLinuxExit, 128); */
/* close(fd); */
/* EZBENCH2("spawn", donothing, BenchmarkProcessLifecycle()); */
/* unlink("tiny64"); */
/* } */
/* } */
#endif /* __x86_64__ */
TEST(posix_spawn, bench) {
long n = 128L * 1000 * 1000;
memset(gc(malloc(n)), -1, n);
creat("tiny64", 0755);
write(3, kTinyLinuxExit, 128);
close(3);
testlib_extract("/zip/life.com", "life.com", 0755);
testlib_extract("/zip/life.elf", "life.elf", 0755);
kprintf("%s %s (MODE=" MODE
" rss=%'zu tiny64=%'zu life.com=%'zu life.elf=%'zu)\n",
__describe_os(), GetHost(), GetRss(), GetSize("tiny64"),
GetSize("life.com"), GetSize("life.elf"));
ForkExecveWait("./life.com");
EZBENCH2("posix_spawn life.com", donothing, PosixSpawnWait("./life.com"));
EZBENCH2("vfork life.com", donothing, VforkExecveWait("./life.com"));
EZBENCH2("fork life.com", donothing, ForkExecveWait("./life.com"));
if (IsXnu() || IsWindows() || IsMetal()) return;
EZBENCH2("posix_spawn life.elf", donothing, PosixSpawnWait("./life.elf"));
EZBENCH2("vfork life.elf", donothing, VforkExecveWait("./life.elf"));
EZBENCH2("fork life.elf", donothing, ForkExecveWait("./life.elf"));
#ifdef __x86_64__
if (!IsLinux()) return;
EZBENCH2("posix_spawn tiny64", donothing, PosixSpawnWait("tiny64"));
EZBENCH2("vfork tiny64", donothing, VforkExecveWait("tiny64"));
EZBENCH2("fork tiny64", donothing, ForkExecveWait("tiny64"));
#endif
}

View file

@ -85,11 +85,16 @@ o/$(MODE)/test/libc/stdio/popen_test.com.dbg: \
$(APE_NO_MODIFY_SELF)
@$(APELINK)
o/$(MODE)/test/libc/stdio/posix_spawn_test.com.runs: \
private QUOTA += -M8192m
o/$(MODE)/test/libc/stdio/posix_spawn_test.com.dbg: \
$(TEST_LIBC_STDIO_DEPS) \
o/$(MODE)/test/libc/stdio/posix_spawn_test.o \
o/$(MODE)/test/libc/stdio/stdio.pkg \
o/$(MODE)/tool/build/echo.com.zip.o \
o/$(MODE)/test/libc/mem/prog/life.com.zip.o \
o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \
$(LIBC_TESTMAIN) \
$(CRT) \
$(APE_NO_MODIFY_SELF)

View file

@ -396,7 +396,7 @@ void Recv(struct Client *client, void *output, size_t outputsize) {
}
}
void SendProgramOutut(struct Client *client) {
void SendProgramOutput(struct Client *client) {
if (client->output) {
SendOutputFragmentMessage(kRunitStderr, client->output,
appendz(client->output).i);
@ -570,7 +570,7 @@ RetryOnEtxtbsyRaceCondition:
WARNF("killing %d %s and hanging up %d due to interrupt", client->fd,
exename, client->pid);
HangupClientAndTerminateJob:
SendProgramOutut(client);
SendProgramOutput(client);
mbedtls_ssl_close_notify(&ezssl);
TerminateJob:
PrintProgramOutput(client);
@ -687,7 +687,7 @@ WaitAgain:
AppendResourceReport(&client->output, &rusage, "\n");
PrintProgramOutput(client);
}
SendProgramOutut(client);
SendProgramOutput(client);
SendExitMessage(exitcode);
mbedtls_ssl_close_notify(&ezssl);
if (etxtbsy_tries) {