/* -*- mode:c; c-file-style:"k&r"; c-basic-offset: 4; tab-width:4; indent-tabs-mode:nil; mode:auto-fill; fill-column:78; -*- */
/* vim: set ts=4 sw=4 et tw=78 fo=cqt wm=0: */
/* Copyright (C) 2014 OSCAR lab, 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
/*
* shim_clone.c
*
* Implementation of system call "clone". (using "clone" as "fork" is not
* implemented yet.)
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* from **sysdeps/unix/sysv/linux/x86_64/clone.S:
The userland implementation is:
int clone (int (*fn)(void *arg), void *child_stack, int flags, void *arg),
the kernel entry is:
int clone (long flags, void *child_stack).
The parameters are passed in register and on the stack from userland:
rdi: fn
rsi: child_stack
rdx: flags
rcx: arg
r8d: TID field in parent
r9d: thread pointer
%esp+8: TID field in child
The kernel expects:
rax: system call number
rdi: flags
rsi: child_stack
rdx: TID field in parent
r10: TID field in child
r8: thread pointer
*/
/*
* This Function is a wrapper around the user provided function.
* Code flow for clone is as follows -
* 1) User application allocates stack for child process and
* calls clone. The clone code sets up the user function
* address and the argument address on the child stack.
* 2)we Hijack the clone call and control flows to shim_clone
* 3)In Shim Clone we just call the DK Api to create a thread by providing a
* wrapper function around the user provided function
* 4)PAL layer allocates a stack and then invokes the clone syscall
* 5)PAL runs thread_init function on PAL allocated Stack
* 6)thread_init calls our wrapper and gives the user provided stack
* address.
* 7.In the wrapper function ,we just do the stack switch to user
* Provided stack and execute the user Provided function.
*/
int clone_implementation_wrapper(struct clone_args * arg)
{
//The child thread created by PAL is now running on the
//PAL allocated stack. We need to switch the stack to use
//the user provided stack.
struct clone_args *pcargs = arg;
DkObjectsWaitAny(1, &pcargs->create_event, NO_TIMEOUT);
DkObjectClose(pcargs->create_event);
struct shim_thread * my_thread = pcargs->thread;
assert(my_thread);
get_thread(my_thread);
if (my_thread->set_child_tid) {
*(my_thread->set_child_tid) = my_thread->tid;
debug("clone: tid set at %p\n", my_thread->set_child_tid);
}
void * stack = pcargs->stack;
void * return_pc = pcargs->return_pc;
struct shim_vma * vma = NULL;
lookup_supervma(ALIGN_DOWN(stack), allocsize, &vma);
assert(vma);
my_thread->stack_top = stack;
my_thread->stack_red = my_thread->stack = vma->addr;
__libc_tcb_t tcb;
allocate_tls(&tcb, my_thread);
debug_setbuf(&tcb.shim_tcb, true);
/* Don't signal the initialize event until we are actually init-ed */
DkEventSet(pcargs->initialize_event);
/***** From here down, we are switching to the user-provided stack ****/
//user_stack_addr[0] ==> user provided function address
//user_stack_addr[1] ==> arguments to user provided function.
debug("child swapping stack to %p return %p: %d\n",
stack, return_pc, my_thread->tid);
asm volatile("movq %0, %%rsp\r\n"
"pushq %1\r\n"
"retq\r\n"
: : "r"(stack), "r"(return_pc), "a"(0));
return 0;
}
/* long int __arg0 - flags
* long int __arg1 - 16 bytes ( 2 words ) offset into the child stack allocated
* by the parent */
int shim_do_clone (int flags, void * user_stack_addr, int * parent_tidptr,
void * tls, int * child_tidptr)
{
//The Clone Implementation in glibc has setup the child's stack
//with the function pointer and the argument to the funciton.
INC_PROFILE_OCCURENCE(syscall_use_ipc);
struct shim_thread * self = get_cur_thread();
assert(self);
struct clone_args * new_args = __alloca(sizeof(struct clone_args));
memset(new_args, 0, sizeof(struct clone_args));
int ret = 0;
assert((flags & ~(CLONE_PARENT_SETTID|CLONE_CHILD_SETTID|
CLONE_CHILD_CLEARTID|CLONE_SETTLS|
CLONE_VM|CLONE_FILES|
CLONE_FS|CLONE_SIGHAND|CLONE_THREAD|
CLONE_DETACHED| // Unused
#ifdef CLONE_PTRACE
CLONE_PTRACE| // Unused
#endif
CLONE_SYSVSEM|CSIGNAL)) == 0);
new_args->create_event = DkNotificationEventCreate(0);
if (!new_args->create_event) {
ret = -PAL_ERRNO;
goto failed;
}
new_args->initialize_event = DkNotificationEventCreate(0);
if (!new_args->initialize_event) {
ret = -PAL_ERRNO;
goto failed;
}
new_args->thread = get_new_thread(0);
if (!new_args->thread) {
ret = -ENOMEM;
goto failed;
}
new_args->stack = user_stack_addr;
new_args->return_pc = *(void **) user_stack_addr;
if (flags & CLONE_PARENT_SETTID)
new_args->thread->set_child_tid = parent_tidptr;
if ((flags & CLONE_CHILD_SETTID) && (flags & CLONE_CHILD_CLEARTID)) {
ret = -EINVAL;
goto failed;
}
if (flags & CLONE_CHILD_SETTID)
new_args->thread->set_child_tid = child_tidptr;
if (flags & CLONE_CHILD_CLEARTID)
/* Implemented in shim_futex.c: release_clear_child_id */
new_args->thread->clear_child_tid = parent_tidptr;
if (flags & CLONE_SETTLS) {
new_args->thread->tcb = tls;
} else {
new_args->thread->tcb = NULL;
}
if ((flags & CLONE_VM) != CLONE_VM)
debug("Fork-like behavior is not yet implemented in clone\n");
if ((flags & CLONE_FS) != CLONE_FS)
debug("clone without CLONE_FS is not yet implemented\n");
if ((flags & CLONE_SIGHAND) != CLONE_SIGHAND)
debug("clone without CLONE_SIGHAND is not yet implemented\n");
if ((flags & CLONE_SYSVSEM) != CLONE_SYSVSEM)
debug("clone without CLONE_SYSVSEM is not yet implemented\n");
if ((flags & CLONE_THREAD) != CLONE_THREAD)
new_args->thread->tgid = new_args->thread->tid;
struct shim_handle_map * handle_map = get_cur_handle_map(self);
if (flags & CLONE_FILES) {
set_handle_map(new_args->thread, handle_map);
} else {
/* if CLONE_FILES is not given, the new thread should receive
a copy of current descriptor table */
struct shim_handle_map * new_map = NULL;
get_handle_map(handle_map);
dup_handle_map(&new_map, handle_map);
set_handle_map(new_args->thread, new_map);
put_handle_map(handle_map);
}
// Invoke DkThreadCreate to spawn off a child process using the actual
// "clone" system call. DkThreadCreate allocates a stack for the child
// and then runs the given function on that stack However, we want our
// child to run on the Parent allocated stack , so once the DkThreadCreate
// returns .The parent comes back here - however, the child is Happily
// running the function we gave to DkThreadCreate.
PAL_HANDLE pal_handle = thread_create(clone_implementation_wrapper,
new_args, flags);
if (!pal_handle) {
ret = -PAL_ERRNO;
goto failed;
}
new_args->thread->pal_handle = pal_handle;
new_args->thread->in_vm = new_args->thread->is_alive = true;
add_thread(new_args->thread);
set_as_child(NULL, new_args->thread);
put_thread(new_args->thread);
if (new_args->thread->set_child_tid)
*new_args->thread->set_child_tid = new_args->thread->tid;
DkEventSet(new_args->create_event);
DkObjectsWaitAny(1, &new_args->initialize_event, NO_TIMEOUT);
DkObjectClose(new_args->initialize_event);
return new_args->thread->tid;
failed:
if (new_args->create_event)
DkObjectClose(new_args->create_event);
if (new_args->initialize_event)
DkObjectClose(new_args->initialize_event);
if (new_args->thread)
put_thread(new_args->thread);
return ret;
}