mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
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:
parent
1965d7488e
commit
77a7873057
31 changed files with 599 additions and 195 deletions
2
Makefile
2
Makefile
|
@ -112,6 +112,8 @@ endif
|
|||
/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
211
README.md
|
@ -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.
|
||||
|
|
28
ape/ape-m1.c
28
ape/ape-m1.c
|
@ -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) {
|
||||
if (MemChr(ps->name, '/', ps->namelen)) {
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
28
ape/loader.c
28
ape/loader.c
|
@ -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) {
|
||||
if (MemChr(ps->name, '/', ps->namelen)) {
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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) {
|
||||
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];
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *Join(const char *a, const char *b, char buf[PATH_MAX]) {
|
||||
size_t n, m;
|
||||
if (a && *a) {
|
||||
n = strlen(a);
|
||||
m = strlen(b);
|
||||
if (n + 1 + m + 1 < PATH_MAX) {
|
||||
stpcpy(stpcpy(stpcpy(buf, a), "/"), b);
|
||||
if (n + m + 1 < PATH_MAX) {
|
||||
stpcpy(stpcpy(buf, a), b);
|
||||
return buf;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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[]) {
|
||||
size_t i;
|
||||
int e, rc;
|
||||
char *buf;
|
||||
char **shargs;
|
||||
const char *ape;
|
||||
e = errno;
|
||||
|
||||
// try kernel
|
||||
// this also checks execute bit
|
||||
__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;
|
||||
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, (i + 1) * sizeof(char *));
|
||||
errno = e;
|
||||
rc = __sys_execve(shargs[0], shargs, envp);
|
||||
} else if (CanExecute(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, i * sizeof(char *));
|
||||
errno = e;
|
||||
rc = __sys_execve(shargs[0], shargs, envp);
|
||||
} else {
|
||||
rc = enoexec();
|
||||
}
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
memcpy(shargs + 2, argv + 1, argc * sizeof(char *));
|
||||
RetryExecve(shargs[0], shargs, envp);
|
||||
|
||||
enoexec();
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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) */
|
|
@ -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) &&
|
||||
|
|
|
@ -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)) ||
|
||||
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))) {
|
||||
!sys_faccessat(AT_FDCWD, q, F_OK, 0)))) {
|
||||
if (*q != '/') {
|
||||
if (q[0] == '.' && q[1] == '/') {
|
||||
q += 2;
|
||||
|
|
29
libc/calls/isapemagic.c
Normal file
29
libc/calls/isapemagic.c
Normal 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='");
|
||||
}
|
|
@ -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__ */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
15
libc/sysv/dos2errno/ENOEXEC.S
Normal file
15
libc/sysv/dos2errno/ENOEXEC.S
Normal 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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue