Get fork() working on Windows

This is done without using Microsoft's internal APIs. MAP_PRIVATE
mappings are copied to the subprocess via a pipe, since Microsoft
doesn't want us to have proper COW pages. MAP_SHARED mappings are
remapped without needing to do any copying. Global variables need
copying along with the stack and the whole heap of anonymous mem.
This actually improves the reliability of the redbean http server
although one shouldn't expect 10k+ connections on a home computer
that isn't running software built to serve like Linux or FreeBSD.
This commit is contained in:
Justine Tunney 2020-11-13 01:27:49 -08:00
parent aea89fe832
commit db33973e0a
105 changed files with 1476 additions and 912 deletions

View file

@ -28,10 +28,13 @@
/ @param edi is exit code ∈ [0,256)
/ @note _exit() is same thing
/ @asyncsignalsafe
_Exit: orl $RUNSTATE_TERMINATE,g_runstate(%rip)
_Exit: push %rbp
mov %rsp,%rbp
orl $RUNSTATE_TERMINATE,g_runstate(%rip)
#if SupportsWindows()
testb IsWindows()
jz 1f
sub $32,%rsp
movzbl %dil,%ecx # %ERRORLEVEL% is limitless
4: call *__imp_ExitProcess(%rip)
jmp 4b

View file

@ -25,7 +25,7 @@ bool AreMemoryIntervalsOk(const struct MemoryIntervals *mm) {
for (i = 0; i < mm->i; ++i) {
if (mm->p[i].y < mm->p[i].x) return false;
if (i) {
if (mm->h[i] || mm->h[i - 1]) {
if (mm->p[i].h || mm->p[i - 1].h) {
if (mm->p[i].x <= mm->p[i - 1].y) return false;
} else {
if (mm->p[i].x <= mm->p[i - 1].y + 1) return false;

View file

@ -17,42 +17,10 @@
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/macros.h"
#include "libc/nt/memory.h"
#include "libc/nt/runtime.h"
#include "libc/runtime/directmap.h"
static textwindows struct DirectMap DirectMapNt(void *addr, size_t size,
unsigned prot, unsigned flags,
int fd, int64_t off) {
int64_t handle;
struct DirectMap res;
uint32_t protect, access;
if (fd != -1) {
handle = g_fds.p[fd].handle;
} else {
handle = kNtInvalidHandleValue;
}
protect = prot2nt(prot, flags);
access = fprot2nt(prot, flags);
if ((res.maphandle =
CreateFileMappingNuma(handle, NULL, protect, size >> 32, size, NULL,
kNtNumaNoPreferredNode))) {
if (!(res.addr = MapViewOfFileExNuma(res.maphandle, access, off >> 32, off,
size, addr, kNtNumaNoPreferredNode))) {
CloseHandle(res.maphandle);
res.maphandle = kNtInvalidHandleValue;
res.addr = (void *)(intptr_t)winerr();
}
} else {
res.maphandle = kNtInvalidHandleValue;
res.addr = (void *)(intptr_t)winerr();
}
return res;
}
struct DirectMap DirectMap(void *addr, size_t size, unsigned prot,
unsigned flags, int fd, int64_t off) {
if (!IsWindows()) {

View file

@ -9,6 +9,7 @@ struct DirectMap {
};
struct DirectMap DirectMap(void *, size_t, unsigned, unsigned, int, int64_t);
struct DirectMap DirectMapNt(void *, size_t, unsigned, unsigned, int, int64_t);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -0,0 +1,51 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "libc/calls/internal.h"
#include "libc/nt/memory.h"
#include "libc/nt/runtime.h"
#include "libc/runtime/directmap.h"
textwindows struct DirectMap DirectMapNt(void *addr, size_t size, unsigned prot,
unsigned flags, int fd, int64_t off) {
int64_t handle;
struct DirectMap res;
uint32_t protect, access;
if (fd != -1) {
handle = g_fds.p[fd].handle;
} else {
handle = kNtInvalidHandleValue;
}
protect = prot2nt(prot, flags);
access = fprot2nt(prot, flags);
if ((res.maphandle =
CreateFileMappingNuma(handle, NULL, protect, size >> 32, size, NULL,
kNtNumaNoPreferredNode))) {
if (!(res.addr = MapViewOfFileExNuma(res.maphandle, access, off >> 32, off,
size, addr, kNtNumaNoPreferredNode))) {
CloseHandle(res.maphandle);
res.maphandle = kNtInvalidHandleValue;
res.addr = (void *)(intptr_t)winerr();
}
} else {
res.maphandle = kNtInvalidHandleValue;
res.addr = (void *)(intptr_t)winerr();
}
return res;
}

View file

@ -27,12 +27,6 @@
#include "libc/str/tpenc.h"
#include "libc/str/utf16.h"
/* TODO(jart): Make early-stage data structures happen. */
#undef isspace
#undef iswspace
#define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r')
#define iswspace(c) isspace(c)
struct DosArgv {
const char16_t *s;
char *p;

View file

@ -25,6 +25,7 @@ void *__cxa_finalize(void *) hidden;
void _executive(int, char **, char **, long (*)[2]) hidden noreturn;
void __stack_chk_fail(void) noreturn relegated;
void __stack_chk_fail_local(void) noreturn relegated hidden;
void _jmpstack(void *, void *, ...) hidden noreturn;
long _setstack(void *, void *, ...) hidden;
int GetDosArgv(const char16_t *, char *, size_t, char **, size_t) hidden;

38
libc/runtime/jmpstack.S Normal file
View file

@ -0,0 +1,38 @@
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
This program is free software; you can redistribute it and/or modify │
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License. │
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of │
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software │
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "libc/macros.h"
/ Switches stack.
/
/ @param rdi is new rsp, passed as malloc(size) + size
/ @param rsi is function to call in new stack space
/ @param rdx,rcx,r8,r9 get passed as args to rsi
/ @noreturn
_jmpstack:
mov %rdi,%rsp
mov %rsi,%rax
mov %rdx,%rdi
mov %rcx,%rsi
mov %r8,%rdx
mov %r9,%rcx
xor %rbp,%rbp
call *%rax
ud2
.endfn _jmpstack,globl,hidden

View file

@ -30,8 +30,6 @@ static void RemoveMemoryIntervals(struct MemoryIntervals *mm, int i, int n) {
assert(i + n <= mm->i);
memcpy(mm->p + i, mm->p + i + n,
(intptr_t)(mm->p + mm->i) - (intptr_t)(mm->p + i + n));
memcpy(mm->h + i, mm->h + i + n,
(intptr_t)(mm->h + mm->i) - (intptr_t)(mm->h + i + n));
mm->i -= n;
}
@ -41,8 +39,6 @@ static void CreateMemoryInterval(struct MemoryIntervals *mm, int i) {
assert(mm->i < ARRAYLEN(mm->p));
memmove(mm->p + i + 1, mm->p + i,
(intptr_t)(mm->p + mm->i) - (intptr_t)(mm->p + i));
memmove(mm->h + i + 1, mm->h + i,
(intptr_t)(mm->h + mm->i) - (intptr_t)(mm->h + i));
++mm->i;
}
@ -94,25 +90,31 @@ int ReleaseMemoryIntervals(struct MemoryIntervals *mm, int x, int y,
return 0;
}
int TrackMemoryInterval(struct MemoryIntervals *mm, int x, int y, long h) {
int TrackMemoryInterval(struct MemoryIntervals *mm, int x, int y, long h,
int prot, int flags) {
unsigned i;
assert(y >= x);
assert(AreMemoryIntervalsOk(mm));
i = FindMemoryInterval(mm, x);
if (i && x == mm->p[i - 1].y + 1 && h == mm->h[i - 1]) {
if (i && x == mm->p[i - 1].y + 1 && h == mm->p[i - 1].h &&
prot == mm->p[i - 1].prot && flags == mm->p[i - 1].flags) {
mm->p[i - 1].y = y;
if (i < mm->i && y + 1 == mm->p[i].x && h == mm->h[i]) {
if (i < mm->i && y + 1 == mm->p[i].x && h == mm->p[i].h &&
prot == mm->p[i].prot && flags == mm->p[i].flags) {
mm->p[i - 1].y = mm->p[i].y;
RemoveMemoryIntervals(mm, i, 1);
}
} else if (i < mm->i && y + 1 == mm->p[i].x && h == mm->h[i]) {
} else if (i < mm->i && y + 1 == mm->p[i].x && h == mm->p[i].h &&
prot == mm->p[i].prot && flags == mm->p[i].flags) {
mm->p[i].x = x;
} else {
if (mm->i == ARRAYLEN(mm->p)) return enomem();
CreateMemoryInterval(mm, i);
mm->p[i].x = x;
mm->p[i].y = y;
mm->h[i] = h;
mm->p[i].h = h;
mm->p[i].prot = prot;
mm->p[i].flags = flags;
}
return 0;
}

View file

@ -14,8 +14,10 @@ struct MemoryIntervals {
struct MemoryInterval {
int x;
int y;
long h;
int prot;
int flags;
} p[128];
long h[128];
};
extern struct MemoryIntervals _mmi;
@ -23,7 +25,7 @@ extern struct MemoryIntervals _mmi;
unsigned FindMemoryInterval(const struct MemoryIntervals *, int) nosideeffect;
bool AreMemoryIntervalsOk(const struct MemoryIntervals *) nosideeffect;
void PrintMemoryIntervals(int, const struct MemoryIntervals *);
int TrackMemoryInterval(struct MemoryIntervals *, int, int, long);
int TrackMemoryInterval(struct MemoryIntervals *, int, int, long, int, int);
int ReleaseMemoryIntervals(struct MemoryIntervals *, int, int,
void (*)(struct MemoryIntervals *, int, int));
void ReleaseMemoryNt(struct MemoryIntervals *, int, int);

View file

@ -35,7 +35,7 @@ void ReleaseMemoryNt(struct MemoryIntervals *mm, int l, int r) {
for (i = l; i <= r; ++i) {
ok = UnmapViewOfFile(GetFrameAddr(mm->p[i].x));
assert(ok);
ok = CloseHandle(mm->h[i]);
ok = CloseHandle(mm->p[i].h);
assert(ok);
}
}

View file

@ -39,8 +39,6 @@
#define ALIGNED(p) (!(IP(p) & (FRAMESIZE - 1)))
#define CANONICAL(p) (-0x800000000000 <= IP(p) && IP(p) <= 0x7fffffffffff)
struct MemoryIntervals _mmi;
/**
* Beseeches system for page-table entries.
*
@ -92,7 +90,7 @@ void *mmap(void *addr, size_t size, int prot, int flags, int fd, int64_t off) {
}
a = ROUNDDOWN((intptr_t)addr, FRAMESIZE) >> 16;
b = ROUNDDOWN((intptr_t)addr + size - 1, FRAMESIZE) >> 16;
if (TrackMemoryInterval(&_mmi, a, b, dm.maphandle) == -1) {
if (TrackMemoryInterval(&_mmi, a, b, dm.maphandle, prot, flags) == -1) {
abort();
}
if (weaken(__asan_map_shadow)) {

22
libc/runtime/mmi.c Normal file
View file

@ -0,0 +1,22 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "libc/runtime/memtrack.h"
struct MemoryIntervals _mmi;

View file

@ -30,7 +30,7 @@ textwindows int msync$nt(void *addr, size_t size, int flags) {
for (i = FindMemoryInterval(&_mmi, x); i < _mmi.i; ++i) {
if ((x >= _mmi.p[i].x && x <= _mmi.p[i].y) ||
(y >= _mmi.p[i].x && y <= _mmi.p[i].y)) {
FlushFileBuffers(_mmi.h[i]);
FlushFileBuffers(_mmi.p[i].h);
} else {
break;
}

View file

@ -12,7 +12,6 @@ extern int g_argc; /* CRT */
extern char **g_argv; /* CRT */
extern char **environ; /* CRT */
extern unsigned long *g_auxv; /* CRT */
extern jmp_buf g_winmain; /* CRT */
extern char *program_invocation_name; /* RII */
extern char *program_invocation_short_name; /* RII */
extern uint64_t g_syscount; /* RII */

View file

@ -64,11 +64,6 @@ $(LIBC_RUNTIME_A_OBJS): \
OVERRIDE_CFLAGS += \
$(NO_MAGIC)
# @see ape/ape.s for tuning parameters that make this safe
o/$(MODE)/libc/runtime/winmain.greg.o: \
DEFAULT_CPPFLAGS += \
-DSTACK_FRAME_UNLIMITED
LIBC_RUNTIME_LIBS = $(foreach x,$(LIBC_RUNTIME_ARTIFACTS),$($(x)))
LIBC_RUNTIME_SRCS = $(foreach x,$(LIBC_RUNTIME_ARTIFACTS),$($(x)_SRCS))
LIBC_RUNTIME_HDRS = $(foreach x,$(LIBC_RUNTIME_ARTIFACTS),$($(x)_HDRS))

View file

@ -20,7 +20,10 @@
#include "libc/bits/bits.h"
#include "libc/bits/pushpop.h"
#include "libc/bits/weaken.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
#include "libc/fmt/fmt.h"
#include "libc/macros.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/consolemodeflags.h"
#include "libc/nt/enum/filetype.h"
@ -34,8 +37,21 @@
#include "libc/nt/struct/teb.h"
#include "libc/runtime/getdosenviron.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/memtrack.h"
#include "libc/runtime/missioncritical.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/winmain.h"
#include "libc/sock/internal.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
struct WinArgs {
char *argv[512];
char *envp[512];
long auxv[1][2];
char argblock[ARG_MAX];
char envblock[ENV_MAX];
};
static struct CmdExe {
bool result;
@ -46,13 +62,7 @@ static struct CmdExe {
} oldin, oldout;
} g_cmdexe;
static textwindows void MitigateDriveByDownloads(void) {
unsigned wrote;
if (!SetDefaultDllDirectories(kNtLoadLibrarySearchSearchSystem32)) {
WriteFile(GetStdHandle(kNtStdErrorHandle), "nodll\n", 6, &wrote, NULL);
ExitProcess(1);
}
}
struct WinMain g_winmain;
static textwindows void RestoreCmdExe(void) {
if (g_cmdexe.oldin.handle) {
@ -104,6 +114,53 @@ static textwindows void NormalizeCmdExe(void) {
}
}
static textwindows char *AllocateMemory(void *addr, size_t size, int64_t *h) {
return MapViewOfFileExNuma(
(*h = CreateFileMappingNuma(-1, NULL, pushpop(kNtPageReadwrite), 0, size,
NULL, kNtNumaNoPreferredNode)),
kNtFileMapRead | kNtFileMapWrite, 0, 0, size, addr,
kNtNumaNoPreferredNode);
}
static textwindows noreturn void WinMainNew(int64_t hInstance,
int64_t hPrevInstance,
const char *lpCmdLine,
int nCmdShow) {
int64_t h;
size_t size;
int i, count;
uint64_t data;
struct WinArgs *wa;
const char16_t *env16;
g_winmain.nCmdShow = nCmdShow;
g_winmain.hInstance = hInstance;
g_winmain.hPrevInstance = hPrevInstance;
NormalizeCmdExe();
*(/*unconst*/ int *)&hostos = WINDOWS;
size = ROUNDUP(STACKSIZE + sizeof(struct WinArgs), FRAMESIZE);
data = 0x777000000000;
data = (intptr_t)AllocateMemory((char *)data, size, &_mmi.p[0].h);
_mmi.p[0].x = data >> 16;
_mmi.p[0].y = (data >> 16) + ((size >> 16) - 1);
_mmi.p[0].prot = PROT_READ | PROT_WRITE;
_mmi.p[0].flags = MAP_PRIVATE | MAP_ANONYMOUS;
_mmi.i = pushpop(1L);
wa = (struct WinArgs *)(data + size - sizeof(struct WinArgs));
count = GetDosArgv(GetCommandLine(), wa->argblock, ARG_MAX, wa->argv, 512);
for (i = 0; wa->argv[0][i]; ++i) {
if (wa->argv[0][i] == '\\') {
wa->argv[0][i] = '/';
}
}
env16 = GetEnvironmentStrings();
GetDosEnviron(env16, wa->envblock, ENV_MAX, wa->envp, 512);
FreeEnvironmentStrings(env16);
wa->auxv[0][0] = pushpop(0L);
wa->auxv[0][1] = pushpop(0L);
_jmpstack((char *)data + STACKSIZE, _executive, count, wa->argv, wa->envp,
wa->auxv);
}
/**
* Main function on Windows NT.
*
@ -130,30 +187,15 @@ static textwindows void NormalizeCmdExe(void) {
* the downloads folder. Since we don't even use dynamic linking,
* we've cargo culted some API calls, that may harden against it.
*
* 6. Finally, we need fork. Microsoft designed Windows to prevent us
* from having fork() so we pass pipe handles in an environment
* variable literally copy all the memory.
*
*/
textwindows int WinMain(void *hInstance, void *hPrevInstance,
const char *lpCmdLine, int nCmdShow) {
char *stack;
int i, count;
const char16_t *cmd16, *env16;
char *argv[512], *envp[512];
char argblock[ARG_MAX], envblock[ENV_MAX];
long auxv[][2] = {{pushpop(0L), pushpop(0L)}};
MitigateDriveByDownloads();
NormalizeCmdExe();
*(/*unconst*/ int *)&hostos = WINDOWS;
cmd16 = GetCommandLine();
env16 = GetEnvironmentStrings();
count = GetDosArgv(cmd16, argblock, ARG_MAX, argv, 512);
for (i = 0; argv[0][i]; ++i) {
if (argv[0][i] == '\\') argv[0][i] = '/';
}
GetDosEnviron(env16, envblock, ENV_MAX, envp, 512);
FreeEnvironmentStrings(env16);
stack = MapViewOfFileExNuma(
CreateFileMappingNuma(-1, NULL, pushpop(kNtPageReadwrite), 0, STACKSIZE,
NULL, kNtNumaNoPreferredNode),
kNtFileMapRead | kNtFileMapWrite, 0, 0, STACKSIZE,
(char *)0x777000000000 - STACKSIZE, kNtNumaNoPreferredNode);
return _setstack(stack + STACKSIZE, _executive, count, argv, envp, auxv);
textwindows int64_t WinMain(int64_t hInstance, int64_t hPrevInstance,
const char *lpCmdLine, int nCmdShow) {
SetDefaultDllDirectories(kNtLoadLibrarySearchSearchSystem32);
if (weaken(winsockinit)) weaken(winsockinit)();
if (weaken(WinMainForked)) weaken(WinMainForked)();
WinMainNew(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

16
libc/runtime/winmain.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef COSMOPOLITAN_LIBC_RUNTIME_WINMAIN_H_
#define COSMOPOLITAN_LIBC_RUNTIME_WINMAIN_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct WinMain {
int nCmdShow;
int64_t hInstance;
int64_t hPrevInstance;
};
extern struct WinMain g_winmain;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_RUNTIME_WINMAIN_H_ */