Trying a safer approach for a fork() alternative

This commit is contained in:
Snaipe 2015-03-26 22:18:43 +01:00
parent f652d174fe
commit 243ed3a8b8
6 changed files with 81 additions and 75 deletions

View file

@ -29,9 +29,9 @@
struct criterion_test_extra_data {
int sentinel_;
const char *const identifier_;
const char *const file_;
const unsigned line_;
const char *identifier_;
const char *file_;
unsigned line_;
void (*init)(void);
void (*fini)(void);
int signal;
@ -44,12 +44,14 @@ struct criterion_test {
const char *name;
const char *category;
void (*test)(void);
struct criterion_test_extra_data *const data;
struct criterion_test_extra_data *data;
};
struct criterion_suite {
const char *name;
struct criterion_test_extra_data *const data;
struct criterion_test_extra_data *data;
};
typedef void (*f_worker_func)(struct criterion_test *, struct criterion_suite *);
#endif /* !CRITERION_TYPES_H_ */

View file

@ -1,4 +1,5 @@
#include "posix-compat.h"
#include "process.h"
#ifdef _WIN32
# define VC_EXTRALEAN
@ -7,7 +8,6 @@
# include <io.h>
# include <fcntl.h>
# include <winnt.h>
# include <setjmp.h>
# include <stdint.h>
# define CREATE_SUSPENDED_(Filename, CmdLine, StartupInfo, Info) \
@ -22,11 +22,7 @@
&(StartupInfo), \
&(Info))
# ifdef _WIN64
# define Register(Reg, Context) ((Context).R ## Reg)
# else
# define Register(Reg, Context) ((Context).E ## Reg)
# endif
# define CONTEXT_INIT { .ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_FLOATING_POINT }
#else
# include <unistd.h>
# include <sys/wait.h>
@ -52,40 +48,24 @@ struct pipe_handle {
#endif
};
struct worker_context g_worker_context = {.test = NULL};
#ifdef _WIN32
# define CONTEXT_INIT { .ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_FLOATING_POINT }
struct region_info {
char *ptr;
char *base;
size_t size;
};
static inline void get_memory_info(struct region_info *stack, struct region_info *heap) {
CONTEXT context = CONTEXT_INIT;
MEMORY_BASIC_INFORMATION mbi;
GetThreadContext(GetCurrentThread(), &context);
stack->ptr = (char *) Register(sp, context);
VirtualQuery(stack->ptr, &mbi, sizeof (mbi));
stack->base = mbi.BaseAddress;
stack->size = mbi.RegionSize;
void *ptr = malloc(1);
VirtualQuery(ptr, &mbi, sizeof (mbi));
heap->ptr = NULL;
heap->base = mbi.BaseAddress;
heap->size = mbi.RegionSize;
free(ptr);
}
static jmp_buf g_jmp;
static void resume_child(void) {
longjmp(g_jmp, 1);
}
static struct criterion_test child_test;
static struct criterion_test_extra_data child_test_data;
static struct criterion_suite child_suite;
static struct criterion_test_extra_data child_suite_data;
static struct pipe_handle child_pipe;
#endif
int resume_child(void) {
if (g_worker_context.test) {
run_worker(&g_worker_context);
return 1;
}
return 0;
}
s_proc_handle *fork_process() {
#ifdef _WIN32
PROCESS_INFORMATION info;
@ -95,35 +75,32 @@ s_proc_handle *fork_process() {
CONTEXT context = CONTEXT_INIT;
// Initialize longjmp buffer
if (setjmp(g_jmp))
return NULL; // we are the child, return
// Create the suspended child process
wchar_t filename[MAX_PATH];
GetModuleFileNameW(NULL, filename, MAX_PATH);
if (!CREATE_SUSPENDED_(filename, GetCommandLineW(), si, info))
return NULL;
return (void *) -1;
// Set child's instruction pointer to resume_child
GetThreadContext(info.hThread, &context);
Register(ip, context) = (intptr_t) resume_child;
SetThreadContext(info.hThread, &context);
// Copy context over
f_worker_func child_func = g_worker_context.func;
// Copy stack, heap
struct region_info stack;
struct region_info heap;
get_memory_info(&stack, &heap);
child_test = *g_worker_context.test;
child_test_data = *g_worker_context.test->data;
child_suite = *g_worker_context.suite;
child_suite_data = *g_worker_context.suite->data;
child_pipe = *g_worker_context.pipe;
VirtualAllocEx(info.hProcess, stack.base, stack.size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(info.hProcess, stack.base, stack.base, stack.size, NULL);
g_worker_context = (struct worker_context) { &child_test, &child_suite, child_func, &child_pipe };
child_test.data = &child_test_data;
child_suite.data = &child_suite_data;
VirtualAllocEx(info.hProcess, heap.base, heap.size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(info.hProcess, heap.base, heap.base, heap.size, NULL);
// Copy jmp_buf
WriteProcessMemory(info.hProcess, &g_jmp, &g_jmp, sizeof (jmp_buf), NULL);
WriteProcessMemory(info.hProcess, &child_test, &child_test, sizeof (child_test), NULL);
WriteProcessMemory(info.hProcess, &child_test_data, &child_test_data, sizeof (child_test_data), NULL);
WriteProcessMemory(info.hProcess, &child_suite, &child_suite, sizeof (child_suite), NULL);
WriteProcessMemory(info.hProcess, &child_suite_data, &child_suite_data, sizeof (child_suite_data), NULL);
WriteProcessMemory(info.hProcess, &child_pipe, &child_pipe, sizeof (child_pipe), NULL);
WriteProcessMemory(info.hProcess, &g_worker_context, &g_worker_context, sizeof (struct worker_context), NULL);
ResumeThread(info.hThread);
CloseHandle(info.hThread);

View file

@ -20,7 +20,7 @@
# include <sys/wait.h>
# endif
#include <stdbool.h>
#include <criterion/types.h>
struct proc_handle;
typedef struct proc_handle s_proc_handle;
@ -28,6 +28,17 @@ typedef struct proc_handle s_proc_handle;
struct pipe_handle;
typedef struct pipe_handle s_pipe_handle;
struct worker_context {
struct criterion_test *test;
struct criterion_suite *suite;
f_worker_func func;
struct pipe_handle *pipe;
};
extern struct worker_context g_worker_context;
int resume_child(void);
s_pipe_handle *stdpipe();
FILE *pipe_in(s_pipe_handle *p);
FILE *pipe_out(s_pipe_handle *p);

View file

@ -60,28 +60,38 @@ struct event *worker_read_event(struct process *proc) {
return read_event(proc->in);
}
void run_worker(struct worker_context *ctx) {
fclose(stdin);
g_event_pipe = pipe_out(ctx->pipe);
ctx->func(ctx->test, ctx->suite);
fclose(g_event_pipe);
fflush(NULL); // flush all opened streams
if (criterion_options.no_early_exit)
return;
_Exit(0);
}
struct process *spawn_test_worker(struct criterion_test *test,
struct criterion_suite *suite,
void (*func)(struct criterion_test *, struct criterion_suite *)) {
f_worker_func func) {
smart s_pipe_handle *pipe = stdpipe();
if (pipe == NULL)
abort();
g_worker_context = (struct worker_context) {
.test = test,
.suite = suite,
.func = func,
.pipe = pipe
};
s_proc_handle *proc = fork_process();
if (proc == (void *) -1) {
return NULL;
} else if (proc == NULL) {
fclose(stdin);
g_event_pipe = pipe_out(pipe);
func(test, suite);
fclose(g_event_pipe);
fflush(NULL); // flush all opened streams
if (criterion_options.no_early_exit)
return NULL;
else
_Exit(0);
run_worker(&g_worker_context);
return NULL;
}
return unique_ptr(struct process, { .proc = proc, .in = pipe_in(pipe) }, close_process);

View file

@ -25,6 +25,7 @@
# define PROCESS_H_
# include <stdbool.h>
# include "posix-compat.h"
struct process;
@ -39,6 +40,7 @@ struct process_status {
int status;
};
void run_worker(struct worker_context *ctx);
void set_runner_process(void);
void unset_runner_process(void);
bool is_runner(void);

View file

@ -33,6 +33,7 @@
#include "event.h"
#include "process.h"
#include "timer.h"
#include "posix-compat.h"
IMPL_SECTION_LIMITS(struct criterion_test, criterion_tests);
IMPL_SECTION_LIMITS(struct criterion_suite, crit_suites);
@ -199,6 +200,9 @@ static void run_test(struct criterion_global_stats *stats,
}
static int criterion_run_all_tests_impl(void) {
if (resume_child()) // (windows only) resume from the fork
return -1;
smart struct criterion_test_set *set = criterion_init();
report(PRE_ALL, set);