Write more runtime tests and fix bugs

This change adds tests for the new memory manager code particularly with
its windows support. Function call tracing now works reliably on Silicon
since our function hooker was missing new Apple self-modifying code APIs

Many tests that were disabled a long time ago on aarch64 are reactivated
by this change, now that arm support is on equal terms with x86. There's
been a lot of places where ftrace could cause deadlocks, which have been
hunted down across all platforms thanks to new tests. A bug in Windows's
kill() function has been identified.
This commit is contained in:
Justine Tunney 2025-01-01 22:25:22 -08:00
parent 0b3c81dd4e
commit f24c854b28
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
45 changed files with 550 additions and 872 deletions

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/proc/ntspawn.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/strace.h"

View file

@ -190,7 +190,9 @@ textstartup void __init_fds(int argc, char **argv, char **envp) {
map->prot = PROT_READ | PROT_WRITE;
map->flags = MAP_SHARED | MAP_ANONYMOUS;
map->hand = shand;
__maps_lock();
__maps_insert(map);
__maps_unlock();
}
}
}

View file

@ -112,6 +112,13 @@ void __maps_init(void) {
}
bool __maps_held(void) {
return !__tls_enabled || (__get_tls()->tib_flags & TIB_FLAG_VFORKED) ||
MUTEX_OWNER(
atomic_load_explicit(&__maps.lock.word, memory_order_relaxed)) ==
atomic_load_explicit(&__get_tls()->tib_ptid, memory_order_relaxed);
}
bool __maps_reentrant(void) {
return __tls_enabled && !(__get_tls()->tib_flags & TIB_FLAG_VFORKED) &&
MUTEX_OWNER(
atomic_load_explicit(&__maps.lock.word, memory_order_relaxed)) ==

View file

@ -84,6 +84,7 @@ void __maps_init(void);
void __maps_lock(void);
void __maps_check(void);
void __maps_unlock(void);
bool __maps_reentrant(void);
void *__maps_randaddr(void);
void __maps_add(struct Map *);
void __maps_free(struct Map *);
@ -103,28 +104,28 @@ forceinline optimizespeed int __maps_search(const void *key,
return (addr > map->addr) - (addr < map->addr);
}
dontinstrument static inline struct Map *__maps_next(struct Map *map) {
static inline struct Map *__maps_next(struct Map *map) {
struct Tree *node;
if ((node = tree_next(&map->tree)))
return MAP_TREE_CONTAINER(node);
return 0;
}
dontinstrument static inline struct Map *__maps_prev(struct Map *map) {
static inline struct Map *__maps_prev(struct Map *map) {
struct Tree *node;
if ((node = tree_prev(&map->tree)))
return MAP_TREE_CONTAINER(node);
return 0;
}
dontinstrument static inline struct Map *__maps_first(void) {
static inline struct Map *__maps_first(void) {
struct Tree *node;
if ((node = tree_first(__maps.maps)))
return MAP_TREE_CONTAINER(node);
return 0;
}
dontinstrument static inline struct Map *__maps_last(void) {
static inline struct Map *__maps_last(void) {
struct Tree *node;
if ((node = tree_last(__maps.maps)))
return MAP_TREE_CONTAINER(node);

View file

@ -49,7 +49,7 @@
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#define MMDEBUG 1
#define MMDEBUG 0
#define MAX_SIZE 0x0ff800000000ul
#define MAP_FIXED_NOREPLACE_linux 0x100000
@ -94,8 +94,11 @@ privileged optimizespeed struct Map *__maps_floor(const char *addr) {
}
static bool __maps_overlaps(const char *addr, size_t size) {
struct Map *map, *floor = __maps_floor(addr);
for (map = floor; map && map->addr <= addr + size; map = __maps_next(map))
struct Map *map;
ASSERT(__maps_held());
if (!(map = __maps_floor(addr)))
map = __maps_first();
for (; map && map->addr <= addr + size; map = __maps_next(map))
if (MAX(addr, map->addr) <
MIN(addr + PGUP(size), map->addr + PGUP(map->size)))
return true;
@ -105,30 +108,33 @@ static bool __maps_overlaps(const char *addr, size_t size) {
// returns true if all fragments of all allocations which overlap
// [addr,addr+size) are completely contained by [addr,addr+size).
textwindows static bool __maps_envelops(const char *addr, size_t size) {
struct Map *map, *next;
struct Map *map;
size = PGUP(size);
ASSERT(__maps_held());
if (!(map = __maps_floor(addr)))
if (!(map = __maps_first()))
return true;
do {
if (MAX(addr, map->addr) >= MIN(addr + size, map->addr + map->size))
break; // didn't overlap mapping
if (!__maps_isalloc(map))
return false; // didn't include first fragment of alloc
if (addr > map->addr)
return false; // excluded leading pages of first fragment
// set map to last fragment in allocation
for (; (next = __maps_next(map)) && !__maps_isalloc(next); map = next)
// fragments within an allocation must be perfectly contiguous
ASSERT(map->addr + map->size == next->addr);
if (addr + size < map->addr + PGUP(map->size))
return false; // excluded trailing pages of allocation
} while ((map = next));
map = __maps_first();
while (map && map->addr <= addr + size) {
if (MAX(addr, map->addr) < MIN(addr + size, map->addr + map->size)) {
if (!__maps_isalloc(map))
return false; // didn't include first fragment of alloc
if (addr > map->addr)
return false; // excluded leading pages of first fragment
struct Map *next; // set map to last fragment in allocation
for (; (next = __maps_next(map)) && !__maps_isalloc(next); map = next)
ASSERT(map->addr + map->size == next->addr); // contiguous
if (addr + size < map->addr + PGUP(map->size))
return false; // excluded trailing pages of allocation
map = next;
} else {
map = __maps_next(map);
}
}
return true;
}
void __maps_check(void) {
#if MMDEBUG
ASSERT(__maps_held());
size_t maps = 0;
size_t pages = 0;
static unsigned mono;
@ -152,6 +158,22 @@ void __maps_check(void) {
#endif
}
#if MMDEBUG
static void __maps_ok(void) {
ASSERT(!__maps_reentrant());
__maps_lock();
__maps_check();
__maps_unlock();
}
__attribute__((__constructor__)) static void __maps_ctor(void) {
atexit(__maps_ok);
__maps_ok();
}
__attribute__((__destructor__)) static void __maps_dtor(void) {
__maps_ok();
}
#endif
static int __muntrack(char *addr, size_t size, struct Map **deleted,
struct Map **untracked, struct Map temp[2]) {
int rc = 0;
@ -159,13 +181,14 @@ static int __muntrack(char *addr, size_t size, struct Map **deleted,
struct Map *map;
struct Map *next;
size = PGUP(size);
ASSERT(__maps_held());
if (!(map = __maps_floor(addr)))
map = __maps_first();
for (; map && map->addr <= addr + size; map = next) {
next = __maps_next(map);
char *map_addr = map->addr;
size_t map_size = map->size;
if (!(MAX(addr, map_addr) < MIN(addr + size, map_addr + PGUP(map_size))))
if (MAX(addr, map_addr) >= MIN(addr + size, map_addr + PGUP(map_size)))
continue;
if (addr <= map_addr && addr + size >= map_addr + PGUP(map_size)) {
if (map->hand == MAPS_RESERVATION)
@ -350,6 +373,7 @@ static bool __maps_mergeable(const struct Map *x, const struct Map *y) {
void __maps_insert(struct Map *map) {
struct Map *left, *right;
ASSERT(map->size);
ASSERT(__maps_held());
ASSERT(!__maps_overlaps(map->addr, map->size));
__maps.pages += (map->size + __pagesize - 1) / __pagesize;
@ -460,7 +484,7 @@ static int __munmap(char *addr, size_t size) {
return einval();
// test for signal handler tragedy
if (__maps_held())
if (__maps_reentrant())
return edeadlk();
// lock the memory manager
@ -469,8 +493,10 @@ static int __munmap(char *addr, size_t size) {
// on windows we can only unmap whole allocations
if (IsWindows())
if (!__maps_envelops(addr, size))
if (!__maps_envelops(addr, size)) {
__maps_unlock();
return enotsup();
}
// untrack mappings
int rc;
@ -500,6 +526,7 @@ void *__maps_randaddr(void) {
}
static void *__maps_pickaddr(size_t size) {
ASSERT(__maps_held());
char *addr = 0;
struct Map *map, *prev;
size = GRUP(size);
@ -569,11 +596,15 @@ static void *__mmap_impl(char *addr, size_t size, int prot, int flags, int fd,
noreplace = true;
sysflags |= MAP_FIXED_NOREPLACE_linux;
} else if (IsFreebsd() || IsNetbsd()) {
// todo: insert a reservation like windows
sysflags |= MAP_FIXED;
__maps_lock();
if (__maps_overlaps(addr, size)) {
__maps_unlock();
__maps_free(map);
return (void *)eexist();
}
__maps_unlock();
} else {
noreplace = true;
}
@ -729,7 +760,7 @@ static void *__mmap(char *addr, size_t size, int prot, int flags, int fd,
return (void *)enomem();
// test for signal handler reentry
if (__maps_held())
if (__maps_reentrant())
return (void *)edeadlk();
// create memory mappping
@ -874,7 +905,7 @@ static void *__mremap(char *old_addr, size_t old_size, size_t new_size,
return (void *)enomem();
// test for signal handler reentry
if (__maps_held())
if (__maps_reentrant())
return (void *)edeadlk();
// lock the memory manager

View file

@ -67,16 +67,17 @@ int __mprotect(char *addr, size_t size, int prot) {
size = (size + pagesz - 1) & -pagesz;
// test for signal handler reentry
if (__maps_held())
if (__maps_reentrant())
return edeadlk();
// change mappings
int rc = 0;
bool found = false;
__maps_lock();
struct Map *map, *floor;
floor = __maps_floor(addr);
for (map = floor; map && map->addr <= addr + size; map = __maps_next(map)) {
struct Map *map;
if (!(map = __maps_floor(addr)))
map = __maps_first();
for (; map && map->addr <= addr + size; map = __maps_next(map)) {
char *map_addr = map->addr;
size_t map_size = map->size;
char *beg = MAX(addr, map_addr);
@ -85,7 +86,7 @@ int __mprotect(char *addr, size_t size, int prot) {
continue;
found = true;
if (addr <= map_addr && addr + size >= map_addr + PGUP(map_size)) {
// change protection of entire mapping
// change protection status of pages
if (!__mprotect_chunk(map_addr, map_size, prot, map->iscow)) {
map->prot = prot;
} else {

View file

@ -31,32 +31,29 @@ textwindows int sys_msync_nt(char *addr, size_t size, int flags) {
if ((uintptr_t)addr & (__pagesize - 1))
return einval();
if (__maps_held())
if (__maps_reentrant())
return edeadlk();
int rc = 0;
__maps_lock();
struct Map *map, *next;
struct Map *next, *map;
if (!(map = __maps_floor(addr)))
if (!(map = __maps_first()))
return true;
for (; map; map = next) {
map = __maps_first();
for (; map && map->addr <= addr + size; map = next) {
next = __maps_next(map);
if (!__maps_isalloc(map))
continue;
if (map->flags & MAP_ANONYMOUS)
continue;
if (MAX(addr, map->addr) >= MIN(addr + size, map->addr + map->size))
break; // didn't overlap mapping
continue; // didn't overlap mapping
// get true size of win32 allocation
size_t allocsize = map->size;
for (struct Map *map2 = next; map2; map2 = __maps_next(map2)) {
if (!__maps_isalloc(map2) && map->addr + allocsize == map2->addr) {
allocsize += map2->size;
} else {
break;
}
while (next && !__maps_isalloc(next) &&
next->addr + allocsize == next->addr) {
allocsize += next->size;
next = __maps_next(next);
}
// perform the flush

View file

@ -47,6 +47,7 @@
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/auxv.h"

View file

@ -82,11 +82,7 @@ void ShowCrashReports(void) {
ss.ss_sp = crashstack;
unassert(!sigaltstack(&ss, 0));
InstallCrashHandler(SIGQUIT, 0);
#ifdef __x86_64__
InstallCrashHandler(SIGTRAP, 0);
#else
InstallCrashHandler(SIGTRAP, 0);
#endif
InstallCrashHandler(SIGFPE, 0);
InstallCrashHandler(SIGILL, 0);
InstallCrashHandler(SIGBUS, 0);

View file

@ -18,6 +18,7 @@
*/
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/intrin/directmap.h"

View file

@ -24,7 +24,6 @@
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/creationdisposition.h"
@ -84,6 +83,23 @@ textwindows int sys_kill_nt(int pid, int sig) {
}
}
// attempt to signal via shared memory file
//
// now that we know the process exists, if it has a shared memory file
// then we can be reasonably certain it's a cosmo process which should
// be trusted to deliver its signal, unless it's a nine exterminations
if (pid > 0 && sig != 9) {
atomic_ulong *sigproc;
if ((sigproc = __sig_map_process(pid, kNtOpenExisting))) {
if (sig > 0)
atomic_fetch_or_explicit(sigproc, 1ull << (sig - 1),
memory_order_release);
UnmapViewOfFile(sigproc);
if (sig != 9)
return 0;
}
}
// find existing handle we own for process
//
// this step should come first to verify process existence. this is
@ -91,31 +107,9 @@ textwindows int sys_kill_nt(int pid, int sig) {
// file exists, the process actually exists.
int64_t handle, closeme = 0;
if (!(handle = __proc_handle(pid))) {
if ((handle = OpenProcess(kNtProcessTerminate, false, pid))) {
STRACE("warning: kill() using raw win32 pid");
closeme = handle;
} else {
goto OnError;
}
}
// attempt to signal via shared memory file
//
// now that we know the process exists, if it has a shared memory file
// then we can be reasonably certain it's a cosmo process which should
// be trusted to deliver its signal, unless it's a nine exterminations
if (pid > 0) {
atomic_ulong *sigproc;
if ((sigproc = __sig_map_process(pid, kNtOpenExisting))) {
if (sig > 0)
atomic_fetch_or_explicit(sigproc, 1ull << (sig - 1),
memory_order_release);
UnmapViewOfFile(sigproc);
if (closeme)
CloseHandle(closeme);
if (sig != 9)
return 0;
}
if (!(handle = OpenProcess(kNtProcessTerminate, false, pid)))
return eperm();
closeme = handle;
}
// perform actual kill
@ -127,16 +121,7 @@ textwindows int sys_kill_nt(int pid, int sig) {
CloseHandle(closeme);
if (ok)
return 0;
// handle error
OnError:
switch (GetLastError()) {
case kNtErrorInvalidHandle:
case kNtErrorInvalidParameter:
return esrch();
default:
return eperm();
}
return esrch();
}
#endif /* __x86_64__ */

View file

@ -112,7 +112,7 @@ __msabi extern typeof(GetCurrentThreadId) *const __imp_GetCurrentThreadId;
__msabi extern typeof(TlsSetValue) *const __imp_TlsSetValue;
__msabi extern typeof(WakeByAddressAll) *const __imp_WakeByAddressAll;
static textwindows dontinstrument wontreturn void //
textwindows dontinstrument wontreturn static void //
WinThreadEntry(int rdi, // rcx
int rsi, // rdx
int rdx, // r8
@ -185,7 +185,7 @@ asm("XnuThreadThunk:\n\t"
".size\tXnuThreadThunk,.-XnuThreadThunk");
__attribute__((__used__))
static wontreturn void
static dontinstrument wontreturn void
XnuThreadMain(void *pthread, // rdi
int tid, // rsi
int (*func)(void *arg, int tid), // rdx
@ -265,7 +265,7 @@ static errno_t CloneXnu(int (*fn)(void *), char *stk, size_t stksz, int flags,
// we can't use address sanitizer because:
// 1. __asan_handle_no_return wipes stack [todo?]
relegated static wontreturn void OpenbsdThreadMain(void *p) {
relegated dontinstrument wontreturn static void OpenbsdThreadMain(void *p) {
struct CloneArgs *wt = p;
atomic_init(wt->ptid, wt->tid);
atomic_init(wt->ctid, wt->tid);
@ -318,11 +318,12 @@ relegated errno_t CloneOpenbsd(int (*func)(void *, int), char *stk,
////////////////////////////////////////////////////////////////////////////////
// NET BESIYATA DISHMAYA
static wontreturn void NetbsdThreadMain(void *arg, // rdi
int (*func)(void *, int), // rsi
int flags, // rdx
atomic_int *ctid, // rcx
atomic_int *ptid) { // r8
wontreturn dontinstrument static void NetbsdThreadMain(
void *arg, // rdi
int (*func)(void *, int), // rsi
int flags, // rdx
atomic_int *ctid, // rcx
atomic_int *ptid) { // r8
int ax, dx;
static atomic_int clobber;
atomic_int *ztid = &clobber;
@ -420,7 +421,7 @@ static int CloneNetbsd(int (*func)(void *, int), char *stk, size_t stksz,
////////////////////////////////////////////////////////////////////////////////
// FREE BESIYATA DISHMAYA
static wontreturn void FreebsdThreadMain(void *p) {
wontreturn dontinstrument static void FreebsdThreadMain(void *p) {
struct CloneArgs *wt = p;
#ifdef __aarch64__
asm volatile("mov\tx28,%0" : /* no outputs */ : "r"(wt->tls));
@ -519,7 +520,7 @@ static errno_t CloneFreebsd(int (*func)(void *, int), char *stk, size_t stksz,
////////////////////////////////////////////////////////////////////////////////
// APPLE SILICON
static void *SiliconThreadMain(void *arg) {
dontinstrument static void *SiliconThreadMain(void *arg) {
struct CloneArgs *wt = arg;
asm volatile("mov\tx28,%0" : /* no outputs */ : "r"(wt->tls));
atomic_init(wt->ctid, wt->this);
@ -595,7 +596,7 @@ int sys_clone_linux(int flags, // rdi
void *func, // r9
void *arg); // 8(rsp)
static int LinuxThreadEntry(void *arg, int tid) {
dontinstrument static int LinuxThreadEntry(void *arg, int tid) {
struct LinuxCloneArgs *wt = arg;
#if defined(__x86_64__)
sys_set_tls(ARCH_SET_GS, wt->tls);

View file

@ -119,6 +119,7 @@ privileged int __hook(void *dest, struct SymbolTable *st) {
if (!st)
return -1;
__morph_begin();
__jit_begin();
lowest = MAX((intptr_t)__executable_start, (intptr_t)_ereal);
for (i = 0; i < st->count; ++i) {
if (st->symbols[i].x < 9)
@ -138,6 +139,9 @@ privileged int __hook(void *dest, struct SymbolTable *st) {
// kprintf("can't hook %t at %lx\n", p, p);
}
}
__clear_cache(MAX((char *)__executable_start, (char *)_ereal),
MIN((char *)__privileged_start, (char *)_etext));
__jit_end();
__morph_end();
return 0;
}

View file

@ -20,7 +20,7 @@
#include "libc/runtime/runtime.h"
#include "libc/runtime/syslib.internal.h"
void __jit_begin(void) {
privileged void __jit_begin(void) {
if (IsXnuSilicon()) {
if (__syslib->__pthread_jit_write_protect_supported_np()) {
__syslib->__pthread_jit_write_protect_np(false);
@ -28,7 +28,7 @@ void __jit_begin(void) {
}
}
void __jit_end(void) {
privileged void __jit_end(void) {
if (IsXnuSilicon()) {
if (__syslib->__pthread_jit_write_protect_supported_np()) {
__syslib->__pthread_jit_write_protect_np(true);

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/kprintf.h"
#include "libc/macros.h"
#include "libc/runtime/zipos.internal.h"
#include "libc/str/str.h"
@ -44,9 +43,8 @@ ssize_t __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) {
// strip trailing slash from search name
int len = name->len;
if (len && name->path[len - 1] == '/') {
if (len && name->path[len - 1] == '/')
--len;
}
// empty string means the /zip root directory
if (!len) {
@ -91,9 +89,8 @@ ssize_t __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) {
dx = dx < -1 ? -1 : dx;
for (l += dx; 0 <= l && l < zipos->records; l += dx) {
ssize_t cf;
if ((cf = __zipos_match(zipos, name, len, l)) != -1) {
if ((cf = __zipos_match(zipos, name, len, l)) != -1)
return cf;
}
cfile = zipos->index[l];
zname = ZIP_CFILE_NAME(zipos->map + cfile);
zsize = ZIP_CFILE_NAMESIZE(zipos->map + cfile);

View file

@ -118,9 +118,10 @@ int main(int argc, char *argv[]) {
GetOpts(argc, argv);
for (fd = 3; fd < 100; ++fd) {
int oe = errno;
for (fd = 3; fd < 100; ++fd)
close(fd);
}
errno = oe;
#ifndef TINY
setenv("GDB", "", true);