diff --git a/LibOS/shim/include/shim_internal.h b/LibOS/shim/include/shim_internal.h index 6ab25b00..37e49381 100644 --- a/LibOS/shim/include/shim_internal.h +++ b/LibOS/shim/include/shim_internal.h @@ -173,9 +173,12 @@ attribute_nofp void fpstate_reset(void); /* definition for syscall table */ void handle_signal (void); +void handle_sysret_signal(void); +void handle_exit_signal(void); +int handle_next_signal(ucontext_t* user_uc); long convert_pal_errno (long err); void syscall_wrapper(void); -void syscall_wrapper_after_syscalldb(void); +void syscalldb_check_sigpending(void); #define PAL_ERRNO convert_pal_errno(PAL_NATIVE_ERRNO) @@ -210,13 +213,12 @@ static inline int64_t get_cur_preempt (void) { SHIM_ARG_TYPE ret = 0; \ int64_t preempt = get_cur_preempt(); \ __UNUSED(preempt); \ - /* handle_signal(); */ \ /* check_stack_hook(); */ \ BEGIN_SYSCALL_PROFILE(); #define END_SHIM(name) \ END_SYSCALL_PROFILE(name); \ - handle_signal(); \ + handle_sysret_signal(); \ assert(preempt == get_cur_preempt()); \ return ret; \ } @@ -494,8 +496,6 @@ static inline void __enable_preempt (shim_tcb_t * tcb) //debug("enable preempt: %d\n", preempt); } -void __handle_signal (shim_tcb_t * tcb, int sig); - static inline void enable_preempt (shim_tcb_t * tcb) { if (!tcb && !(tcb = shim_get_tcb())) @@ -505,9 +505,6 @@ static inline void enable_preempt (shim_tcb_t * tcb) if (!preempt) return; - if (preempt == 1) - __handle_signal(tcb, 0); - __enable_preempt(tcb); } @@ -771,9 +768,13 @@ static inline bool memory_migrated(void * mem) return mem >= migrated_memory_start && mem < migrated_memory_end; } - extern void * __load_address, * __load_address_end; extern void * __code_address, * __code_address_end; +extern void* __syscallas_return_begin; +extern void* __syscallas_return_before_jmp; +extern void* __syscallas_return_end; +extern void* __syscalldb_check_sigpending_begin; +extern void* __syscalldb_check_sigpending_end; unsigned long parse_int (const char * str); diff --git a/LibOS/shim/include/shim_signal.h b/LibOS/shim/include/shim_signal.h index cab49c22..0b45c593 100644 --- a/LibOS/shim/include/shim_signal.h +++ b/LibOS/shim/include/shim_signal.h @@ -93,9 +93,6 @@ __SIGSETFN (shim_sigdelset, ((__set->__val[__word] &= ~__mask), 0), ) /* NB: Check shim_signal.c if this changes. Some memset(0) elision*/ struct shim_signal { siginfo_t info; - bool context_stored; - ucontext_t context; - PAL_CONTEXT * pal_context; }; #define MAX_SIGNAL_LOG 32 diff --git a/LibOS/shim/include/shim_tcb.h b/LibOS/shim/include/shim_tcb.h index d8bb42fd..71b00185 100644 --- a/LibOS/shim/include/shim_tcb.h +++ b/LibOS/shim/include/shim_tcb.h @@ -9,7 +9,6 @@ struct shim_regs { uint64_t orig_rax; - uint64_t rsp; uint64_t r15; uint64_t r14; uint64_t r13; @@ -26,12 +25,12 @@ struct shim_regs { uint64_t rbp; uint64_t rflags; uint64_t rip; + uint64_t rsp; }; struct shim_context { struct shim_regs * regs; uint64_t fs_base; - struct shim_context * next; uint64_t enter_time; struct atomic_int preempt; }; @@ -47,6 +46,9 @@ struct shim_tcb { unsigned int tid; int pal_errno; struct debug_buf * debug_buf; +#define SHIM_FLAG_SIGPENDING 0 + unsigned long flags; + uint64_t tmp_rip; /* syscall stack range */ void* syscall_stack_low; diff --git a/LibOS/shim/include/shim_types.h b/LibOS/shim/include/shim_types.h index 70446da5..268f42cf 100644 --- a/LibOS/shim/include/shim_types.h +++ b/LibOS/shim/include/shim_types.h @@ -274,6 +274,17 @@ enum # define REG_CR2 REG_CR2 }; +union csgsfs { + struct { + uint16_t cs; + uint16_t gs; + uint16_t fs; + uint16_t ss; + + }; + uint64_t csgsfs; +}; + struct _libc_fpxreg { unsigned short int significand[4]; unsigned short int exponent; @@ -360,14 +371,28 @@ typedef struct { /* Userlevel context. */ typedef struct ucontext { +#define UC_FP_XSTATE 0x1 +#define UC_SIGCONTEXT_SS 0x2 +#define UC_STRICT_RESTORE_SS 0x4 unsigned long int uc_flags; struct ucontext *uc_link; +// stack_t::ss_flags +#define SS_ONSTACK 1 +#define SS_DISABLE 2 +#define SS_AUTODISARM (1U << 31) /* disable sas during sighandling */ stack_t uc_stack; mcontext_t uc_mcontext; __sigset_t uc_sigmask; struct _libc_fpstate __fpregs_mem; } ucontext_t; +struct sigframe { + void * restorer; + ucontext_t uc; + siginfo_t info; + /* fpstate follows */ +}; + #define RED_ZONE_SIZE 128 /* bits/ustat.h */ diff --git a/LibOS/shim/src/bookkeep/shim_signal.c b/LibOS/shim/src/bookkeep/shim_signal.c index 054d140e..774b31c4 100644 --- a/LibOS/shim/src/bookkeep/shim_signal.c +++ b/LibOS/shim/src/bookkeep/shim_signal.c @@ -34,11 +34,14 @@ #include +static void __handle_signal(shim_tcb_t * tcb, int sig, PAL_CONTEXT* context); + // __rt_sighandler_t is different from __sighandler_t in : // typedef void __signalfn_t(int); // typedef __signalfn_t *__sighandler_t typedef void (*__rt_sighandler_t)(int, siginfo_t*, void*); +typedef void (*restorer_t)(void); static __rt_sighandler_t default_sighandler[NUM_SIGS]; @@ -65,6 +68,7 @@ allocate_signal_log (struct shim_thread * thread, int sig) head, tail, thread->has_signal.counter + 1); atomic_inc(&thread->has_signal); + set_bit(SHIM_FLAG_SIGPENDING, &thread->shim_tcb->flags); return &log->logs[old_tail]; } @@ -102,54 +106,12 @@ fetch_signal_log (struct shim_thread * thread, int sig) return signal; } -static void -__handle_one_signal (shim_tcb_t * tcb, int sig, struct shim_signal * signal); - static void __store_info (siginfo_t * info, struct shim_signal * signal) { if (info) memcpy(&signal->info, info, sizeof(siginfo_t)); } -void __store_context (shim_tcb_t * tcb, PAL_CONTEXT * pal_context, - struct shim_signal * signal) -{ - ucontext_t * context = &signal->context; - - if (tcb && tcb->context.regs && tcb->context.regs->orig_rax) { - struct shim_context * ct = &tcb->context; - - if (ct->regs) { - struct shim_regs * regs = ct->regs; - context->uc_mcontext.gregs[REG_RIP] = regs->rip; - context->uc_mcontext.gregs[REG_EFL] = regs->rflags; - context->uc_mcontext.gregs[REG_R15] = regs->r15; - context->uc_mcontext.gregs[REG_R14] = regs->r14; - context->uc_mcontext.gregs[REG_R13] = regs->r13; - context->uc_mcontext.gregs[REG_R12] = regs->r12; - context->uc_mcontext.gregs[REG_R11] = regs->r11; - context->uc_mcontext.gregs[REG_R10] = regs->r10; - context->uc_mcontext.gregs[REG_R9] = regs->r9; - context->uc_mcontext.gregs[REG_R8] = regs->r8; - context->uc_mcontext.gregs[REG_RCX] = regs->rcx; - context->uc_mcontext.gregs[REG_RDX] = regs->rdx; - context->uc_mcontext.gregs[REG_RSI] = regs->rsi; - context->uc_mcontext.gregs[REG_RDI] = regs->rdi; - context->uc_mcontext.gregs[REG_RBX] = regs->rbx; - context->uc_mcontext.gregs[REG_RBP] = regs->rbp; - context->uc_mcontext.gregs[REG_RSP] = regs->rsp; - } - - signal->context_stored = true; - return; - } - - if (pal_context) { - memcpy(context->uc_mcontext.gregs, pal_context, sizeof(PAL_CONTEXT)); - signal->context_stored = true; - } -} - void deliver_signal (siginfo_t * info, PAL_CONTEXT * context) { shim_tcb_t * tcb = shim_get_tcb(); @@ -163,30 +125,26 @@ void deliver_signal (siginfo_t * info, PAL_CONTEXT * context) struct shim_thread * cur_thread = (struct shim_thread *) tcb->tp; int sig = info->si_signo; + struct shim_signal* signal = malloc(sizeof(*signal)); + if (!signal) { + return; + } + int64_t preempt = __disable_preempt(tcb); - struct shim_signal * signal = __alloca(sizeof(struct shim_signal)); /* save in signal */ memset(signal, 0, sizeof(struct shim_signal)); __store_info(info, signal); - __store_context(tcb, context, signal); - signal->pal_context = context; - - if (preempt > 1 || - __sigismember(&cur_thread->signal_mask, sig)) { - struct shim_signal ** signal_log = NULL; - if ((signal = malloc_copy(signal,sizeof(struct shim_signal))) && - (signal_log = allocate_signal_log(cur_thread, sig))) { - *signal_log = signal; - } - if (signal && !signal_log) { - SYS_PRINTF("signal queue is full (TID = %u, SIG = %d)\n", - tcb->tid, sig); - free(signal); - } + struct shim_signal** signal_log = allocate_signal_log(cur_thread, sig); + if (signal_log) { + *signal_log = signal; } else { - __handle_signal(tcb, sig); - __handle_one_signal(tcb, sig, signal); + SYS_PRINTF("signal queue is full (TID = %u, SIG = %d)\n", + tcb->tid, sig); + free(signal); + } + if (preempt <= 1) { + __handle_signal(tcb, sig, context); } __enable_preempt(tcb); @@ -208,11 +166,16 @@ void deliver_signal (siginfo_t * info, PAL_CONTEXT * context) #define IP eip #endif -static inline bool context_is_internal(PAL_CONTEXT * context) -{ +static inline bool DkInPal(PAL_CONTEXT* context) { return context && - (void *) context->IP >= (void *) &__code_address && - (void *) context->IP < (void *) &__code_address_end; + PAL_CB(pal_text.start) <= (void*)context->IP && + (void*)context->IP < PAL_CB(pal_text.end); +} + +static inline bool context_is_internal(PAL_CONTEXT* context) { + return context && + (void *)&__code_address <= (void *)context->IP && + (void *)context->IP < (void *)&__code_address_end; } static inline void internal_fault(const char* errstr, @@ -233,7 +196,8 @@ static inline void internal_fault(const char* errstr, static void arithmetic_error_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) { - if (is_internal_tid(get_cur_tid()) || context_is_internal(context)) { + if (is_internal_tid(get_cur_tid()) || context_is_internal(context) || + DkInPal(context)) { internal_fault("Internal arithmetic fault", arg, context); } else { if (context) @@ -259,7 +223,8 @@ static void memfault_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) goto ret_exception; } - if (is_internal_tid(get_cur_tid()) || context_is_internal(context)) { + if (is_internal_tid(get_cur_tid()) || context_is_internal(context) || + DkInPal(context)) { internal_fault("Internal memory fault", arg, context); goto ret_exception; } @@ -474,21 +439,13 @@ ret_fault: return has_fault; } -void __attribute__((weak)) syscall_wrapper(void) -{ - /* - * work around for link. - * syscalldb.S is excluded for libsysdb_debug.so so it fails to link - * due to missing syscall_wrapper. - */ -} - static void illegal_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) { struct shim_vma_val vma; if (!is_internal_tid(get_cur_tid()) && !context_is_internal(context) && + !DkInPal(context) && !(lookup_vma((void *) arg, &vma)) && !(vma.flags & VMA_INTERNAL)) { @@ -541,12 +498,87 @@ static void illegal_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) DkExceptionReturn(event); } +/* + * workaround for link syscalldb_debug.so + * syscalldb.S is excluded from libsysdb_debug.so so it fails to link + * due to missing symbols. + */ +void* __syscallas_return_begin __attribute__((weak)) = NULL; +void* __syscallas_return_before_jmp __attribute__((weak)) = NULL; +void* __syscallas_return_end __attribute__((weak)) = NULL; +void* __syscalldb_check_sigpending_begin __attribute__((weak)) = NULL; +void* __syscalldb_check_sigpending_end __attribute__((weak)) = NULL; + +void __attribute__((weak)) syscall_wrapper(void) { +} + +void __attribute__((weak)) syscalldb_check_sigpending(void) { +} + +static void syscallas_return_emulate(PAL_CONTEXT* context) { + if (!context) { + return; + } + + /* see syscallas.S + * Emulate returning to app. + * We've past the last check of signal pending, but still in LibOS. + * Emulate last instructions returning to app so that it's in app. + * Then we can handle async signal safely. + */ + void* rip = (void *)context->IP; + if (rip == (void*)&__syscallas_return_before_jmp) { + // emulate jmp *r11 + shim_tcb_t* tcb = shim_get_tcb(); + assert(tcb->context.regs == NULL); + context->rip = tcb->tmp_rip; + } else if ((void*)&__syscallas_return_begin <= rip && + rip <= (void*)&__syscallas_return_end) { + // emulate __syscallas_return_begin to __syscallas_return_end + shim_tcb_t* tcb = shim_get_tcb(); + assert(tcb); + + struct shim_regs* regs = tcb->context.regs; + assert(regs); + tcb->context.regs = NULL; + + context->r15 = regs->r15; + context->r14 = regs->r14; + context->r13 = regs->r13; + context->r12 = regs->r12; + context->r11 = regs->r11; + context->r10 = regs->r10; + context->r9 = regs->r9; + context->r8 = regs->r8; + context->rcx = regs->rcx; + context->rdx = regs->rdx; + context->rsi = regs->rsi; + context->rdi = regs->rdi; + context->rbx = regs->rbx; + context->rbp = regs->rbp; + context->efl = regs->rflags; + context->rsp = regs->rsp; + context->rip = regs->rip; + } else if ((void*)&__syscalldb_check_sigpending_begin <= rip && + rip <= (void*)&__syscalldb_check_sigpending_end) { + /* + * emulate ret instruction. + * As we'll deliver signal, sigpending check in + * syscalldb_check_sigpending can be safely skipped. + */ + uint64_t* rsp = (uint64_t*)context->rsp; + context->rip = *rsp; + rsp++; + context->rsp = (uint64_t)rsp; + } +} + static void quit_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) { __UNUSED(arg); - __UNUSED(context); + syscallas_return_emulate(context); if (!is_internal_tid(get_cur_tid())) { - deliver_signal(ALLOC_SIGINFO(SIGTERM, SI_USER, si_pid, 0), NULL); + deliver_signal(ALLOC_SIGINFO(SIGTERM, SI_USER, si_pid, 0), context); } DkExceptionReturn(event); } @@ -554,9 +586,9 @@ static void quit_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) static void suspend_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) { __UNUSED(arg); - __UNUSED(context); + syscallas_return_emulate(context); if (!is_internal_tid(get_cur_tid())) { - deliver_signal(ALLOC_SIGINFO(SIGINT, SI_USER, si_pid, 0), NULL); + deliver_signal(ALLOC_SIGINFO(SIGINT, SI_USER, si_pid, 0), context); } DkExceptionReturn(event); } @@ -564,15 +596,15 @@ static void suspend_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) static void resume_upcall (PAL_PTR event, PAL_NUM arg, PAL_CONTEXT * context) { __UNUSED(arg); - __UNUSED(context); shim_tcb_t * tcb = shim_get_tcb(); if (!tcb || !tcb->tp) return; + syscallas_return_emulate(context); if (!is_internal_tid(get_cur_tid())) { int64_t preempt = __disable_preempt(tcb); if (preempt <= 1) - __handle_signal(tcb, 0); + __handle_signal(tcb, 0, context); __enable_preempt(tcb); } DkExceptionReturn(event); @@ -618,106 +650,204 @@ __sigset_t * set_sig_mask (struct shim_thread * thread, return &thread->signal_mask; } -static __rt_sighandler_t __get_sighandler(struct shim_thread* thread, int sig) { - struct shim_signal_handle* sighdl = &thread->signal_handles[sig - 1]; - __rt_sighandler_t handler = NULL; +static void __get_sighandler(struct shim_thread* thread, int sig, + __rt_sighandler_t* handler, restorer_t* restorer) { + *handler = NULL; + *restorer = NULL; + struct shim_signal_handle* sighdl = &thread->signal_handles[sig - 1]; if (sighdl->action) { - struct __kernel_sigaction * act = sighdl->action; - /* - * on amd64, sa_handler can be treated as sa_sigaction - * because 1-3 arguments are passed by register and - * sa_handler simply ignores 2nd and 3rd argument. - */ -#ifdef __i386__ -# error "x86-32 support is heavily broken." -#endif - handler = (void*)act->k_sa_handler; + struct __kernel_sigaction* act = sighdl->action; + *handler = (void*)act->k_sa_handler; + *restorer = act->sa_restorer; if (act->sa_flags & SA_RESETHAND) { sighdl->action = NULL; free(act); } } - if ((void*)handler == SIG_IGN) - return NULL; - - return handler ? : default_sighandler[sig - 1]; + if ((void*)*handler == SIG_IGN) { + *handler = NULL; + } else if (!*handler) { + *handler = default_sighandler[sig - 1]; + } } -static void -__handle_one_signal(shim_tcb_t* tcb, int sig, struct shim_signal* signal) { - struct shim_thread* thread = (struct shim_thread*)tcb->tp; - __rt_sighandler_t handler = NULL; +static void get_sighandler(struct shim_thread * thread, int sig, + __rt_sighandler_t* handler, restorer_t* restorer) { + lock(&thread->lock); + __get_sighandler(thread, sig, handler, restorer); + unlock(&thread->lock); +} + +static unsigned int fpstate_size_get(const struct _libc_fpstate* fpstate) { + if (fpstate == NULL) + return 0; + + const struct _fpx_sw_bytes* sw = &fpstate->sw_reserved; + if (sw->magic1 == FP_XSTATE_MAGIC1 && + sw->xstate_size < sw->extended_size && + *((__typeof__(FP_XSTATE_MAGIC2)*)((void*)fpstate + sw->xstate_size)) == + FP_XSTATE_MAGIC2) + return sw->extended_size; + + return sizeof(struct swregs_state); +} + +static void direct_call_if_default_handler( + int sig, siginfo_t* info, __rt_sighandler_t handler); + +static void* __get_signal_stack(struct shim_thread* thread, void* current_stack) { + const stack_t* ss = &thread->signal_altstack; + if (ss->ss_flags & SS_DISABLE) + return current_stack - RED_ZONE_SIZE; + if (ss->ss_sp < current_stack && current_stack <= ss->ss_sp + ss->ss_size) + return current_stack - RED_ZONE_SIZE; + + return ss->ss_sp + ss->ss_size; +} + +static void* aligndown_sigframe(void* sp) { + return ALIGN_DOWN_PTR(sp, 16UL) - 8; +} + +static void __setup_sig_frame( + shim_tcb_t* tcb, int sig, struct shim_signal* signal, PAL_CONTEXT* context, + __rt_sighandler_t handler, restorer_t restorer) { + __UNUSED(tcb); + direct_call_if_default_handler(sig, &signal->info, handler); + + struct _libc_xregs_state* xregs_state = + (struct _libc_xregs_state* )context->fpregs; + struct _libc_fpstate* fpstate = &xregs_state->fpstate; + unsigned int fpstate_size = fpstate_size_get(fpstate); + + void* sp = __get_signal_stack(tcb->tp, (void*)context->rsp); + fpregset_t user_fp = ALIGN_DOWN_PTR(sp - fpstate_size, 64UL); + struct sigframe* user_sigframe = + aligndown_sigframe((void*)user_fp - sizeof(struct sigframe)); + assert(&user_sigframe->uc == ALIGN_UP_PTR(&user_sigframe->uc, 16UL)); + user_sigframe->restorer = restorer; + user_sigframe->uc.uc_flags = UC_SIGCONTEXT_SS | UC_STRICT_RESTORE_SS; + user_sigframe->uc.uc_link = NULL; + /* the layout of PAL_CONTEXT is same to gregs */ + memcpy(&user_sigframe->uc.uc_mcontext.gregs, context, + sizeof(user_sigframe->uc.uc_mcontext.gregs)); + + stack_t* stack = &user_sigframe->uc.uc_stack; + *stack = tcb->tp->signal_altstack; + + memcpy(&user_sigframe->info, &signal->info, sizeof(signal->info)); + if (fpstate_size > 0) { + user_sigframe->uc.uc_flags |= UC_FP_XSTATE; + memcpy(user_fp, fpstate, fpstate_size); + user_sigframe->uc.uc_mcontext.fpregs = user_fp; + } else { + user_sigframe->uc.uc_flags &= ~UC_FP_XSTATE; + user_sigframe->uc.uc_mcontext.fpregs = NULL; + } + + context->rsp = (long)user_sigframe; + context->rip = (long)handler; + context->rdi = (long)signal->info.si_signo; + context->rsi = (long)&user_sigframe->info; + context->rdx = (long)&user_sigframe->uc; + context->rax = 0; + context->fpregs = NULL; + + debug("deliver signal handler to user stack %p (%d, %p) sigframe: %p uc: %p fpstate %p\n", + handler, sig, &signal->info, + user_sigframe, &user_sigframe->uc, + user_sigframe->uc.uc_mcontext.fpregs); +} + +static void __handle_signal(shim_tcb_t* tcb, int sig, PAL_CONTEXT* context) { + /* + * check if we're in LibOS or Pal before get_sighandler() which + * acquires thread->lock. It may cause deadlock if we tries to lock + * from host signal handler. + */ + if (context == NULL || context_is_internal(context) || DkInPal(context)) { + /* + * host signal handler is called during PAL or LibOS. + * It means thread is in systeam call emulation. actual signal + * delivery is done by deliver_signal_on_sysret() + */ + set_bit(SHIM_FLAG_SIGPENDING, &tcb->flags); + return; + } + + struct shim_thread* thread = tcb->tp; + assert(thread); + if (!atomic_read(&thread->has_signal)) + return; + + int begin_sig = 1; + int end_sig = NUM_KNOWN_SIGS; + if (sig) { + begin_sig = sig; + end_sig = sig + 1; + } + + struct shim_signal* signal = NULL; + for (sig = begin_sig; sig < end_sig; sig++) { + if (!__sigismember(&thread->signal_mask, sig) && + (signal = fetch_signal_log(thread, sig))) + break; + } + if (!signal) + return; if (signal->info.si_signo == SIGCP) { join_checkpoint(thread, SI_CP_SESSION(&signal->info)); - return; + } else { + /* + * host signal arrived while application is running. + * setup signal frame on app stack and return back to app signal handler + * thourgh host sigreturn. + */ + __rt_sighandler_t handler; + restorer_t restorer; + get_sighandler(thread, sig, &handler, &restorer); + if (handler) { + debug("%s handled\n", signal_name(sig)); + __setup_sig_frame(tcb, sig, signal, context, handler, restorer); + } } - - lock(&thread->lock); - handler = __get_sighandler(thread, sig); - unlock(&thread->lock); - - if (!handler) - return; - - debug("%s handled\n", signal_name(sig)); - - // If the context is never stored in the signal, it means the signal is handled during - // system calls, and before the thread is resumed. - if (!signal->context_stored) - __store_context(tcb, NULL, signal); - - struct shim_context * context = NULL; - - if (tcb->context.regs && tcb->context.regs->orig_rax) { - context = __alloca(sizeof(struct shim_context)); - memcpy(context, &tcb->context, sizeof(struct shim_context)); - tcb->context.regs->orig_rax = 0; - tcb->context.next = context; - } - - debug("run signal handler %p (%d, %p, %p)\n", handler, sig, &signal->info, - &signal->context); - - (*handler) (sig, &signal->info, &signal->context); - - if (context) - memcpy(&tcb->context, context, sizeof(struct shim_context)); - - if (signal->pal_context) - memcpy(signal->pal_context, signal->context.uc_mcontext.gregs, sizeof(PAL_CONTEXT)); + free(signal); } -void __handle_signal (shim_tcb_t * tcb, int sig) -{ - struct shim_thread * thread = tcb->tp; +void handle_exit_signal(void) { + struct shim_thread* thread = get_cur_thread(); assert(thread); - int begin_sig = 1, end_sig = NUM_KNOWN_SIGS; - - if (sig) - end_sig = (begin_sig = sig) + 1; - - sig = begin_sig; - while (atomic_read(&thread->has_signal)) { - struct shim_signal * signal = NULL; + for (int sig = 1; sig < NUM_KNOWN_SIGS; sig++) { + while (true) { + struct shim_signal* signal = fetch_signal_log(thread, sig); + if (!signal) + break; - for ( ; sig < end_sig ; sig++) - if (!__sigismember(&thread->signal_mask, sig) && - (signal = fetch_signal_log(thread, sig))) - break; + if (!__sigismember(&thread->signal_mask, sig)) { + __rt_sighandler_t handler; + restorer_t restorer; + get_sighandler(thread, sig, &handler, &restorer); + direct_call_if_default_handler(sig, &signal->info, handler); + } + free(signal); + } + } + } +} - if (!signal) - break; +void handle_sysret_signal(void) { + shim_tcb_t* tcb = shim_get_tcb(); + struct shim_thread* thread = (struct shim_thread*)tcb->tp; - if (!signal->context_stored) - __store_context(tcb, NULL, signal); - - __handle_one_signal(tcb, sig, signal); - free(signal); - DkThreadYieldExecution(); + clear_bit(SHIM_FLAG_SIGPENDING, &tcb->flags); + /* This doesn't take user signal mask into account. + peek_signal_log would be needed. not fetch_signal_log */ + if (atomic_read(&thread->has_signal)) { + set_bit(SHIM_FLAG_SIGPENDING, &tcb->flags); } } @@ -737,15 +867,209 @@ void handle_signal (void) if (preempt > 1) debug("signal delayed (%ld)\n", preempt); else - __handle_signal(tcb, 0); + __handle_signal(tcb, 0, NULL); __enable_preempt(tcb); debug("__enable_preempt: %s:%d\n", __FILE__, __LINE__); } +struct sig_deliver { + int sig; + struct shim_signal* signal; + __rt_sighandler_t handler; + restorer_t restorer; +}; + +static bool __get_signal_to_deliver(struct sig_deliver* deliver) { + deliver->signal = NULL; + struct shim_thread* thread = get_cur_thread(); + + while (atomic_read(&thread->has_signal)) { + struct shim_signal* signal = NULL; + /* signul number starts from 1 */ + int sig; + for (sig = 1 ; sig < NUM_KNOWN_SIGS ; sig++) + if (!__sigismember(&thread->signal_mask, sig) && + (signal = fetch_signal_log(thread, sig))) + break; + + if (!signal) + break; + + __rt_sighandler_t handler; + restorer_t restorer; + get_sighandler(thread, sig, &handler, &restorer); + if (!handler) + continue; + + deliver->sig = sig; + deliver->signal = signal; + deliver->handler = handler; + deliver->restorer = restorer; + return true; + } + return false; +} + +/* + * sigreturn uses this. + * If other signal are pending still, deliver it instead of return back + * to app. The existing sigframe can be reused. + */ +int handle_next_signal(ucontext_t* user_uc) { + struct sig_deliver deliver; + if (!__get_signal_to_deliver(&deliver)) + return 0; + + struct shim_regs* regs = shim_get_tcb()->context.regs; + struct sigframe* user_sigframe = (struct sigframe*)(((void*)user_uc) - 8); + + user_sigframe->restorer = deliver.restorer; + regs->rsp = (uint64_t)user_sigframe; + regs->rip = (uint64_t)deliver.handler; + regs->rdi = (uint64_t)deliver.sig; + regs->rsi = (uint64_t)&user_sigframe->info; + regs->rdx = (uint64_t)&user_sigframe->uc; + + // TODO signal mask + + free(deliver.signal); + return 1; +} + +/* + * 16-byte alignment on ucontext_t on signal frame + * align struct shim_regs to 8 (mod 16) bytes + * => align sigframe->us to 16 bytes + */ +static_assert( + (((8 + sizeof(struct shim_regs)) + offsetof(struct sigframe, uc)) % 16) == 0, + "signal stack frame isn't aligned to 16 byte on calling deliver_signal_on_sysret"); + +/* + * host signal arrived while LibOS or PAL was running. So the emulated + * signal was queued. + * Now we're returning back to app. + * setup signal frame and return from system call to signal handler. + */ +attribute_nofp uint64_t deliver_signal_on_sysret(uint64_t syscall_ret) { + shim_tcb_t* tcb = shim_get_tcb(); + struct shim_regs* regs = tcb->context.regs; + void* stack = (void*)regs->rsp; + + struct sig_deliver deliver; + debug("regs: %p sp: %08lx ip: %08lx stack: %p &tcb %p tcb %p\n", + regs, regs->rsp, regs->rip, stack, &tcb, tcb); + + clear_bit(SHIM_FLAG_SIGPENDING, &tcb->flags); + /* FIXME: sigsuspend, sigwait, sigwaitinfo, pselect, ppoll are + * broken because signal mask was changed when blocking and + * is restored on returning from system call. + * So we miss the signal which is masked in user space and + * unmasked during blocking. + */ + if (!__get_signal_to_deliver(&deliver)) { + debug("no deliverable signal\n"); + return syscall_ret; + } + + int sig = deliver.sig; + struct shim_signal* signal = deliver.signal; + __rt_sighandler_t handler = deliver.handler; + restorer_t restorer = deliver.restorer; + direct_call_if_default_handler(sig, &signal->info, handler); + + void* sp = __get_signal_stack(tcb->tp, stack); + /* + * FIXME: + * For now we can't distinguish how system call is invoked + * i.e. through calling syscalldb or jumping into syscall_wrapper. + * If syscall is invoked as + * subq $RED_ZONE_SIZE, %rsp + * callq syscalldb + * addq $RED_ZONE_SIZE, %rsp + * red zone is avoided twice unnecessarily for signal handler. + */ + + /* allocate signal frame */ + sp -= sizeof(struct sigframe) + FP_XSTATE_MAGIC2_SIZE + 64; + sp -= fpu_xstate_size; + sp = aligndown_sigframe(sp); + stack = sp; + struct sigframe* user_sigframe = stack; + + assert(&user_sigframe->uc == ALIGN_UP_PTR(&user_sigframe->uc, 16UL)); + stack += sizeof(*user_sigframe); + stack = ALIGN_UP_PTR(stack, 64UL); + struct _libc_fpstate* user_fpstate = stack; + + debug("regs: %p sigframe: %p uc: %p fpstate: %p\n", + regs, user_sigframe, &user_sigframe->uc, user_fpstate); + + /* setup sigframe */ + user_sigframe->restorer = restorer; + + ucontext_t* user_uc = &user_sigframe->uc; + user_uc->uc_flags = UC_FP_XSTATE; + user_uc->uc_link = NULL; + user_uc->uc_stack = tcb->tp->signal_altstack; + + gregset_t * gregs = &user_uc->uc_mcontext.gregs; + (*gregs)[REG_R8] = regs->r8; + (*gregs)[REG_R9] = regs->r9; + (*gregs)[REG_R10] = regs->r10; + (*gregs)[REG_R11] = regs->r11; + (*gregs)[REG_R12] = regs->r12; + (*gregs)[REG_R13] = regs->r13; + (*gregs)[REG_R14] = regs->r14; + (*gregs)[REG_R15] = regs->r15; + (*gregs)[REG_RDI] = regs->rdi; + (*gregs)[REG_RSI] = regs->rsi; + (*gregs)[REG_RBP] = regs->rbp; + (*gregs)[REG_RBX] = regs->rbx; + (*gregs)[REG_RDX] = regs->rdx; + (*gregs)[REG_RAX] = syscall_ret; + (*gregs)[REG_RCX] = regs->rcx; + (*gregs)[REG_RSP] = regs->rsp; + (*gregs)[REG_RIP] = regs->rip; + (*gregs)[REG_EFL] = regs->rflags; + union csgsfs sr = { + .cs = 0x33, // __USER_CS(5) | 0(GDT) | 3(RPL) + .fs = 0, + .gs = 0, + .ss = 0x2b, // __USER_DS(6) | 0(GDT) | 3(RPL) + }; + (*gregs)[REG_CSGSFS] = sr.csgsfs; + + (*gregs)[REG_ERR] = signal->info.si_errno; + (*gregs)[REG_TRAPNO] = signal->info.si_code; + (*gregs)[REG_OLDMASK] = 0; + (*gregs)[REG_CR2] = (long)signal->info.si_addr; + + user_uc->uc_mcontext.fpregs = user_fpstate; + memset(user_fpstate, 0, fpu_xstate_size); + fpstate_save(user_fpstate); + fpstate_reset(); + + // TODO: get current sigmask and mask signal + __sigemptyset(&user_uc->uc_sigmask); + + free(signal); + + // setup to return to signal handler + regs->rsp = (uint64_t)user_sigframe; + regs->rip = (unsigned long)handler; + regs->rdi = (unsigned long)sig; + regs->rsi = (unsigned long)&user_sigframe->info; + regs->rdx = (unsigned long)&user_sigframe->uc; + return /*rax=*/0; +} + // Need to hold thread->lock when calling this function void append_signal(struct shim_thread* thread, int sig, siginfo_t* info, bool need_interrupt) { - __rt_sighandler_t handler = __get_sighandler(thread, sig); + __rt_sighandler_t handler; + restorer_t restorer; + __get_sighandler(thread, sig, &handler, &restorer); if (!handler) { // SIGSTOP and SIGKILL cannot be ignored @@ -774,7 +1098,6 @@ void append_signal(struct shim_thread* thread, int sig, siginfo_t* info, bool ne /* save in signal */ if (info) { __store_info(info, signal); - signal->context_stored = false; } else { memset(signal, 0, sizeof(struct shim_signal)); } @@ -840,6 +1163,17 @@ static void sighandler_core (int sig, siginfo_t * info, void * ucontext) sighandler_kill(sig, info, ucontext); } +static void direct_call_if_default_handler( + int sig, siginfo_t* info, __rt_sighandler_t handler) { + /* we know sighandler_kill only kill the thread + * without using info and context */ + if (handler == &sighandler_kill || handler == &sighandler_core) { + debug("direct calling sighandler_kill\n"); + // this thread exits. + handler(sig, info, NULL); + } +} + static __rt_sighandler_t default_sighandler[NUM_SIGS] = { /* SIGHUP */ &sighandler_kill, /* SIGINT */ &sighandler_kill, diff --git a/LibOS/shim/src/bookkeep/shim_thread.c b/LibOS/shim/src/bookkeep/shim_thread.c index 0342f06c..04726491 100644 --- a/LibOS/shim/src/bookkeep/shim_thread.c +++ b/LibOS/shim/src/bookkeep/shim_thread.c @@ -813,10 +813,15 @@ BEGIN_CP_FUNC(running_thread) new_thread->shim_tcb = (void *)(base + toff); struct shim_tcb* new_tcb = new_thread->shim_tcb; memcpy(new_tcb, thread->shim_tcb, sizeof(*new_tcb)); + struct shim_regs* regs = thread->shim_tcb->context.regs; + if (regs) { + ptr_t roff = ADD_CP_OFFSET(sizeof(*regs)); + new_tcb->context.regs = (struct shim_regs*)(base + roff); + memcpy(new_tcb->context.regs, regs, sizeof(*regs)); + } /* don't export stale pointers */ new_tcb->self = NULL; new_tcb->tp = NULL; - new_tcb->context.next = NULL; new_tcb->debug_buf = NULL; } } @@ -861,8 +866,12 @@ BEGIN_RS_FUNC(running_thread) thread->vmid = cur_process.vmid; - if (thread->shim_tcb) + if (thread->shim_tcb) { CP_REBASE(thread->shim_tcb); + if (thread->shim_tcb->context.regs) { + CP_REBASE(thread->shim_tcb->context.regs); + } + } if (thread->set_child_tid) { /* CLONE_CHILD_SETTID */ diff --git a/LibOS/shim/src/elf/shim_rtld.c b/LibOS/shim/src/elf/shim_rtld.c index 08dd0928..6e81258d 100644 --- a/LibOS/shim/src/elf/shim_rtld.c +++ b/LibOS/shim/src/elf/shim_rtld.c @@ -1392,6 +1392,7 @@ static ElfW(Addr)* __vdso_shim_clock_gettime __attribute_migratable = NULL; static ElfW(Addr)* __vdso_shim_gettimeofday __attribute_migratable = NULL; static ElfW(Addr)* __vdso_shim_time __attribute_migratable = NULL; static ElfW(Addr)* __vdso_shim_getcpu __attribute_migratable = NULL; +static ElfW(Addr)* __vdso_shim_check_sigpending __attribute_migratable = NULL; static const struct { const char* name; @@ -1416,7 +1417,13 @@ static const struct { .name = "__vdso_shim_getcpu", .value = (ElfW(Addr))&__shim_getcpu, .func = &__vdso_shim_getcpu, - }}; + }, + { + .name = "__vdso_shim_check_sigpending", + .value = (ElfW(Addr))&syscalldb_check_sigpending, + .func = &__vdso_shim_check_sigpending, + } +}; static int vdso_map_init(void) { /* diff --git a/LibOS/shim/src/generated-offsets.c b/LibOS/shim/src/generated-offsets.c index c2ebb188..e4bac4c1 100644 --- a/LibOS/shim/src/generated-offsets.c +++ b/LibOS/shim/src/generated-offsets.c @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include @@ -9,14 +11,18 @@ void dummy(void) { OFFSET_T(SHIM_TCB_OFFSET, PAL_TCB, libos_tcb); OFFSET_T(TCB_REGS, shim_tcb_t, context.regs); + OFFSET_T(SHIM_TCB_FLAGS, shim_tcb_t, flags); + OFFSET_T(SHIM_TCB_TMP_RIP, shim_tcb_t, tmp_rip); OFFSET_T(SHIM_TCB_SYSCALL_STACK_LOW, shim_tcb_t, syscall_stack_low); OFFSET_T(SHIM_TCB_SYSCALL_STACK_HIGH, shim_tcb_t, syscall_stack_high); - OFFSET(SHIM_REGS_RSP, shim_regs, rsp); - OFFSET(SHIM_REGS_R15, shim_regs, r15); + OFFSET(SHIM_REGS_R11, shim_regs, r11); OFFSET(SHIM_REGS_RIP, shim_regs, rip); + OFFSET(SHIM_REGS_RSP, shim_regs, rsp); DEFINE(SHIM_REGS_SIZE, sizeof(struct shim_regs)); /* definitions */ DEFINE(RED_ZONE_SIZE, RED_ZONE_SIZE); + DEFINE(SHIM_FLAG_SIGPENDING, SHIM_FLAG_SIGPENDING); + DEFINE(__NR_gettid, __NR_gettid); } diff --git a/LibOS/shim/src/shim_checkpoint.c b/LibOS/shim/src/shim_checkpoint.c index 01494a24..a42b4d3c 100644 --- a/LibOS/shim/src/shim_checkpoint.c +++ b/LibOS/shim/src/shim_checkpoint.c @@ -1059,10 +1059,6 @@ void restore_context (struct shim_context * context) struct shim_regs regs = *context->regs; debug("restore context: SP = 0x%08lx, IP = 0x%08lx\n", regs.rsp, regs.rip); - /* don't clobber redzone. If sigaltstack is used, - * this area won't be clobbered by signal context */ - *(unsigned long*) (regs.rsp - RED_ZONE_SIZE - 8) = regs.rip; - /* Ready to resume execution, re-enable preemption. */ shim_tcb_t * tcb = shim_get_tcb(); assert(get_cur_thread()->syscall_stack); @@ -1075,12 +1071,13 @@ void restore_context (struct shim_context * context) context->fs_base = fs_base; __asm__ volatile("movq %0, %%rsp\r\n" - "addq $2 * 8, %%rsp\r\n" /* skip orig_rax and rsp */ + "addq $8, %%rsp\r\n" /* skip orig_rax */ + "movq $0, %%rax\r\n" "popq %%r15\r\n" "popq %%r14\r\n" "popq %%r13\r\n" "popq %%r12\r\n" - "popq %%r11\r\n" + "addq $8, %%rsp\r\n" /* skip %r11 as it's used below */ "popq %%r10\r\n" "popq %%r9\r\n" "popq %%r8\r\n" @@ -1091,8 +1088,14 @@ void restore_context (struct shim_context * context) "popq %%rbx\r\n" "popq %%rbp\r\n" "popfq\r\n" - "movq "XSTRINGIFY(SHIM_REGS_RSP)" - "XSTRINGIFY(SHIM_REGS_RIP)"(%%rsp), %%rsp\r\n" - "movq $0, %%rax\r\n" - "jmp *-"XSTRINGIFY(RED_ZONE_SIZE)"-8(%%rsp)\r\n" - :: "g"(®s) : "memory"); + "popq %%r11\r\n" + "movq %%r11, %%gs:%c1\r\n" + "movq %c2(%%rsp), %%r11\r\n" + "popq %%rsp\r\n" + "jmp *%%gs:%c1\r\n" + :: "g"(®s), + "i"(offsetof(PAL_TCB, libos_tcb) + + offsetof(shim_tcb_t, tmp_rip)), + "i"(offsetof(struct shim_regs, r11) - offsetof(struct shim_regs, rsp)) + : "memory"); } diff --git a/LibOS/shim/src/sys/shim_clone.c b/LibOS/shim/src/sys/shim_clone.c index d06794b8..b75d7b95 100644 --- a/LibOS/shim/src/sys/shim_clone.c +++ b/LibOS/shim/src/sys/shim_clone.c @@ -38,35 +38,6 @@ #include #include -void __attribute__((weak)) syscall_wrapper_after_syscalldb(void) -{ - /* - * workaround for linking. - * syscalldb.S is excluded for libsysdb_debug.so so it fails to link - * due to missing syscall_wrapper_after_syscalldb. - */ -} - -/* - * See syscall_wrapper @ syscalldb.S and illegal_upcall() @ shim_signal.c - * for details. - * child thread can _not_ use parent stack. So return right after syscall - * instruction as if syscall_wrapper is executed. - */ -static void fixup_child_context(struct shim_regs * regs) -{ - if (regs->rip == (unsigned long)&syscall_wrapper_after_syscalldb) { - /* - * we don't need to emulate stack pointer change because %rsp is - * initialized to new child user stack passed to clone() system call. - * See the caller of fixup_child_context(). - */ - /* regs->rsp += RED_ZONE_SIZE; */ - regs->rflags = regs->r11; - regs->rip = regs->rcx; - } -} - /* 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), @@ -154,16 +125,13 @@ static int clone_implementation_wrapper(struct shim_clone_args * arg) DkEventSet(arg->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 0x%lx: %d\n", stack, regs.rip, my_thread->tid); tcb->context.regs = ®s; - fixup_child_context(tcb->context.regs); tcb->context.regs->rsp = (unsigned long)stack; + assert((void*)regs.rip < (void*)&__code_address || + (void*)&__code_address_end <= (void*)regs.rip); put_thread(my_thread); diff --git a/LibOS/shim/src/sys/shim_exit.c b/LibOS/shim/src/sys/shim_exit.c index b7e8916a..422c5b9c 100644 --- a/LibOS/shim/src/sys/shim_exit.c +++ b/LibOS/shim/src/sys/shim_exit.c @@ -180,8 +180,10 @@ noreturn int shim_do_exit_group (int error_code) * out of this loop.*/ static struct atomic_int first = ATOMIC_INIT(0); if (atomic_cmpxchg(&first, 0, 1) == 1) { - while (1) + while (true) { + handle_exit_signal(); DkThreadYieldExecution(); + } } if (debug_handle) diff --git a/LibOS/shim/src/sys/shim_migrate.c b/LibOS/shim/src/sys/shim_migrate.c index aee6dcd0..5e773b9e 100644 --- a/LibOS/shim/src/sys/shim_migrate.c +++ b/LibOS/shim/src/sys/shim_migrate.c @@ -263,8 +263,6 @@ int shim_do_checkpoint(const char* filename) { shim_tcb_t* tcb = shim_get_tcb(); assert(tcb && tcb->tp); - struct shim_signal signal; - __store_context(tcb, NULL, &signal); ret = create_checkpoint(filename, &session); if (ret < 0) { diff --git a/LibOS/shim/src/sys/shim_sigaction.c b/LibOS/shim/src/sys/shim_sigaction.c index 27c96ccd..403cb184 100644 --- a/LibOS/shim/src/sys/shim_sigaction.c +++ b/LibOS/shim/src/sys/shim_sigaction.c @@ -87,8 +87,48 @@ out: attribute_nofp int shim_do_sigreturn(int __unused) { __UNUSED(__unused); - /* do nothing */ - return 0; + shim_tcb_t* tcb = shim_get_tcb(); + struct shim_regs* regs = tcb->context.regs; + ucontext_t* user_uc = (ucontext_t*)regs->rsp; + + debug("sigreturn thread %d regs: %p sp: %08lx " + "user_uc: %p gregs.rsp: %08lx gregs.rip: %08lx fpstate %p\n", + tcb->tp->tid, regs, regs->rsp, user_uc, + user_uc->uc_mcontext.gregs[REG_RSP], + user_uc->uc_mcontext.gregs[REG_RIP], + user_uc->uc_mcontext.fpregs); + + if (handle_next_signal(user_uc)) { + fpstate_reset(); + return 0; + } + + /* no more signals pending. return back */ + gregset_t* gregs = &user_uc->uc_mcontext.gregs; + /* RAX: will be restored as return value of sigreturn. see below */ + regs->r15 = (*gregs)[REG_R15]; + regs->r14 = (*gregs)[REG_R14]; + regs->r13 = (*gregs)[REG_R13]; + regs->r12 = (*gregs)[REG_R12]; + regs->r11 = (*gregs)[REG_R11]; + regs->r10 = (*gregs)[REG_R10]; + regs->r9 = (*gregs)[REG_R9]; + regs->r8 = (*gregs)[REG_R8]; + regs->rcx = (*gregs)[REG_RCX]; + regs->rdx = (*gregs)[REG_RDX]; + regs->rsi = (*gregs)[REG_RSI]; + regs->rdi = (*gregs)[REG_RDI]; + regs->rbx = (*gregs)[REG_RBX]; + regs->rbp = (*gregs)[REG_RBP]; + regs->rflags = (*gregs)[REG_EFL]; + regs->rip = (*gregs)[REG_RIP]; + regs->rsp = (*gregs)[REG_RSP]; + + struct _libc_fpstate * user_fpstate = user_uc->uc_mcontext.fpregs; + fpstate_restore(user_fpstate); + + /* syscalldb_return doesn't restore %rax. but %rax is return value */ + return (*gregs)[REG_RAX]; } int shim_do_sigprocmask(int how, const __sigset_t* set, __sigset_t* oldset) { diff --git a/LibOS/shim/src/syscallas.S b/LibOS/shim/src/syscallas.S index efc66bd6..1039059f 100644 --- a/LibOS/shim/src/syscallas.S +++ b/LibOS/shim/src/syscallas.S @@ -30,14 +30,55 @@ .extern shim_table, debug_unsupp .global syscall_wrapper .type syscall_wrapper, @function - .global syscall_wrapper_after_syscalldb - .type syscall_wrapper_after_syscalldb, @function + + .global __syscallas_return_begin + .global __syscallas_return_before_jmp + .global __syscallas_return_end + .type __syscallas_return_begin, @function + .type __syscallas_return_before_jmp, @function + .type __syscallas_return_end, @function + + .global syscalldb_check_sigpending + .type syscalldb_check_sigpending, @function + + .global __syscalldb_check_sigpending_begin + .global __syscalldb_check_sigpending_end + .type __syscalldb_check_sigpending_begin, @function + .type __syscalldb_check_sigpending_end, @function + +.macro CHECK_SIGNAL_PENDING_BIT + lock btrq $SHIM_FLAG_SIGPENDING, %gs:(SHIM_TCB_OFFSET + SHIM_TCB_FLAGS) +.endm syscalldb: .cfi_startproc + .cfi_def_cfa %rsp, 8 + + movq %rsp, %r11 + .cfi_def_cfa_register %r11 + .cfi_register %rsp, %r11 + .cfi_undefined %r11 + + # switch stack + movq %gs:(SHIM_TCB_OFFSET + SHIM_TCB_SYSCALL_STACK_HIGH), %rsp # Create shim_regs struct on the stack. + pushq %r11 # save old %rsp + # saved rsp has to point to the top of the application stack + # (before call to this stub) so we move it past saved return address + addq $8, (%rsp) + .cfi_def_cfa %rsp, 0 + .cfi_offset %rsp, 0 + + pushq (%r11) # copy return address from original stack + .cfi_adjust_cfa_offset 8 + .cfi_offset %rip, -8 + + +.Lsyscalldb_from_wrapper: pushfq + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rflags, 0 # Under GDB, single-stepping sets Trap Flag (TP) of EFLAGS, # thus TP=1 is stored on pushfq above. Upon consequent popfq, @@ -46,31 +87,58 @@ syscalldb: cld pushq %rbp + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rbp, 0 pushq %rbx + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rbx, 0 pushq %rdi + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rdi, 0 pushq %rsi + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rsi, 0 pushq %rdx + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rdx, 0 pushq %rcx + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rcx, 0 pushq %r8 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r8, 0 pushq %r9 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r9, 0 pushq %r10 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r10, 0 pushq %r11 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r11, 0 pushq %r12 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r12, 0 pushq %r13 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r13, 0 pushq %r14 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r14, 0 pushq %r15 - leaq SHIM_REGS_SIZE - SHIM_REGS_R15(%rsp), %rbx - pushq %rbx + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r15, 0 pushq %rax + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rax, 0 # shim_regs struct ends here. movq %rsp, %rbp - .cfi_def_cfa_offset SHIM_REGS_SIZE - .cfi_offset %rbp, -3 * 8 # saved_rbp is at CFA-24 (saved_rflags + saved_rbp) - .cfi_def_cfa_register %rbp # %rbp + .cfi_def_cfa_register %rbp cmp $LIBOS_SYSCALL_BOUND, %rax jae isundef + .cfi_remember_state movq shim_table@GOTPCREL(%rip), %rbx movq (%rbx,%rax,8), %rbx @@ -84,39 +152,95 @@ syscalldb: movq %r10, %rcx andq $~0xF, %rsp # Required by System V AMD64 ABI. call *%rbx + jmp __syscallas_return_begin + +.Ldeliver_signal: + movq %rax, %rdi # to preverve returning value + call *deliver_signal_on_sysret@GOTPCREL(%rip) + +__syscallas_return_begin: + CHECK_SIGNAL_PENDING_BIT + jc .Ldeliver_signal + + movq %rbp, %rsp + .cfi_def_cfa_register %rsp + addq $8, %rsp # skip orig_rax + .cfi_adjust_cfa_offset -8 + popq %r15 + .cfi_restore %r15 + .cfi_adjust_cfa_offset -8 + popq %r14 + .cfi_restore %r14 + .cfi_adjust_cfa_offset -8 + popq %r13 + .cfi_restore %r13 + .cfi_adjust_cfa_offset -8 + popq %r12 + .cfi_restore %r12 + .cfi_adjust_cfa_offset -8 + addq $8, %rsp # skip %r11 as it's used below + .cfi_adjust_cfa_offset -8 + popq %r10 + .cfi_restore %r10 + .cfi_adjust_cfa_offset -8 + popq %r9 + .cfi_restore %r9 + .cfi_adjust_cfa_offset -8 + popq %r8 + .cfi_restore %r8 + .cfi_adjust_cfa_offset -8 + popq %rcx + .cfi_restore %rcx + .cfi_adjust_cfa_offset -8 + popq %rdx + .cfi_restore %rdx + .cfi_adjust_cfa_offset -8 + popq %rsi + .cfi_restore %rsi + .cfi_adjust_cfa_offset -8 + popq %rdi + .cfi_restore %rdi + .cfi_adjust_cfa_offset -8 + popq %rbx + .cfi_restore %rbx + .cfi_adjust_cfa_offset -8 + popq %rbp + .cfi_restore %rbp + .cfi_adjust_cfa_offset -8 + popfq + .cfi_restore %rflags + .cfi_adjust_cfa_offset -8 + + popq %r11 + .cfi_register %rip, %r11 + .cfi_adjust_cfa_offset -8 + movq %r11, %gs:(SHIM_TCB_OFFSET + SHIM_TCB_TMP_RIP) + movq SHIM_REGS_R11 - SHIM_REGS_RSP(%rsp), %r11 + .cfi_restore %r11 + + # how to specify + # previous %rip = %gs:(SHIM_TCB_OFFSET + SHIM_TCB_TMP_RIP)? + .cfi_undefined %rip + + # restore application stack + popq %rsp + .cfi_restore %rsp movq $0, %gs:(SHIM_TCB_OFFSET + TCB_REGS) -ret: - movq %rbp, %rsp - addq $2 * 8, %rsp # skip orig_rax and rsp - popq %r15 - popq %r14 - popq %r13 - popq %r12 - popq %r11 - popq %r10 - popq %r9 - popq %r8 - popq %rcx - popq %rdx - popq %rsi - popq %rdi - popq %rbx - popq %rbp - .cfi_def_cfa %rsp, 2 * 8 # +8 for ret_addr, +8 for saved_rflags - popfq - .cfi_def_cfa_offset 8 # +8 for ret_addr - retq +__syscallas_return_before_jmp: + jmp *%gs:(SHIM_TCB_OFFSET + SHIM_TCB_TMP_RIP) +__syscallas_return_end: isundef: + .cfi_restore_state #ifdef DEBUG mov %rax, %rdi andq $~0xF, %rsp # Required by System V AMD64 ABI. call *debug_unsupp@GOTPCREL(%rip) #endif movq $-38, %rax # ENOSYS - jmp ret + jmp __syscallas_return_begin .cfi_endproc .size syscalldb, .-syscalldb @@ -124,38 +248,61 @@ isundef: /* * syscall_wrapper: emulate syscall instruction * prohibited in e.g. Linux-SGX PAL which raises a SIGILL exception - * See illegal_upcall() @ shim_signal.c and - * fixup_child_context() @ shim_clone.c + * This switches stack to dedicated one allocated by LibOS to avoid + * small application stack. e.g. as goroutine. + * See illegal_upcall() @ shim_signal.c * - * input: + * argument: * %rcx: Instruction address to continue app execution after trapped * syscall instruction - * %r11: rflags on entering syscall + * %r11: rflags on entering syscall: unused as %rflags is preserved + * + * also this function can be used to invoke systemcall without + * consuming application stack. The code snippet looks like + * leaq 1f(%rip), %rcx + * jmp *syscall_wrapper(%rip) + * 1: */ syscall_wrapper: .cfi_startproc - .cfi_def_cfa %rsp, 0 + .cfi_def_cfa %rsp, 0 # we don't have %rip saved on stack. # %rcx is used as input for returning %rip .cfi_register %rip, %rcx - # %r11 is used as input to keep %rflags - .cfi_register %rflags, %r11 - subq $RED_ZONE_SIZE, %rsp - .cfi_adjust_cfa_offset RED_ZONE_SIZE - callq *syscalldb@GOTPCREL(%rip) -syscall_wrapper_after_syscalldb: - addq $RED_ZONE_SIZE, %rsp - .cfi_adjust_cfa_offset -RED_ZONE_SIZE - # restore %rflags for syscall abi compatibility. - # This must be done after "addq $RED_ZONE_SIZE, %rsp" above - # which destroys %rflags - xchg %r11, (%rsp) - .cfi_offset %rflags, 0 - popfq - .cfi_adjust_cfa_offset -8 - .cfi_same_value %rflags + + # %r11(saved %rflags) isn't needed, + # so re-use %r11 to store application stack + movq %rsp, %r11 + .cfi_def_cfa_register %r11 + .cfi_register %rsp, %r11 + .cfi_undefined %r11 + + # switch stack to the one allocated by LibOS + movq %gs:(SHIM_TCB_OFFSET + SHIM_TCB_SYSCALL_STACK_HIGH), %rsp pushq %r11 + .cfi_def_cfa %rsp, 0 + .cfi_offset %rsp, 0 + pushq %rcx .cfi_adjust_cfa_offset 8 - jmp *%rcx + .cfi_offset %rip, -8 + jmp .Lsyscalldb_from_wrapper .cfi_endproc .size syscall_wrapper, .-syscall_wrapper + +syscalldb_check_sigpending: + .cfi_startproc +__syscalldb_check_sigpending_begin: + CHECK_SIGNAL_PENDING_BIT + jc .Lsigpending + ret + +.Lsigpending: + # call nop syscall to trigger signal delivery. + # Any syscall with no side effect is okay. + # (Or dedicated syscall can be introduced in shim_table.c) + movq $__NR_gettid, %rax + jmp syscalldb + +__syscalldb_check_sigpending_end: + .cfi_endproc + .size syscalldb_check_sigpending, .-syscalldb_check_sigpending diff --git a/LibOS/shim/src/vdso/vdso.c b/LibOS/shim/src/vdso/vdso.c index bdddcee5..c15efb42 100644 --- a/LibOS/shim/src/vdso/vdso.c +++ b/LibOS/shim/src/vdso/vdso.c @@ -37,33 +37,48 @@ EXPORT_SYMBOL(shim_gettimeofday); EXPORT_SYMBOL(shim_time); EXPORT_SYMBOL(shim_getcpu); +static void (*shim_check_sigpending)(void) = NULL; +EXPORT_SYMBOL(shim_check_sigpending); + #define EXPORT_WEAK_SYMBOL(name) \ __typeof__(__vdso_##name) name __attribute__((weak, alias("__vdso_" #name))) int __vdso_clock_gettime(clockid_t clock, struct timespec* t) { - if (shim_clock_gettime) - return (*shim_clock_gettime)(clock, t); + if (shim_clock_gettime && shim_check_sigpending) { + int ret = (*shim_clock_gettime)(clock, t); + (*shim_check_sigpending)(); + return ret; + } return -ENOSYS; } EXPORT_WEAK_SYMBOL(clock_gettime); int __vdso_gettimeofday(struct timeval* tv, struct timezone* tz) { - if (shim_gettimeofday) - return (*shim_gettimeofday)(tv, tz); + if (shim_gettimeofday && shim_check_sigpending) { + int ret = (*shim_gettimeofday)(tv, tz); + (*shim_check_sigpending)(); + return ret; + } return -ENOSYS; } EXPORT_WEAK_SYMBOL(gettimeofday); time_t __vdso_time(time_t* t) { - if (shim_time) - return (*shim_time)(t); + if (shim_time && shim_check_sigpending) { + time_t ret = (*shim_time)(t); + (*shim_check_sigpending)(); + return ret; + } return -ENOSYS; } EXPORT_WEAK_SYMBOL(time); long __vdso_getcpu(unsigned* cpu, struct getcpu_cache* unused) { - if (shim_getcpu) - return (*shim_getcpu)(cpu, unused); + if (shim_getcpu && shim_check_sigpending) { + long ret = (*shim_getcpu)(cpu, unused); + (*shim_check_sigpending)(); + return ret; + } return -ENOSYS; } EXPORT_WEAK_SYMBOL(getcpu); diff --git a/LibOS/shim/src/vdso/vdso.lds b/LibOS/shim/src/vdso/vdso.lds index f278c015..232b38e8 100644 --- a/LibOS/shim/src/vdso/vdso.lds +++ b/LibOS/shim/src/vdso/vdso.lds @@ -77,6 +77,7 @@ VERSION { __vdso_shim_gettimeofday; __vdso_shim_getcpu; __vdso_shim_time; + __vdso_shim_check_sigpending; local: *; }; }