mirror of
https://github.com/clearlinux/graphene.git
synced 2026-09-06 13:51:28 +00:00
[LibOS] Remove ALIAS_VFORK_AS_FORK and always emulate vfork via fork
Graphene has been emulating vfork() via fork() for a year already, and there were no real-world cases when this wasn't sufficient. It is time to retire the old code, broken and commented out anyway.
This commit is contained in:
@@ -9,33 +9,6 @@
|
||||
|
||||
#define CPSTORE_DERANDOMIZATION 1
|
||||
|
||||
/* This macro disables current vfork implementation and aliases it to fork.
|
||||
*
|
||||
* Rationale:
|
||||
* Current vfork() implementation is broken and works only in simple cases.
|
||||
* The implementation creates a new thread in the same process and runs it
|
||||
* in place of the previous (parent) thread which called vfork(). When the
|
||||
* "pseudo-process" new thread reaches execve(), it silently dies and
|
||||
* switches execution back to the suspended parent thread (as per vfork
|
||||
* semantics). Because execve() emulation creates a new host-OS process,
|
||||
* this vfork implementation works in simple benign cases.
|
||||
*
|
||||
* However, this co-existence of the "pseudo-process" thread with threads
|
||||
* of the parent process leads to bugs elsewhere in Graphene. In general,
|
||||
* the rest of Graphene is not aware of such situation when two processes
|
||||
* co-exist in the same Graphene instance and share memory. If the new
|
||||
* "pseudo-process" thread makes syscalls in-between vfork() and execve()
|
||||
* or abnormally dies or receives a signal, Graphene may hang or segfault
|
||||
* or end up with inconsistent internal state.
|
||||
*
|
||||
* Therefore, instead of trying to support Linux semantics for vfork() --
|
||||
* which requires adding corner-cases in signal handling and syscalls --
|
||||
* we simply redirect vfork() as fork(). We assume that performance hit is
|
||||
* negligible (Graphene has to migrate internal state anyway which is slow)
|
||||
* and apps do not rely on insane Linux-specific semantics of vfork().
|
||||
* */
|
||||
#define ALIAS_VFORK_AS_FORK 1
|
||||
|
||||
#define DEFAULT_HEAP_MIN_SIZE (256 * 1024 * 1024) /* 256MB */
|
||||
#define DEFAULT_MEM_MAX_NPAGES (1024 * 1024) /* 4GB */
|
||||
#define DEFAULT_BRK_MAX_SIZE (256 * 1024) /* 256KB */
|
||||
|
||||
@@ -53,10 +53,6 @@ struct shim_thread {
|
||||
struct shim_thread * parent;
|
||||
/* thread leader */
|
||||
struct shim_thread * leader;
|
||||
#ifndef ALIAS_VFORK_AS_FORK
|
||||
/* dummy thread: stores blocked parent thread for vfork */
|
||||
struct shim_thread * dummy;
|
||||
#endif
|
||||
/* child handles; protected by thread->lock */
|
||||
LISTP_TYPE(shim_thread) children;
|
||||
/* nodes in child handles; protected by the parent's lock */
|
||||
@@ -286,10 +282,6 @@ void del_thread (struct shim_thread * thread);
|
||||
void cleanup_thread(IDTYPE caller, void* thread);
|
||||
int check_last_thread(struct shim_thread* self);
|
||||
|
||||
#ifndef ALIAS_VFORK_AS_FORK
|
||||
void switch_dummy_thread (struct shim_thread * thread);
|
||||
#endif
|
||||
|
||||
int walk_thread_list (int (*callback) (struct shim_thread *, void *, bool *),
|
||||
void * arg);
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ objs = \
|
||||
sys/shim_stat.o \
|
||||
sys/shim_time.o \
|
||||
sys/shim_uname.o \
|
||||
sys/shim_vfork.o \
|
||||
sys/shim_wait.o \
|
||||
sys/shim_wrappers.o \
|
||||
utils/md5.o \
|
||||
|
||||
@@ -554,42 +554,6 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef ALIAS_VFORK_AS_FORK
|
||||
void switch_dummy_thread (struct shim_thread * thread)
|
||||
{
|
||||
struct shim_thread * real_thread = thread->dummy;
|
||||
IDTYPE child = thread->tid;
|
||||
|
||||
assert(thread->frameptr);
|
||||
assert(real_thread->stack);
|
||||
assert(real_thread->stack_top > real_thread->stack);
|
||||
|
||||
memcpy(thread->frameptr, real_thread->stack,
|
||||
real_thread->stack_top - real_thread->stack);
|
||||
|
||||
real_thread->stack = thread->stack;
|
||||
real_thread->stack_top = thread->stack_top;
|
||||
real_thread->frameptr = thread->frameptr;
|
||||
|
||||
DkSegmentRegister(PAL_SEGMENT_FS, real_thread->tcb);
|
||||
set_cur_thread(real_thread);
|
||||
debug("set tcb to %p\n", real_thread->tcb);
|
||||
|
||||
debug("jump to the stack %p\n", real_thread->frameptr);
|
||||
debug("shim_vfork success (returning %d)\n", child);
|
||||
|
||||
/* jump onto old stack
|
||||
we actually pop rbp as rsp, and later we will call 'ret' */
|
||||
__asm__ volatile("movq %0, %%rbp\r\n"
|
||||
"leaveq\r\n"
|
||||
"retq\r\n" :
|
||||
: "g"(real_thread->frameptr),
|
||||
"a"(child)
|
||||
: "memory");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
#endif
|
||||
|
||||
BEGIN_CP_FUNC(signal_handles)
|
||||
{
|
||||
__UNUSED(size);
|
||||
@@ -660,9 +624,6 @@ BEGIN_CP_FUNC(thread)
|
||||
|
||||
new_thread->in_vm = false;
|
||||
new_thread->parent = NULL;
|
||||
#ifndef ALIAS_VFORK_AS_FORK
|
||||
new_thread->dummy = NULL;
|
||||
#endif
|
||||
new_thread->handle_map = NULL;
|
||||
new_thread->root = NULL;
|
||||
new_thread->cwd = NULL;
|
||||
|
||||
@@ -181,14 +181,6 @@ noreturn int shim_do_exit_group (int error_code)
|
||||
if (debug_handle)
|
||||
sysparser_printf("---- shim_exit_group (returning %d)\n", error_code);
|
||||
|
||||
#ifndef ALIAS_VFORK_AS_FORK
|
||||
if (cur_thread->dummy) {
|
||||
cur_thread->term_signal = 0;
|
||||
thread_exit(cur_thread, true);
|
||||
switch_dummy_thread(cur_thread);
|
||||
}
|
||||
#endif
|
||||
|
||||
debug("now kill other threads in the process\n");
|
||||
do_kill_proc(cur_thread->tgid, cur_thread->tgid, SIGKILL, false);
|
||||
while (check_last_thread(cur_thread)) {
|
||||
@@ -208,13 +200,5 @@ noreturn int shim_do_exit (int error_code)
|
||||
if (debug_handle)
|
||||
sysparser_printf("---- shim_exit (returning %d)\n", error_code);
|
||||
|
||||
#ifndef ALIAS_VFORK_AS_FORK
|
||||
if (cur_thread->dummy) {
|
||||
cur_thread->term_signal = 0;
|
||||
thread_exit(cur_thread, true);
|
||||
switch_dummy_thread(cur_thread);
|
||||
}
|
||||
#endif
|
||||
|
||||
thread_or_process_exit(error_code, 0);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
/*
|
||||
* shim_fork.c
|
||||
*
|
||||
* Implementation of system call "fork".
|
||||
* Implementation of system calls "fork" and "vfork".
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
@@ -99,3 +99,12 @@ int shim_do_fork(void) {
|
||||
put_thread(new_thread);
|
||||
return tid;
|
||||
}
|
||||
|
||||
/* Instead of trying to support Linux semantics for vfork() -- which requires adding corner-cases in
|
||||
* signal handling and syscalls -- we simply treat vfork() as fork(). We assume that performance hit
|
||||
* is negligible (Graphene has to migrate internal state anyway which is slow) and apps do not rely
|
||||
* on insane Linux-specific semantics of vfork(). */
|
||||
int shim_do_vfork(void) {
|
||||
debug("vfork() was called by the application, implemented as alias to fork() in Graphene\n");
|
||||
return shim_do_fork();
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/* Copyright (C) 2014 Stony Brook University
|
||||
This file is part of Graphene Library OS.
|
||||
|
||||
Graphene Library OS is free software: you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public License
|
||||
as published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
Graphene Library OS 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 Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
/*
|
||||
* shim_vfork.c
|
||||
*
|
||||
* Implementation of system call "vfork".
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <linux/futex.h>
|
||||
#include <pal.h>
|
||||
#include <pal_error.h>
|
||||
#include <shim_checkpoint.h>
|
||||
#include <shim_internal.h>
|
||||
#include <shim_table.h>
|
||||
#include <shim_thread.h>
|
||||
#include <shim_utils.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
int shim_do_vfork(void) {
|
||||
#ifdef ALIAS_VFORK_AS_FORK
|
||||
debug("vfork() is an alias to fork() in Graphene, calling fork() now\n");
|
||||
return shim_do_fork();
|
||||
#else
|
||||
/* NOTE: leaving this old implementation for historical reference */
|
||||
|
||||
/* DEP 7/7/12 - Why r13?
|
||||
*
|
||||
* Chia-che: when libc call vfork, they store the pointer to the
|
||||
* caller in rdi. (reference: sysdeps/unix/sysv/linux/x86_64/vfork.S.
|
||||
* Because rdi might be used in SHIM, I cache rdi in r13 (reference:
|
||||
* syscallas.S).
|
||||
*/
|
||||
struct shim_thread* cur_thread = get_cur_thread();
|
||||
struct shim_thread* new_thread = get_new_thread(0);
|
||||
/* put the new thread in a new process (thread group) */
|
||||
|
||||
__asm__ volatile ("movq %%rbp, %0\r\n" : "=r"(new_thread->frameptr));
|
||||
|
||||
size_t stack_size = 4096;
|
||||
|
||||
if (new_thread->frameptr <= cur_thread->stack_top && new_thread->frameptr > cur_thread->stack)
|
||||
stack_size = cur_thread->stack_top - new_thread->frameptr;
|
||||
|
||||
void* dummy_stack = system_malloc(stack_size);
|
||||
|
||||
if (!dummy_stack) {
|
||||
debug("creation of stack failed\n");
|
||||
put_thread(new_thread);
|
||||
return -PAL_ERRNO;
|
||||
}
|
||||
|
||||
memcpy(dummy_stack, new_thread->frameptr, stack_size);
|
||||
|
||||
/* assigned the stack of the thread */
|
||||
lock(&cur_thread->lock);
|
||||
new_thread->tgid = new_thread->tid;
|
||||
new_thread->in_vm = true;
|
||||
new_thread->is_alive = true;
|
||||
new_thread->stack = cur_thread->stack;
|
||||
new_thread->stack_top = cur_thread->stack_top;
|
||||
cur_thread->stack = dummy_stack;
|
||||
cur_thread->stack_top = dummy_stack + stack_size;
|
||||
cur_thread->frameptr = NULL;
|
||||
unlock(&cur_thread->lock);
|
||||
|
||||
/* Now we are good, set this child as ours */
|
||||
set_as_child(NULL, new_thread);
|
||||
/* add the child to the global list */
|
||||
add_thread(new_thread);
|
||||
new_thread->dummy = cur_thread;
|
||||
|
||||
struct shim_handle_map* handle_map = get_cur_handle_map(cur_thread);
|
||||
/* pop the ref count of current handle map to prevent revocation */
|
||||
get_handle_map(handle_map);
|
||||
struct shim_handle_map* new_map = NULL;
|
||||
/* duplicate handle map intp a new handle map */
|
||||
dup_handle_map(&new_map, handle_map);
|
||||
/* set the new handle map to new thread */
|
||||
set_handle_map(new_thread, new_map);
|
||||
/* push back the ref count of handle map */
|
||||
put_handle_map(handle_map);
|
||||
|
||||
/* we have the thread handle from PAL, now set it to the child */
|
||||
new_thread->pal_handle = cur_thread->pal_handle;
|
||||
|
||||
/* set the current thread running */
|
||||
set_cur_thread(new_thread);
|
||||
put_thread(new_thread);
|
||||
|
||||
/* here we return immediately, no letting the hooks mes up our stack */
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user