mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-12 01:08:00 +00:00
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.
299 lines
11 KiB
C
299 lines
11 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2020 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/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"
|
|
#include "libc/intrin/dll.h"
|
|
#include "libc/intrin/kprintf.h"
|
|
#include "libc/intrin/maps.h"
|
|
#include "libc/intrin/strace.h"
|
|
#include "libc/intrin/weaken.h"
|
|
#include "libc/limits.h"
|
|
#include "libc/macros.h"
|
|
#include "libc/nt/enum/creationdisposition.h"
|
|
#include "libc/nt/enum/filemapflags.h"
|
|
#include "libc/nt/enum/memflags.h"
|
|
#include "libc/nt/enum/pageflags.h"
|
|
#include "libc/nt/enum/processcreationflags.h"
|
|
#include "libc/nt/enum/startf.h"
|
|
#include "libc/nt/errors.h"
|
|
#include "libc/nt/memory.h"
|
|
#include "libc/nt/process.h"
|
|
#include "libc/nt/runtime.h"
|
|
#include "libc/nt/struct/processinformation.h"
|
|
#include "libc/nt/struct/startupinfo.h"
|
|
#include "libc/nt/thread.h"
|
|
#include "libc/nt/thunk/msabi.h"
|
|
#include "libc/nt/winsock.h"
|
|
#include "libc/proc/proc.internal.h"
|
|
#include "libc/runtime/internal.h"
|
|
#include "libc/runtime/symbols.internal.h"
|
|
#include "libc/sysv/consts/map.h"
|
|
#include "libc/sysv/consts/prot.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/sysv/errfuns.h"
|
|
#include "libc/thread/tls.h"
|
|
#ifdef __x86_64__
|
|
|
|
extern long __klog_handle;
|
|
extern bool __winmain_isfork;
|
|
extern intptr_t __winmain_jmpbuf[5];
|
|
extern struct CosmoTib *__winmain_tib;
|
|
|
|
__msabi extern typeof(TlsAlloc) *const __imp_TlsAlloc;
|
|
__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx;
|
|
__msabi extern typeof(VirtualProtectEx) *const __imp_VirtualProtectEx;
|
|
|
|
textwindows wontreturn static void AbortFork(const char *func, void *addr) {
|
|
#if SYSDEBUG
|
|
kprintf("fork() %!s(%lx) failed with win32 error %u\n", func, addr,
|
|
GetLastError());
|
|
#endif
|
|
TerminateThisProcess(SIGSTKFLT);
|
|
}
|
|
|
|
textwindows static void ViewOrDie(int64_t h, uint32_t access, size_t pos,
|
|
size_t size, void *base) {
|
|
TryAgain:
|
|
if (!__imp_MapViewOfFileEx(h, access, pos >> 32, pos, size, base)) {
|
|
if ((access & kNtFileMapExecute) &&
|
|
GetLastError() == kNtErrorAccessDenied) {
|
|
access &= ~kNtFileMapExecute;
|
|
goto TryAgain;
|
|
}
|
|
AbortFork("ViewOrDie", base);
|
|
}
|
|
}
|
|
|
|
textwindows static void sys_fork_nt_child(void) {
|
|
|
|
// setup runtime
|
|
__klog_handle = 0;
|
|
__tls_index = __imp_TlsAlloc();
|
|
__set_tls_win32(__winmain_tib);
|
|
__tls_enabled = true;
|
|
|
|
// resurrect shared memory mappings
|
|
struct Map *next;
|
|
for (struct Map *map = __maps_first(); map; map = next) {
|
|
next = __maps_next(map);
|
|
|
|
// cleanup nofork mappings
|
|
if (map->flags & MAP_NOFORK) {
|
|
if ((map->flags & MAP_TYPE) != MAP_FILE) {
|
|
tree_remove(&__maps.maps, &map->tree);
|
|
__maps.pages -= (map->size + __pagesize - 1) / __pagesize;
|
|
__maps.count -= 1;
|
|
__maps_free(map);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// private maps already copied/protected to child by parent
|
|
if ((map->flags & MAP_TYPE) != MAP_SHARED) {
|
|
// it's not copy-on-write anymore
|
|
map->iscow = false;
|
|
// but it used VirtualAlloc() so munmap() must VirtualFree()
|
|
if (map->hand > 0) {
|
|
CloseHandle(map->hand);
|
|
map->hand = MAPS_VIRTUAL;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// handle granularity aligned shared mapping
|
|
if (__maps_isalloc(map)) {
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// create allocation with most permissive access possible
|
|
// if we don't create as rwx then we can't mprotect(rwx) later
|
|
unsigned access;
|
|
if (map->readonlyfile) {
|
|
access = kNtFileMapRead | kNtFileMapExecute;
|
|
} else {
|
|
access = kNtFileMapWrite | kNtFileMapExecute;
|
|
}
|
|
|
|
// resurrect copyless memory via inherited win32 handle
|
|
ViewOrDie(map->hand, access, map->off, allocsize, map->addr);
|
|
}
|
|
|
|
// restore memory protection status on pages
|
|
unsigned old_protect;
|
|
if (!__imp_VirtualProtectEx(GetCurrentProcess(), map->addr, map->size,
|
|
__prot2nt(map->prot, false), &old_protect))
|
|
AbortFork("VirtualProtectEx", map->addr);
|
|
}
|
|
|
|
// function tracing is now safe
|
|
ftrace_enabled(+1);
|
|
|
|
// initialize winsock
|
|
void WinSockFork(void);
|
|
if (_weaken(WinSockFork))
|
|
_weaken(WinSockFork)();
|
|
|
|
// rewrap the stdin named pipe hack
|
|
// since the handles closed on fork
|
|
g_fds.p[0].handle = GetStdHandle(kNtStdInputHandle);
|
|
g_fds.p[1].handle = GetStdHandle(kNtStdOutputHandle);
|
|
g_fds.p[2].handle = GetStdHandle(kNtStdErrorHandle);
|
|
}
|
|
|
|
textwindows static int sys_fork_nt_parent(uint32_t dwCreationFlags) {
|
|
|
|
// allocate process object
|
|
struct Proc *proc;
|
|
if (!(proc = __proc_new()))
|
|
return -1;
|
|
|
|
// get path of this executable
|
|
char16_t prog[PATH_MAX];
|
|
unsigned got = GetModuleFileName(0, prog, ARRAYLEN(prog));
|
|
if (!got || got >= ARRAYLEN(prog)) {
|
|
dll_make_first(&__proc.free, &proc->elem);
|
|
enomem();
|
|
return -1;
|
|
}
|
|
|
|
// spawn new process in suspended state
|
|
struct NtProcessInformation procinfo;
|
|
struct NtStartupInfo startinfo = {
|
|
.cb = sizeof(struct NtStartupInfo),
|
|
.dwFlags = kNtStartfUsestdhandles,
|
|
.hStdInput = g_fds.p[0].handle,
|
|
.hStdOutput = g_fds.p[1].handle,
|
|
.hStdError = g_fds.p[2].handle,
|
|
};
|
|
if (!CreateProcess(prog, 0, 0, 0, true,
|
|
dwCreationFlags | kNtCreateSuspended |
|
|
kNtInheritParentAffinity |
|
|
kNtCreateUnicodeEnvironment |
|
|
GetPriorityClass(GetCurrentProcess()),
|
|
0, 0, &startinfo, &procinfo)) {
|
|
STRACE("fork() %s() failed w/ %m %d", "CreateProcess", GetLastError());
|
|
dll_make_first(&__proc.free, &proc->elem);
|
|
if (errno != ENOMEM)
|
|
eagain();
|
|
return -1;
|
|
}
|
|
|
|
// ensure process can be signaled before returning
|
|
UnmapViewOfFile(__sig_map_process(procinfo.dwProcessId, kNtOpenAlways));
|
|
|
|
// let's go
|
|
bool ok = true;
|
|
uint32_t child_old_protect;
|
|
uint32_t parent_old_protect;
|
|
|
|
// copy memory manager maps
|
|
for (struct MapSlab *slab =
|
|
atomic_load_explicit(&__maps.slabs, memory_order_acquire);
|
|
slab; slab = slab->next) {
|
|
ok = ok && !!VirtualAllocEx(procinfo.hProcess, slab, MAPS_SIZE,
|
|
kNtMemReserve | kNtMemCommit, kNtPageReadwrite);
|
|
ok =
|
|
ok && !!WriteProcessMemory(procinfo.hProcess, slab, slab, MAPS_SIZE, 0);
|
|
}
|
|
|
|
// copy private memory maps
|
|
for (struct Map *map = __maps_first(); map; map = __maps_next(map)) {
|
|
if ((map->flags & MAP_TYPE) == MAP_SHARED)
|
|
continue;
|
|
if ((map->flags & MAP_NOFORK) && (map->flags & MAP_TYPE) != MAP_FILE)
|
|
continue;
|
|
if (__maps_isalloc(map)) {
|
|
size_t allocsize = map->size;
|
|
for (struct Map *m2 = __maps_next(map); m2; m2 = __maps_next(m2)) {
|
|
if (!__maps_isalloc(m2) && map->addr + allocsize == m2->addr) {
|
|
allocsize += m2->size;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if ((map->flags & MAP_NOFORK) && (map->flags & MAP_TYPE) == MAP_FILE) {
|
|
ok = ok && !!VirtualProtectEx(procinfo.hProcess, map->addr, allocsize,
|
|
kNtPageReadwrite, &child_old_protect);
|
|
} else {
|
|
ok = ok && !!VirtualAllocEx(procinfo.hProcess, map->addr, allocsize,
|
|
kNtMemReserve | kNtMemCommit,
|
|
kNtPageExecuteReadwrite);
|
|
}
|
|
}
|
|
if (!(map->prot & PROT_READ))
|
|
ok = ok && !!VirtualProtect(map->addr, map->size, kNtPageReadwrite,
|
|
&parent_old_protect);
|
|
ok = ok && !!WriteProcessMemory(procinfo.hProcess, map->addr, map->addr,
|
|
map->size, 0);
|
|
ok = ok &&
|
|
!!VirtualProtectEx(procinfo.hProcess, map->addr, map->size,
|
|
__prot2nt(map->prot, false), &child_old_protect);
|
|
if (!(map->prot & PROT_READ))
|
|
ok = ok && !!VirtualProtect(map->addr, map->size, parent_old_protect,
|
|
&parent_old_protect);
|
|
}
|
|
|
|
// set process loose
|
|
ok = ok && ResumeThread(procinfo.hThread) != -1u;
|
|
ok &= !!CloseHandle(procinfo.hThread);
|
|
|
|
// return pid of new process
|
|
if (ok) {
|
|
proc->wasforked = true;
|
|
proc->handle = procinfo.hProcess;
|
|
proc->pid = procinfo.dwProcessId;
|
|
__proc_add(proc);
|
|
return procinfo.dwProcessId;
|
|
} else {
|
|
if (errno != ENOMEM)
|
|
eagain(); // posix fork() only specifies two errors
|
|
TerminateProcess(procinfo.hProcess, SIGKILL);
|
|
CloseHandle(procinfo.hProcess);
|
|
dll_make_first(&__proc.free, &proc->elem);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
textwindows int sys_fork_nt(uint32_t dwCreationFlags) {
|
|
int rc;
|
|
__winmain_isfork = true;
|
|
__winmain_tib = __get_tls();
|
|
if (!__builtin_setjmp(__winmain_jmpbuf)) {
|
|
rc = sys_fork_nt_parent(dwCreationFlags);
|
|
} else {
|
|
sys_fork_nt_child();
|
|
rc = 0;
|
|
}
|
|
__winmain_isfork = false;
|
|
return rc;
|
|
}
|
|
|
|
#endif /* __x86_64__ */
|