Trying a safer approach for a fork() alternative
This commit is contained in:
parent
f652d174fe
commit
243ed3a8b8
6 changed files with 81 additions and 75 deletions
|
@ -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_ */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue