| /* ********************************************************** |
| * Copyright (c) 2011-2020 Google, Inc. All rights reserved. |
| * Copyright (c) 2000-2010 VMware, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Copyright (c) 2003-2007 Determina Corp. */ |
| /* Copyright (c) 2001-2003 Massachusetts Institute of Technology */ |
| /* Copyright (c) 2000-2001 Hewlett-Packard Company */ |
| |
| /* |
| * signal.c - dynamorio signal handler |
| */ |
| |
| #include <errno.h> |
| #undef errno |
| |
| #include "signal_private.h" /* pulls in globals.h for us, in right order */ |
| |
| /* We want to build on older toolchains so we have our own copy of signal |
| * data structures |
| */ |
| #include "include/siginfo.h" |
| #ifdef LINUX |
| # include "include/sigcontext.h" |
| # include "include/signalfd.h" |
| # include "../globals.h" /* after our sigcontext.h, to preclude bits/sigcontext.h */ |
| #elif defined(MACOS) |
| # include "../globals.h" /* this defines _XOPEN_SOURCE for Mac */ |
| # include <signal.h> /* after globals.h, for _XOPEN_SOURCE from os_exports.h */ |
| #endif |
| |
| #ifdef LINUX |
| # include <linux/sched.h> |
| #endif |
| |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <ucontext.h> |
| #include "os_private.h" |
| #include "../fragment.h" |
| #include "../fcache.h" |
| #include "../perfctr.h" |
| #include "arch.h" |
| #include "../monitor.h" /* for trace_abort */ |
| #include "../link.h" /* for linking interrupted fragment_t */ |
| #include "instr.h" /* to find target of SIGSEGV */ |
| #include "decode.h" /* to find target of SIGSEGV */ |
| #include "decode_fast.h" /* to handle self-mod code */ |
| #include "../synch.h" |
| #include "../nudge.h" |
| #include "disassemble.h" |
| #include "ksynch.h" |
| #include "tls.h" /* tls_reinstate_selector */ |
| #include "../translate.h" |
| |
| #ifdef LINUX |
| # include "include/syscall.h" |
| #else |
| # include <sys/syscall.h> |
| #endif |
| |
| #ifdef CLIENT_INTERFACE |
| # include "instrument.h" |
| #endif |
| |
| #ifdef VMX86_SERVER |
| # include <errno.h> |
| #endif |
| |
| /* Define the Linux names, which the code is already using */ |
| #ifndef SA_NOMASK |
| # define SA_NOMASK SA_NODEFER |
| #endif |
| #ifndef SA_ONESHOT |
| # define SA_ONESHOT SA_RESETHAND |
| #endif |
| #ifndef SS_AUTODISARM |
| # define SS_AUTODISARM (1U << 31) |
| #endif |
| #ifndef SS_FLAG_BITS |
| # define SS_FLAG_BITS SS_AUTODISARM |
| #endif |
| #ifdef X86 |
| /* Kernel-only flags. */ |
| # define SA_IA32_ABI 0x02000000U |
| # define SA_X32_ABI 0x01000000U |
| #endif |
| |
| /**** data structures ***************************************************/ |
| |
| /* The signal numbers are slightly different between operating systems. |
| * To support differing default actions, we have separate arrays, rather |
| * than indirecting to a single all-signals array. |
| */ |
| extern int default_action[]; |
| |
| /* We know that many signals are always asynchronous. |
| * Others, however, may be synchronous or may not -- e.g., another process |
| * could send us a SIGSEGV, and there is no way we can tell whether it |
| * was generated by a real memory fault or not. Thus we have to assume |
| * that we must not delay any SIGSEGV deliveries. |
| */ |
| extern bool can_always_delay[]; |
| |
| static inline bool |
| sig_is_alarm_signal(int sig) |
| { |
| return (sig == SIGALRM || sig == SIGVTALRM || sig == SIGPROF); |
| } |
| |
| /* we do not use SIGSTKSZ b/c for things like code modification |
| * we end up calling many core routines and so want more space |
| * (though currently non-debug stack size == SIGSTKSZ (8KB)) |
| */ |
| #define SIGSTACK_SIZE (DYNAMO_OPTION(signal_stack_size)) |
| |
| /* this flag not defined in our headers */ |
| #define SA_RESTORER 0x04000000 |
| |
| /* if no app sigaction, it's RT, since that's our handler */ |
| #ifdef LINUX |
| # define IS_RT_FOR_APP(info, sig) \ |
| IF_X64_ELSE(true, \ |
| ((info)->app_sigaction[(sig)] == NULL \ |
| ? true \ |
| : (TEST(SA_SIGINFO, (info)->app_sigaction[(sig)]->flags)))) |
| #elif defined(MACOS) |
| # define IS_RT_FOR_APP(info, sig) (true) |
| #endif |
| |
| /* kernel sets size and sp to 0 for SS_DISABLE |
| * when asked, will hand back SS_ONSTACK only if current xsp is inside the |
| * alt stack; otherwise, if an alt stack is registered, it will give flags of 0 |
| * We do not support the "legacy stack switching" that uses the restorer field |
| * as seen in kernel sources. |
| */ |
| #define APP_HAS_SIGSTACK(info) \ |
| ((info)->app_sigstack.ss_sp != NULL && (info)->app_sigstack.ss_flags != SS_DISABLE) |
| |
| /* Under normal circumstances the app_sigaction is lazily initialized when the |
| * app registers a signal handler, but during detach there are points where we |
| * are still intercepting signals after app_sigaction has been set to |
| * zeros. To be extra defensive, we do a NULL check. |
| */ |
| #define USE_APP_SIGSTACK(info, sig) \ |
| (APP_HAS_SIGSTACK(info) && (info)->app_sigaction[sig] != NULL && \ |
| TEST(SA_ONSTACK, (info)->app_sigaction[sig]->flags)) |
| |
| /* If we only intercept a few signals, we leave whether un-intercepted signals |
| * are blocked unchanged and stored in the kernel. If we intercept all (not |
| * quite yet: PR 297033, hence the need for this macro) we emulate the mask for |
| * all. |
| */ |
| #define EMULATE_SIGMASK(info, sig) \ |
| (DYNAMO_OPTION(intercept_all_signals) || (info)->we_intercept[(sig)]) |
| |
| /* i#27: custom data to pass to the child of a clone */ |
| /* PR i#149/403015: clone record now passed via a new dstack */ |
| typedef struct _clone_record_t { |
| byte *dstack; /* dstack for new thread - allocated by parent thread */ |
| #ifdef MACOS |
| /* XXX i#1403: once we have lower-level, earlier thread interception we can |
| * likely switch to something closer to what we do on Linux. |
| * This is used for bsdthread_create, where app_thread_xsp is NULL; |
| * for vfork, app_thread_xsp is non-NULL and this is unused. |
| */ |
| void *thread_arg; |
| #endif |
| reg_t app_thread_xsp; /* app xsp preserved for new thread to use */ |
| app_pc continuation_pc; |
| thread_id_t caller_id; |
| int clone_sysnum; |
| uint clone_flags; |
| thread_sig_info_t info; |
| thread_sig_info_t *parent_info; |
| void *pcprofile_info; |
| #ifdef AARCHXX |
| /* To ensure we have the right value as of the point of the clone, we |
| * store it here (we'll have races if we try to get it during new thread |
| * init). |
| */ |
| reg_t app_stolen_value; |
| # ifndef AARCH64 |
| dr_isa_mode_t isa_mode; |
| # endif |
| /* To ensure we have the right app lib tls base in child thread, |
| * we store it here if necessary (clone w/o CLONE_SETTLS or vfork). |
| */ |
| void *app_lib_tls_base; |
| #endif |
| /* we leave some padding at base of stack for dynamorio_clone |
| * to store values |
| */ |
| reg_t for_dynamorio_clone[4]; |
| } __attribute__((__aligned__(ABI_STACK_ALIGNMENT))) clone_record_t; |
| |
| /* i#350: set up signal handler for safe_read/faults during init */ |
| static thread_sig_info_t init_info; |
| static kernel_sigset_t init_sigmask; |
| |
| #ifdef DEBUG |
| static bool removed_sig_handler; |
| #endif |
| |
| os_cxt_ptr_t osc_empty; |
| |
| /* For delivering signals that arrive in now-native threads while we're still |
| * detaching other threads. The thread that receives the signal has no dcontext |
| * anymore, so we need a global record of its handler (DR's handler is still registered |
| * with the kernel). |
| * XXX i#1921: To properly handle multiple separate signal handler thread groups |
| * within this single DR process domain, we would need a list of these, along with |
| * lists of threads within each. Since that is very rare, and requires either |
| * hardcoded maximums or complexities with exit-time heap, we currently do not |
| * support that and die if asked to deliver a native signal in such circumstances. |
| */ |
| DECLARE_CXTSWPROT_VAR(static read_write_lock_t detached_sigact_lock, |
| INIT_READWRITE_LOCK(detached_sigact_lock)); |
| static kernel_sigaction_t detached_sigact[SIGARRAY_SIZE]; |
| static bool multiple_handlers_present; /* Accessed using atomics, not the lock. */ |
| |
| /**** function prototypes ***********************************************/ |
| |
| /* in x86.asm */ |
| void |
| master_signal_handler(int sig, kernel_siginfo_t *siginfo, kernel_ucontext_t *ucxt); |
| |
| static void |
| set_handler_and_record_app(dcontext_t *dcontext, thread_sig_info_t *info, int sig, |
| kernel_sigaction_t *act); |
| |
| static void |
| intercept_signal(dcontext_t *dcontext, thread_sig_info_t *info, int sig); |
| |
| static void |
| signal_info_init_sigaction(dcontext_t *dcontext, thread_sig_info_t *info); |
| |
| static void |
| signal_info_exit_sigaction(dcontext_t *dcontext, thread_sig_info_t *info, |
| bool other_thread); |
| |
| static bool |
| execute_handler_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *our_frame, |
| sigcontext_t *sc_orig, |
| fragment_t *f _IF_CLIENT(byte *access_address)); |
| |
| static bool |
| execute_handler_from_dispatch(dcontext_t *dcontext, int sig); |
| |
| static void |
| execute_native_handler(dcontext_t *dcontext, int sig, sigframe_rt_t *our_frame); |
| |
| /* Execute default action from code cache and may terminate the process. |
| * If returns, the return value decides if caller should restore |
| * the untranslated context. |
| */ |
| static bool |
| execute_default_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *frame, |
| sigcontext_t *sc_orig, bool forged); |
| |
| static void |
| execute_default_from_dispatch(dcontext_t *dcontext, int sig, sigframe_rt_t *frame); |
| |
| static bool |
| handle_alarm(dcontext_t *dcontext, int sig, kernel_ucontext_t *ucxt); |
| |
| static bool |
| handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt, |
| sigframe_rt_t *frame); |
| |
| static bool |
| handle_nudge_signal(dcontext_t *dcontext, kernel_siginfo_t *siginfo, |
| kernel_ucontext_t *ucxt); |
| |
| static void |
| init_itimer(dcontext_t *dcontext, bool first); |
| |
| static bool |
| set_actual_itimer(dcontext_t *dcontext, int which, thread_sig_info_t *info, bool enable); |
| |
| static bool |
| alarm_signal_has_DR_only_itimer(dcontext_t *dcontext, int signal); |
| |
| #ifdef DEBUG |
| static void |
| dump_sigset(dcontext_t *dcontext, kernel_sigset_t *set); |
| #endif |
| |
| static bool |
| is_sys_kill(dcontext_t *dcontext, byte *pc, byte *xsp, kernel_siginfo_t *info); |
| |
| int |
| sigaction_syscall(int sig, kernel_sigaction_t *act, kernel_sigaction_t *oact) |
| { |
| #if !defined(VMX86_SERVER) && defined(LINUX) |
| /* PR 305020: must have SA_RESTORER for x64 */ |
| /* i#2812: must have SA_RESTORER to handle vsyscall32 being disabled */ |
| if (act != NULL && !TEST(SA_RESTORER, act->flags)) { |
| act->flags |= SA_RESTORER; |
| act->restorer = (void (*)(void))dynamorio_sigreturn; |
| } |
| #endif |
| return dynamorio_syscall(IF_MACOS_ELSE(SYS_sigaction, SYS_rt_sigaction), 4, sig, act, |
| oact, sizeof(kernel_sigset_t)); |
| } |
| |
| static inline bool |
| signal_is_interceptable(int sig) |
| { |
| return (sig != SIGKILL && sig != SIGSTOP); |
| } |
| |
| static inline int |
| sigaltstack_syscall(const stack_t *newstack, stack_t *oldstack) |
| { |
| return dynamorio_syscall(SYS_sigaltstack, 2, newstack, oldstack); |
| } |
| |
| static inline int |
| getitimer_syscall(int which, struct itimerval *val) |
| { |
| return dynamorio_syscall(SYS_getitimer, 2, which, val); |
| } |
| |
| static inline int |
| setitimer_syscall(int which, struct itimerval *val, struct itimerval *old) |
| { |
| return dynamorio_syscall(SYS_setitimer, 3, which, val, old); |
| } |
| |
| static inline int |
| sigprocmask_syscall(int how, kernel_sigset_t *set, kernel_sigset_t *oset, |
| size_t sigsetsize) |
| { |
| return dynamorio_syscall(IF_MACOS_ELSE(SYS_sigprocmask, SYS_rt_sigprocmask), 4, how, |
| set, oset, sigsetsize); |
| } |
| |
| void |
| block_all_noncrash_signals_except(kernel_sigset_t *oset, int num_signals, |
| ... /* list of signals */) |
| { |
| kernel_sigset_t set; |
| kernel_sigfillset(&set); |
| va_list ap; |
| va_start(ap, num_signals); |
| for (int i = 0; i < num_signals; ++i) { |
| kernel_sigdelset(&set, va_arg(ap, int)); |
| } |
| va_end(ap); |
| /* We never block SIGSEGV or SIGBUS: we need them for various safe reads and to |
| * properly report crashes. |
| */ |
| kernel_sigdelset(&set, SIGSEGV); |
| kernel_sigdelset(&set, SIGBUS); |
| sigprocmask_syscall(SIG_SETMASK, &set, oset, sizeof(set)); |
| } |
| |
| static void |
| unblock_all_signals(kernel_sigset_t *oset) |
| { |
| kernel_sigset_t set; |
| kernel_sigemptyset(&set); |
| sigprocmask_syscall(SIG_SETMASK, &set, oset, sizeof(set)); |
| } |
| |
| /* exported for stackdump.c */ |
| bool |
| set_default_signal_action(int sig) |
| { |
| kernel_sigset_t set; |
| kernel_sigaction_t act; |
| int rc; |
| memset(&act, 0, sizeof(act)); |
| act.handler = (handler_t)SIG_DFL; |
| /* arm the signal */ |
| rc = sigaction_syscall(sig, &act, NULL); |
| DODEBUG({ removed_sig_handler = true; }); |
| |
| /* If we're in our handler now, we have to unblock */ |
| kernel_sigemptyset(&set); |
| kernel_sigaddset(&set, sig); |
| sigprocmask_syscall(SIG_UNBLOCK, &set, NULL, sizeof(set)); |
| |
| return (rc == 0); |
| } |
| |
| static bool |
| set_ignore_signal_action(int sig) |
| { |
| kernel_sigaction_t act; |
| int rc; |
| memset(&act, 0, sizeof(act)); |
| act.handler = (handler_t)SIG_IGN; |
| /* arm the signal */ |
| rc = sigaction_syscall(sig, &act, NULL); |
| return (rc == 0); |
| } |
| |
| /* We assume that signal handlers will be shared most of the time |
| * (pthreads shares them) |
| * Rather than start out with the handler table in local memory and then |
| * having to transfer to global, we just always use global |
| */ |
| static void |
| handler_free(dcontext_t *dcontext, void *p, size_t size) |
| { |
| global_heap_free(p, size HEAPACCT(ACCT_OTHER)); |
| } |
| |
| static void * |
| handler_alloc(dcontext_t *dcontext, size_t size) |
| { |
| return global_heap_alloc(size HEAPACCT(ACCT_OTHER)); |
| } |
| |
| /* Does not return. */ |
| static void |
| report_unhandleable_signal_and_exit(int sig, const char *sub_message) |
| { |
| /* TODO i#1921: Add more info such as the PC and disasm of surrounding instrs. */ |
| char signum_str[8]; |
| snprintf(signum_str, BUFFER_SIZE_ELEMENTS(signum_str), "%d", sig); |
| NULL_TERMINATE_BUFFER(signum_str); |
| char tid_str[16]; |
| snprintf(tid_str, BUFFER_SIZE_ELEMENTS(tid_str), TIDFMT, get_sys_thread_id()); |
| NULL_TERMINATE_BUFFER(tid_str); |
| REPORT_FATAL_ERROR_AND_EXIT(FAILED_TO_HANDLE_SIGNAL, 5, get_application_name(), |
| get_application_pid(), signum_str, tid_str, sub_message); |
| } |
| |
| /**** top-level routines ***********************************************/ |
| |
| static bool |
| os_itimers_thread_shared(void) |
| { |
| static bool itimers_shared; |
| static bool cached = false; |
| if (!cached) { |
| file_t f = os_open("/proc/version", OS_OPEN_READ); |
| if (f != INVALID_FILE) { |
| char buf[128]; |
| int major, minor, rel; |
| os_read(f, buf, BUFFER_SIZE_ELEMENTS(buf)); |
| NULL_TERMINATE_BUFFER(buf); |
| if (sscanf(buf, "%*s %*s %d.%d.%d", &major, &minor, &rel) == 3) { |
| /* Linux NPTL in kernel 2.6.12+ has POSIX-style itimers shared |
| * among threads. |
| */ |
| LOG(GLOBAL, LOG_ASYNCH, 1, "kernel version = %d.%d.%d\n", major, minor, |
| rel); |
| itimers_shared = ((major == 2 && minor >= 6 && rel >= 12) || |
| (major >= 3 /* linux-3.0 or above */)); |
| cached = true; |
| } |
| os_close(f); |
| } |
| if (!cached) { |
| /* assume not shared */ |
| itimers_shared = false; |
| cached = true; |
| } |
| LOG(GLOBAL, LOG_ASYNCH, 1, "itimers are %s\n", |
| itimers_shared ? "thread-shared" : "thread-private"); |
| } |
| return itimers_shared; |
| } |
| |
| static void |
| unset_initial_crash_handlers(dcontext_t *dcontext) |
| { |
| ASSERT(init_info.app_sigaction != NULL); |
| signal_info_exit_sigaction(GLOBAL_DCONTEXT, &init_info, false /*!other_thread*/); |
| /* Undo the unblock-all */ |
| sigprocmask_syscall(SIG_SETMASK, &init_sigmask, NULL, sizeof(init_sigmask)); |
| DOLOG(2, LOG_ASYNCH, { |
| LOG(THREAD, LOG_ASYNCH, 2, "initial app signal mask:\n"); |
| dump_sigset(dcontext, &init_sigmask); |
| }); |
| } |
| |
| void |
| d_r_signal_init(void) |
| { |
| kernel_sigset_t set; |
| IF_LINUX(IF_X86_64(ASSERT(ALIGNED(offsetof(sigpending_t, xstate), AVX_ALIGNMENT)))); |
| IF_MACOS(ASSERT(sizeof(kernel_sigset_t) == sizeof(__darwin_sigset_t))); |
| os_itimers_thread_shared(); |
| |
| /* Set up a handler for safe_read (or other fault detection) during |
| * DR init before thread is initialized. |
| * |
| * XXX: could set up a clone_record_t and pass to the initial |
| * signal_thread_inherit() but that would require further code changes. |
| * Could also call signal_thread_inherit to init this, but we don't want |
| * to intercept timer signals, etc. before we're ready to handle them, |
| * so we do a partial init. |
| */ |
| signal_info_init_sigaction(GLOBAL_DCONTEXT, &init_info); |
| intercept_signal(GLOBAL_DCONTEXT, &init_info, SIGSEGV); |
| intercept_signal(GLOBAL_DCONTEXT, &init_info, SIGBUS); |
| kernel_sigemptyset(&set); |
| kernel_sigaddset(&set, SIGSEGV); |
| kernel_sigaddset(&set, SIGBUS); |
| sigprocmask_syscall(SIG_UNBLOCK, &set, &init_sigmask, sizeof(set)); |
| |
| IF_LINUX(signalfd_init()); |
| signal_arch_init(); |
| } |
| |
| void |
| d_r_signal_exit() |
| { |
| DELETE_READWRITE_LOCK(detached_sigact_lock); |
| IF_LINUX(signalfd_exit()); |
| if (init_info.app_sigaction != NULL) { |
| /* We never took over the app (e.g., standalone mode). Restore its state. */ |
| unset_initial_crash_handlers(GLOBAL_DCONTEXT); |
| } |
| #ifdef DEBUG |
| if (d_r_stats->loglevel > 0 && (d_r_stats->logmask & (LOG_ASYNCH | LOG_STATS)) != 0) { |
| LOG(GLOBAL, LOG_ASYNCH | LOG_STATS, 1, "Total signals delivered: %d\n", |
| GLOBAL_STAT(num_signals)); |
| } |
| #endif |
| } |
| |
| #ifdef HAVE_SIGALTSTACK |
| /* Separated out to run from the dstack (i#2016: see below). */ |
| static void |
| set_our_alt_stack(void *arg) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)arg; |
| DEBUG_DECLARE(int rc =) |
| sigaltstack_syscall(&info->sigstack, &info->app_sigstack); |
| ASSERT(rc == 0); |
| } |
| #endif |
| |
| void |
| signal_thread_init(dcontext_t *dcontext, void *os_data) |
| { |
| thread_sig_info_t *info = |
| HEAP_TYPE_ALLOC(dcontext, thread_sig_info_t, ACCT_OTHER, PROTECTED); |
| size_t pend_unit_size = sizeof(sigpending_t) + |
| /* include alignment for xsave on xstate */ |
| signal_frame_extra_size(true) |
| /* sigpending_t has xstate inside it already */ |
| IF_LINUX(IF_X86(-sizeof(kernel_xstate_t))); |
| IF_X86(ASSERT(YMM_ENABLED() || !ZMM_ENABLED())); |
| /* pend_unit_size may not be aligned, even for AVX. We request alignment from the |
| * allocator for all pending units (xref i#3749, i#3380). |
| */ |
| |
| /* all fields want to be initialized to 0 */ |
| memset(info, 0, sizeof(thread_sig_info_t)); |
| dcontext->signal_field = (void *)info; |
| |
| /* our special heap to avoid reentrancy problems |
| * composed entirely of sigpending_t units |
| * Note that it's fine to have the special heap do page-at-a-time |
| * committing, which does not use locks (unless triggers reset!), |
| * but if we need a new unit that will grab a lock: we try to |
| * avoid that by limiting the # of pending alarm signals (PR 596768). |
| */ |
| info->sigheap = special_heap_init_aligned( |
| pend_unit_size, IF_X86_ELSE(AVX_ALIGNMENT, 0), |
| false /* cannot have any locking */, false /* -x */, true /* persistent */, |
| pend_unit_size * DYNAMO_OPTION(max_pending_signals)); |
| |
| #ifdef HAVE_SIGALTSTACK |
| /* set up alternate stack |
| * i#552 we may terminate the process without freeing the stack, so we |
| * stack_alloc it to exempt from the memory leak check. |
| */ |
| info->sigstack.ss_sp = (char *)stack_alloc(SIGSTACK_SIZE, NULL) - SIGSTACK_SIZE; |
| info->sigstack.ss_size = SIGSTACK_SIZE; |
| /* kernel will set xsp to sp+size to grow down from there, we don't have to */ |
| info->sigstack.ss_flags = 0; |
| |
| /* i#2016: for late takeover, this app thread may already be on its own alt |
| * stack. Not setting SA_ONSTACK for SUSPEND_SIGNAL is not sufficient to avoid |
| * this, as our SUSPEND_SIGNAL can interrupt the app inside its own signal |
| * handler. Thus, we simply swap to another stack temporarily to avoid the |
| * kernel complaining. The dstack is set up but it has the clone record and |
| * initial mcxt, so we use the new alt stack. |
| */ |
| call_switch_stack((void *)info, (byte *)info->sigstack.ss_sp + info->sigstack.ss_size, |
| set_our_alt_stack, NULL, true /*return*/); |
| LOG(THREAD, LOG_ASYNCH, 1, "signal stack is " PFX " - " PFX "\n", |
| info->sigstack.ss_sp, info->sigstack.ss_sp + info->sigstack.ss_size); |
| /* app_sigstack dealt with below, based on parentage */ |
| #endif |
| |
| kernel_sigemptyset(&info->app_sigblocked); |
| |
| ASSIGN_INIT_LOCK_FREE(info->child_lock, child_lock); |
| |
| /* signal_thread_inherit() finishes per-thread init and is invoked |
| * by os_thread_init_finalize(): we need it after synch_thread_init() and |
| * other post-os_thread_init() setup b/c we can't yet record pending signals, |
| * but we need it before we give up thread_initexit_lock so we can handle |
| * our own suspend signals (i#2779). |
| */ |
| } |
| |
| bool |
| is_thread_signal_info_initialized(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| return info->fully_initialized; |
| } |
| |
| /* i#27: create custom data to pass to the child of a clone |
| * since we can't rely on being able to find the caller, or that |
| * its syscall data is still valid, once in the child. |
| * |
| * i#149/ PR 403015: The clone record is passed to the new thread via the dstack |
| * created for it. Unlike before, where the child thread would create its own |
| * dstack, now the parent thread creates the dstack. Also, switches app stack |
| * to dstack. |
| * |
| * XXX i#1403: for Mac we want to eventually do lower-level earlier interception |
| * of threads, but for now we're later and higher-level, intercepting the user |
| * thread function on the new thread's stack. We ignore app_thread_xsp. |
| */ |
| void * |
| #ifdef MACOS |
| create_clone_record(dcontext_t *dcontext, reg_t *app_thread_xsp, app_pc thread_func, |
| void *thread_arg) |
| #else |
| create_clone_record(dcontext_t *dcontext, reg_t *app_thread_xsp) |
| #endif |
| { |
| clone_record_t *record; |
| byte *dstack = stack_alloc(DYNAMORIO_STACK_SIZE, NULL); |
| LOG(THREAD, LOG_ASYNCH, 1, "create_clone_record: dstack for new thread is " PFX "\n", |
| dstack); |
| |
| #ifdef MACOS |
| if (app_thread_xsp == NULL) { |
| record = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, clone_record_t, ACCT_THREAD_MGT, |
| true /*prot*/); |
| record->app_thread_xsp = 0; |
| record->continuation_pc = thread_func; |
| record->thread_arg = thread_arg; |
| record->clone_flags = CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | SIGCHLD; |
| } else { |
| #endif |
| /* Note, the stack grows to low memory addr, so dstack points to the high |
| * end of the allocated stack region. So, we must subtract to get space for |
| * the clone record. |
| */ |
| record = (clone_record_t *)(dstack - sizeof(clone_record_t)); |
| ASSERT(ALIGNED(record, get_ABI_stack_alignment())); |
| record->app_thread_xsp = *app_thread_xsp; |
| /* asynch_target is set in d_r_dispatch() prior to calling pre_system_call(). */ |
| record->continuation_pc = dcontext->asynch_target; |
| record->clone_flags = dcontext->sys_param0; |
| #ifdef MACOS |
| } |
| #endif |
| LOG(THREAD, LOG_ASYNCH, 1, "allocated clone record: " PFX "\n", record); |
| |
| record->dstack = dstack; |
| record->caller_id = dcontext->owning_thread; |
| record->clone_sysnum = dcontext->sys_num; |
| record->info = *((thread_sig_info_t *)dcontext->signal_field); |
| /* Sigstack is not inherited so clear it now to avoid having to figure out |
| * where it got its value in signal_thread_inherit (i#3116). |
| */ |
| memset(&record->info.app_sigstack, 0, sizeof(record->info.app_sigstack)); |
| record->info.app_sigstack.ss_flags = SS_DISABLE; |
| record->parent_info = (thread_sig_info_t *)dcontext->signal_field; |
| record->pcprofile_info = dcontext->pcprofile_field; |
| #ifdef AARCHXX |
| record->app_stolen_value = get_stolen_reg_val(get_mcontext(dcontext)); |
| # ifndef AARCH64 |
| record->isa_mode = dr_get_isa_mode(dcontext); |
| # endif |
| /* If the child thread shares the same TLS with parent by not setting |
| * CLONE_SETTLS or vfork, we put the TLS base here and clear the |
| * thread register in new_thread_setup, so that DR can distinguish |
| * this case from normal pthread thread creation. |
| */ |
| record->app_lib_tls_base = (!TEST(CLONE_SETTLS, record->clone_flags)) |
| ? os_get_app_tls_base(dcontext, TLS_REG_LIB) |
| : NULL; |
| #endif |
| LOG(THREAD, LOG_ASYNCH, 1, "create_clone_record: thread " TIDFMT ", pc " PFX "\n", |
| record->caller_id, record->continuation_pc); |
| |
| #ifdef MACOS |
| if (app_thread_xsp != NULL) { |
| #endif |
| /* Set the thread stack to point to the dstack, below the clone record. |
| * Note: it's glibc who sets up the arg to the thread start function; |
| * the kernel just does a fork + stack swap, so we can get away w/ our |
| * own stack swap if we restore before the glibc asm code takes over. |
| * We restore this parameter to the app value in |
| * restore_clone_param_from_clone_record(). |
| */ |
| /* i#754: set stack to be XSTATE aligned for saving YMM registers */ |
| ASSERT(ALIGNED(XSTATE_ALIGNMENT, REGPARM_END_ALIGN)); |
| *app_thread_xsp = ALIGN_BACKWARD(record, XSTATE_ALIGNMENT); |
| #ifdef MACOS |
| } |
| #endif |
| |
| return (void *)record; |
| } |
| |
| /* This is to support dr_create_client_thread() */ |
| void |
| set_clone_record_fields(void *record, reg_t app_thread_xsp, app_pc continuation_pc, |
| uint clone_sysnum, uint clone_flags) |
| { |
| clone_record_t *rec = (clone_record_t *)record; |
| ASSERT(rec != NULL); |
| rec->app_thread_xsp = app_thread_xsp; |
| rec->continuation_pc = continuation_pc; |
| rec->clone_sysnum = clone_sysnum; |
| rec->clone_flags = clone_flags; |
| } |
| |
| /* i#149/PR 403015: The clone record is passed to the new thread by placing it |
| * at the bottom of the dstack, i.e., the high memory. So the new thread gets |
| * it from the base of the dstack. The dstack is then set as the app stack. |
| * |
| * CAUTION: don't use a lot of stack in this routine as it gets invoked on the |
| * dstack from new_thread_setup - this is because this routine assumes |
| * no more than a page of dstack has been used so far since the clone |
| * system call was done. |
| */ |
| void * |
| get_clone_record(reg_t xsp) |
| { |
| clone_record_t *record; |
| byte *dstack_base; |
| |
| /* xsp should be in a dstack, i.e., dynamorio heap. */ |
| ASSERT(is_dynamo_address((app_pc)xsp)); |
| |
| /* The (size of the clone record + |
| * stack used by new_thread_start (only for setting up priv_mcontext_t) + |
| * stack used by new_thread_setup before calling get_clone_record()) |
| * is less than a page. This is verified by the assert below. If it does |
| * exceed a page, it won't happen at random during runtime, but in a |
| * predictable way during development, which will be caught by the assert. |
| * The current usage is about 800 bytes for clone_record + |
| * sizeof(priv_mcontext_t) + few words in new_thread_setup before |
| * get_clone_record() is called. |
| */ |
| dstack_base = (byte *)ALIGN_FORWARD(xsp, PAGE_SIZE); |
| record = (clone_record_t *)(dstack_base - sizeof(clone_record_t)); |
| |
| /* dstack_base and the dstack in the clone record should be the same. */ |
| ASSERT(dstack_base == record->dstack); |
| #ifdef MACOS |
| ASSERT(record->app_thread_xsp != 0); /* else it's not in dstack */ |
| #endif |
| return (void *)record; |
| } |
| |
| /* i#149/PR 403015: App xsp is passed to the new thread via the clone record. */ |
| reg_t |
| get_clone_record_app_xsp(void *record) |
| { |
| ASSERT(record != NULL); |
| return ((clone_record_t *)record)->app_thread_xsp; |
| } |
| |
| #ifdef MACOS |
| void * |
| get_clone_record_thread_arg(void *record) |
| { |
| ASSERT(record != NULL); |
| return ((clone_record_t *)record)->thread_arg; |
| } |
| #endif |
| |
| byte * |
| get_clone_record_dstack(void *record) |
| { |
| ASSERT(record != NULL); |
| return ((clone_record_t *)record)->dstack; |
| } |
| |
| #ifdef AARCHXX |
| reg_t |
| get_clone_record_stolen_value(void *record) |
| { |
| ASSERT(record != NULL); |
| return ((clone_record_t *)record)->app_stolen_value; |
| } |
| |
| # ifndef AARCH64 |
| uint /* dr_isa_mode_t but we have a header ordering problem */ |
| get_clone_record_isa_mode(void *record) |
| { |
| ASSERT(record != NULL); |
| return ((clone_record_t *)record)->isa_mode; |
| } |
| # endif |
| |
| void |
| set_thread_register_from_clone_record(void *record) |
| { |
| /* If record->app_lib_tls_base is not NULL, it means the parent |
| * thread did not setup TLS for the child, and we need clear the |
| * thread register. |
| */ |
| if (((clone_record_t *)record)->app_lib_tls_base != NULL) |
| write_thread_register(NULL); |
| } |
| |
| void |
| set_app_lib_tls_base_from_clone_record(dcontext_t *dcontext, void *record) |
| { |
| if (((clone_record_t *)record)->app_lib_tls_base != NULL) { |
| /* child and parent share the same TLS */ |
| os_set_app_tls_base(dcontext, TLS_REG_LIB, |
| ((clone_record_t *)record)->app_lib_tls_base); |
| } |
| } |
| #endif |
| |
| void |
| restore_clone_param_from_clone_record(dcontext_t *dcontext, void *record) |
| { |
| #ifdef LINUX |
| ASSERT(record != NULL); |
| clone_record_t *crec = (clone_record_t *)record; |
| if (crec->clone_sysnum == SYS_clone && TEST(CLONE_VM, crec->clone_flags)) { |
| /* Restore the original stack parameter to the syscall, which we clobbered |
| * in create_clone_record(). Some apps examine it post-syscall (i#3171). |
| */ |
| set_syscall_param(dcontext, SYSCALL_PARAM_CLONE_STACK, |
| get_mcontext(dcontext)->xsp); |
| } |
| #endif |
| } |
| |
| /* Initializes info's app_sigaction, restorer_valid, and we_intercept fields */ |
| static void |
| signal_info_init_sigaction(dcontext_t *dcontext, thread_sig_info_t *info) |
| { |
| info->app_sigaction = (kernel_sigaction_t **)handler_alloc( |
| dcontext, SIGARRAY_SIZE * sizeof(kernel_sigaction_t *)); |
| memset(info->app_sigaction, 0, SIGARRAY_SIZE * sizeof(kernel_sigaction_t *)); |
| memset(&info->restorer_valid, -1, SIGARRAY_SIZE * sizeof(info->restorer_valid[0])); |
| info->we_intercept = (bool *)handler_alloc(dcontext, SIGARRAY_SIZE * sizeof(bool)); |
| memset(info->we_intercept, 0, SIGARRAY_SIZE * sizeof(bool)); |
| } |
| |
| /* Cleans up info's app_sigaction and we_intercept entries */ |
| static void |
| signal_info_exit_sigaction(dcontext_t *dcontext, thread_sig_info_t *info, |
| bool other_thread) |
| { |
| int i; |
| kernel_sigaction_t act; |
| memset(&act, 0, sizeof(act)); |
| act.handler = (handler_t)SIG_DFL; |
| kernel_sigemptyset(&act.mask); /* does mask matter for SIG_DFL? */ |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (sig_is_alarm_signal(i) && doing_detach && |
| IF_CLIENT_INTERFACE(!standalone_library &&) |
| alarm_signal_has_DR_only_itimer(dcontext, i)) { |
| /* We ignore alarms *during* detach in signal_remove_alarm_handlers(), |
| * but to avoid crashing on an alarm arriving post-detach we set to |
| * SIG_IGN if we have an itimer and the app does not (a slight |
| * transparency violation to gain robustness: i#2270). |
| */ |
| set_ignore_signal_action(i); |
| } else if (!other_thread) { |
| if (info->app_sigaction[i] != NULL) { |
| /* Restore to old handler, but not if exiting whole process: |
| * else may get itimer during cleanup, so we set to SIG_IGN. We |
| * do this during detach in signal_remove_alarm_handlers() (and |
| * post-detach above). |
| */ |
| if (dynamo_exited && !doing_detach) { |
| info->app_sigaction[i]->handler = (handler_t)SIG_IGN; |
| } |
| LOG(THREAD, LOG_ASYNCH, 2, "\trestoring " PFX " as handler for %d\n", |
| info->app_sigaction[i]->handler, i); |
| sigaction_syscall(i, info->app_sigaction[i], NULL); |
| } else if (info->we_intercept[i]) { |
| /* restore to default */ |
| LOG(THREAD, LOG_ASYNCH, 2, "\trestoring SIG_DFL as handler for %d\n", i); |
| sigaction_syscall(i, &act, NULL); |
| } |
| } |
| if (info->app_sigaction[i] != NULL) { |
| handler_free(dcontext, info->app_sigaction[i], sizeof(kernel_sigaction_t)); |
| } |
| } |
| handler_free(dcontext, info->app_sigaction, |
| SIGARRAY_SIZE * sizeof(kernel_sigaction_t *)); |
| info->app_sigaction = NULL; |
| handler_free(dcontext, info->we_intercept, SIGARRAY_SIZE * sizeof(bool)); |
| info->we_intercept = NULL; |
| } |
| |
| /* Called to finalize per-thread initialization. |
| * Inherited and shared fields are set up here. |
| * The clone_record contains the continuation pc, which is stored in dcontext->next_tag. |
| */ |
| void |
| signal_thread_inherit(dcontext_t *dcontext, void *clone_record) |
| { |
| clone_record_t *record = (clone_record_t *)clone_record; |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| if (record != NULL) { |
| LOG(THREAD, LOG_ASYNCH, 1, "continuation pc is " PFX "\n", |
| record->continuation_pc); |
| dcontext->next_tag = record->continuation_pc; |
| LOG(THREAD, LOG_ASYNCH, 1, |
| "parent tid is " TIDFMT ", parent sysnum is %d(%s), clone flags=" PIFX "\n", |
| record->caller_id, record->clone_sysnum, |
| #ifdef SYS_vfork |
| (record->clone_sysnum == SYS_vfork) |
| ? "vfork" |
| : |
| #endif |
| (IF_LINUX(record->clone_sysnum == SYS_clone ? "clone" :) IF_MACOS( |
| record->clone_sysnum == SYS_bsdthread_create ? "bsdthread_create" |
| :) "unexpected"), |
| record->clone_flags); |
| #ifdef SYS_vfork |
| if (record->clone_sysnum == SYS_vfork) { |
| /* The above clone_flags argument is bogus. |
| SYS_vfork doesn't have a free register to keep the hardcoded value |
| see /usr/src/linux/arch/i386/kernel/process.c */ |
| /* CHECK: is this the only place real clone flags are needed? */ |
| record->clone_flags = CLONE_VFORK | CLONE_VM | SIGCHLD; |
| } |
| #endif |
| |
| /* handlers are either inherited or shared */ |
| if (TEST(CLONE_SIGHAND, record->clone_flags)) { |
| /* need to share table of handlers! */ |
| LOG(THREAD, LOG_ASYNCH, 2, "sharing signal handlers with parent\n"); |
| info->shared_app_sigaction = true; |
| info->shared_refcount = record->info.shared_refcount; |
| info->shared_lock = record->info.shared_lock; |
| info->app_sigaction = record->info.app_sigaction; |
| info->we_intercept = record->info.we_intercept; |
| d_r_mutex_lock(info->shared_lock); |
| (*info->shared_refcount)++; |
| #ifdef DEBUG |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (info->app_sigaction[i] != NULL) { |
| LOG(THREAD, LOG_ASYNCH, 2, "\thandler for signal %d is " PFX "\n", i, |
| info->app_sigaction[i]->handler); |
| } |
| } |
| #endif |
| d_r_mutex_unlock(info->shared_lock); |
| } else { |
| /* copy handlers */ |
| LOG(THREAD, LOG_ASYNCH, 2, "inheriting signal handlers from parent\n"); |
| if (!atomic_read_bool(&multiple_handlers_present)) |
| ATOMIC_1BYTE_WRITE(&multiple_handlers_present, true, false); |
| info->app_sigaction = (kernel_sigaction_t **)handler_alloc( |
| dcontext, SIGARRAY_SIZE * sizeof(kernel_sigaction_t *)); |
| memset(info->app_sigaction, 0, SIGARRAY_SIZE * sizeof(kernel_sigaction_t *)); |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| info->restorer_valid[i] = -1; /* clear cache */ |
| if (record->info.app_sigaction[i] != NULL) { |
| info->app_sigaction[i] = (kernel_sigaction_t *)handler_alloc( |
| dcontext, sizeof(kernel_sigaction_t)); |
| memcpy(info->app_sigaction[i], record->info.app_sigaction[i], |
| sizeof(kernel_sigaction_t)); |
| LOG(THREAD, LOG_ASYNCH, 2, "\thandler for signal %d is " PFX "\n", i, |
| info->app_sigaction[i]->handler); |
| } |
| } |
| info->we_intercept = |
| (bool *)handler_alloc(dcontext, SIGARRAY_SIZE * sizeof(bool)); |
| memcpy(info->we_intercept, record->info.we_intercept, |
| SIGARRAY_SIZE * sizeof(bool)); |
| d_r_mutex_lock(&record->info.child_lock); |
| record->info.num_unstarted_children--; |
| d_r_mutex_unlock(&record->info.child_lock); |
| /* this should be safe since parent should wait for us */ |
| d_r_mutex_lock(&record->parent_info->child_lock); |
| record->parent_info->num_unstarted_children--; |
| d_r_mutex_unlock(&record->parent_info->child_lock); |
| } |
| |
| /* itimers are either private or shared */ |
| if (TEST(CLONE_THREAD, record->clone_flags) && os_itimers_thread_shared()) { |
| ASSERT(record->info.shared_itimer); |
| LOG(THREAD, LOG_ASYNCH, 2, "sharing itimers with parent\n"); |
| info->shared_itimer = true; |
| info->shared_itimer_refcount = record->info.shared_itimer_refcount; |
| info->shared_itimer_underDR = record->info.shared_itimer_underDR; |
| info->itimer = record->info.itimer; |
| atomic_add_exchange_int((volatile int *)info->shared_itimer_refcount, 1); |
| /* shared_itimer_underDR will be incremented in start_itimer() */ |
| } else { |
| info->shared_itimer = false; |
| init_itimer(dcontext, false /*!first thread*/); |
| } |
| |
| /* rest of state is never shared. |
| * app_sigstack should already be in place, when we set up our sigstack |
| * we asked for old sigstack. |
| * FIXME: are current pending or blocked inherited? |
| */ |
| #ifdef MACOS |
| if (record->app_thread_xsp != 0) { |
| HEAP_TYPE_FREE(GLOBAL_DCONTEXT, record, clone_record_t, ACCT_THREAD_MGT, |
| true /*prot*/); |
| } |
| #endif |
| } else { |
| /* Initialize in isolation */ |
| |
| if (APP_HAS_SIGSTACK(info)) { |
| /* parent was NOT under our control, so the real sigstack we see is |
| * a real sigstack that was present before we took control |
| */ |
| LOG(THREAD, LOG_ASYNCH, 1, "app already has signal stack " PFX " - " PFX "\n", |
| info->app_sigstack.ss_sp, |
| info->app_sigstack.ss_sp + info->app_sigstack.ss_size); |
| } |
| |
| signal_info_init_sigaction(dcontext, info); |
| |
| info->shared_itimer = false; /* we'll set to true if a child is created */ |
| init_itimer(dcontext, true /*first*/); |
| |
| /* We split init vs start for the signal handlers and mask. We do not |
| * install ours until we start running the app, to avoid races like |
| * i#2335. We'll set them up when os_process_under_dynamorio_*() invokes |
| * signal_reinstate_handlers(). All we do now is mark which signals we |
| * want to intercept. |
| */ |
| if (DYNAMO_OPTION(intercept_all_signals)) { |
| /* PR 304708: to support client signal handlers without |
| * the complexity of per-thread and per-signal callbacks |
| * we always intercept all signals. We also check here |
| * for handlers the app registered before our init. |
| */ |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| /* cannot intercept KILL or STOP */ |
| if (signal_is_interceptable(i) && |
| /* FIXME PR 297033: we don't support intercepting DEFAULT_STOP / |
| * DEFAULT_CONTINUE signals. Once add support, update |
| * dr_register_signal_event() comments. |
| */ |
| default_action[i] != DEFAULT_STOP && |
| default_action[i] != DEFAULT_CONTINUE) |
| info->we_intercept[i] = true; |
| } |
| } else { |
| /* we intercept the following signals ourselves: */ |
| info->we_intercept[SIGSEGV] = true; |
| /* PR 313665: look for DR crashes on unaligned memory or mmap bounds */ |
| info->we_intercept[SIGBUS] = true; |
| /* PR 212090: the signal we use to suspend threads */ |
| info->we_intercept[SUSPEND_SIGNAL] = true; |
| #ifdef PAPI |
| /* use SIGPROF for updating gui so it can be distinguished from SIGVTALRM */ |
| info->we_intercept[SIGPROF] = true; |
| #endif |
| /* vtalarm only used with pc profiling. it interferes w/ PAPI |
| * so arm this signal only if necessary |
| */ |
| if (INTERNAL_OPTION(profile_pcs)) { |
| info->we_intercept[SIGVTALRM] = true; |
| } |
| #ifdef CLIENT_INTERFACE |
| info->we_intercept[SIGALRM] = true; |
| #endif |
| #ifdef SIDELINE |
| info->we_intercept[SIGCHLD] = true; |
| #endif |
| /* i#61/PR 211530: the signal we use for nudges */ |
| info->we_intercept[NUDGESIG_SIGNUM] = true; |
| } |
| |
| /* should be 1st thread */ |
| if (d_r_get_num_threads() > 1) |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* only when SIGVTALRM handler is in place should we start itimer (PR 537743) */ |
| if (INTERNAL_OPTION(profile_pcs)) { |
| /* even if the parent thread exits, we can use a pointer to its |
| * pcprofile_info b/c when shared it's process-shared and is not freed |
| * until the entire process exits |
| */ |
| pcprofile_thread_init(dcontext, info->shared_itimer, |
| (record == NULL) ? NULL : record->pcprofile_info); |
| } |
| |
| info->pre_syscall_app_sigprocmask_valid = false; |
| |
| /* Assumed to be async safe. */ |
| info->fully_initialized = true; |
| } |
| |
| /* When taking over existing app threads, we assume they're using pthreads and |
| * expect to share signal handlers, memory, thread group id, etc. |
| * Invokes dynamo_thread_init() with the appropriate os_data. |
| */ |
| dcontext_t * |
| init_thread_with_shared_siginfo(priv_mcontext_t *mc, dcontext_t *takeover_dc) |
| { |
| clone_record_t crec = { |
| 0, |
| }; |
| thread_sig_info_t *parent_siginfo = (thread_sig_info_t *)takeover_dc->signal_field; |
| /* Create a fake clone record with the given siginfo. All threads in the |
| * same thread group must share signal handlers since Linux 2.5.35, but we |
| * have to guess at the other flags. |
| * FIXME i#764: If we take over non-pthreads threads, we'll need some way to |
| * tell if they're sharing signal handlers or not. |
| */ |
| crec.caller_id = takeover_dc->owning_thread; |
| #ifdef LINUX |
| crec.clone_sysnum = SYS_clone; |
| #else |
| ASSERT_NOT_IMPLEMENTED(false); /* FIXME i#58: NYI on Mac */ |
| #endif |
| crec.clone_flags = PTHREAD_CLONE_FLAGS; |
| crec.parent_info = parent_siginfo; |
| crec.info = *parent_siginfo; |
| crec.pcprofile_info = takeover_dc->pcprofile_field; |
| IF_DEBUG(int r =) |
| dynamo_thread_init(NULL, mc, &crec _IF_CLIENT_INTERFACE(false)); |
| ASSERT(r == SUCCESS); |
| return get_thread_private_dcontext(); |
| } |
| |
| static void |
| free_pending_signal(thread_sig_info_t *info, int sig) |
| { |
| sigpending_t *temp = info->sigpending[sig]; |
| info->sigpending[sig] = temp->next; |
| special_heap_free(info->sigheap, temp); |
| info->num_pending--; |
| } |
| |
| /* This is split from os_fork_init() so the new logfiles are available |
| * (xref i#189/PR 452168). It had to be after dynamo_other_thread_exit() |
| * called in dynamorio_fork_init() after os_fork_init() else we clean |
| * up data structs used in signal_thread_exit(). |
| */ |
| void |
| signal_fork_init(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| /* Child of fork is a single thread in a new process so should |
| * start over w/ no sharing (xref i#190/PR 452178) |
| */ |
| if (info->shared_app_sigaction) { |
| info->shared_app_sigaction = false; |
| if (info->shared_lock != NULL) { |
| DELETE_LOCK(*info->shared_lock); |
| global_heap_free(info->shared_lock, sizeof(mutex_t) HEAPACCT(ACCT_OTHER)); |
| } |
| if (info->shared_refcount != NULL) |
| global_heap_free(info->shared_refcount, sizeof(int) HEAPACCT(ACCT_OTHER)); |
| info->shared_lock = NULL; |
| info->shared_refcount = NULL; |
| } |
| if (info->shared_itimer) { |
| /* itimers are not inherited across fork */ |
| info->shared_itimer = false; |
| for (i = 0; i < NUM_ITIMERS; i++) |
| DELETE_RECURSIVE_LOCK((*info->itimer)[i].lock); |
| if (os_itimers_thread_shared()) |
| global_heap_free(info->itimer, sizeof(*info->itimer) HEAPACCT(ACCT_OTHER)); |
| else |
| heap_free(dcontext, info->itimer, sizeof(*info->itimer) HEAPACCT(ACCT_OTHER)); |
| info->itimer = NULL; /* reset by init_itimer */ |
| ASSERT(info->shared_itimer_refcount != NULL); |
| global_heap_free(info->shared_itimer_refcount, sizeof(int) HEAPACCT(ACCT_OTHER)); |
| info->shared_itimer_refcount = NULL; |
| ASSERT(info->shared_itimer_underDR != NULL); |
| global_heap_free(info->shared_itimer_underDR, sizeof(int) HEAPACCT(ACCT_OTHER)); |
| info->shared_itimer_underDR = NULL; |
| init_itimer(dcontext, true /*first*/); |
| } |
| info->num_unstarted_children = 0; |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| /* "A child created via fork(2) initially has an empty pending signal set" */ |
| dcontext->signals_pending = 0; |
| while (info->sigpending[i] != NULL) { |
| free_pending_signal(info, i); |
| } |
| info->num_pending = 0; |
| } |
| if (INTERNAL_OPTION(profile_pcs)) { |
| pcprofile_fork_init(dcontext); |
| } |
| |
| info->pre_syscall_app_sigprocmask_valid = false; |
| |
| /* Assumed to be async safe. */ |
| info->fully_initialized = true; |
| } |
| |
| #ifdef DEBUG |
| static bool |
| sigsegv_handler_is_ours(void) |
| { |
| int rc; |
| kernel_sigaction_t oldact; |
| rc = sigaction_syscall(SIGSEGV, NULL, &oldact); |
| return (rc == 0 && oldact.handler == (handler_t)master_signal_handler); |
| } |
| #endif /* DEBUG */ |
| |
| #if defined(X86) && defined(LINUX) |
| static byte * |
| get_and_initialize_xstate_buffer(dcontext_t *dcontext) |
| { |
| /* See thread_sig_info_t.xstate_buf comments for why this is in TLS. */ |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| if (info->xstate_buf == NULL) { |
| info->xstate_alloc = |
| heap_alloc(dcontext, signal_frame_extra_size(true) HEAPACCT(ACCT_OTHER)); |
| info->xstate_buf = (byte *)ALIGN_FORWARD(info->xstate_alloc, XSTATE_ALIGNMENT); |
| ASSERT(info->xstate_alloc + signal_frame_extra_size(true) >= |
| info->xstate_buf + signal_frame_extra_size(false)); |
| } |
| kernel_fpstate_t *fpstate = (kernel_fpstate_t *)info->xstate_buf; |
| /* If we pass uninitialized values for kernel_xsave_hdr_t.reserved1 through |
| * sigreturn, we'll get a SIGSEGV. Best to zero it all out. |
| */ |
| memset(fpstate, 0, signal_frame_extra_size(false)); |
| fpstate->sw_reserved.extended_size = signal_frame_extra_size(false); |
| if (YMM_ENABLED()) { /* ZMM_ENABLED() always implies YMM_ENABLED() too. */ |
| fpstate->sw_reserved.magic1 = FP_XSTATE_MAGIC1; |
| fpstate->sw_reserved.xstate_size = signal_frame_extra_size(false) - |
| FP_XSTATE_MAGIC2_SIZE IF_X86_32(-FSAVE_FPSTATE_PREFIX_SIZE); |
| uint bv_high, bv_low; |
| dr_xgetbv(&bv_high, &bv_low); |
| fpstate->sw_reserved.xstate_bv = (((uint64)bv_high) << 32) | bv_low; |
| *(int *)((byte *)fpstate + fpstate->sw_reserved.extended_size - |
| FP_XSTATE_MAGIC2_SIZE) = FP_XSTATE_MAGIC2; |
| } else { |
| fpstate->sw_reserved.magic1 = 0; |
| fpstate->sw_reserved.xstate_size = sizeof(kernel_fpstate_t); |
| fpstate->sw_reserved.xstate_bv = 0; |
| } |
| return info->xstate_buf; |
| } |
| #endif |
| |
| void |
| signal_thread_exit(dcontext_t *dcontext, bool other_thread) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| |
| /* i#1012: DR's signal handler should always be installed before this point. |
| */ |
| ASSERT(sigsegv_handler_is_ours() || removed_sig_handler); |
| |
| while (info->num_unstarted_children > 0) { |
| /* must wait for children to start and copy our state |
| * before we destroy it! |
| */ |
| os_thread_yield(); |
| } |
| |
| /* stop_itimer() was already called by os_thread_not_under_dynamo() called |
| * from dynamo_thread_exit_common(). We need to leave the app itimers in place |
| * in case we're detaching. |
| */ |
| |
| #if defined(X86) && defined(LINUX) |
| if (info->xstate_alloc != NULL) { |
| heap_free(dcontext, info->xstate_alloc, |
| signal_frame_extra_size(true) HEAPACCT(ACCT_OTHER)); |
| } |
| #endif |
| |
| /* FIXME: w/ shared handlers, if parent (the owner here) dies, |
| * can children keep living w/ a copy of the handlers? |
| */ |
| if (info->shared_app_sigaction) { |
| d_r_mutex_lock(info->shared_lock); |
| (*info->shared_refcount)--; |
| d_r_mutex_unlock(info->shared_lock); |
| } |
| if (!info->shared_app_sigaction || *info->shared_refcount == 0) { |
| LOG(THREAD, LOG_ASYNCH, 2, "signal handler cleanup:\n"); |
| signal_info_exit_sigaction(dcontext, info, other_thread); |
| if (info->shared_lock != NULL) { |
| DELETE_LOCK(*info->shared_lock); |
| global_heap_free(info->shared_lock, sizeof(mutex_t) HEAPACCT(ACCT_OTHER)); |
| } |
| if (info->shared_refcount != NULL) |
| global_heap_free(info->shared_refcount, sizeof(int) HEAPACCT(ACCT_OTHER)); |
| } |
| |
| if (info->shared_itimer) { |
| atomic_add_exchange_int((volatile int *)info->shared_itimer_refcount, -1); |
| } |
| if (!info->shared_itimer || *info->shared_itimer_refcount == 0) { |
| if (INTERNAL_OPTION(profile_pcs)) { |
| /* no cleanup needed for non-final thread in group */ |
| pcprofile_thread_exit(dcontext); |
| } |
| for (i = 0; i < NUM_ITIMERS; i++) |
| DELETE_RECURSIVE_LOCK((*info->itimer)[i].lock); |
| if (os_itimers_thread_shared()) |
| global_heap_free(info->itimer, sizeof(*info->itimer) HEAPACCT(ACCT_OTHER)); |
| else |
| heap_free(dcontext, info->itimer, sizeof(*info->itimer) HEAPACCT(ACCT_OTHER)); |
| if (info->shared_itimer_refcount != NULL) { |
| global_heap_free(info->shared_itimer_refcount, |
| sizeof(int) HEAPACCT(ACCT_OTHER)); |
| ASSERT(info->shared_itimer_underDR != NULL); |
| global_heap_free(info->shared_itimer_underDR, |
| sizeof(int) HEAPACCT(ACCT_OTHER)); |
| } |
| } |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| /* pending queue is per-thread and not shared */ |
| while (info->sigpending[i] != NULL) { |
| sigpending_t *temp = info->sigpending[i]; |
| info->sigpending[i] = temp->next; |
| special_heap_free(info->sigheap, temp); |
| } |
| info->num_pending = 0; |
| } |
| /* If no detach flag is set, we assume that this thread is on its way to exit. |
| * In order to prevent receiving signals while a thread is on its way to exit |
| * without a valid dcontext, signals at this stage are blocked. The exceptions |
| * are the suspend signal and any signal that a terminating SYS_kill may need. |
| * (i#2921). In this case, we do not want to restore the signal mask. For detach, |
| * we do need to restore the app's mask. |
| */ |
| if (!other_thread && doing_detach) |
| signal_swap_mask(dcontext, true /*to_app*/); |
| #ifdef HAVE_SIGALTSTACK |
| /* Remove our sigstack and restore the app sigstack if it had one. */ |
| if (!other_thread) { |
| LOG(THREAD, LOG_ASYNCH, 2, "removing our signal stack " PFX " - " PFX "\n", |
| info->sigstack.ss_sp, info->sigstack.ss_sp + info->sigstack.ss_size); |
| if (APP_HAS_SIGSTACK(info)) { |
| LOG(THREAD, LOG_ASYNCH, 2, "restoring app signal stack " PFX " - " PFX "\n", |
| info->app_sigstack.ss_sp, |
| info->app_sigstack.ss_sp + info->app_sigstack.ss_size); |
| } else { |
| ASSERT(TEST(SS_DISABLE, info->app_sigstack.ss_flags)); |
| } |
| if (info->sigstack.ss_sp != NULL) { |
| /* i#552: to raise client exit event, we may call dynamo_process_exit |
| * on sigstack in signal handler. |
| * In that case we set sigstack (ss_sp) NULL to avoid stack swap. |
| */ |
| # ifdef MACOS |
| if (info->app_sigstack.ss_sp == NULL) { |
| /* Kernel fails w/ ENOMEM (even for SS_DISABLE) if ss_size is too small */ |
| info->sigstack.ss_flags = SS_DISABLE; |
| i = sigaltstack_syscall(&info->sigstack, NULL); |
| /* i#1814: kernel gives EINVAL if last handler didn't call sigreturn! */ |
| ASSERT(i == 0 || i == -EINVAL); |
| } else { |
| i = sigaltstack_syscall(&info->app_sigstack, NULL); |
| /* i#1814: kernel gives EINVAL if last handler didn't call sigreturn! */ |
| ASSERT(i == 0 || i == -EINVAL); |
| } |
| # else |
| i = sigaltstack_syscall(&info->app_sigstack, NULL); |
| ASSERT(i == 0); |
| # endif |
| } |
| } |
| #endif |
| IF_LINUX(signalfd_thread_exit(dcontext, info)); |
| special_heap_exit(info->sigheap); |
| DELETE_LOCK(info->child_lock); |
| #ifdef HAVE_SIGALTSTACK |
| if (info->sigstack.ss_sp != NULL) { |
| /* i#552: to raise client exit event, we may call dynamo_process_exit |
| * on sigstack in signal handler. |
| * In that case we set sigstack (ss_sp) NULL to avoid stack free. |
| */ |
| stack_free(info->sigstack.ss_sp + info->sigstack.ss_size, info->sigstack.ss_size); |
| } |
| #endif |
| #ifdef DEBUG |
| /* for non-debug we do fast exit path and don't free local heap */ |
| HEAP_TYPE_FREE(dcontext, info, thread_sig_info_t, ACCT_OTHER, PROTECTED); |
| #endif |
| #ifdef PAPI |
| /* use SIGPROF for updating gui so it can be distinguished from SIGVTALRM */ |
| set_itimer_callback( |
| dcontext, ITIMER_PROF, 500, |
| (void (*func)(dcontext_t *, priv_mcontext_t *))perfctr_update_gui()); |
| #endif |
| } |
| |
| void |
| set_handler_sigact(kernel_sigaction_t *act, int sig, handler_t handler) |
| { |
| act->handler = handler; |
| #ifdef MACOS |
| /* This is the real target */ |
| act->tramp = (tramp_t)handler; |
| #endif |
| |
| act->flags = SA_SIGINFO; /* send 3 args to handler */ |
| #ifdef HAVE_SIGALTSTACK |
| act->flags |= SA_ONSTACK; /* use our sigstack */ |
| #endif |
| /* We want the kernel to help us auto-restart syscalls, esp. when our signals |
| * interrupt native code such as during attach or in client or DR code (i#2659). |
| */ |
| act->flags |= SA_RESTART; |
| |
| #if !defined(VMX86_SERVER) && defined(LINUX) |
| /* PR 305020: must have SA_RESTORER for x64 */ |
| /* i#2812: must have SA_RESTORER to handle vsyscall32 being disabled */ |
| act->flags |= SA_RESTORER; |
| act->restorer = (void (*)(void))dynamorio_sigreturn; |
| #endif |
| |
| /* We block most signals within our handler */ |
| kernel_sigfillset(&act->mask); |
| /* i#184/PR 450670: we let our suspend signal interrupt our own handler |
| * We never send more than one before resuming, so no danger to stack usage |
| * from our own: but app could pile them up. |
| */ |
| kernel_sigdelset(&act->mask, SUSPEND_SIGNAL); |
| /* i#193/PR 287309: we need to NOT suppress further SIGSEGV, for decode faults, |
| * for try/except, and for !HAVE_MEMINFO probes. |
| * Just like SUSPEND_SIGNAL, if app sends repeated SEGV, could run out of |
| * alt stack: seems too corner-case to be worth increasing stack size. |
| */ |
| kernel_sigdelset(&act->mask, SIGSEGV); |
| if (sig == SUSPEND_SIGNAL || sig == SIGSEGV) |
| act->flags |= SA_NODEFER; |
| /* Sigset is a 1 or 2 elt array of longs on X64/X86. Treat as 2 elt of |
| * uint32. */ |
| IF_DEBUG(uint32 *mask_sig = (uint32 *)&act->mask.sig[0]); |
| LOG(THREAD_GET, LOG_ASYNCH, 3, "mask for our handler is " PFX " " PFX "\n", |
| mask_sig[0], mask_sig[1]); |
| } |
| |
| static void |
| set_our_handler_sigact(kernel_sigaction_t *act, int sig) |
| { |
| set_handler_sigact(act, sig, (handler_t)master_signal_handler); |
| } |
| |
| static void |
| set_handler_and_record_app(dcontext_t *dcontext, thread_sig_info_t *info, int sig, |
| kernel_sigaction_t *act) |
| { |
| int rc; |
| kernel_sigaction_t oldact; |
| ASSERT(sig <= MAX_SIGNUM); |
| |
| /* arm the signal */ |
| rc = sigaction_syscall(sig, act, &oldact); |
| ASSERT(rc == |
| 0 |
| /* Workaround for PR 223720, which was fixed in ESX4.0 but |
| * is present in ESX3.5 and earlier: vmkernel treats |
| * 63 and 64 as invalid signal numbers. |
| */ |
| IF_VMX86(|| (sig >= 63 && rc == -EINVAL))); |
| if (rc != 0) /* be defensive: app will probably still work */ |
| return; |
| |
| if (oldact.handler != (handler_t)SIG_DFL && |
| oldact.handler != (handler_t)master_signal_handler) { |
| /* save the app's action for sig */ |
| if (info->shared_app_sigaction) { |
| /* app_sigaction structure is shared */ |
| d_r_mutex_lock(info->shared_lock); |
| } |
| if (info->app_sigaction[sig] != NULL) { |
| /* go ahead and toss the old one, it's up to the app to store |
| * and then restore later if it wants to |
| */ |
| handler_free(dcontext, info->app_sigaction[sig], sizeof(kernel_sigaction_t)); |
| } |
| info->app_sigaction[sig] = |
| (kernel_sigaction_t *)handler_alloc(dcontext, sizeof(kernel_sigaction_t)); |
| memcpy(info->app_sigaction[sig], &oldact, sizeof(kernel_sigaction_t)); |
| /* clear cache */ |
| info->restorer_valid[sig] = -1; |
| if (info->shared_app_sigaction) |
| d_r_mutex_unlock(info->shared_lock); |
| #ifdef DEBUG |
| if (oldact.handler == (handler_t)SIG_IGN) { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "app already installed SIG_IGN as sigaction for signal %d\n", sig); |
| } else { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "app already installed " PFX " as sigaction flags=0x%x for signal %d\n", |
| oldact.handler, oldact.flags, sig); |
| } |
| #endif |
| } else { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "prior handler is " PFX " vs master " PFX " with flags=0x%x for signal %d\n", |
| oldact.handler, master_signal_handler, oldact.flags, sig); |
| if (info->app_sigaction[sig] != NULL) { |
| if (info->shared_app_sigaction) |
| d_r_mutex_lock(info->shared_lock); |
| handler_free(dcontext, info->app_sigaction[sig], sizeof(kernel_sigaction_t)); |
| info->app_sigaction[sig] = NULL; |
| if (info->shared_app_sigaction) |
| d_r_mutex_unlock(info->shared_lock); |
| } |
| } |
| LOG(THREAD, LOG_ASYNCH, 3, "\twe intercept signal %d\n", sig); |
| } |
| |
| /* Set up master_signal_handler as the handler for signal "sig", |
| * for the current thread. Since we deal with kernel data structures |
| * in our interception of system calls, we use them here as well, |
| * to avoid having to translate to/from libc data structures. |
| */ |
| static void |
| intercept_signal(dcontext_t *dcontext, thread_sig_info_t *info, int sig) |
| { |
| kernel_sigaction_t act; |
| ASSERT(sig <= MAX_SIGNUM); |
| set_our_handler_sigact(&act, sig); |
| set_handler_and_record_app(dcontext, info, sig, &act); |
| } |
| |
| static void |
| intercept_signal_ignore_initially(dcontext_t *dcontext, thread_sig_info_t *info, int sig) |
| { |
| kernel_sigaction_t act; |
| ASSERT(sig <= MAX_SIGNUM); |
| memset(&act, 0, sizeof(act)); |
| act.handler = (handler_t)SIG_IGN; |
| set_handler_and_record_app(dcontext, info, sig, &act); |
| } |
| |
| static void |
| intercept_signal_no_longer_ignore(dcontext_t *dcontext, thread_sig_info_t *info, int sig) |
| { |
| kernel_sigaction_t act; |
| int rc; |
| ASSERT(sig <= MAX_SIGNUM); |
| set_our_handler_sigact(&act, sig); |
| rc = sigaction_syscall(sig, &act, NULL); |
| ASSERT(rc == 0); |
| } |
| |
| /* i#1921: For proper single-threaded native execution with re-takeover we need |
| * to propagate signals. For now we only support going completely native in |
| * this thread but without a full detach, so we abandon our signal handlers w/o |
| * freeing memory up front. |
| * We also use this for the start/stop interface where we are going fully native |
| * for all threads. |
| */ |
| void |
| signal_remove_handlers(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| kernel_sigaction_t act; |
| memset(&act, 0, sizeof(act)); |
| act.handler = (handler_t)SIG_DFL; |
| kernel_sigemptyset(&act.mask); |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (info->app_sigaction[i] != NULL) { |
| LOG(THREAD, LOG_ASYNCH, 2, "\trestoring " PFX " as handler for %d\n", |
| info->app_sigaction[i]->handler, i); |
| sigaction_syscall(i, info->app_sigaction[i], NULL); |
| } else if (info->we_intercept[i]) { |
| /* restore to default */ |
| LOG(THREAD, LOG_ASYNCH, 2, "\trestoring SIG_DFL as handler for %d\n", i); |
| sigaction_syscall(i, &act, NULL); |
| } |
| } |
| DODEBUG({ removed_sig_handler = true; }); |
| } |
| |
| void |
| signal_remove_alarm_handlers(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (!info->we_intercept[i]) |
| continue; |
| if (sig_is_alarm_signal(i)) { |
| set_ignore_signal_action(i); |
| } |
| } |
| } |
| |
| /* For attaching mid-run, we assume regular POSIX with handlers global to just one |
| * thread group in the process. |
| * We also use this routine for the initial setup of our handlers, which we |
| * split from signal_thread_inherit() to support start/stop. |
| */ |
| void |
| signal_reinstate_handlers(dcontext_t *dcontext, bool ignore_alarm) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| bool skip = false; |
| if (!info->we_intercept[i]) { |
| skip = true; |
| if (signal_is_interceptable(i)) { |
| /* We do have to intercept everything the app does. |
| * If the app removes its handler, we'll never remove ours, which we |
| * can live with. |
| */ |
| kernel_sigaction_t oldact; |
| int rc = sigaction_syscall(i, NULL, &oldact); |
| ASSERT(rc == 0); |
| if (rc == 0 && oldact.handler != (handler_t)SIG_DFL && |
| oldact.handler != (handler_t)master_signal_handler) { |
| skip = false; |
| } |
| } |
| } |
| if (skip) |
| continue; |
| if (sig_is_alarm_signal(i) && ignore_alarm) { |
| LOG(THREAD, LOG_ASYNCH, 2, "\tignoring %d initially\n", i); |
| intercept_signal_ignore_initially(dcontext, info, i); |
| } else { |
| LOG(THREAD, LOG_ASYNCH, 2, "\trestoring DR handler for %d\n", i); |
| intercept_signal(dcontext, info, i); |
| } |
| } |
| DODEBUG({ removed_sig_handler = false; }); |
| } |
| |
| void |
| signal_reinstate_alarm_handlers(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (!info->we_intercept[i] || !sig_is_alarm_signal(i)) |
| continue; |
| LOG(THREAD, LOG_ASYNCH, 2, "\trestoring DR handler for %d\n", i); |
| intercept_signal_no_longer_ignore(dcontext, info, i); |
| } |
| } |
| |
| /**** system call handlers ***********************************************/ |
| |
| /* FIXME: invalid pointer passed to kernel will currently show up |
| * probably as a segfault in our handlers below...need to make them |
| * look like kernel, and pass error code back to os.c |
| */ |
| |
| void |
| handle_clone(dcontext_t *dcontext, uint flags) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| if ((flags & CLONE_VM) == 0) { |
| /* separate process not sharing memory */ |
| if ((flags & CLONE_SIGHAND) != 0) { |
| /* FIXME: how deal with this? |
| * "man clone" says: "Since Linux 2.6.0-test6, flags must also |
| * include CLONE_VM if CLONE_SIGHAND is specified" |
| */ |
| LOG(THREAD, LOG_ASYNCH, 1, "WARNING: !CLONE_VM but CLONE_SIGHAND!\n"); |
| ASSERT_NOT_IMPLEMENTED(false); |
| } |
| return; |
| } |
| |
| pre_second_thread(); |
| |
| if ((flags & CLONE_SIGHAND) != 0) { |
| /* need to share table of handlers! */ |
| LOG(THREAD, LOG_ASYNCH, 2, "handle_clone: CLONE_SIGHAND set!\n"); |
| if (!info->shared_app_sigaction) { |
| /* this is the start of a chain of sharing |
| * no synch needed here, child not created yet |
| */ |
| info->shared_app_sigaction = true; |
| info->shared_refcount = |
| (int *)global_heap_alloc(sizeof(int) HEAPACCT(ACCT_OTHER)); |
| *info->shared_refcount = 1; |
| info->shared_lock = |
| (mutex_t *)global_heap_alloc(sizeof(mutex_t) HEAPACCT(ACCT_OTHER)); |
| ASSIGN_INIT_LOCK_FREE(*info->shared_lock, shared_lock); |
| } /* else, some ancestor is already owner */ |
| } else { |
| /* child will inherit copy of current table -> cannot modify it |
| * until child is scheduled! FIXME: any other way? |
| */ |
| d_r_mutex_lock(&info->child_lock); |
| info->num_unstarted_children++; |
| d_r_mutex_unlock(&info->child_lock); |
| } |
| |
| if (TEST(CLONE_THREAD, flags) && os_itimers_thread_shared()) { |
| if (!info->shared_itimer) { |
| /* this is the start of a chain of sharing |
| * no synch needed here, child not created yet |
| */ |
| info->shared_itimer = true; |
| info->shared_itimer_refcount = |
| (int *)global_heap_alloc(sizeof(int) HEAPACCT(ACCT_OTHER)); |
| *info->shared_itimer_refcount = 1; |
| info->shared_itimer_underDR = |
| (int *)global_heap_alloc(sizeof(int) HEAPACCT(ACCT_OTHER)); |
| *info->shared_itimer_underDR = 1; |
| } /* else, some ancestor already created */ |
| } |
| } |
| |
| /* Returns false if should NOT issue syscall. |
| * In such a case, the result is in "result". |
| * If *result is non-zero, the syscall should fail. |
| * We could instead issue the syscall and expect it to fail, which would have a more |
| * accurate error code, but that risks missing a failure (e.g., RT on Android |
| * which in some cases returns success on bugus params). |
| * It seems better to err on the side of the wrong error code or failing when |
| * we shouldn't, than to think it failed when it didn't, which is more complex |
| * to deal with. |
| */ |
| bool |
| handle_sigaction(dcontext_t *dcontext, int sig, const kernel_sigaction_t *act, |
| prev_sigaction_t *oact, size_t sigsetsize, OUT uint *result) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| kernel_sigaction_t *save; |
| kernel_sigaction_t local_act; |
| if (sigsetsize != sizeof(kernel_sigset_t)) { |
| *result = EINVAL; |
| return false; |
| } |
| if (act != NULL) { |
| /* Linux checks readability before checking the signal number. */ |
| if (!d_r_safe_read(act, sizeof(local_act), &local_act)) { |
| *result = EFAULT; |
| return false; |
| } |
| } |
| /* i#1135: app may pass invalid signum to find MAX_SIGNUM */ |
| if (sig <= 0 || sig > MAX_SIGNUM || (act != NULL && !signal_is_interceptable(sig))) { |
| *result = EINVAL; |
| return false; |
| } |
| if (act != NULL) { |
| /* app is installing a new action */ |
| while (info->num_unstarted_children > 0) { |
| /* must wait for children to start and copy our state |
| * before we modify it! |
| */ |
| os_thread_yield(); |
| } |
| info->sigaction_param = act; |
| } |
| if (info->shared_app_sigaction) { |
| /* app_sigaction structure is shared */ |
| d_r_mutex_lock(info->shared_lock); |
| } |
| if (oact != NULL) { |
| /* Keep a copy of the prior one for post-syscall to hand to the app. */ |
| info->use_kernel_prior_sigaction = false; |
| if (info->app_sigaction[sig] == NULL) { |
| if (info->we_intercept[sig]) { |
| /* need to pretend there is no handler */ |
| memset(&info->prior_app_sigaction, 0, sizeof(info->prior_app_sigaction)); |
| info->prior_app_sigaction.handler = (handler_t)SIG_DFL; |
| } else { |
| info->use_kernel_prior_sigaction = true; |
| } |
| } else { |
| memcpy(&info->prior_app_sigaction, info->app_sigaction[sig], |
| sizeof(info->prior_app_sigaction)); |
| } |
| } |
| if (act != NULL) { |
| if (local_act.handler == (handler_t)SIG_IGN || |
| local_act.handler == (handler_t)SIG_DFL) { |
| LOG(THREAD, LOG_ASYNCH, 2, "app installed %s as sigaction for signal %d\n", |
| (local_act.handler == (handler_t)SIG_IGN) ? "SIG_IGN" : "SIG_DFL", sig); |
| if (!info->we_intercept[sig]) { |
| /* let the SIG_IGN/SIG_DFL go through, we want to remove our |
| * handler. we delete the stored app_sigaction in post_ |
| */ |
| if (info->shared_app_sigaction) |
| d_r_mutex_unlock(info->shared_lock); |
| return true; |
| } |
| } else { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "app installed " PFX " as sigaction for signal %d\n", local_act.handler, |
| sig); |
| DOLOG(2, LOG_ASYNCH, { |
| LOG(THREAD, LOG_ASYNCH, 2, "signal mask for handler:\n"); |
| dump_sigset(dcontext, (kernel_sigset_t *)&local_act.mask); |
| }); |
| } |
| |
| /* save app's entire sigaction struct */ |
| save = (kernel_sigaction_t *)handler_alloc(dcontext, sizeof(kernel_sigaction_t)); |
| memcpy(save, &local_act, sizeof(kernel_sigaction_t)); |
| /* Remove the unblockable sigs */ |
| kernel_sigdelset(&save->mask, SIGKILL); |
| kernel_sigdelset(&save->mask, SIGSTOP); |
| #ifdef X86 |
| /* Remove flags not allowed to be passed to the kernel (this also zeroes |
| * the top 32 bits, like the kernel does: i#3681). |
| */ |
| save->flags &= ~(SA_IA32_ABI | SA_X32_ABI); |
| #endif |
| if (info->app_sigaction[sig] != NULL) { |
| /* go ahead and toss the old one, it's up to the app to store |
| * and then restore later if it wants to |
| */ |
| handler_free(dcontext, info->app_sigaction[sig], sizeof(kernel_sigaction_t)); |
| } |
| info->app_sigaction[sig] = save; |
| LOG(THREAD, LOG_ASYNCH, 3, "\tflags = " PFX ", %s = " PFX "\n", local_act.flags, |
| IF_MACOS_ELSE("tramp", "restorer"), |
| IF_MACOS_ELSE(local_act.tramp, local_act.restorer)); |
| /* clear cache */ |
| info->restorer_valid[sig] = -1; |
| } |
| if (info->shared_app_sigaction) |
| d_r_mutex_unlock(info->shared_lock); |
| if (info->we_intercept[sig]) { |
| /* cancel the syscall */ |
| *result = handle_post_sigaction(dcontext, true, sig, act, oact, sigsetsize); |
| return false; |
| } |
| if (act != NULL) { |
| /* Now hand kernel our master handler instead of app's. */ |
| set_our_handler_sigact(&info->our_sigaction, sig); |
| set_syscall_param(dcontext, 1, (reg_t)&info->our_sigaction); |
| |
| /* FIXME PR 297033: we don't support intercepting DEFAULT_STOP / |
| * DEFAULT_CONTINUE signals b/c we can't generate the default |
| * action: if the app registers a handler, though, we should work |
| * properly if we never see SIG_DFL. |
| */ |
| } |
| return true; |
| } |
| |
| /* os.c thinks it's passing us struct_sigaction, really it's kernel_sigaction_t, |
| * which has fields in different order. |
| * Only called on success. |
| * Returns the desired app return value (caller will negate if nec). |
| */ |
| uint |
| handle_post_sigaction(dcontext_t *dcontext, bool success, int sig, |
| const kernel_sigaction_t *act, prev_sigaction_t *oact, |
| size_t sigsetsize) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| if (act != NULL) { |
| /* Restore app register value, in case we changed it. */ |
| set_syscall_param(dcontext, 1, (reg_t)info->sigaction_param); |
| } |
| if (!success) |
| return 0; /* don't change return value */ |
| ASSERT(sig <= MAX_SIGNUM && sig > 0); |
| if (oact != NULL) { |
| if (info->use_kernel_prior_sigaction) { |
| /* Real syscall succeeded with oact so it must be readable, barring races. */ |
| ASSERT(oact->handler == (handler_t)SIG_IGN || |
| oact->handler == (handler_t)SIG_DFL); |
| } else { |
| /* We may have skipped the syscall so we have to check writability */ |
| #ifdef MACOS |
| /* On MacOS prev_sigaction_t is a different type (i#2105) */ |
| bool fault = true; |
| TRY_EXCEPT(dcontext, |
| { |
| oact->handler = info->prior_app_sigaction.handler; |
| oact->mask = info->prior_app_sigaction.mask; |
| oact->flags = info->prior_app_sigaction.flags; |
| fault = false; |
| }, |
| { |
| /* EXCEPT */ |
| /* nothing: fault is already true */ |
| }); |
| if (fault) |
| return EFAULT; |
| #else |
| if (!safe_write_ex(oact, sizeof(*oact), &info->prior_app_sigaction, NULL)) { |
| /* We actually don't have to undo installing any passed action |
| * b/c the Linux kernel does that *before* checking oact perms. |
| */ |
| return EFAULT; |
| } |
| #endif |
| } |
| } |
| /* If installing IGN or DFL, delete ours. |
| * XXX: This is racy. We can't hold the lock across the syscall, though. |
| * What we should do is just drop support for -no_intercept_all_signals, |
| * which is off by default anyway and never turned off. |
| */ |
| if (act != NULL && |
| /* De-ref here should work barring races: already racy and non-default so not |
| * bothering with safe_read. |
| */ |
| ((act->handler == (handler_t)SIG_IGN || act->handler == (handler_t)SIG_DFL) && |
| !info->we_intercept[sig]) && |
| info->app_sigaction[sig] != NULL) { |
| if (info->shared_app_sigaction) |
| d_r_mutex_lock(info->shared_lock); |
| /* remove old stored app action */ |
| handler_free(dcontext, info->app_sigaction[sig], sizeof(kernel_sigaction_t)); |
| info->app_sigaction[sig] = NULL; |
| if (info->shared_app_sigaction) |
| d_r_mutex_unlock(info->shared_lock); |
| } |
| return 0; |
| } |
| |
| #ifdef LINUX |
| static bool |
| convert_old_sigaction_to_kernel(dcontext_t *dcontext, kernel_sigaction_t *ks, |
| const old_sigaction_t *os) |
| { |
| bool res = false; |
| TRY_EXCEPT(dcontext, |
| { |
| ks->handler = os->handler; |
| ks->flags = os->flags; |
| ks->restorer = os->restorer; |
| kernel_sigemptyset(&ks->mask); |
| ks->mask.sig[0] = os->mask; |
| res = true; |
| }, |
| { |
| /* EXCEPT */ |
| /* nothing: res is already false */ |
| }); |
| return res; |
| } |
| |
| static bool |
| convert_kernel_sigaction_to_old(dcontext_t *dcontext, old_sigaction_t *os, |
| const kernel_sigaction_t *ks) |
| { |
| bool res = false; |
| TRY_EXCEPT(dcontext, |
| { |
| os->handler = ks->handler; |
| os->flags = ks->flags; |
| os->restorer = ks->restorer; |
| os->mask = ks->mask.sig[0]; |
| res = true; |
| }, |
| { |
| /* EXCEPT */ |
| /* nothing: res is already false */ |
| }); |
| return res; |
| } |
| |
| /* Returns false (and "result") if should NOT issue syscall. */ |
| bool |
| handle_old_sigaction(dcontext_t *dcontext, int sig, const old_sigaction_t *act, |
| old_sigaction_t *oact, OUT uint *result) |
| { |
| kernel_sigaction_t kact; |
| kernel_sigaction_t okact; |
| bool res; |
| if (act != NULL) { |
| if (!convert_old_sigaction_to_kernel(dcontext, &kact, act)) { |
| *result = EFAULT; |
| return false; |
| } |
| } |
| res = handle_sigaction(dcontext, sig, act == NULL ? NULL : &kact, |
| oact == NULL ? NULL : &okact, sizeof(kernel_sigset_t), result); |
| if (!res) |
| *result = handle_post_old_sigaction(dcontext, true, sig, act, oact); |
| return res; |
| } |
| |
| /* Returns the desired app return value (caller will negate if nec). */ |
| uint |
| handle_post_old_sigaction(dcontext_t *dcontext, bool success, int sig, |
| const old_sigaction_t *act, old_sigaction_t *oact) |
| { |
| kernel_sigaction_t kact; |
| kernel_sigaction_t okact; |
| ptr_uint_t res; |
| if (act != NULL && success) { |
| if (!convert_old_sigaction_to_kernel(dcontext, &kact, act)) { |
| ASSERT(!success); |
| return EFAULT; |
| } |
| } |
| if (oact != NULL && success) { |
| if (!convert_old_sigaction_to_kernel(dcontext, &okact, oact)) { |
| ASSERT(!success); |
| return EFAULT; |
| } |
| } |
| res = handle_post_sigaction(dcontext, success, sig, act == NULL ? NULL : &kact, |
| oact == NULL ? NULL : &okact, sizeof(kernel_sigset_t)); |
| if (res == 0 && oact != NULL) { |
| if (!convert_kernel_sigaction_to_old(dcontext, oact, &okact)) { |
| return EFAULT; |
| } |
| } |
| return res; |
| } |
| #endif /* LINUX */ |
| |
| /* Returns false and sets *result if should NOT issue syscall. |
| * If *result is non-zero, the syscall should fail. |
| */ |
| bool |
| handle_sigaltstack(dcontext_t *dcontext, const stack_t *stack, stack_t *old_stack, |
| reg_t cur_xsp, OUT uint *result) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| stack_t local_stack; |
| if (old_stack != NULL) { |
| if (!safe_write_ex(old_stack, sizeof(*old_stack), &info->app_sigstack, NULL)) { |
| *result = EFAULT; |
| return false; |
| } |
| } |
| if (stack != NULL) { |
| /* Fail in the same way the kernel does. */ |
| if (!d_r_safe_read(stack, sizeof(local_stack), &local_stack)) { |
| *result = EFAULT; |
| return false; |
| } |
| if (APP_HAS_SIGSTACK(info)) { |
| /* The app is not allowed to set a new altstack while on the current one. */ |
| reg_t cur_sigstk = (reg_t)info->app_sigstack.ss_sp; |
| if (cur_xsp >= cur_sigstk && |
| cur_xsp < cur_sigstk + info->app_sigstack.ss_size) { |
| *result = EPERM; |
| return false; |
| } |
| } |
| uint key_flag = local_stack.ss_flags & ~SS_FLAG_BITS; |
| if (key_flag != SS_DISABLE && key_flag != SS_ONSTACK && key_flag != 0) { |
| *result = EINVAL; |
| return false; |
| } |
| if (key_flag == SS_DISABLE) { |
| /* Zero the other params and don't even check them. */ |
| local_stack.ss_sp = NULL; |
| local_stack.ss_size = 0; |
| } else { |
| if (local_stack.ss_size < MINSIGSTKSZ) { |
| *result = ENOMEM; |
| return false; |
| } |
| } |
| info->app_sigstack = local_stack; |
| LOG(THREAD, LOG_ASYNCH, 2, "Setting app signal stack to " PFX "-" PFX " %d=%s\n", |
| local_stack.ss_sp, local_stack.ss_sp + local_stack.ss_size - 1, |
| local_stack.ss_flags, (APP_HAS_SIGSTACK(info)) ? "enabled" : "disabled"); |
| } |
| *result = 0; |
| return false; /* always cancel syscall */ |
| } |
| |
| /* Blocked signals: |
| * In general, we don't need to keep track of blocked signals. |
| * We only need to do so for those signals we intercept ourselves. |
| * Thus, info->app_sigblocked ONLY contains entries for signals |
| * we intercept ourselves. |
| * PR 304708: we now intercept all signals. |
| */ |
| |
| static void |
| set_blocked(dcontext_t *dcontext, kernel_sigset_t *set, bool absolute) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| if (absolute) { |
| /* discard current blocked signals, re-set from new mask */ |
| kernel_sigemptyset(&info->app_sigblocked); |
| } /* else, OR in the new set */ |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (EMULATE_SIGMASK(info, i) && kernel_sigismember(set, i)) { |
| kernel_sigaddset(&info->app_sigblocked, i); |
| } |
| } |
| #ifdef DEBUG |
| if (d_r_stats->loglevel >= 3 && (d_r_stats->logmask & LOG_ASYNCH) != 0) { |
| LOG(THREAD, LOG_ASYNCH, 3, "blocked signals are now:\n"); |
| dump_sigset(dcontext, &info->app_sigblocked); |
| } |
| #endif |
| } |
| |
| void |
| signal_set_mask(dcontext_t *dcontext, kernel_sigset_t *sigset) |
| { |
| set_blocked(dcontext, sigset, true /*absolute*/); |
| } |
| |
| void |
| signal_swap_mask(dcontext_t *dcontext, bool to_app) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| if (to_app) { |
| if (init_info.app_sigaction != NULL) { |
| /* This is the first execution of the app. |
| * We need to remove our own init-time handler and mask. |
| */ |
| unset_initial_crash_handlers(dcontext); |
| return; |
| } |
| sigprocmask_syscall(SIG_SETMASK, &info->app_sigblocked, NULL, |
| sizeof(info->app_sigblocked)); |
| } else { |
| unblock_all_signals(&info->app_sigblocked); |
| DOLOG(2, LOG_ASYNCH, { |
| LOG(THREAD, LOG_ASYNCH, 2, "thread %d's initial app signal mask:\n", |
| d_r_get_thread_id()); |
| dump_sigset(dcontext, &info->app_sigblocked); |
| }); |
| } |
| } |
| |
| /* Scans over info->sigpending to see if there are any unblocked, pending |
| * signals, and sets dcontext->signals_pending if there are. Do this after |
| * modifying the set of signals blocked by the application. |
| */ |
| void |
| check_signals_pending(dcontext_t *dcontext, thread_sig_info_t *info) |
| { |
| int i; |
| |
| if (dcontext->signals_pending != 0) |
| return; |
| |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (info->sigpending[i] != NULL && |
| !kernel_sigismember(&info->app_sigblocked, i) && !dcontext->signals_pending) { |
| /* We only update the application's set of blocked signals from |
| * syscall handlers, so we know we'll go back to d_r_dispatch and see |
| * this flag right away. |
| */ |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsetting signals_pending flag\n"); |
| dcontext->signals_pending = 1; |
| break; |
| } |
| } |
| } |
| |
| /* Returns whether to execute the syscall */ |
| bool |
| handle_sigprocmask(dcontext_t *dcontext, int how, kernel_sigset_t *app_set, |
| kernel_sigset_t *oset, size_t sigsetsize) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| kernel_sigset_t safe_set; |
| /* If we're intercepting all, we emulate the whole thing */ |
| bool execute_syscall = !DYNAMO_OPTION(intercept_all_signals); |
| LOG(THREAD, LOG_ASYNCH, 2, "handle_sigprocmask\n"); |
| if (oset != NULL) |
| info->pre_syscall_app_sigblocked = info->app_sigblocked; |
| if (app_set != NULL && d_r_safe_read(app_set, sizeof(safe_set), &safe_set)) { |
| if (execute_syscall) { |
| /* The syscall will execute, so remove from the set passed |
| * to it. We restore post-syscall. |
| * XXX i#1187: we could crash here touching app memory -- could |
| * use TRY, but the app could pass read-only memory and it |
| * would work natively! Better to swap in our own |
| * allocated data struct. There's a transparency issue w/ |
| * races too if another thread looks at this memory. This |
| * won't happen by default b/c -intercept_all_signals is |
| * on by default so we don't try to solve all these |
| * issues. |
| */ |
| info->pre_syscall_app_sigprocmask = safe_set; |
| } |
| if (how == SIG_BLOCK) { |
| /* The set of blocked signals is the union of the current |
| * set and the set argument. |
| */ |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (EMULATE_SIGMASK(info, i) && kernel_sigismember(&safe_set, i)) { |
| kernel_sigaddset(&info->app_sigblocked, i); |
| if (execute_syscall) |
| kernel_sigdelset(app_set, i); |
| } |
| } |
| } else if (how == SIG_UNBLOCK) { |
| /* The signals in set are removed from the current set of |
| * blocked signals. |
| */ |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (EMULATE_SIGMASK(info, i) && kernel_sigismember(&safe_set, i)) { |
| kernel_sigdelset(&info->app_sigblocked, i); |
| if (execute_syscall) |
| kernel_sigdelset(app_set, i); |
| } |
| } |
| } else if (how == SIG_SETMASK) { |
| /* The set of blocked signals is set to the argument set. */ |
| kernel_sigemptyset(&info->app_sigblocked); |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (EMULATE_SIGMASK(info, i) && kernel_sigismember(&safe_set, i)) { |
| kernel_sigaddset(&info->app_sigblocked, i); |
| if (execute_syscall) |
| kernel_sigdelset(app_set, i); |
| } |
| } |
| } |
| #ifdef DEBUG |
| if (d_r_stats->loglevel >= 3 && (d_r_stats->logmask & LOG_ASYNCH) != 0) { |
| LOG(THREAD, LOG_ASYNCH, 3, "blocked signals are now:\n"); |
| dump_sigset(dcontext, &info->app_sigblocked); |
| } |
| #endif |
| /* make sure we deliver pending signals that are now unblocked |
| * FIXME: consider signal #S, which we intercept ourselves. |
| * If S arrives, then app blocks it prior to our delivering it, |
| * we then won't deliver it until app unblocks it...is this a |
| * problem? Could have arrived a little later and then we would |
| * do same thing, but this way kernel may send one more than would |
| * get w/o dynamo? This goes away if we deliver signals |
| * prior to letting app do a syscall. |
| */ |
| check_signals_pending(dcontext, info); |
| } |
| if (!execute_syscall) { |
| handle_post_sigprocmask(dcontext, how, app_set, oset, sigsetsize); |
| return false; /* skip syscall */ |
| } else |
| return true; |
| } |
| |
| /* need to add in our signals that the app thinks are blocked */ |
| void |
| handle_post_sigprocmask(dcontext_t *dcontext, int how, kernel_sigset_t *app_set, |
| kernel_sigset_t *oset, size_t sigsetsize) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| if (!DYNAMO_OPTION(intercept_all_signals)) { |
| /* Restore app memory */ |
| safe_write_ex(app_set, sizeof(*app_set), &info->pre_syscall_app_sigprocmask, |
| NULL); |
| } |
| if (oset != NULL) { |
| if (DYNAMO_OPTION(intercept_all_signals)) |
| safe_write_ex(oset, sizeof(*oset), &info->pre_syscall_app_sigblocked, NULL); |
| else { |
| /* the syscall wrote to oset already, so just add any additional */ |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (EMULATE_SIGMASK(info, i) && |
| /* use the pre-syscall value: do not take into account changes |
| * from this syscall itself! (PR 523394) |
| */ |
| kernel_sigismember(&info->pre_syscall_app_sigblocked, i)) { |
| kernel_sigaddset(oset, i); |
| } |
| } |
| } |
| } |
| } |
| |
| void |
| handle_sigsuspend(dcontext_t *dcontext, kernel_sigset_t *set, size_t sigsetsize) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| ASSERT(set != NULL); |
| LOG(THREAD, LOG_ASYNCH, 2, "handle_sigsuspend\n"); |
| info->in_sigsuspend = true; |
| info->app_sigblocked_save = info->app_sigblocked; |
| kernel_sigemptyset(&info->app_sigblocked); |
| for (i = 1; i <= MAX_SIGNUM; i++) { |
| if (EMULATE_SIGMASK(info, i) && kernel_sigismember(set, i)) { |
| kernel_sigaddset(&info->app_sigblocked, i); |
| kernel_sigdelset(set, i); |
| } |
| } |
| #ifdef DEBUG |
| if (d_r_stats->loglevel >= 3 && (d_r_stats->logmask & LOG_ASYNCH) != 0) { |
| LOG(THREAD, LOG_ASYNCH, 3, "in sigsuspend, blocked signals are now:\n"); |
| dump_sigset(dcontext, &info->app_sigblocked); |
| } |
| #endif |
| } |
| |
| /**** utility routines ***********************************************/ |
| #ifdef DEBUG |
| static void |
| dump_sigset(dcontext_t *dcontext, kernel_sigset_t *set) |
| { |
| int sig; |
| for (sig = 1; sig <= MAX_SIGNUM; sig++) { |
| if (kernel_sigismember(set, sig)) |
| LOG(THREAD, LOG_ASYNCH, 1, "\t%d = blocked\n", sig); |
| } |
| } |
| #endif /* DEBUG */ |
| |
| /* PR 205795: to avoid lock problems w/ in_fcache (it grabs a lock, we |
| * could have interrupted someone holding that), we first check |
| * whereami --- if whereami is DR_WHERE_FCACHE we still check the pc |
| * to distinguish generated routines, but at least we're certain |
| * it's not in DR where it could own a lock. |
| * We can't use is_on_dstack() here b/c we need to handle clean call |
| * arg crashes -- which is too bad since checking client dll and DR dll is |
| * not sufficient due to calls to ntdll, libc, or pc being in gencode. |
| */ |
| static bool |
| safe_is_in_fcache(dcontext_t *dcontext, app_pc pc, app_pc xsp) |
| { |
| if (dcontext->whereami != DR_WHERE_FCACHE || |
| IF_CLIENT_INTERFACE(is_in_client_lib(pc) ||) is_in_dynamo_dll(pc) || |
| is_on_initstack(xsp)) |
| return false; |
| /* Reasonably certain not in DR code, so no locks should be held */ |
| return in_fcache(pc); |
| } |
| |
| static bool |
| safe_is_in_coarse_stubs(dcontext_t *dcontext, app_pc pc, app_pc xsp) |
| { |
| if (dcontext->whereami != DR_WHERE_FCACHE || |
| IF_CLIENT_INTERFACE(is_in_client_lib(pc) ||) is_in_dynamo_dll(pc) || |
| is_on_initstack(xsp)) |
| return false; |
| /* Reasonably certain not in DR code, so no locks should be held */ |
| return in_coarse_stubs(pc); |
| } |
| |
| static bool |
| is_on_alt_stack(dcontext_t *dcontext, byte *sp) |
| { |
| #ifdef HAVE_SIGALTSTACK |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| return (sp >= (byte *)info->sigstack.ss_sp && |
| /* deliberate equality check since stacks often init to top */ |
| sp <= (byte *)(info->sigstack.ss_sp + info->sigstack.ss_size)); |
| #else |
| return false; |
| #endif |
| } |
| |
| /* The caller must initialize ucxt, including its fpstate pointer for x86 Linux. */ |
| static void |
| sig_full_initialize(sig_full_cxt_t *sc_full, kernel_ucontext_t *ucxt) |
| { |
| sc_full->sc = SIGCXT_FROM_UCXT(ucxt); |
| #ifdef X86 |
| sc_full->fp_simd_state = NULL; /* we have a ptr inside sigcontext_t */ |
| #elif defined(ARM) |
| sc_full->fp_simd_state = &ucxt->coproc.uc_vfp; |
| #elif defined(AARCH64) |
| sc_full->fp_simd_state = &ucxt->uc_mcontext.__reserved; |
| #else |
| ASSERT_NOT_IMPLEMENTED(false); |
| #endif |
| } |
| |
| void |
| sigcontext_to_mcontext(priv_mcontext_t *mc, sig_full_cxt_t *sc_full, |
| dr_mcontext_flags_t flags) |
| { |
| sigcontext_t *sc = sc_full->sc; |
| ASSERT(mc != NULL && sc != NULL); |
| #ifdef X86 |
| if (TEST(DR_MC_INTEGER, flags)) { |
| mc->xax = sc->SC_XAX; |
| mc->xbx = sc->SC_XBX; |
| mc->xcx = sc->SC_XCX; |
| mc->xdx = sc->SC_XDX; |
| mc->xsi = sc->SC_XSI; |
| mc->xdi = sc->SC_XDI; |
| mc->xbp = sc->SC_XBP; |
| # ifdef X64 |
| mc->r8 = sc->SC_FIELD(r8); |
| mc->r9 = sc->SC_FIELD(r9); |
| mc->r10 = sc->SC_FIELD(r10); |
| mc->r11 = sc->SC_FIELD(r11); |
| mc->r12 = sc->SC_FIELD(r12); |
| mc->r13 = sc->SC_FIELD(r13); |
| mc->r14 = sc->SC_FIELD(r14); |
| mc->r15 = sc->SC_FIELD(r15); |
| # endif /* X64 */ |
| } |
| if (TEST(DR_MC_CONTROL, flags)) { |
| mc->xsp = sc->SC_XSP; |
| mc->xflags = sc->SC_XFLAGS; |
| mc->pc = (app_pc)sc->SC_XIP; |
| } |
| #elif defined(AARCH64) |
| if (TEST(DR_MC_INTEGER, flags)) |
| memcpy(&mc->r0, &sc->SC_FIELD(regs[0]), sizeof(mc->r0) * 31); |
| if (TEST(DR_MC_CONTROL, flags)) { |
| /* XXX i#2710: the link register should be under DR_MC_CONTROL */ |
| mc->sp = sc->SC_FIELD(sp); |
| mc->pc = (void *)sc->SC_FIELD(pc); |
| mc->nzcv = sc->SC_FIELD(pstate); |
| } |
| #elif defined(ARM) |
| if (TEST(DR_MC_INTEGER, flags)) { |
| mc->r0 = sc->SC_FIELD(arm_r0); |
| mc->r1 = sc->SC_FIELD(arm_r1); |
| mc->r2 = sc->SC_FIELD(arm_r2); |
| mc->r3 = sc->SC_FIELD(arm_r3); |
| mc->r4 = sc->SC_FIELD(arm_r4); |
| mc->r5 = sc->SC_FIELD(arm_r5); |
| mc->r6 = sc->SC_FIELD(arm_r6); |
| mc->r7 = sc->SC_FIELD(arm_r7); |
| mc->r8 = sc->SC_FIELD(arm_r8); |
| mc->r9 = sc->SC_FIELD(arm_r9); |
| mc->r10 = sc->SC_FIELD(arm_r10); |
| mc->r11 = sc->SC_FIELD(arm_fp); |
| mc->r12 = sc->SC_FIELD(arm_ip); |
| /* XXX i#2710: the link register should be under DR_MC_CONTROL */ |
| mc->r14 = sc->SC_FIELD(arm_lr); |
| } |
| if (TEST(DR_MC_CONTROL, flags)) { |
| mc->r13 = sc->SC_FIELD(arm_sp); |
| mc->r15 = sc->SC_FIELD(arm_pc); |
| mc->cpsr = sc->SC_FIELD(arm_cpsr); |
| } |
| # ifdef X64 |
| # error NYI on AArch64 |
| # endif /* X64 */ |
| #endif /* X86/ARM */ |
| if (TEST(DR_MC_MULTIMEDIA, flags)) |
| sigcontext_to_mcontext_simd(mc, sc_full); |
| } |
| |
| /* Note that unlike mcontext_to_context(), this routine does not fill in |
| * any state that is not present in the mcontext: in particular, it assumes |
| * the sigcontext already contains the native fpstate. If the caller |
| * is generating a synthetic sigcontext, the caller should call |
| * save_fpstate() before calling this routine. |
| */ |
| /* XXX: on ARM, sigreturn needs the T bit set in the sigcontext_t cpsr field in |
| * order to return to Thumb mode. But, our mcontext doesn't have the T bit (b/c |
| * usermode can't read it). Thus callers must either modify an mcontext |
| * obtained from sigcontext_to_mcontext() or must call set_pc_mode_in_cpsr() in |
| * order to create a proper sigcontext for sigreturn. All callers here do so. |
| * The only external non-Windows caller of thread_set_mcontext() is |
| * translate_from_synchall_to_dispatch() who first does a thread_get_mcontext(); |
| * thread_get_mcontext() sets the dcontext isa_mode from cpsr, and thread_set_mcontext() |
| * sets it. |
| */ |
| void |
| mcontext_to_sigcontext(sig_full_cxt_t *sc_full, priv_mcontext_t *mc, |
| dr_mcontext_flags_t flags) |
| { |
| sigcontext_t *sc = sc_full->sc; |
| ASSERT(mc != NULL && sc != NULL); |
| #ifdef X86 |
| if (TEST(DR_MC_INTEGER, flags)) { |
| sc->SC_XAX = mc->xax; |
| sc->SC_XBX = mc->xbx; |
| sc->SC_XCX = mc->xcx; |
| sc->SC_XDX = mc->xdx; |
| sc->SC_XSI = mc->xsi; |
| sc->SC_XDI = mc->xdi; |
| sc->SC_XBP = mc->xbp; |
| # ifdef X64 |
| sc->SC_FIELD(r8) = mc->r8; |
| sc->SC_FIELD(r9) = mc->r9; |
| sc->SC_FIELD(r10) = mc->r10; |
| sc->SC_FIELD(r11) = mc->r11; |
| sc->SC_FIELD(r12) = mc->r12; |
| sc->SC_FIELD(r13) = mc->r13; |
| sc->SC_FIELD(r14) = mc->r14; |
| sc->SC_FIELD(r15) = mc->r15; |
| # endif /* X64 */ |
| } |
| if (TEST(DR_MC_CONTROL, flags)) { |
| sc->SC_XSP = mc->xsp; |
| sc->SC_XFLAGS = mc->xflags; |
| sc->SC_XIP = (ptr_uint_t)mc->pc; |
| } |
| #elif defined(AARCH64) |
| if (TEST(DR_MC_INTEGER, flags)) { |
| memcpy(&sc->SC_FIELD(regs[0]), &mc->r0, sizeof(mc->r0) * 31); |
| } |
| if (TEST(DR_MC_CONTROL, flags)) { |
| /* XXX i#2710: the link register should be under DR_MC_CONTROL */ |
| sc->SC_FIELD(sp) = mc->sp; |
| sc->SC_FIELD(pc) = (ptr_uint_t)mc->pc; |
| sc->SC_FIELD(pstate) = mc->nzcv; |
| } |
| #elif defined(ARM) |
| if (TEST(DR_MC_INTEGER, flags)) { |
| sc->SC_FIELD(arm_r0) = mc->r0; |
| sc->SC_FIELD(arm_r1) = mc->r1; |
| sc->SC_FIELD(arm_r2) = mc->r2; |
| sc->SC_FIELD(arm_r3) = mc->r3; |
| sc->SC_FIELD(arm_r4) = mc->r4; |
| sc->SC_FIELD(arm_r5) = mc->r5; |
| sc->SC_FIELD(arm_r6) = mc->r6; |
| sc->SC_FIELD(arm_r7) = mc->r7; |
| sc->SC_FIELD(arm_r8) = mc->r8; |
| sc->SC_FIELD(arm_r9) = mc->r9; |
| sc->SC_FIELD(arm_r10) = mc->r10; |
| sc->SC_FIELD(arm_fp) = mc->r11; |
| sc->SC_FIELD(arm_ip) = mc->r12; |
| /* XXX i#2710: the link register should be under DR_MC_CONTROL */ |
| sc->SC_FIELD(arm_lr) = mc->r14; |
| } |
| if (TEST(DR_MC_CONTROL, flags)) { |
| sc->SC_FIELD(arm_sp) = mc->r13; |
| sc->SC_FIELD(arm_pc) = mc->r15; |
| sc->SC_FIELD(arm_cpsr) = mc->cpsr; |
| } |
| # ifdef X64 |
| # error NYI on AArch64 |
| # endif /* X64 */ |
| #endif /* X86/ARM */ |
| if (TEST(DR_MC_MULTIMEDIA, flags)) |
| mcontext_to_sigcontext_simd(sc_full, mc); |
| } |
| |
| static void |
| ucontext_to_mcontext(priv_mcontext_t *mc, kernel_ucontext_t *uc) |
| { |
| sig_full_cxt_t sc_full; |
| sig_full_initialize(&sc_full, uc); |
| sigcontext_to_mcontext(mc, &sc_full, DR_MC_ALL); |
| } |
| |
| static void |
| mcontext_to_ucontext(kernel_ucontext_t *uc, priv_mcontext_t *mc) |
| { |
| sig_full_cxt_t sc_full; |
| sig_full_initialize(&sc_full, uc); |
| mcontext_to_sigcontext(&sc_full, mc, DR_MC_ALL); |
| } |
| |
| #ifdef AARCHXX |
| static void |
| set_sigcxt_stolen_reg(sigcontext_t *sc, reg_t val) |
| { |
| *(&sc->SC_R0 + (dr_reg_stolen - DR_REG_R0)) = val; |
| } |
| |
| static reg_t |
| get_sigcxt_stolen_reg(sigcontext_t *sc) |
| { |
| return *(&sc->SC_R0 + (dr_reg_stolen - DR_REG_R0)); |
| } |
| |
| # ifndef AARCH64 |
| static dr_isa_mode_t |
| get_pc_mode_from_cpsr(sigcontext_t *sc) |
| { |
| return TEST(EFLAGS_T, sc->SC_XFLAGS) ? DR_ISA_ARM_THUMB : DR_ISA_ARM_A32; |
| } |
| |
| dr_isa_mode_t |
| get_sigcontext_isa_mode(sig_full_cxt_t *sc_full) |
| { |
| return get_pc_mode_from_cpsr(sc_full->sc); |
| } |
| |
| static void |
| set_pc_mode_in_cpsr(sigcontext_t *sc, dr_isa_mode_t isa_mode) |
| { |
| if (isa_mode == DR_ISA_ARM_THUMB) |
| sc->SC_XFLAGS |= EFLAGS_T; |
| else |
| sc->SC_XFLAGS &= ~EFLAGS_T; |
| } |
| |
| void |
| set_sigcontext_isa_mode(sig_full_cxt_t *sc_full, dr_isa_mode_t isa_mode) |
| { |
| set_pc_mode_in_cpsr(sc_full->sc, isa_mode); |
| } |
| # endif |
| #endif |
| |
| /* Returns whether successful. If avoid_failure, tries to translate |
| * at least pc if not successful. Pass f if known. |
| */ |
| static bool |
| translate_sigcontext(dcontext_t *dcontext, kernel_ucontext_t *uc, bool avoid_failure, |
| fragment_t *f) |
| { |
| bool success = false; |
| priv_mcontext_t mcontext; |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); |
| |
| ucontext_to_mcontext(&mcontext, uc); |
| /* FIXME: if cannot find exact match, we're in trouble! |
| * probably ok to delay, since that indicates not a synchronous |
| * signal. |
| */ |
| /* FIXME : in_fcache() (called by recreate_app_state) grabs fcache |
| * fcache_unit_areas.lock, we could deadlock! Also on initexit_lock |
| * == PR 205795/1317 |
| */ |
| /* For safe recreation we need to either be couldbelinking or hold the |
| * initexit lock (to keep someone from flushing current fragment), the |
| * initexit lock is easier |
| */ |
| d_r_mutex_lock(&thread_initexit_lock); |
| /* PR 214962: we assume we're going to relocate to this stored context, |
| * so we restore memory now |
| */ |
| if (translate_mcontext(dcontext->thread_record, &mcontext, true /*restore memory*/, |
| f)) { |
| mcontext_to_ucontext(uc, &mcontext); |
| success = true; |
| } else { |
| if (avoid_failure) { |
| ASSERT_NOT_REACHED(); /* Raise a visible debug error: sthg is wrong. */ |
| /* FIXME : what to do? reg state might be wrong at least get pc */ |
| if (safe_is_in_fcache(dcontext, (cache_pc)sc->SC_XIP, (app_pc)sc->SC_XSP)) { |
| sc->SC_XIP = (ptr_uint_t)recreate_app_pc(dcontext, mcontext.pc, f); |
| ASSERT(sc->SC_XIP != (ptr_uint_t)NULL); |
| } else { |
| /* FIXME : can't even get pc right, what do we do here? */ |
| sc->SC_XIP = 0; |
| } |
| } |
| } |
| d_r_mutex_unlock(&thread_initexit_lock); |
| |
| /* FIXME i#2095: restore the app's segment register value(s). */ |
| |
| LOG(THREAD, LOG_ASYNCH, 3, |
| "\ttranslate_sigcontext: just set frame's eip to " PFX "\n", sc->SC_XIP); |
| return success; |
| } |
| |
| /* Takes an os-specific context */ |
| void |
| thread_set_self_context(void *cxt) |
| { |
| #ifdef X86 |
| if (!INTERNAL_OPTION(use_sigreturn_setcontext)) { |
| sigcontext_t *sc = (sigcontext_t *)cxt; |
| dr_jmp_buf_t buf; |
| buf.xbx = sc->SC_XBX; |
| buf.xcx = sc->SC_XCX; |
| buf.xdi = sc->SC_XDI; |
| buf.xsi = sc->SC_XSI; |
| buf.xbp = sc->SC_XBP; |
| /* XXX: this is not fully transparent: it assumes the target stack |
| * is valid and that we can clobber the slot beyond TOS. |
| * Using this instead of sigreturn is meant mainly as a diagnostic |
| * to help debug future issues with sigreturn (xref i#2080). |
| */ |
| buf.xsp = sc->SC_XSP - XSP_SZ; /* extra slot for retaddr */ |
| buf.xip = sc->SC_XIP; |
| # ifdef X64 |
| buf.r8 = sc->SC_R8; |
| buf.r9 = sc->SC_R9; |
| buf.r10 = sc->SC_R10; |
| buf.r11 = sc->SC_R11; |
| buf.r12 = sc->SC_R12; |
| buf.r13 = sc->SC_R13; |
| buf.r14 = sc->SC_R14; |
| buf.r15 = sc->SC_R15; |
| # endif |
| dr_longjmp(&buf, sc->SC_XAX); |
| return; |
| } |
| #endif |
| |
| /* Unlike Windows we can't say "only set this subset of the |
| * full machine state", so we need to get the rest of the state, |
| */ |
| sigframe_rt_t frame; |
| #if defined(LINUX) || defined(DEBUG) |
| sigcontext_t *sc = (sigcontext_t *)cxt; |
| #endif |
| app_pc xsp_for_sigreturn; |
| #ifdef VMX86_SERVER |
| ASSERT_NOT_IMPLEMENTED(false); /* PR 405694: can't use regular sigreturn! */ |
| #endif |
| memset(&frame, 0, sizeof(frame)); |
| #if defined(X86) |
| dcontext_t *dcontext = get_thread_private_dcontext(); |
| #endif |
| #ifdef LINUX |
| # ifdef X86 |
| byte *xstate = get_and_initialize_xstate_buffer(dcontext); |
| frame.uc.uc_mcontext.fpstate = &((kernel_xstate_t *)xstate)->fpstate; |
| # endif /* X86 */ |
| frame.uc.uc_mcontext = *sc; |
| #endif |
| IF_ARM(ASSERT_NOT_TESTED()); |
| #if defined(X86) |
| save_fpstate(dcontext, &frame); |
| #endif |
| /* The kernel calls do_sigaltstack on sys_rt_sigreturn primarily to ensure |
| * the frame is ok, but the side effect is we can mess up our own altstack |
| * settings if we're not careful. Having invalid ss_size looks good for |
| * kernel 2.6.23.9 at least so we leave frame.uc.uc_stack as all zeros. |
| */ |
| /* make sure sigreturn's mask setting doesn't change anything */ |
| sigprocmask_syscall(SIG_SETMASK, NULL, (kernel_sigset_t *)&frame.uc.uc_sigmask, |
| sizeof(frame.uc.uc_sigmask)); |
| LOG(THREAD_GET, LOG_ASYNCH, 2, "thread_set_self_context: pc=" PFX "\n", sc->SC_XIP); |
| LOG(THREAD_GET, LOG_ASYNCH, 3, "full sigcontext\n"); |
| DOLOG(LOG_ASYNCH, 3, { |
| dcontext_t *dc = get_thread_private_dcontext(); |
| dump_sigcontext(dc, get_sigcontext_from_rt_frame(&frame)); |
| }); |
| /* set up xsp to point at &frame */ |
| /* For x86 only, we need to skip pretcode while setting xsp. */ |
| xsp_for_sigreturn = ((app_pc)&frame)IF_X86(+sizeof(char *)); |
| #ifdef DR_HOST_NOT_TARGET |
| ASSERT_NOT_REACHED(); |
| #elif defined(X86) |
| asm("mov %0, %%" ASM_XSP : : "m"(xsp_for_sigreturn)); |
| # ifdef MACOS |
| ASSERT_NOT_IMPLEMENTED(false && "need to pass 2 params to SYS_sigreturn"); |
| asm("jmp _dynamorio_sigreturn"); |
| # else |
| /* i#2632: recent clang for 32-bit annoyingly won't do the right thing for |
| * "jmp dynamorio_sigreturn" and leaves relocs so we ensure it's PIC: |
| */ |
| void (*asm_jmp_tgt)() = dynamorio_sigreturn; |
| asm("mov %0, %%" ASM_XCX : : "m"(asm_jmp_tgt)); |
| asm("jmp *%" ASM_XCX); |
| # endif /* MACOS/LINUX */ |
| #elif defined(AARCH64) |
| asm("mov " ASM_XSP ", %0" : : "r"(xsp_for_sigreturn)); |
| asm("b dynamorio_sigreturn"); |
| #elif defined(ARM) |
| asm("ldr " ASM_XSP ", %0" : : "m"(xsp_for_sigreturn)); |
| asm("b dynamorio_sigreturn"); |
| #endif /* X86/AARCH64/ARM */ |
| ASSERT_NOT_REACHED(); |
| } |
| |
| static void |
| thread_set_segment_registers(sigcontext_t *sc) |
| { |
| #ifdef X86 |
| /* Fill in the segment registers */ |
| __asm__ __volatile__("mov %%cs, %%ax; mov %%ax, %0" |
| : "=m"(sc->SC_FIELD(cs)) |
| : |
| : "eax"); |
| # ifndef X64 |
| __asm__ __volatile__("mov %%ss, %%ax; mov %%ax, %0" |
| : "=m"(sc->SC_FIELD(ss)) |
| : |
| : "eax"); |
| __asm__ __volatile__("mov %%ds, %%ax; mov %%ax, %0" |
| : "=m"(sc->SC_FIELD(ds)) |
| : |
| : "eax"); |
| __asm__ __volatile__("mov %%es, %%ax; mov %%ax, %0" |
| : "=m"(sc->SC_FIELD(es)) |
| : |
| : "eax"); |
| # endif |
| __asm__ __volatile__("mov %%fs, %%ax; mov %%ax, %0" |
| : "=m"(sc->SC_FIELD(fs)) |
| : |
| : "eax"); |
| __asm__ __volatile__("mov %%gs, %%ax; mov %%ax, %0" |
| : "=m"(sc->SC_FIELD(gs)) |
| : |
| : "eax"); |
| #endif |
| } |
| |
| /* Takes a priv_mcontext_t */ |
| void |
| thread_set_self_mcontext(priv_mcontext_t *mc) |
| { |
| kernel_ucontext_t ucxt; |
| sig_full_cxt_t sc_full; |
| sig_full_initialize(&sc_full, &ucxt); |
| #if defined(LINUX) && defined(X86) |
| sc_full.sc->fpstate = NULL; /* for mcontext_to_sigcontext */ |
| #endif |
| mcontext_to_sigcontext(&sc_full, mc, DR_MC_ALL); |
| thread_set_segment_registers(sc_full.sc); |
| /* sigreturn takes the mode from cpsr */ |
| IF_ARM( |
| set_pc_mode_in_cpsr(sc_full.sc, dr_get_isa_mode(get_thread_private_dcontext()))); |
| /* thread_set_self_context will fill in the real fp/simd state for x86 */ |
| thread_set_self_context((void *)sc_full.sc); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| #ifdef LINUX |
| static bool |
| sig_has_restorer(thread_sig_info_t *info, int sig) |
| { |
| # ifdef VMX86_SERVER |
| /* vmkernel ignores SA_RESTORER (PR 405694) */ |
| return false; |
| # endif |
| if (info->app_sigaction[sig] == NULL) |
| return false; |
| if (TEST(SA_RESTORER, info->app_sigaction[sig]->flags)) |
| return true; |
| if (info->app_sigaction[sig]->restorer == NULL) |
| return false; |
| /* we cache the result due to the safe_read cost */ |
| if (info->restorer_valid[sig] == -1) { |
| /* With older kernels, don't seem to need flag: if sa_restorer != |
| * NULL kernel will use it. But with newer kernels that's not |
| * true, and sometimes libc does pass non-NULL. |
| */ |
| # ifdef X86 |
| /* Signal restorer code for Ubuntu 7.04: |
| * 0xffffe420 <__kernel_sigreturn+0>: pop %eax |
| * 0xffffe421 <__kernel_sigreturn+1>: mov $0x77,%eax |
| * 0xffffe426 <__kernel_sigreturn+6>: int $0x80 |
| * |
| * 0xffffe440 <__kernel_rt_sigreturn+0>: mov $0xad,%eax |
| * 0xffffe445 <__kernel_rt_sigreturn+5>: int $0x80 |
| */ |
| static const byte SIGRET_NONRT[8] = { 0x58, 0xb8, 0x77, 0x00, |
| 0x00, 0x00, 0xcd, 0x80 }; |
| static const byte SIGRET_RT[8] = { 0xb8, 0xad, 0x00, 0x00, 0x00, 0xcd, 0x80 }; |
| # elif defined(ARM) |
| static const byte SIGRET_NONRT[8] = { 0x77, 0x70, 0xa0, 0xe3, |
| 0x00, 0x00, 0x00, 0xef }; |
| static const byte SIGRET_RT[8] = { |
| 0xad, 0x70, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0xef |
| }; |
| # elif defined(AARCH64) |
| static const byte SIGRET_NONRT[8] = { 0 }; /* unused */ |
| static const byte SIGRET_RT[8] = |
| /* FIXME i#1569: untested */ |
| /* mov w8, #139 ; svc #0 */ |
| { 0x68, 0x11, 0x80, 0x52, 0x01, 0x00, 0x00, 0xd4 }; |
| # endif |
| byte buf[MAX(sizeof(SIGRET_NONRT), sizeof(SIGRET_RT))] = { 0 }; |
| if (d_r_safe_read(info->app_sigaction[sig]->restorer, sizeof(buf), buf) && |
| ((IS_RT_FOR_APP(info, sig) && |
| memcmp(buf, SIGRET_RT, sizeof(SIGRET_RT)) == 0) || |
| (!IS_RT_FOR_APP(info, sig) && |
| memcmp(buf, SIGRET_NONRT, sizeof(SIGRET_NONRT)) == 0))) { |
| LOG(THREAD_GET, LOG_ASYNCH, 2, |
| "sig_has_restorer %d: " PFX " looks like restorer, using w/o flag\n", sig, |
| info->app_sigaction[sig]->restorer); |
| info->restorer_valid[sig] = 1; |
| } else |
| info->restorer_valid[sig] = 0; |
| } |
| return (info->restorer_valid[sig] == 1); |
| } |
| #endif |
| |
| /* Returns the size of the frame for delivering to the app. |
| * For x64 this does NOT include kernel_fpstate_t. |
| */ |
| static uint |
| get_app_frame_size(thread_sig_info_t *info, int sig) |
| { |
| if (IS_RT_FOR_APP(info, sig)) |
| return sizeof(sigframe_rt_t); |
| #ifdef LINUX |
| else |
| return sizeof(sigframe_plain_t); |
| #endif |
| } |
| |
| static kernel_ucontext_t * |
| get_ucontext_from_rt_frame(sigframe_rt_t *frame) |
| { |
| #if defined(MACOS) && !defined(X64) |
| /* Padding makes it unsafe to access uc on frame from kernel */ |
| return frame->puc; |
| #else |
| return &frame->uc; |
| #endif |
| } |
| |
| sigcontext_t * |
| get_sigcontext_from_rt_frame(sigframe_rt_t *frame) |
| { |
| return SIGCXT_FROM_UCXT(get_ucontext_from_rt_frame(frame)); |
| } |
| |
| static sigcontext_t * |
| get_sigcontext_from_app_frame(thread_sig_info_t *info, int sig, void *frame) |
| { |
| sigcontext_t *sc = NULL; /* initialize to satisfy Mac clang */ |
| bool rtframe = IS_RT_FOR_APP(info, sig); |
| if (rtframe) |
| sc = get_sigcontext_from_rt_frame((sigframe_rt_t *)frame); |
| #ifdef LINUX |
| else { |
| # ifdef X86 |
| sc = (sigcontext_t *)&(((sigframe_plain_t *)frame)->sc); |
| # elif defined(ARM) |
| sc = SIGCXT_FROM_UCXT(&(((sigframe_plain_t *)frame)->uc)); |
| # else |
| ASSERT_NOT_REACHED(); |
| # endif |
| } |
| #endif |
| return sc; |
| } |
| |
| static sigcontext_t * |
| get_sigcontext_from_pending(thread_sig_info_t *info, int sig) |
| { |
| ASSERT(info->sigpending[sig] != NULL); |
| return get_sigcontext_from_rt_frame(&info->sigpending[sig]->rt_frame); |
| } |
| |
| /* Returns the address on the appropriate signal stack where we should copy |
| * the frame. |
| * If frame is NULL, assumes signal happened while in DR and has been delayed, |
| * and thus we need to provide fpstate regardless of whether the original |
| * had it. If frame is non-NULL, matches frame's amount of fpstate. |
| * dcontext can be NULL if frame is non-NULL. |
| */ |
| static byte * |
| get_sigstack_frame_ptr(dcontext_t *dcontext, thread_sig_info_t *info, int sig, |
| sigframe_rt_t *frame) |
| { |
| ASSERT(dcontext != NULL || frame != NULL); |
| sigcontext_t *sc = (frame == NULL) ? get_sigcontext_from_pending(info, sig) |
| : get_sigcontext_from_rt_frame(frame); |
| byte *sp; |
| |
| if (frame != NULL) { |
| /* Signal happened natively or while in the cache: grab interrupted xsp. */ |
| sp = (byte *)sc->SC_XSP; |
| LOG(THREAD, LOG_ASYNCH, 3, "get_sigstack_frame_ptr: using frame's xsp " PFX "\n", |
| sp); |
| } else { |
| /* signal happened while in DR, use stored xsp */ |
| sp = (byte *)get_mcontext(dcontext)->xsp; |
| LOG(THREAD, LOG_ASYNCH, 3, "get_sigstack_frame_ptr: using app xsp " PFX "\n", sp); |
| } |
| |
| if (USE_APP_SIGSTACK(info, sig)) { |
| /* app has own signal stack which is enabled for this handler */ |
| LOG(THREAD, LOG_ASYNCH, 3, "get_sigstack_frame_ptr: app has own stack " PFX "\n", |
| info->app_sigstack.ss_sp); |
| LOG(THREAD, LOG_ASYNCH, 3, "\tcur sp=" PFX " vs app stack " PFX "-" PFX "\n", sp, |
| info->app_sigstack.ss_sp, |
| info->app_sigstack.ss_sp + info->app_sigstack.ss_size); |
| if (sp > (byte *)info->app_sigstack.ss_sp && |
| sp - (byte *)info->app_sigstack.ss_sp < info->app_sigstack.ss_size) { |
| /* we're currently in the alt stack, so use current xsp */ |
| LOG(THREAD, LOG_ASYNCH, 3, |
| "\tinside alt stack, so using current xsp " PFX "\n", sp); |
| } else { |
| /* need to go to top, stack grows down */ |
| sp = info->app_sigstack.ss_sp + info->app_sigstack.ss_size; |
| LOG(THREAD, LOG_ASYNCH, 3, |
| "\tnot inside alt stack, so using base xsp " PFX "\n", sp); |
| } |
| } |
| |
| if (frame != NULL) { |
| /* Handle DR's frame already being on the app stack. For native delivery we |
| * could try to re-use this frame, but that doesn't work with plain vs rt. |
| * Instead we move below and live with the downsides of a potential stack |
| * overflow. |
| */ |
| size_t frame_sz_max = sizeof(sigframe_rt_t) + REDZONE_SIZE + |
| IF_LINUX(IF_X86((sc->fpstate == NULL ? 0 |
| : signal_frame_extra_size( |
| true)) +)) 64 /* align + slop */; |
| if (sp > (byte *)frame && sp < (byte *)frame + frame_sz_max) { |
| sp = (byte *)frame; |
| /* We're probably *on* the stack right where we want to put the frame! */ |
| byte *cur_sp; |
| GET_STACK_PTR(cur_sp); |
| /* We have to copy the frame below our own stack usage high watermark |
| * in the rest of the code we'll execute from execute_native_handler(). |
| * Kind of a mess. For now we estimate two pages which should be plenty |
| * and still not be egregious usage for most app stacks. For the altstack |
| * we check the size below. |
| */ |
| #define EXECUTE_NATIVE_STACK_USAGE (4096 * 2) |
| if (sp > cur_sp && sp - frame_sz_max - EXECUTE_NATIVE_STACK_USAGE < cur_sp) { |
| sp = cur_sp - frame_sz_max - EXECUTE_NATIVE_STACK_USAGE; |
| } |
| if (USE_APP_SIGSTACK(info, sig)) { |
| #define APP_ALTSTACK_USAGE (SIGSTKSZ / 2) |
| if (sp - APP_ALTSTACK_USAGE < (byte *)info->app_sigstack.ss_sp) { |
| /* There's not enough stack space. The only solution would be to |
| * re-use DR's frame and limit our own stack usage here, which |
| * gets complex. We bail. |
| */ |
| report_unhandleable_signal_and_exit( |
| sig, "sigaltstack too small in native thread"); |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| LOG(THREAD, LOG_ASYNCH, 3, |
| "get_sigstack_frame_ptr: moving beyond same-stack frame to %p\n", sp); |
| } |
| } |
| |
| /* now get frame pointer: need to go down to first field of frame */ |
| sp -= get_app_frame_size(info, sig); |
| #if defined(LINUX) && defined(X86) |
| if (frame == NULL) { |
| /* XXX i#641: we always include space for full xstate, |
| * even if we don't use it all, which does not match what the |
| * kernel does, but we're not tracking app actions to know whether |
| * we can skip lazy fpstate on the delay |
| */ |
| sp -= signal_frame_extra_size(true); |
| } else { |
| if (sc->fpstate != NULL) { |
| /* The kernel doesn't seem to lazily include avx, so we don't either, |
| * which simplifies all our frame copying: if YMM_ENABLED() and the |
| * fpstate pointer is non-NULL, then we assume there's space for |
| * full xstate |
| */ |
| sp -= signal_frame_extra_size(true); |
| DOCHECK(1, { |
| ASSERT(YMM_ENABLED() || !ZMM_ENABLED()); |
| if (YMM_ENABLED()) { |
| ASSERT_CURIOSITY(sc->fpstate->sw_reserved.magic1 == FP_XSTATE_MAGIC1); |
| ASSERT(sc->fpstate->sw_reserved.extended_size <= |
| signal_frame_extra_size(true)); |
| } |
| }); |
| } |
| } |
| #endif /* LINUX && X86 */ |
| /* PR 369907: don't forget the redzone */ |
| sp -= REDZONE_SIZE; |
| |
| /* Align to 16-bytes. The kernel does this for both 32 and 64-bit code |
| * these days, so we do as well. |
| */ |
| sp = (byte *)ALIGN_BACKWARD(sp, 16); |
| IF_X86(sp -= sizeof(reg_t)); /* Model retaddr. */ |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "\tplacing frame at " PFX "\n", sp); |
| return sp; |
| } |
| |
| #if defined(LINUX) && !defined(X64) |
| static void |
| convert_rt_mask_to_nonrt(sigframe_plain_t *f_plain, kernel_sigset_t *sigmask) |
| { |
| # ifdef X86 |
| f_plain->sc.oldmask = sigmask->sig[0]; |
| memcpy(&f_plain->extramask, &sigmask->sig[1], (_NSIG_WORDS - 1) * sizeof(uint)); |
| # elif defined(ARM) |
| f_plain->uc.uc_mcontext.oldmask = sigmask->sig[0]; |
| memcpy(&f_plain->uc.sigset_ex, &sigmask->sig[1], (_NSIG_WORDS - 1) * sizeof(uint)); |
| # else |
| # error NYI |
| # endif |
| } |
| |
| static void |
| convert_frame_to_nonrt(dcontext_t *dcontext, int sig, sigframe_rt_t *f_old, |
| sigframe_plain_t *f_new) |
| { |
| # ifdef X86 |
| sigcontext_t *sc_old = get_sigcontext_from_rt_frame(f_old); |
| f_new->pretcode = f_old->pretcode; |
| f_new->sig = f_old->sig; |
| memcpy(&f_new->sc, get_sigcontext_from_rt_frame(f_old), sizeof(sigcontext_t)); |
| if (sc_old->fpstate != NULL) { |
| /* up to caller to include enough space for fpstate at end */ |
| byte *new_fpstate = |
| (byte *)ALIGN_FORWARD(((byte *)f_new) + sizeof(*f_new), XSTATE_ALIGNMENT); |
| memcpy(new_fpstate, sc_old->fpstate, signal_frame_extra_size(false)); |
| f_new->sc.fpstate = (kernel_fpstate_t *)new_fpstate; |
| } |
| convert_rt_mask_to_nonrt(f_new, &f_old->uc.uc_sigmask); |
| memcpy(&f_new->retcode, &f_old->retcode, RETCODE_SIZE); |
| /* now fill in our extra field */ |
| f_new->sig_noclobber = f_new->sig; |
| # elif defined(ARM) |
| memcpy(&f_new->uc, &f_old->uc, sizeof(f_new->uc)); |
| memcpy(f_new->retcode, f_old->retcode, sizeof(f_new->retcode)); |
| /* now fill in our extra field */ |
| f_new->sig_noclobber = f_old->info.si_signo; |
| # endif /* X86 */ |
| LOG(THREAD, LOG_ASYNCH, 3, "\tconverted sig=%d rt frame to non-rt frame\n", |
| f_new->sig_noclobber); |
| } |
| #endif |
| |
| /* Exported for call from master_signal_handler asm routine. |
| * For the rt signal frame f_old that was copied to f_new, updates |
| * the intra-frame absolute pointers to point to the new addresses |
| * in f_new. |
| * Only updates the pretcode to the stored app restorer if for_app. |
| * dcontext can be NULL (which is why we take in info). |
| */ |
| static void |
| fixup_rtframe_pointers(dcontext_t *dcontext, thread_sig_info_t *info, int sig, |
| sigframe_rt_t *f_old, sigframe_rt_t *f_new, bool for_app, |
| size_t f_new_alloc_size) |
| { |
| ASSERT(info != NULL); |
| #if defined(X86) && defined(LINUX) |
| bool has_restorer = sig_has_restorer(info, sig); |
| # ifdef DEBUG |
| uint level = 3; |
| # if !defined(HAVE_MEMINFO) |
| /* avoid logging every single TRY probe fault */ |
| if (!dynamo_initialized) |
| level = 5; |
| # endif |
| # endif |
| |
| if (has_restorer && for_app) |
| f_new->pretcode = (char *)info->app_sigaction[sig]->restorer; |
| else { |
| # ifdef VMX86_SERVER |
| /* PR 404712: skip kernel's restorer code */ |
| if (for_app) |
| f_new->pretcode = (char *)dynamorio_sigreturn; |
| # else |
| # ifdef X64 |
| ASSERT(!for_app || doing_detach); /* detach uses a frame to go native */ |
| # else |
| /* only point at retcode if old one was -- with newer OS, points at |
| * vsyscall page and there is no restorer, yet stack restorer code left |
| * there for gdb compatibility |
| */ |
| if (f_old->pretcode == f_old->retcode) |
| f_new->pretcode = f_new->retcode; |
| /* else, pointing at vsyscall, or we set it to dynamorio_sigreturn in |
| * master_signal_handler |
| */ |
| LOG(THREAD, LOG_ASYNCH, level, "\tleaving pretcode with old value\n"); |
| # endif |
| # endif |
| } |
| # ifndef X64 |
| f_new->pinfo = &(f_new->info); |
| f_new->puc = &(f_new->uc); |
| # endif |
| if (f_old->uc.uc_mcontext.fpstate != NULL) { |
| uint frame_size = get_app_frame_size(info, sig); |
| byte *frame_end = ((byte *)f_new) + frame_size; |
| byte *tgt = (byte *)ALIGN_FORWARD(frame_end, XSTATE_ALIGNMENT); |
| size_t extra_size = signal_frame_extra_size(false); |
| ASSERT(extra_size == f_new->uc.uc_mcontext.fpstate->sw_reserved.extended_size); |
| /* XXX: It may be better to move this into memcpy_rt_frame (or another |
| * routine, since copy_frame_to_pending() wants them separate), since |
| * fixup_rtframe_pointers() was originally meant to just update a few |
| * pointers and not do a big memcpy. Compare to MACOS which calls it in |
| * copy_frame_to_pending(): if Linux did that we'd have memory clobbering. |
| */ |
| ASSERT(tgt + extra_size <= (byte *)f_new + f_new_alloc_size); |
| memcpy(tgt, f_new->uc.uc_mcontext.fpstate, extra_size); |
| f_new->uc.uc_mcontext.fpstate = (kernel_fpstate_t *)tgt; |
| LOG(THREAD, LOG_ASYNCH, level + 1, "\tfpstate old=" PFX " new=" PFX "\n", |
| f_old->uc.uc_mcontext.fpstate, f_new->uc.uc_mcontext.fpstate); |
| /* Be sure we're keeping both magic words. Failure to do so causes the |
| * kernel to only restore x87 and SSE state and zero out all AVX+ state |
| * (i#3812). |
| */ |
| ASSERT(f_new->uc.uc_mcontext.fpstate->sw_reserved.magic1 == |
| f_old->uc.uc_mcontext.fpstate->sw_reserved.magic1); |
| ASSERT((f_new->uc.uc_mcontext.fpstate->sw_reserved.magic1 == 0 && |
| f_new->uc.uc_mcontext.fpstate->sw_reserved.extended_size == |
| sizeof(kernel_fpstate_t)) || |
| (f_new->uc.uc_mcontext.fpstate->sw_reserved.magic1 == FP_XSTATE_MAGIC1 && |
| *(int *)((byte *)f_new->uc.uc_mcontext.fpstate + |
| f_new->uc.uc_mcontext.fpstate->sw_reserved.extended_size - |
| FP_XSTATE_MAGIC2_SIZE) == FP_XSTATE_MAGIC2)); |
| } else { |
| /* if fpstate is not set up, we're delivering signal immediately, |
| * and we shouldn't need an fpstate since DR code won't modify it; |
| * only if we delayed will we need it, and when delaying we make |
| * room and set up the pointer in copy_frame_to_pending. |
| * xref i#641. |
| */ |
| LOG(THREAD, LOG_ASYNCH, level + 1, "\tno fpstate needed\n"); |
| } |
| LOG(THREAD, LOG_ASYNCH, level, "\tretaddr = " PFX "\n", f_new->pretcode); |
| # ifdef RETURN_AFTER_CALL |
| info->signal_restorer_retaddr = (app_pc)f_new->pretcode; |
| # endif |
| /* 32-bit kernel copies to aligned buf first */ |
| IF_X64(ASSERT(ALIGNED(f_new->uc.uc_mcontext.fpstate, 16))); |
| #elif defined(MACOS) |
| # ifdef X64 |
| /* Nothing to do. */ |
| # else |
| f_new->pinfo = &(f_new->info); |
| f_new->puc = &(f_new->uc); |
| f_new->puc->uc_mcontext = |
| (IF_X64_ELSE(_STRUCT_MCONTEXT64, _STRUCT_MCONTEXT32) *)&f_new->mc; |
| LOG(THREAD, LOG_ASYNCH, 3, "\tf_new=" PFX ", &handler=" PFX "\n", f_new, |
| &f_new->handler); |
| ASSERT(!for_app || ALIGNED(&f_new->handler, 16)); |
| # endif |
| #endif /* X86 && LINUX */ |
| } |
| |
| /* Only operates on rt frames, so call before converting to plain. |
| * Must be called *after* translating the sigcontext. |
| */ |
| static void |
| fixup_siginfo(dcontext_t *dcontext, int sig, sigframe_rt_t *frame) |
| { |
| /* For some signals, si_addr is a PC which we must translate. */ |
| if (sig != SIGILL && sig != SIGTRAP && sig != SIGFPE) |
| return; /* nothing to do */ |
| sigcontext_t *sc = get_sigcontext_from_rt_frame(frame); |
| kernel_siginfo_t *siginfo = SIGINFO_FROM_RT_FRAME(frame); |
| LOG(THREAD, LOG_ASYNCH, 3, "%s: updating si_addr from " PFX " to " PFX "\n", |
| __FUNCTION__, siginfo->si_addr, sc->SC_XIP); |
| siginfo->si_addr = (void *)sc->SC_XIP; |
| #ifdef LINUX |
| siginfo->si_addr_lsb = sc->SC_XIP & 0x1; |
| #endif |
| } |
| |
| static void |
| memcpy_rt_frame(sigframe_rt_t *frame, byte *dst, bool from_pending) |
| { |
| #if defined(MACOS) && !defined(X64) |
| if (!from_pending) { |
| /* The kernel puts padding in the middle. We collapse that padding here |
| * and re-align when we copy to the app stack. |
| * We should not reference fields from mc onward in what the kernel put |
| * on the stack, as our sigframe_rt_t layout does not match the kernel's |
| * variable mid-struct padding. |
| */ |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(frame->puc); |
| memcpy(dst, frame, offsetof(sigframe_rt_t, puc) + sizeof(frame->puc)); |
| memcpy(&((sigframe_rt_t *)dst)->mc, sc, |
| sizeof(sigframe_rt_t) - offsetof(sigframe_rt_t, mc)); |
| return; |
| } |
| #endif |
| /* Ensure no overlap. */ |
| ASSERT(dst >= (byte *)(frame + 1) + signal_frame_extra_size(true) || |
| dst + sizeof(sigframe_rt_t) + signal_frame_extra_size(true) <= (byte *)frame); |
| memcpy(dst, frame, sizeof(sigframe_rt_t)); |
| } |
| |
| /* Copies frame to sp. |
| * PR 304708: we now leave in rt form right up until we copy to the |
| * app stack, so that we can deliver to a client at a safe spot |
| * in rt form, so this routine now converts to a plain frame if necessary. |
| * If no restorer, touches up pretcode |
| * (and if rt_frame, touches up pinfo and puc) |
| * Also touches up fpstate pointer |
| * dcontext can be NULL (which is why we take in info) in which case no check |
| * versus executable memory is performed (we assume this is a native frame). |
| */ |
| static void |
| copy_frame_to_stack(dcontext_t *dcontext, thread_sig_info_t *info, int sig, |
| sigframe_rt_t *frame, byte *sp, bool from_pending) |
| { |
| bool rtframe = IS_RT_FOR_APP(info, sig); |
| uint frame_size = get_app_frame_size(info, sig); |
| #if defined(LINUX) && defined(X86_32) |
| bool has_restorer = sig_has_restorer(info, sig); |
| #endif |
| byte *flush_pc; |
| bool stack_unwritable = false; |
| uint size = frame_size; |
| #if defined(LINUX) && defined(X86) |
| sigcontext_t *sc = get_sigcontext_from_rt_frame(frame); |
| size += (sc->fpstate == NULL ? 0 : signal_frame_extra_size(true)); |
| #endif /* LINUX && X86 */ |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "copy_frame_to_stack: rt=%d, src=" PFX ", sp=" PFX "\n", |
| rtframe, frame, sp); |
| fixup_siginfo(dcontext, sig, frame); |
| |
| /* We avoid querying memory as it incurs global contended locks. */ |
| flush_pc = |
| dcontext == NULL ? false : is_executable_area_writable_overlap(sp, sp + size); |
| if (flush_pc != NULL) { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "\tcopy_frame_to_stack: part of stack is unwritable-by-us @" PFX "\n", |
| flush_pc); |
| flush_fragments_and_remove_region(dcontext, flush_pc, sp + size - flush_pc, |
| false /* don't own initexit_lock */, |
| false /* keep futures */); |
| } |
| ASSERT(!rtframe || frame_size == sizeof(*frame)); |
| if (dcontext == NULL) { |
| /* We have no safe-read mechanism so we do the best we can: blindly copy. */ |
| if (rtframe) |
| memcpy_rt_frame(frame, sp, from_pending); |
| IF_NOT_X64(IF_LINUX( |
| else convert_frame_to_nonrt(dcontext, sig, frame, (sigframe_plain_t *)sp);)); |
| } else { |
| TRY_EXCEPT(dcontext, /* try */ |
| { |
| if (rtframe) |
| memcpy_rt_frame(frame, sp, from_pending); |
| IF_NOT_X64( |
| IF_LINUX(else convert_frame_to_nonrt( |
| dcontext, sig, frame, (sigframe_plain_t *)sp);)); |
| }, |
| /* except */ { stack_unwritable = true; }); |
| } |
| if (stack_unwritable) { |
| /* Override the no-nested check in record_pending_signal(): it's ok b/c |
| * receive_pending_signal() calls to here at a consistent point, |
| * and we won't return there. |
| */ |
| info->nested_pending_ok = true; |
| /* Just throw away this signal and deliver SIGSEGV instead with the |
| * same sigcontext, like the kernel does. |
| */ |
| free_pending_signal(info, sig); |
| os_forge_exception(0, UNREADABLE_MEMORY_EXECUTION_EXCEPTION); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| kernel_sigset_t *mask_to_restore = NULL; |
| if (info->pre_syscall_app_sigprocmask_valid) { |
| mask_to_restore = &info->pre_syscall_app_sigprocmask; |
| info->pre_syscall_app_sigprocmask_valid = false; |
| } else { |
| mask_to_restore = &info->app_sigblocked; |
| } |
| |
| /* if !has_restorer we do NOT add the restorer code to the exec list here, |
| * to avoid removal problems (if handler never returns) and consistency problems |
| * (would have to mark as selfmod right now if on stack). |
| * for PROGRAM_SHEPHERDING we recognize as a pattern, and for consistency we |
| * allow entire region once try to execute -- not a performance worry since should |
| * very rarely be on the stack: should either be libc restorer code or with recent |
| * OS in rx vsyscall page. |
| */ |
| |
| /* fix up pretcode, pinfo, puc, fpstate */ |
| if (rtframe) { |
| sigframe_rt_t *f_new = (sigframe_rt_t *)sp; |
| fixup_rtframe_pointers(dcontext, info, sig, frame, f_new, true /*for app*/, size); |
| #ifdef HAVE_SIGALTSTACK |
| /* Make sure the frame's sigstack reflects the app stack, both for transparency |
| * of the app examining it and for correctness if we detach mid-handler. |
| */ |
| LOG(THREAD, LOG_ASYNCH, 3, "updated uc_stack @" PFX " to " PFX "\n", |
| &f_new->uc.uc_stack, info->app_sigstack.ss_sp); |
| f_new->uc.uc_stack = info->app_sigstack; |
| #endif |
| |
| /* Store the prior mask, for restoring in sigreturn. */ |
| memcpy(&f_new->uc.uc_sigmask, mask_to_restore, sizeof(info->app_sigblocked)); |
| } else { |
| #ifdef X64 |
| ASSERT_NOT_REACHED(); |
| #endif |
| #if defined(LINUX) && !defined(X64) |
| sigframe_plain_t *f_new = (sigframe_plain_t *)sp; |
| # ifdef X86 |
| # ifndef VMX86_SERVER |
| sigframe_plain_t *f_old = (sigframe_plain_t *)frame; |
| # endif |
| if (has_restorer) |
| f_new->pretcode = (char *)info->app_sigaction[sig]->restorer; |
| else { |
| # ifdef VMX86_SERVER |
| /* PR 404712: skip kernel's restorer code */ |
| f_new->pretcode = (char *)dynamorio_nonrt_sigreturn; |
| # else |
| /* see comments in rt case above */ |
| if (f_old->pretcode == f_old->retcode) |
| f_new->pretcode = f_new->retcode; |
| else { |
| /* whether we set to dynamorio_sigreturn in master_signal_handler |
| * or it's still vsyscall page, we have to convert to non-rt |
| */ |
| f_new->pretcode = (char *)dynamorio_nonrt_sigreturn; |
| } /* else, pointing at vsyscall most likely */ |
| LOG(THREAD, LOG_ASYNCH, 3, "\tleaving pretcode with old value\n"); |
| # endif |
| } |
| /* convert_frame_to_nonrt*() should have updated fpstate pointer. |
| * The inlined fpstate is no longer used on new kernels, and we do that |
| * as well on older kernels. |
| */ |
| ASSERT(f_new->sc.fpstate != &f_new->fpstate); |
| /* 32-bit kernel copies to aligned buf so no assert on fpstate alignment */ |
| LOG(THREAD, LOG_ASYNCH, 3, "\tretaddr = " PFX "\n", f_new->pretcode); |
| /* There is no stored alt stack in a plain frame to update. */ |
| # ifdef RETURN_AFTER_CALL |
| info->signal_restorer_retaddr = (app_pc)f_new->pretcode; |
| # endif |
| # endif /* X86 */ |
| /* Store the prior mask, for restoring in sigreturn. */ |
| convert_rt_mask_to_nonrt(f_new, mask_to_restore); |
| #endif /* LINUX && !X64 */ |
| } |
| |
| #if defined(MACOS) && !defined(X64) |
| /* Update handler field, which is passed to the libc trampoline, to app */ |
| ASSERT(info->app_sigaction[sig] != NULL); |
| ((sigframe_rt_t *)sp)->handler = (app_pc)info->app_sigaction[sig]->handler; |
| #endif |
| } |
| |
| /* Copies frame to pending slot. |
| * PR 304708: we now leave in rt form right up until we copy to the |
| * app stack, so that we can deliver to a client at a safe spot |
| * in rt form. |
| */ |
| static void |
| copy_frame_to_pending(dcontext_t *dcontext, int sig, |
| sigframe_rt_t *frame _IF_CLIENT(byte *access_address)) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| sigframe_rt_t *dst = &(info->sigpending[sig]->rt_frame); |
| memcpy_rt_frame(frame, (byte *)dst, false /*!already pending*/); |
| |
| #if defined(LINUX) && defined(X86) |
| /* For lazy fpstate, it's possible there was no fpstate when the kernel |
| * sent us the frame, but in between then and now the app executed some |
| * fp or xmm/ymm instrs. Today we always add fpstate just in case. |
| * XXX i#641 optimization: track whether any fp/xmm/ymm |
| * instrs happened and avoid this. |
| */ |
| /* we'll fill in updated fpstate at delivery time, but we go ahead and |
| * copy now in case our own retrieval somehow misses some fields |
| */ |
| if (frame->uc.uc_mcontext.fpstate != NULL) { |
| size_t extra_size = signal_frame_extra_size(false); |
| ASSERT(extra_size == frame->uc.uc_mcontext.fpstate->sw_reserved.extended_size); |
| memcpy(&info->sigpending[sig]->xstate, frame->uc.uc_mcontext.fpstate, extra_size); |
| } |
| /* we must set the pointer now so that later save_fpstate, etc. work */ |
| dst->uc.uc_mcontext.fpstate = (kernel_fpstate_t *)&info->sigpending[sig]->xstate; |
| #endif /* LINUX && X86 */ |
| |
| #ifdef CLIENT_INTERFACE |
| info->sigpending[sig]->access_address = access_address; |
| #endif |
| info->sigpending[sig]->use_sigcontext = false; |
| |
| #ifdef MACOS |
| /* We rely on puc to find sc to we have to fix it up */ |
| fixup_rtframe_pointers(dcontext, info, sig, frame, dst, false /*!for app*/, |
| sizeof(*dst)); |
| #endif |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "copy_frame_to_pending from " PFX "\n", frame); |
| DOLOG(3, LOG_ASYNCH, { |
| LOG(THREAD, LOG_ASYNCH, 3, "sigcontext:\n"); |
| dump_sigcontext(dcontext, get_sigcontext_from_rt_frame(dst)); |
| }); |
| } |
| |
| /**** real work ***********************************************/ |
| |
| /* transfer control from signal handler to fcache return routine */ |
| static void |
| transfer_from_sig_handler_to_fcache_return(dcontext_t *dcontext, kernel_ucontext_t *uc, |
| sigcontext_t *sc_interrupted, int sig, |
| app_pc next_pc, linkstub_t *last_exit, |
| bool is_kernel_xfer) |
| { |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); |
| #ifdef CLIENT_INTERFACE |
| if (is_kernel_xfer) { |
| sig_full_cxt_t sc_interrupted_full = { sc_interrupted, NULL /*not provided*/ }; |
| sig_full_cxt_t sc_full; |
| sig_full_initialize(&sc_full, uc); |
| sc->SC_XIP = (ptr_uint_t)next_pc; |
| /* i#4041: Provide the actually-interrupted mid-rseq PC to this event. */ |
| ptr_uint_t official_xl8 = sc_interrupted->SC_XIP; |
| sc_interrupted->SC_XIP = |
| (ptr_uint_t)translate_last_direct_translation(dcontext, (app_pc)official_xl8); |
| if (instrument_kernel_xfer(dcontext, DR_XFER_SIGNAL_DELIVERY, sc_interrupted_full, |
| NULL, NULL, next_pc, sc->SC_XSP, sc_full, NULL, sig)) |
| next_pc = (app_pc)sc->SC_XIP; |
| sc_interrupted->SC_XIP = official_xl8; |
| } |
| #endif |
| dcontext->next_tag = canonicalize_pc_target(dcontext, next_pc); |
| IF_ARM(dr_set_isa_mode(dcontext, get_pc_mode_from_cpsr(sc), NULL)); |
| |
| /* Set our sigreturn context to point to fcache_return! |
| * Then we'll go back through kernel, appear in fcache_return, |
| * and go through d_r_dispatch & interp, without messing up dynamo stack. |
| * Note that even if this is a write in the shared cache, we |
| * still go to the private fcache_return for simplicity. |
| */ |
| sc->SC_XIP = (ptr_uint_t)fcache_return_routine(dcontext); |
| #ifdef AARCHXX |
| /* We do not have to set dr_reg_stolen in dcontext's mcontext here |
| * because dcontext's mcontext is stale and we used the mcontext |
| * created from recreate_app_state_internal with the original sigcontext. |
| */ |
| /* We restore dr_reg_stolen's app value in recreate_app_state_internal, |
| * so now we need set dr_reg_stolen to hold DR's TLS before sigreturn |
| * from DR's handler. |
| */ |
| ASSERT(get_sigcxt_stolen_reg(sc) != (reg_t)*get_dr_tls_base_addr()); |
| /* Preserve the translated value. */ |
| dcontext->local_state->spill_space.reg_stolen = get_sigcxt_stolen_reg(sc); |
| /* Now put DR's base in the sigcontext. */ |
| set_sigcxt_stolen_reg(sc, (reg_t)*get_dr_tls_base_addr()); |
| # ifndef AARCH64 |
| /* We're going to our fcache_return gencode which uses DEFAULT_ISA_MODE */ |
| set_pc_mode_in_cpsr(sc, DEFAULT_ISA_MODE); |
| # endif |
| #endif |
| |
| #if defined(X64) || defined(ARM) |
| /* x64 always uses shared gencode */ |
| dcontext->local_state->spill_space.IF_X86_ELSE(xax, r0) = |
| sc->IF_X86_ELSE(SC_XAX, SC_R0); |
| # ifdef AARCH64 |
| /* X1 needs to be spilled because of br x1 in exit stubs. */ |
| dcontext->local_state->spill_space.r1 = sc->SC_R1; |
| # endif |
| #else |
| get_mcontext(dcontext)->IF_X86_ELSE(xax, r0) = sc->IF_X86_ELSE(SC_XAX, SC_R0); |
| #endif |
| LOG(THREAD, LOG_ASYNCH, 2, "\tsaved xax " PFX "\n", sc->IF_X86_ELSE(SC_XAX, SC_R0)); |
| |
| sc->IF_X86_ELSE(SC_XAX, SC_R0) = (ptr_uint_t)last_exit; |
| LOG(THREAD, LOG_ASYNCH, 2, "\tset next_tag to " PFX ", resuming in fcache_return\n", |
| next_pc); |
| LOG(THREAD, LOG_ASYNCH, 3, "transfer_from_sig_handler_to_fcache_return\n"); |
| DOLOG(3, LOG_ASYNCH, { |
| LOG(THREAD, LOG_ASYNCH, 3, "sigcontext @" PFX ":\n", sc); |
| dump_sigcontext(dcontext, sc); |
| }); |
| } |
| |
| static void |
| restore_orig_sigcontext(sigframe_rt_t *frame, sigcontext_t *sc_orig) |
| { |
| ASSERT(frame != NULL && sc_orig != NULL); |
| |
| sigcontext_t *sc = get_sigcontext_from_rt_frame(frame); |
| #if defined(X86) && defined(LINUX) |
| kernel_fpstate_t *fpstate = sc->fpstate; |
| if (fpstate != NULL) { |
| ASSERT(sc_orig->fpstate != NULL); |
| *fpstate = *sc_orig->fpstate; |
| sc_orig->fpstate = fpstate; |
| } |
| #endif |
| *sc = *sc_orig; |
| } |
| |
| #ifdef CLIENT_INTERFACE |
| static dr_signal_action_t |
| send_signal_to_client(dcontext_t *dcontext, int sig, sigframe_rt_t *frame, |
| sigcontext_t *raw_sc, byte *access_address, bool blocked, |
| fragment_t *fragment) |
| { |
| kernel_ucontext_t *uc = get_ucontext_from_rt_frame(frame); |
| dr_siginfo_t si; |
| dr_signal_action_t action; |
| sig_full_cxt_t raw_sc_full; |
| sig_full_initialize(&raw_sc_full, uc); |
| raw_sc_full.sc = raw_sc; |
| if (!dr_signal_hook_exists()) |
| return DR_SIGNAL_DELIVER; |
| LOG(THREAD, LOG_ASYNCH, 2, "sending signal to client\n"); |
| si.sig = sig; |
| si.drcontext = (void *)dcontext; |
| /* It's safe to allocate since we do not send signals that interrupt DR. |
| * With priv_mcontext_t x2 that's a little big for stack alloc. |
| */ |
| si.mcontext = heap_alloc(dcontext, sizeof(*si.mcontext) HEAPACCT(ACCT_OTHER)); |
| si.raw_mcontext = heap_alloc(dcontext, sizeof(*si.raw_mcontext) HEAPACCT(ACCT_OTHER)); |
| dr_mcontext_init(si.mcontext); |
| dr_mcontext_init(si.raw_mcontext); |
| /* i#207: fragment tag and fcache start pc on fault. */ |
| si.fault_fragment_info.tag = NULL; |
| si.fault_fragment_info.cache_start_pc = NULL; |
| /* i#182/PR 449996: we provide the pre-translation context */ |
| if (raw_sc != NULL) { |
| fragment_t wrapper; |
| si.raw_mcontext_valid = true; |
| sigcontext_to_mcontext(dr_mcontext_as_priv_mcontext(si.raw_mcontext), |
| &raw_sc_full, si.raw_mcontext->flags); |
| /* i#207: fragment tag and fcache start pc on fault. */ |
| /* FIXME: we should avoid the fragment_pclookup since it is expensive |
| * and since we already did the work of a lookup when translating |
| */ |
| if (fragment == NULL) |
| fragment = fragment_pclookup(dcontext, si.raw_mcontext->pc, &wrapper); |
| if (fragment != NULL && !hide_tag_from_client(fragment->tag)) { |
| si.fault_fragment_info.tag = fragment->tag; |
| si.fault_fragment_info.cache_start_pc = FCACHE_ENTRY_PC(fragment); |
| si.fault_fragment_info.is_trace = TEST(FRAG_IS_TRACE, fragment->flags); |
| si.fault_fragment_info.app_code_consistent = |
| !TESTANY(FRAG_WAS_DELETED | FRAG_SELFMOD_SANDBOXED, fragment->flags); |
| } |
| } else |
| si.raw_mcontext_valid = false; |
| /* The client has no way to calculate this when using |
| * instrumentation that deliberately faults (to shift a rare event |
| * out of the fastpath) so we provide it. When raw_mcontext is |
| * available the client can calculate it, but we provide it as a |
| * convenience anyway. |
| */ |
| si.access_address = access_address; |
| si.blocked = blocked; |
| ucontext_to_mcontext(dr_mcontext_as_priv_mcontext(si.mcontext), uc); |
| /* We disallow the client calling dr_redirect_execution(), so we |
| * will not leak si |
| */ |
| action = instrument_signal(dcontext, &si); |
| if (action == DR_SIGNAL_DELIVER || action == DR_SIGNAL_REDIRECT) { |
| /* propagate client changes */ |
| CLIENT_ASSERT(si.mcontext->flags == DR_MC_ALL, |
| "signal mcontext flags cannot be changed"); |
| mcontext_to_ucontext(uc, dr_mcontext_as_priv_mcontext(si.mcontext)); |
| } else if (action == DR_SIGNAL_SUPPRESS && raw_sc != NULL) { |
| /* propagate client changes */ |
| CLIENT_ASSERT(si.raw_mcontext->flags == DR_MC_ALL, |
| "signal mcontext flags cannot be changed"); |
| mcontext_to_sigcontext(&raw_sc_full, |
| dr_mcontext_as_priv_mcontext(si.raw_mcontext), |
| si.raw_mcontext->flags); |
| } |
| heap_free(dcontext, si.mcontext, sizeof(*si.mcontext) HEAPACCT(ACCT_OTHER)); |
| heap_free(dcontext, si.raw_mcontext, sizeof(*si.raw_mcontext) HEAPACCT(ACCT_OTHER)); |
| return action; |
| } |
| |
| /* Returns false if caller should exit */ |
| static bool |
| handle_client_action_from_cache(dcontext_t *dcontext, int sig, dr_signal_action_t action, |
| sigframe_rt_t *our_frame, sigcontext_t *sc_orig, |
| sigcontext_t *sc_interrupted, bool blocked) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| kernel_ucontext_t *uc = get_ucontext_from_rt_frame(our_frame); |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); |
| /* in order to pass to the client, we come all the way here for signals |
| * the app has no handler for |
| */ |
| if (action == DR_SIGNAL_REDIRECT) { |
| /* send_signal_to_client copied mcontext into our |
| * master_signal_handler frame, so we set up for fcache_return w/ |
| * our frame's state |
| */ |
| transfer_from_sig_handler_to_fcache_return( |
| dcontext, uc, sc_interrupted, sig, (app_pc)sc->SC_XIP, |
| (linkstub_t *)get_asynch_linkstub(), true); |
| if (is_building_trace(dcontext)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsquashing trace-in-progress\n"); |
| trace_abort(dcontext); |
| } |
| return false; |
| } else if (action == DR_SIGNAL_SUPPRESS || |
| (!blocked && info->app_sigaction[sig] != NULL && |
| info->app_sigaction[sig]->handler == (handler_t)SIG_IGN)) { |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: not delivering!\n", |
| (action == DR_SIGNAL_SUPPRESS) ? "client suppressing signal" |
| : "app signal handler is SIG_IGN"); |
| /* Restore original (untranslated) sc. */ |
| restore_orig_sigcontext(our_frame, sc_orig); |
| return false; |
| } else if (!blocked && /* no BYPASS for blocked */ |
| (action == DR_SIGNAL_BYPASS || |
| (info->app_sigaction[sig] == NULL || |
| info->app_sigaction[sig]->handler == (handler_t)SIG_DFL))) { |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: executing default action\n", |
| (action == DR_SIGNAL_BYPASS) ? "client forcing default" |
| : "app signal handler is SIG_DFL"); |
| if (execute_default_from_cache(dcontext, sig, our_frame, sc_orig, false)) { |
| /* If we haven't terminated, restore original (untranslated) sc |
| * on request. |
| */ |
| restore_orig_sigcontext(our_frame, sc_orig); |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: restored xsp=" PFX ", xip=" PFX "\n", |
| __FUNCTION__, get_sigcontext_from_rt_frame(our_frame)->SC_XSP, |
| get_sigcontext_from_rt_frame(our_frame)->SC_XIP); |
| } |
| return false; |
| } |
| CLIENT_ASSERT(action == DR_SIGNAL_DELIVER, "invalid signal event return value"); |
| return true; |
| } |
| #endif |
| |
| static void |
| abort_on_fault(dcontext_t *dcontext, uint dumpcore_flag, app_pc pc, byte *target, int sig, |
| sigframe_rt_t *frame, const char *prefix, const char *signame, |
| const char *where) |
| { |
| kernel_ucontext_t *ucxt = &frame->uc; |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); |
| bool stack_overflow = (sig == SIGSEGV && is_stack_overflow(dcontext, target)); |
| #if defined(STATIC_LIBRARY) && defined(LINUX) |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| uint orig_dumpcore_flag = dumpcore_flag; |
| if (init_info.app_sigaction != NULL) |
| info = &init_info; /* use init-time handler */ |
| ASSERT(info->app_sigaction != NULL); |
| #endif |
| const char *fmt = "%s %s at PC " PFX "\n" |
| "Received SIG%s at%s pc " PFX " in thread " TIDFMT "\n" |
| "Base: " PFX "\n" |
| "Registers:" |
| #ifdef X86 |
| "eax=" PFX " ebx=" PFX " ecx=" PFX " edx=" PFX "\n" |
| "\tesi=" PFX " edi=" PFX " esp=" PFX " ebp=" PFX "\n" |
| # ifdef X64 |
| "\tr8 =" PFX " r9 =" PFX " r10=" PFX " r11=" PFX "\n" |
| "\tr12=" PFX " r13=" PFX " r14=" PFX " r15=" PFX "\n" |
| # endif /* X64 */ |
| #elif defined(ARM) |
| # ifndef X64 |
| " r0 =" PFX " r1 =" PFX " r2 =" PFX " r3 =" PFX "\n" |
| "\tr4 =" PFX " r5 =" PFX " r6 =" PFX " r7 =" PFX "\n" |
| "\tr8 =" PFX " r9 =" PFX " r10=" PFX " r11=" PFX "\n" |
| "\tr12=" PFX " r13=" PFX " r14=" PFX " r15=" PFX "\n" |
| # else |
| # error NYI on AArch64 |
| # endif |
| #endif /* X86/ARM */ |
| "\teflags=" PFX; |
| |
| #if defined(STATIC_LIBRARY) && defined(LINUX) |
| /* i#2119: if we're invoking an app handler, disable a fatal coredump. */ |
| if (INTERNAL_OPTION(invoke_app_on_crash) && info->app_sigaction[sig] != NULL && |
| IS_RT_FOR_APP(info, sig) && TEST(dumpcore_flag, DYNAMO_OPTION(dumpcore_mask)) && |
| !DYNAMO_OPTION(live_dump)) |
| dumpcore_flag = 0; |
| #endif |
| |
| report_dynamorio_problem( |
| dcontext, dumpcore_flag | (stack_overflow ? DUMPCORE_STACK_OVERFLOW : 0), pc, |
| (app_pc)sc->SC_FP, fmt, prefix, stack_overflow ? STACK_OVERFLOW_NAME : CRASH_NAME, |
| pc, signame, where, pc, d_r_get_thread_id(), get_dynamorio_dll_start(), |
| #ifdef X86 |
| sc->SC_XAX, sc->SC_XBX, sc->SC_XCX, sc->SC_XDX, sc->SC_XSI, sc->SC_XDI, |
| sc->SC_XSP, sc->SC_XBP, |
| # ifdef X64 |
| sc->SC_FIELD(r8), sc->SC_FIELD(r9), sc->SC_FIELD(r10), sc->SC_FIELD(r11), |
| sc->SC_FIELD(r12), sc->SC_FIELD(r13), sc->SC_FIELD(r14), sc->SC_FIELD(r15), |
| # endif /* X86 */ |
| #elif defined(ARM) |
| # ifndef X64 |
| sc->SC_FIELD(arm_r0), sc->SC_FIELD(arm_r1), sc->SC_FIELD(arm_r2), |
| sc->SC_FIELD(arm_r3), sc->SC_FIELD(arm_r4), sc->SC_FIELD(arm_r5), |
| sc->SC_FIELD(arm_r6), sc->SC_FIELD(arm_r7), sc->SC_FIELD(arm_r8), |
| sc->SC_FIELD(arm_r9), sc->SC_FIELD(arm_r10), sc->SC_FIELD(arm_fp), |
| sc->SC_FIELD(arm_ip), sc->SC_FIELD(arm_sp), sc->SC_FIELD(arm_lr), |
| sc->SC_FIELD(arm_pc), |
| # else |
| # error NYI on AArch64 |
| # endif /* X64 */ |
| #endif /* X86/ARM */ |
| sc->SC_XFLAGS); |
| |
| #if defined(STATIC_LIBRARY) && defined(LINUX) |
| /* i#2119: For static DR, the surrounding app's handler may well be |
| * safe to invoke even when DR state is messed up: it's worth a try, as it |
| * likely has useful reporting features for users of the app. |
| * We limit to Linux and RT for simplicity: it can be expanded later if static |
| * library use expands. |
| */ |
| if (INTERNAL_OPTION(invoke_app_on_crash) && info->app_sigaction[sig] != NULL && |
| IS_RT_FOR_APP(info, sig)) { |
| SYSLOG(SYSLOG_WARNING, INVOKING_APP_HANDLER, 2, get_application_name(), |
| get_application_pid()); |
| (*info->app_sigaction[sig]->handler)(sig, &frame->info, ucxt); |
| /* If the app handler didn't terminate, now get a fatal core. */ |
| if (TEST(orig_dumpcore_flag, DYNAMO_OPTION(dumpcore_mask)) && |
| !DYNAMO_OPTION(live_dump)) |
| os_dump_core("post-app-handler attempt at core dump"); |
| } |
| #endif |
| |
| os_terminate(dcontext, TERMINATE_PROCESS); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| static void |
| abort_on_DR_fault(dcontext_t *dcontext, app_pc pc, byte *target, int sig, |
| sigframe_rt_t *frame, const char *signame, const char *where) |
| { |
| abort_on_fault(dcontext, DUMPCORE_INTERNAL_EXCEPTION, pc, target, sig, frame, |
| exception_label_core, signame, where); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* Returns whether unlinked or mangled syscall. |
| * Restored in receive_pending_signal. |
| */ |
| static bool |
| unlink_fragment_for_signal(dcontext_t *dcontext, fragment_t *f, |
| byte *pc /*interruption pc*/) |
| { |
| /* We only come here if we interrupted a fragment in the cache, |
| * or interrupted transition gencode (i#2019), |
| * which means that this thread's DR state is safe, and so it |
| * should be ok to acquire a lock. xref PR 596069. |
| * |
| * There is a race where if two threads hit a signal in the same |
| * shared fragment, the first could re-link after the second |
| * un-links but before the second exits, and the second could then |
| * execute the syscall, resulting in arbitrary delay prior to |
| * signal delivery. We don't want to allocate global memory, |
| * but we could use a static array of counters (since should |
| * be small # of interrupted shared fragments at any one time) |
| * used as refcounts so we only unlink when all are done. |
| * Not bothering to implement now: going to live w/ chance of |
| * long signal delays. xref PR 596069. |
| */ |
| bool changed = false; |
| bool waslinking = is_couldbelinking(dcontext); |
| if (!waslinking) |
| enter_couldbelinking(dcontext, NULL, false); |
| /* may not be linked if trace_relink or something */ |
| if (TEST(FRAG_COARSE_GRAIN, f->flags)) { |
| /* XXX PR 213040: we don't support unlinking coarse, so we try |
| * not to come here, but for indirect branch and other spots |
| * where we don't yet support translation (since can't fault) |
| * we end up w/ no bound on delivery... |
| */ |
| } else if (TEST(FRAG_LINKED_OUTGOING, f->flags)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tunlinking outgoing for interrupted F%d\n", f->id); |
| SHARED_FLAGS_RECURSIVE_LOCK(f->flags, acquire, change_linking_lock); |
| // Double-check flags to ensure some other thread didn't unlink |
| // while we waited for the change_linking_lock. |
| if (TEST(FRAG_LINKED_OUTGOING, f->flags)) { |
| unlink_fragment_outgoing(dcontext, f); |
| changed = true; |
| } |
| SHARED_FLAGS_RECURSIVE_LOCK(f->flags, release, change_linking_lock); |
| } else { |
| LOG(THREAD, LOG_ASYNCH, 3, "\toutgoing already unlinked for interrupted F%d\n", |
| f->id); |
| } |
| if (TEST(FRAG_HAS_SYSCALL, f->flags)) { |
| /* Syscalls are signal barriers! |
| * Make sure the next syscall (if any) in f is not executed! |
| * instead go back to d_r_dispatch right before the syscall |
| */ |
| /* syscall mangling does a bunch of decodes but only one write, |
| * changing the target of a short jmp, which is atomic |
| * since a one-byte write, so we don't need the change_linking_lock. |
| */ |
| if (mangle_syscall_code(dcontext, f, pc, false /*do not skip exit cti*/)) |
| changed = true; |
| } |
| if (!waslinking) |
| enter_nolinking(dcontext, NULL, false); |
| return changed; |
| } |
| |
| static void |
| relink_interrupted_fragment(dcontext_t *dcontext, thread_sig_info_t *info) |
| { |
| if (info->interrupted == NULL) |
| return; |
| /* i#2066: if we were building a trace, it may already be re-linked */ |
| if (!TEST(FRAG_LINKED_OUTGOING, info->interrupted->flags)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tre-linking outgoing for interrupted F%d\n", |
| info->interrupted->id); |
| SHARED_FLAGS_RECURSIVE_LOCK(info->interrupted->flags, acquire, |
| change_linking_lock); |
| /* Double-check flags to ensure some other thread didn't link |
| * while we waited for the change_linking_lock. |
| */ |
| if (!TEST(FRAG_LINKED_OUTGOING, info->interrupted->flags)) { |
| link_fragment_outgoing(dcontext, info->interrupted, false); |
| } |
| SHARED_FLAGS_RECURSIVE_LOCK(info->interrupted->flags, release, |
| change_linking_lock); |
| } |
| if (TEST(FRAG_HAS_SYSCALL, info->interrupted->flags)) { |
| /* restore syscall (they're a barrier to signals, so signal |
| * handler has cur frag exit before it does a syscall) |
| */ |
| if (info->interrupted_pc != NULL) { |
| mangle_syscall_code(dcontext, info->interrupted, info->interrupted_pc, |
| true /*skip exit cti*/); |
| } |
| } |
| info->interrupted = NULL; |
| info->interrupted_pc = NULL; |
| } |
| |
| static bool |
| interrupted_inlined_syscall(dcontext_t *dcontext, fragment_t *f, |
| byte *pc /*interruption pc*/) |
| { |
| bool pre_or_post_syscall = false; |
| if (TEST(FRAG_HAS_SYSCALL, f->flags)) { |
| /* PR 596147: if the thread is currently in an inlined |
| * syscall when a signal comes in, we can't delay and bound the |
| * delivery time: we need to deliver now. Should decode |
| * backward and see if syscall. We assume our translation of |
| * the interruption state is fine to re-start: i.e., the syscall |
| * is complete if kernel has pc at post-syscall point, and |
| * kernel set EINTR in eax if necessary. |
| */ |
| /* Interrupted fcache, so ok to alloc memory for decode */ |
| instr_t instr; |
| byte *nxt_pc; |
| instr_init(dcontext, &instr); |
| nxt_pc = decode(dcontext, pc, &instr); |
| if (nxt_pc != NULL && instr_valid(&instr) && instr_is_syscall(&instr)) { |
| /* pre-syscall but post-jmp so can't skip syscall */ |
| pre_or_post_syscall = true; |
| } else { |
| size_t syslen = syscall_instr_length(FRAG_ISA_MODE(f->flags)); |
| instr_reset(dcontext, &instr); |
| nxt_pc = decode(dcontext, pc - syslen, &instr); |
| if (nxt_pc != NULL && instr_valid(&instr) && instr_is_syscall(&instr)) { |
| #if defined(X86) && !defined(MACOS) |
| /* decoding backward so check for exit cti jmp prior |
| * to syscall to ensure no mismatch |
| */ |
| instr_reset(dcontext, &instr); |
| nxt_pc = decode(dcontext, pc - syslen - JMP_LONG_LENGTH, &instr); |
| if (nxt_pc != NULL && instr_valid(&instr) && |
| instr_get_opcode(&instr) == OP_jmp) { |
| /* post-inlined-syscall */ |
| pre_or_post_syscall = true; |
| } |
| #else |
| /* On Mac and ARM we have some TLS spills in between so we just |
| * trust that this is a syscall (esp on ARM w/ aligned instrs). |
| */ |
| pre_or_post_syscall = true; |
| #endif |
| } |
| } |
| instr_free(dcontext, &instr); |
| } |
| return pre_or_post_syscall; |
| } |
| |
| /* i#1145: auto-restart syscalls interrupted by signals */ |
| static bool |
| adjust_syscall_for_restart(dcontext_t *dcontext, thread_sig_info_t *info, int sig, |
| sigcontext_t *sc, fragment_t *f, reg_t orig_retval_reg) |
| { |
| byte *pc = (byte *)sc->SC_XIP; |
| int sys_inst_len; |
| |
| if (sc->IF_X86_ELSE(SC_XAX, SC_R0) != -EINTR) { |
| /* The syscall succeeded, so no reason to interrupt. |
| * Some syscalls succeed on a signal coming in. |
| * E.g., SYS_wait4 on SIGCHLD, or reading from a slow device. |
| * XXX: Now that we pass SA_RESTART we should never get here? |
| */ |
| return false; |
| } |
| /* Don't restart if the app's handler says not to */ |
| if (info->app_sigaction[sig] != NULL && |
| !TEST(SA_RESTART, info->app_sigaction[sig]->flags)) { |
| return false; |
| } |
| /* XXX i#1145: some syscalls are never restarted when interrupted by a signal. |
| * We check those that are simple to distinguish below, but not all are. We have |
| * this under an option so it can be disabled if necessary. |
| */ |
| if (!DYNAMO_OPTION(restart_syscalls)) |
| return false; |
| |
| /* Now that we use SA_RESTART we rely on that and ignore our own |
| * inaccurate check sysnum_is_not_restartable(sysnum). |
| * SA_RESTART also means we can just be passed in the register value to restore. |
| */ |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: restored xax/r0 to %ld\n", __FUNCTION__, |
| orig_retval_reg); |
| #ifdef X86 |
| sc->SC_XAX = orig_retval_reg; |
| #elif defined(AARCHXX) |
| sc->SC_R0 = orig_retval_reg; |
| #else |
| # error NYI |
| #endif |
| |
| /* Now adjust the pc to point at the syscall instruction instead of after it, |
| * so when we resume we'll go back to the syscall. |
| * Adjusting solves transparency as well: natively the kernel adjusts |
| * the pc before setting up the signal frame. |
| * We don't pass in the post-syscall pc provided by the kernel because |
| * we want the app pc, not the raw pc. |
| */ |
| dr_isa_mode_t isa_mode; |
| if (is_after_syscall_address(dcontext, pc) || pc == vsyscall_sysenter_return_pc) { |
| isa_mode = dr_get_isa_mode(dcontext); |
| } else { |
| /* We're going to walk back in the fragment, not gencode */ |
| ASSERT(f != NULL); |
| isa_mode = FRAG_ISA_MODE(f->flags); |
| } |
| sys_inst_len = syscall_instr_length(isa_mode); |
| |
| if (pc == vsyscall_sysenter_return_pc) { |
| #ifdef X86 |
| sc->SC_XIP = (ptr_uint_t)(vsyscall_syscall_end_pc - sys_inst_len); |
| /* To restart sysenter we must re-copy xsp into xbp, as xbp is |
| * clobbered by the kernel. |
| * XXX: The kernel points at the int 0x80 in vsyscall on a restart |
| * and so doesn't have to do this: should we do that too? If so we'll |
| * have to avoid interpreting our own hook which is right after the |
| * int 0x80. |
| */ |
| sc->SC_XBP = sc->SC_XSP; |
| #else |
| ASSERT_NOT_REACHED(); |
| #endif |
| } else if (is_after_syscall_address(dcontext, pc)) { |
| /* We're at do_syscall: point at app syscall instr. We want an app |
| * address b/c this signal will be delayed and the delivery will use |
| * a direct app context: no translation from the cache. |
| * The caller sets info->sigpending[sig]->use_sigcontext for us. |
| */ |
| sc->SC_XIP = (ptr_uint_t)(dcontext->asynch_target - sys_inst_len); |
| DODEBUG({ |
| instr_t instr; |
| dr_isa_mode_t old_mode; |
| dr_set_isa_mode(dcontext, isa_mode, &old_mode); |
| instr_init(dcontext, &instr); |
| ASSERT(decode(dcontext, (app_pc)sc->SC_XIP, &instr) != NULL && |
| instr_is_syscall(&instr)); |
| instr_free(dcontext, &instr); |
| dr_set_isa_mode(dcontext, old_mode, NULL); |
| }); |
| } else { |
| ASSERT_NOT_REACHED(); /* Inlined syscalls no longer come here. */ |
| } |
| #ifdef AARCHXX |
| /* dr_reg_stolen is holding DR's TLS on receiving a signal, |
| * so we need to put the app's reg value into the ucontext instead. |
| * The translation process normally does this for us, but here we're doing |
| * a custom translation. |
| */ |
| set_sigcxt_stolen_reg(sc, dcontext->local_state->spill_space.reg_stolen); |
| #endif |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: sigreturn pc is now " PFX "\n", __FUNCTION__, |
| sc->SC_XIP); |
| return true; |
| } |
| |
| /* XXX: Better to get this code inside arch/ but we'd have to convert to an mcontext |
| * which seems overkill. |
| */ |
| static fragment_t * |
| find_next_fragment_from_gencode(dcontext_t *dcontext, sigcontext_t *sc) |
| { |
| fragment_t *f = NULL; |
| fragment_t wrapper; |
| byte *pc = (byte *)sc->SC_XIP; |
| if (in_clean_call_save(dcontext, pc) || in_clean_call_restore(dcontext, pc)) { |
| #ifdef AARCHXX |
| f = fragment_pclookup(dcontext, (cache_pc)sc->SC_LR, &wrapper); |
| #elif defined(X86) |
| cache_pc retaddr = NULL; |
| /* Get the retaddr. We assume this is the adjustment used by |
| * insert_out_of_line_context_switch(). |
| */ |
| byte *ra_slot = |
| dcontext->dstack - get_clean_call_switch_stack_size() - sizeof(retaddr); |
| /* The extra x86 slot is only there for save. */ |
| if (in_clean_call_save(dcontext, pc)) |
| ra_slot -= get_clean_call_temp_stack_size(); |
| if (d_r_safe_read(ra_slot, sizeof(retaddr), &retaddr)) |
| f = fragment_pclookup(dcontext, retaddr, &wrapper); |
| #else |
| # error Unsupported arch. |
| #endif |
| } else if (in_indirect_branch_lookup_code(dcontext, pc)) { |
| /* Try to find the target if the signal arrived in the IBL. |
| * We could try to be a lot more precise by hardcoding the IBL |
| * sequence here but that would make the code less maintainable. |
| * Instead we try the registers that hold the target app address. |
| */ |
| /* First check for the jmp* on the hit path: that is the only place |
| * in the ibl where the target tag is not sitting in a register. |
| */ |
| #if defined(X86) && defined(X64) |
| /* Optimization for the common case of targeting a prefix on x86_64: |
| * ff 61 08 jmp 0x08(%rcx)[8byte] |
| * The tag is in 0x0(%rcx) so we avoid a decode and pclookup. |
| */ |
| if (*pc == 0xff && *(pc + 1) == 0x61 && *(pc + 2) == 0x08) { |
| f = fragment_lookup(dcontext, *(app_pc *)sc->SC_XCX); |
| } |
| #endif |
| if (f == NULL) { |
| instr_t instr; |
| instr_init(dcontext, &instr); |
| decode_cti(dcontext, pc, &instr); |
| if (instr_is_ibl_hit_jump(&instr)) { |
| priv_mcontext_t mc; |
| sig_full_cxt_t sc_full = { sc, NULL /*not provided*/ }; |
| sigcontext_to_mcontext(&mc, &sc_full, DR_MC_INTEGER | DR_MC_CONTROL); |
| byte *target; |
| if (opnd_is_memory_reference(instr_get_target(&instr))) { |
| target = instr_compute_address_priv(&instr, &mc); |
| ASSERT(target != NULL); |
| if (target != NULL) |
| target = *(byte **)target; |
| } else { |
| ASSERT(opnd_is_reg(instr_get_target(&instr))); |
| target = (byte *)reg_get_value_priv( |
| opnd_get_reg(instr_get_target(&instr)), &mc); |
| } |
| ASSERT(target != NULL); |
| if (target != NULL) |
| f = fragment_pclookup(dcontext, target, &wrapper); |
| /* I tried to hit this case running client.cleancallsig in a loop |
| * and while I could on x86 and x86_64 I never did on ARM or |
| * AArch64. We can remove this once someone hits it and it works. |
| */ |
| IF_AARCHXX(ASSERT_NOT_TESTED()); |
| } |
| instr_free(dcontext, &instr); |
| } |
| #ifdef AARCHXX |
| /* The target is in r2 the whole time, w/ or w/o Thumb LSB. */ |
| if (f == NULL && sc->SC_R2 != 0) |
| f = fragment_lookup(dcontext, ENTRY_PC_TO_DECODE_PC(sc->SC_R2)); |
| #elif defined(X86) |
| /* The target is initially in xcx but is then copied to xbx. */ |
| if (f == NULL && sc->SC_XBX != 0) |
| f = fragment_lookup(dcontext, (app_pc)sc->SC_XBX); |
| if (f == NULL && sc->SC_XCX != 0) |
| f = fragment_lookup(dcontext, (app_pc)sc->SC_XCX); |
| #else |
| # error Unsupported arch. |
| #endif |
| } else { |
| /* If in fcache_enter or do_syscall*, we stored the next_tag in asynch_target |
| * in d_r_dispatch. But, we need to avoid using the asynch_target for the |
| * fragment we just exited if we're in fcache_return. |
| */ |
| if (dcontext->asynch_target != NULL && !in_fcache_return(dcontext, pc)) |
| f = fragment_lookup(dcontext, dcontext->asynch_target); |
| } |
| return f; |
| } |
| |
| static void |
| copy_sigcontext(sigcontext_t *dst_sc, void *dst_fpstate_opaque, sigcontext_t *src_sc) |
| { |
| ASSERT(dst_sc != NULL && src_sc != NULL); |
| *dst_sc = *src_sc; |
| |
| #if defined(X86) && defined(LINUX) |
| kernel_fpstate_t *dst_fpstate = (kernel_fpstate_t *)dst_fpstate_opaque; |
| ASSERT(dst_fpstate != NULL); |
| if (src_sc->fpstate != NULL) { |
| *dst_fpstate = *src_sc->fpstate; |
| dst_sc->fpstate = dst_fpstate; |
| } |
| #endif |
| } |
| |
| static void |
| record_pending_signal(dcontext_t *dcontext, int sig, kernel_ucontext_t *ucxt, |
| sigframe_rt_t *frame, bool forged _IF_CLIENT(byte *access_address)) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| os_thread_data_t *ostd = (os_thread_data_t *)dcontext->os_field; |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); |
| /* We need a fpstate to store original pre-xl8 SIMD values. */ |
| #ifdef X86 |
| kernel_fpstate_t fpstate_orig; |
| #endif |
| sigcontext_t sc_orig; |
| byte *pc = (byte *)sc->SC_XIP; |
| byte *xsp = (byte *)sc->SC_XSP; |
| bool receive_now = false; |
| bool blocked = false; |
| bool handled = false; |
| bool at_auto_restart_syscall = false; |
| int syslen = 0; |
| reg_t orig_retval_reg = sc->IF_X86_ELSE(SC_XAX, SC_R0); |
| sigpending_t *pend; |
| fragment_t *f = NULL; |
| fragment_t wrapper; |
| |
| /* We no longer block SUSPEND_SIGNAL (i#184/PR 450670) or SIGSEGV (i#193/PR 287309). |
| * But we can have re-entrancy issues in this routine if the app uses the same |
| * SUSPEND_SIGNAL, or the nested SIGSEGV needs to be sent to the app. The |
| * latter shouldn't happen unless the app sends SIGSEGV via SYS_kill(). |
| */ |
| if (ostd->processing_signal > 0 || |
| /* If we interrupted receive_pending_signal() we can't prepend a new |
| * pending or delete an old b/c we might mess up the state so we |
| * just drop this one: should only happen for alarm signal |
| */ |
| (((info->accessing_sigpending && !info->nested_pending_ok) || |
| /* It's risky to deliver to an exiting thread: data structs are in unstable |
| * states (i#4438). Just drop if we can. |
| */ |
| dcontext->is_exiting) && |
| /* we do want to report a crash in receive_pending_signal() */ |
| (can_always_delay[sig] || |
| is_sys_kill(dcontext, pc, (byte *)sc->SC_XSP, &frame->info)))) { |
| LOG(THREAD, LOG_ASYNCH, 1, "nested or exit-time signal %d\n", sig); |
| ASSERT(ostd->processing_signal == 0 || sig == SUSPEND_SIGNAL || sig == SIGSEGV); |
| ASSERT(can_always_delay[sig] || |
| is_sys_kill(dcontext, pc, (byte *)sc->SC_XSP, &frame->info)); |
| /* To avoid re-entrant execution of special_heap_alloc() and of |
| * prepending to the pending list we just drop this signal. |
| * FIXME i#194/PR 453996: do better. |
| */ |
| STATS_INC(num_signals_dropped); |
| SYSLOG_INTERNAL_WARNING_ONCE("dropping nested/exit-time signal"); |
| return; |
| } |
| ostd->processing_signal++; /* no need for atomicity: thread-private */ |
| |
| /* First, check whether blocked, before we restore for sigsuspend (i#1340). */ |
| if (kernel_sigismember(&info->app_sigblocked, sig)) |
| blocked = true; |
| |
| if (info->in_sigsuspend) { |
| /* sigsuspend ends when a signal is received, so restore the |
| * old blocked set |
| */ |
| info->app_sigblocked = info->app_sigblocked_save; |
| info->in_sigsuspend = false; |
| /* update the set to restore to post-signal-delivery */ |
| #ifdef MACOS |
| ucxt->uc_sigmask = *(__darwin_sigset_t *)&info->app_sigblocked; |
| #else |
| ucxt->uc_sigmask = info->app_sigblocked; |
| #endif |
| #ifdef DEBUG |
| if (d_r_stats->loglevel >= 3 && (d_r_stats->logmask & LOG_ASYNCH) != 0) { |
| LOG(THREAD, LOG_ASYNCH, 3, "after sigsuspend, blocked signals are now:\n"); |
| dump_sigset(dcontext, &info->app_sigblocked); |
| } |
| #endif |
| } |
| |
| if (get_at_syscall(dcontext)) |
| syslen = syscall_instr_length(dr_get_isa_mode(dcontext)); |
| |
| if (info->app_sigaction[sig] != NULL && |
| info->app_sigaction[sig]->handler == |
| (handler_t)SIG_IGN |
| /* If a client registered a handler, put this in the queue. |
| * Races between registering, queueing, and delivering are fine. |
| */ |
| IF_CLIENT_INTERFACE(&&!dr_signal_hook_exists())) { |
| LOG(THREAD, LOG_ASYNCH, 3, |
| "record_pending_signal (%d at pc " PFX "): action is SIG_IGN!\n", sig, pc); |
| ostd->processing_signal--; |
| return; |
| } else if (blocked) { |
| /* signal is blocked by app, so just record it, don't receive now */ |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "record_pending_signal(%d at pc " PFX "): signal is currently blocked\n", sig, |
| pc); |
| IF_LINUX(handled = notify_signalfd(dcontext, info, sig, frame)); |
| } else if (dcontext->currently_stopped) { |
| /* We have no way to delay for a currently-native thread. */ |
| LOG(THREAD, LOG_ASYNCH, 2, "Going to receive signal natively now\n"); |
| execute_native_handler(dcontext, sig, frame); |
| handled = true; |
| } else if (safe_is_in_fcache(dcontext, pc, xsp)) { |
| LOG(THREAD, LOG_ASYNCH, 2, "record_pending_signal(%d) from cache pc " PFX "\n", |
| sig, pc); |
| if (forged || can_always_delay[sig]) { |
| /* to make translation easier, want to delay if can until d_r_dispatch |
| * unlink cur frag, wait for d_r_dispatch |
| */ |
| /* check for coarse first to avoid cost of coarse pclookup */ |
| if (get_fcache_coarse_info(pc) != NULL) { |
| /* PR 213040: we can't unlink coarse. If we fail to translate |
| * we'll switch back to delaying, below. |
| */ |
| if (sig_is_alarm_signal(sig) && info->sigpending[sig] != NULL && |
| info->sigpending[sig]->next != NULL && info->skip_alarm_xl8 > 0) { |
| /* Translating coarse fragments is very expensive so we |
| * avoid doing it when we're having trouble keeping up w/ |
| * the alarm frequency (PR 213040), but we make sure we try |
| * every once in a while to avoid unbounded signal delay |
| */ |
| info->skip_alarm_xl8--; |
| STATS_INC(num_signals_coarse_delayed); |
| } else { |
| if (sig_is_alarm_signal(sig)) |
| info->skip_alarm_xl8 = SKIP_ALARM_XL8_MAX; |
| receive_now = true; |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "signal interrupted coarse fragment so delivering now\n"); |
| } |
| } else { |
| f = fragment_pclookup(dcontext, pc, &wrapper); |
| ASSERT(f != NULL); |
| ASSERT(!TEST(FRAG_COARSE_GRAIN, f->flags)); /* checked above */ |
| LOG(THREAD, LOG_ASYNCH, 2, "\tdelaying until exit F%d\n", f->id); |
| if (interrupted_inlined_syscall(dcontext, f, pc)) { |
| /* PR 596147: if delayable signal arrives after syscall-skipping |
| * jmp, either at syscall or post-syscall, we deliver |
| * immediately, since we can't bound the delay |
| */ |
| receive_now = true; |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "signal interrupted pre/post syscall itself so delivering now\n"); |
| /* We don't set at_auto_restart_syscall because we just leave |
| * the SA_RESTART kernel-supplied resumption point: with no |
| * post-syscall handler to worry about we have no need to |
| * change anything. |
| */ |
| } else { |
| /* could get another signal but should be in same fragment */ |
| ASSERT(info->interrupted == NULL || info->interrupted == f); |
| if (info->interrupted != f) { |
| /* Just in case there's a prior, avoid leaving it unlinked. */ |
| relink_interrupted_fragment(dcontext, info); |
| if (unlink_fragment_for_signal(dcontext, f, pc)) { |
| info->interrupted = f; |
| info->interrupted_pc = pc; |
| } else { |
| /* either was unlinked for trace creation, or we got another |
| * signal before exiting cache to handle 1st |
| */ |
| ASSERT(info->interrupted == NULL || info->interrupted == f); |
| } |
| } |
| } |
| } |
| } else { |
| /* the signal interrupted code cache => run handler now! */ |
| receive_now = true; |
| LOG(THREAD, LOG_ASYNCH, 2, "\tnot certain can delay so handling now\n"); |
| } |
| } else if (in_generated_routine(dcontext, pc) || |
| /* XXX: should also check fine stubs */ |
| safe_is_in_coarse_stubs(dcontext, pc, xsp)) { |
| /* Assumption: dynamo errors have been caught already inside |
| * the master_signal_handler, thus any error in a generated routine |
| * is an asynch signal that can be delayed |
| */ |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "record_pending_signal(%d) from gen routine or stub " PFX "\n", sig, pc); |
| if (get_at_syscall(dcontext)) { |
| /* i#1206: the syscall was interrupted, so we can go back to d_r_dispatch |
| * and don't need to receive it now (which complicates post-syscall handling) |
| * w/o any extra delay. |
| */ |
| /* i#2659: we now use SA_RESTART to handle interrupting native |
| * auto-restart syscalls. That means we have to adjust do_syscall |
| * interruption to give us control so we can deliver the signal. Due to |
| * needing to run post-syscall handlers (we don't want to get into nested |
| * dcontexts like on Windows) it's simplest to go back to d_r_dispatch, which |
| * is most easily done by emulating the non-SA_RESTART behavior. |
| * XXX: This all seems backward: we should revisit this model and see if |
| * we can get rid of this emulation and the auto-restart emulation. |
| */ |
| /* The get_at_syscall() check above distinguishes from just having |
| * arrived at the syscall instr, but with SA_RESTART we can't distinguish |
| * not-yet-executed-syscall from syscall-was-interrupted-in-the-kernel. |
| * This matters for sigreturn (i#2995), whose asynch_target points somewhere |
| * other than right after the syscall, so we exclude it (it can't be |
| * interrupted so we know we haven't executed it yet). |
| */ |
| if (is_after_syscall_address(dcontext, pc + syslen) && |
| !is_sigreturn_syscall_number(sc->SC_SYSNUM_REG)) { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "Adjusting interrupted auto-restart syscall from " PFX " to " PFX |
| "\n", |
| pc, pc + syslen); |
| at_auto_restart_syscall = true; |
| sc->SC_XIP += syslen; |
| sc->IF_X86_ELSE(SC_XAX, SC_R0) = -EINTR; |
| pc = (byte *)sc->SC_XIP; |
| } |
| } |
| /* This could come from another thread's SYS_kill (via our gen do_syscall) */ |
| DOLOG(1, LOG_ASYNCH, { |
| if (!is_after_syscall_address(dcontext, pc) && !forged && |
| !can_always_delay[sig]) { |
| LOG(THREAD, LOG_ASYNCH, 1, |
| "WARNING: signal %d in gen routine: may cause problems!\n", sig); |
| } |
| }); |
| /* i#2019: for a signal arriving in gencode before entry to a fragment, |
| * we need to unlink the fragment just like for a signal arriving inside |
| * the fragment itself. |
| * Multiple signals should all have the same asynch_target so we should |
| * only need a single info->interrupted. |
| */ |
| if (info->interrupted == NULL && !get_at_syscall(dcontext)) { |
| f = find_next_fragment_from_gencode(dcontext, sc); |
| if (f != NULL && !TEST(FRAG_COARSE_GRAIN, f->flags)) { |
| if (unlink_fragment_for_signal(dcontext, f, FCACHE_ENTRY_PC(f))) { |
| info->interrupted = f; |
| info->interrupted_pc = FCACHE_ENTRY_PC(f); |
| } |
| } |
| } |
| } else if (get_at_syscall(dcontext) && pc == vsyscall_sysenter_return_pc - syslen && |
| /* See i#2995 comment above: rule out sigreturn */ |
| !is_sigreturn_syscall_number(sc->SC_SYSNUM_REG)) { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "record_pending_signal(%d) from restart-vsyscall " PFX "\n", sig, pc); |
| /* While the kernel points at int 0x80 for a restart, we leverage our |
| * existing sysenter restart mechanism. |
| */ |
| at_auto_restart_syscall = true; |
| sc->SC_XIP = (reg_t)vsyscall_sysenter_return_pc; |
| sc->IF_X86_ELSE(SC_XAX, SC_R0) = -EINTR; |
| pc = (byte *)sc->SC_XIP; |
| } else if (pc == vsyscall_sysenter_return_pc) { |
| LOG(THREAD, LOG_ASYNCH, 2, "record_pending_signal(%d) from vsyscall " PFX "\n", |
| sig, pc); |
| /* i#1206: the syscall was interrupted but is not auto-restart, so we can go |
| * back to d_r_dispatch and don't need to receive it now (which complicates |
| * post-syscall handling) |
| */ |
| } else if (thread_synch_check_state(dcontext, THREAD_SYNCH_NO_LOCKS) && |
| /* Avoid grabbing locks for xl8 while in a suspended state (i#3026). */ |
| ksynch_get_value(&ostd->suspended) == 0) { |
| /* The signal interrupted DR or the client but it's at a safe spot so |
| * deliver it now. |
| */ |
| receive_now = true; |
| } else { |
| /* the signal interrupted DR itself => do not run handler now! */ |
| LOG(THREAD, LOG_ASYNCH, 2, "record_pending_signal(%d) from DR at pc " PFX "\n", |
| sig, pc); |
| if (!forged && !can_always_delay[sig] && |
| !is_sys_kill(dcontext, pc, (byte *)sc->SC_XSP, &frame->info)) { |
| /* i#195/PR 453964: don't re-execute if will just re-fault. |
| * Our checks for dstack, etc. in master_signal_handler should |
| * have accounted for everything |
| */ |
| ASSERT_NOT_REACHED(); |
| abort_on_DR_fault(dcontext, pc, NULL, sig, frame, |
| (sig == SIGSEGV) ? "SEGV" : "other", " unknown"); |
| } |
| } |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "\taction is not SIG_IGN\n"); |
| #if defined(X86) && defined(LINUX) |
| LOG(THREAD, LOG_ASYNCH, 3, "\tretaddr = " PFX "\n", |
| frame->pretcode); /* pretcode has same offs for plain */ |
| #endif |
| |
| if (receive_now) { |
| /* we need to translate sc before we know whether client wants to |
| * suppress, so we need a backup copy |
| */ |
| bool xl8_success; |
| |
| ASSERT(!at_auto_restart_syscall); /* only used for delayed delivery */ |
| |
| copy_sigcontext( |
| &sc_orig, IF_X86_ELSE(IF_LINUX_ELSE((void *)&fpstate_orig, NULL), NULL), sc); |
| ASSERT(!forged); |
| /* cache the fragment since pclookup is expensive for coarse (i#658) */ |
| f = fragment_pclookup(dcontext, (cache_pc)sc->SC_XIP, &wrapper); |
| xl8_success = translate_sigcontext(dcontext, ucxt, !can_always_delay[sig], f); |
| |
| if (can_always_delay[sig] && !xl8_success) { |
| /* delay: we expect this for coarse fragments if alarm arrives |
| * in middle of ind branch region or sthg (PR 213040) |
| */ |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "signal is in un-translatable spot in coarse fragment: delaying\n"); |
| receive_now = false; |
| } |
| } |
| |
| if (receive_now) { |
| |
| /* N.B.: since we abandon the old context for synchronous signals, |
| * we do not need to mark this fragment as FRAG_CANNOT_DELETE |
| */ |
| #ifdef DEBUG |
| if (d_r_stats->loglevel >= 2 && (d_r_stats->logmask & LOG_ASYNCH) != 0 && |
| safe_is_in_fcache(dcontext, pc, xsp)) { |
| ASSERT(f != NULL); |
| LOG(THREAD, LOG_ASYNCH, 2, "Got signal at pc " PFX " in this fragment:\n", |
| pc); |
| disassemble_fragment(dcontext, f, false); |
| } |
| #endif |
| |
| LOG(THREAD, LOG_ASYNCH, 2, "Going to receive signal now\n"); |
| /* If we end up executing the default action, we'll go native |
| * since we translated the context. If there's a handler, |
| * we'll copy the context to the app stack and then adjust the |
| * original on our stack so we take over. |
| */ |
| execute_handler_from_cache(dcontext, sig, frame, &sc_orig, |
| f _IF_CLIENT(access_address)); |
| |
| } else if (!handled) { |
| |
| #ifdef CLIENT_INTERFACE |
| /* i#182/PR 449996: must let client act on blocked non-delayable signals to |
| * handle instrumentation faults. Make sure we're at a safe spot: i.e., |
| * only raise for in-cache faults. Checking forged and no-delay |
| * to avoid the in-cache check for delayable signals => safer. |
| */ |
| if (blocked && !forged && !can_always_delay[sig] && |
| safe_is_in_fcache(dcontext, pc, xsp)) { |
| dr_signal_action_t action; |
| /* cache the fragment since pclookup is expensive for coarse (i#658) */ |
| f = fragment_pclookup(dcontext, (cache_pc)sc->SC_XIP, &wrapper); |
| copy_sigcontext(&sc_orig, |
| IF_X86_ELSE(IF_LINUX_ELSE((void *)&fpstate_orig, NULL), NULL), |
| sc); |
| translate_sigcontext(dcontext, ucxt, true /*shouldn't fail*/, f); |
| /* make a copy before send_signal_to_client() tweaks it */ |
| sigcontext_t sc_interrupted = *sc; |
| action = send_signal_to_client(dcontext, sig, frame, &sc_orig, access_address, |
| true /*blocked*/, f); |
| /* For blocked signal early event we disallow BYPASS (xref i#182/PR 449996) */ |
| CLIENT_ASSERT(action != DR_SIGNAL_BYPASS, |
| "cannot bypass a blocked signal event"); |
| if (!handle_client_action_from_cache(dcontext, sig, action, frame, &sc_orig, |
| &sc_interrupted, true /*blocked*/)) { |
| ostd->processing_signal--; |
| return; |
| } |
| /* Restore original (untranslated) sc. */ |
| restore_orig_sigcontext(frame, &sc_orig); |
| } |
| #endif |
| |
| /* i#196/PR 453847: avoid infinite loop of signals if try to re-execute */ |
| if (blocked && !can_always_delay[sig] && |
| !is_sys_kill(dcontext, pc, (byte *)sc->SC_XSP, &frame->info)) { |
| ASSERT(default_action[sig] == DEFAULT_TERMINATE || |
| default_action[sig] == DEFAULT_TERMINATE_CORE); |
| LOG(THREAD, LOG_ASYNCH, 1, |
| "blocked fatal signal %d cannot be delayed: terminating\n", sig); |
| copy_sigcontext(&sc_orig, |
| IF_X86_ELSE(IF_LINUX_ELSE((void *)&fpstate_orig, NULL), NULL), |
| sc); |
| /* If forged we're likely couldbelinking, and we don't need to xl8. */ |
| if (forged) |
| ASSERT(is_couldbelinking(dcontext)); |
| else |
| translate_sigcontext(dcontext, ucxt, true /*shouldn't fail*/, NULL); |
| /* the process should be terminated */ |
| execute_default_from_cache(dcontext, sig, frame, &sc_orig, forged); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* Happened in DR, do not translate context. Record for later processing |
| * at a safe point with a clean app state. |
| */ |
| if (!blocked || sig >= OFFS_RT || (blocked && info->sigpending[sig] == NULL)) { |
| /* only have 1 pending for blocked non-rt signals */ |
| |
| /* to avoid accumulating signals if we're slow in presence of |
| * a high-rate itimer we only keep 2 alarm signals (PR 596768) |
| */ |
| if (sig_is_alarm_signal(sig)) { |
| if (info->sigpending[sig] != NULL && |
| info->sigpending[sig]->next != NULL) { |
| ASSERT(info->sigpending[sig]->next->next == NULL); |
| /* keep the oldest, replace newer w/ brand-new one, for |
| * more spread-out alarms |
| */ |
| sigpending_t *temp = info->sigpending[sig]; |
| info->sigpending[sig] = temp->next; |
| special_heap_free(info->sigheap, temp); |
| info->num_pending--; |
| LOG(THREAD, LOG_ASYNCH, 2, "3rd pending alarm %d => dropping 2nd\n", |
| sig); |
| STATS_INC(num_signals_dropped); |
| SYSLOG_INTERNAL_WARNING_ONCE("dropping 3rd pending alarm signal"); |
| } |
| } |
| /* special heap alloc always uses sizeof(sigpending_t) blocks */ |
| pend = special_heap_alloc(info->sigheap); |
| ASSERT(sig > 0 && sig <= MAX_SIGNUM); |
| info->num_pending++; |
| if (info->num_pending > DYNAMO_OPTION(max_pending_signals) && |
| !info->multiple_pending_units) |
| info->multiple_pending_units = true; |
| if (info->num_pending >= DYNAMO_OPTION(max_pending_signals)) { |
| /* We're at the limit of our special heap: one more and it will try to |
| * allocate a new unit, which is unsafe as it acquires locks. We take |
| * several steps: we notify the user; we check for this on delivery as |
| * well and proactively allocate a new unit in a safer context. |
| * XXX: Perhaps we should drop some signals here? |
| */ |
| DO_ONCE({ |
| char max_string[32]; |
| snprintf(max_string, BUFFER_SIZE_ELEMENTS(max_string), "%d", |
| DYNAMO_OPTION(max_pending_signals)); |
| NULL_TERMINATE_BUFFER(max_string); |
| SYSLOG(SYSLOG_WARNING, MAX_PENDING_SIGNALS, 3, get_application_name(), |
| get_application_pid(), max_string); |
| }); |
| } |
| |
| pend->next = info->sigpending[sig]; |
| info->sigpending[sig] = pend; |
| pend->unblocked = !blocked; |
| |
| /* FIXME: note that for asynchronous signals we don't need to |
| * bother to record exact machine context, even entire frame, |
| * since don't want to pass dynamo pc context to app handler. |
| * only copy frame for synchronous signals? those only |
| * happen while in cache? but for asynch, we would have to |
| * construct our own frame...kind of a pain. |
| */ |
| copy_frame_to_pending(dcontext, sig, frame _IF_CLIENT(access_address)); |
| |
| /* i#1145: check whether we should auto-restart an interrupted syscall */ |
| if (at_auto_restart_syscall) { |
| /* Adjust the pending frame to restart the syscall, if applicable */ |
| sigframe_rt_t *frame = &(info->sigpending[sig]->rt_frame); |
| sigcontext_t *sc_pend = get_sigcontext_from_rt_frame(frame); |
| if (adjust_syscall_for_restart(dcontext, info, sig, sc_pend, f, |
| orig_retval_reg)) { |
| /* We're going to re-start this syscall after we go |
| * back to d_r_dispatch, run the post-syscall handler (for -EINTR), |
| * and deliver the signal. We've adjusted the sigcontext |
| * for re-start on the sigreturn, but we need to tell |
| * execute_handler_from_dispatch() to use our sigcontext |
| * and not the mcontext. |
| * A client will see a second set of pre + post handlers for |
| * the restart, which seems reasonable, given the signal in |
| * between. |
| */ |
| info->sigpending[sig]->use_sigcontext = true; |
| } |
| } |
| |
| } else { |
| /* For clients, we document that we do not pass to them |
| * unless we're prepared to deliver to app. We would have |
| * to change our model to pass them non-final-translated |
| * contexts for delayable signals in order to give them |
| * signals as soon as they come in. Xref i#182/PR 449996. |
| */ |
| LOG(THREAD, LOG_ASYNCH, 3, |
| "\tnon-rt signal already in queue, ignoring this one!\n"); |
| } |
| |
| if (!blocked && !dcontext->signals_pending) |
| dcontext->signals_pending = 1; |
| } |
| ostd->processing_signal--; |
| } |
| |
| /* Distinguish SYS_kill-generated from instruction-generated signals. |
| * If sent from another process we can't tell, but if sent from this |
| * thread the interruption point should be our own post-syscall. |
| * FIXME PR 368277: for other threads in same process we should set a flag |
| * and identify them as well. |
| * FIXME: for faults like SIGILL we could examine the interrupted pc |
| * to see whether it is capable of generating such a fault (see code |
| * used in handle_nudge_signal()). |
| */ |
| static bool |
| is_sys_kill(dcontext_t *dcontext, byte *pc, byte *xsp, kernel_siginfo_t *info) |
| { |
| #if !defined(VMX86_SERVER) && !defined(MACOS) /* does not use SI_KERNEL */ |
| /* i#133: use si_code to distinguish user-sent signals. |
| * Even 2.2 Linux kernel supports <=0 meaning user-sent (except |
| * SIGIO) so we assume we can rely on it. |
| */ |
| if (info->si_code <= 0) |
| return true; |
| #endif |
| return (is_at_do_syscall(dcontext, pc, xsp) && |
| (dcontext->sys_num == SYS_kill || |
| #ifdef LINUX |
| dcontext->sys_num == SYS_tkill || dcontext->sys_num == SYS_tgkill || |
| dcontext->sys_num == SYS_rt_sigqueueinfo |
| #elif defined(MACOS) |
| dcontext->sys_num == SYS___pthread_kill |
| #endif |
| )); |
| } |
| |
| static byte * |
| compute_memory_target(dcontext_t *dcontext, cache_pc instr_cache_pc, |
| kernel_ucontext_t *uc, kernel_siginfo_t *si, bool *write) |
| { |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); |
| byte *target = NULL; |
| instr_t instr; |
| priv_mcontext_t mc; |
| uint memopidx, memoppos, memopsize; |
| opnd_t memop; |
| bool found_target = false; |
| bool in_maps; |
| bool use_allmem = false; |
| uint prot; |
| IF_ARM(dr_isa_mode_t old_mode;) |
| |
| LOG(THREAD, LOG_ALL, 2, |
| "computing memory target for " PFX " causing SIGSEGV, kernel claims it is " PFX |
| "\n", |
| instr_cache_pc, (byte *)si->si_addr); |
| /* ARM's sigcontext_t has a "fault_address" field but it also seems unreliable */ |
| IF_ARM(LOG(THREAD, LOG_ALL, 2, "fault_address: " PFX "\n", sc->fault_address)); |
| |
| /* We used to do a memory query to check if instr_cache_pc is readable, but |
| * now we use TRY/EXCEPT because we don't have the instr length and the OS |
| * query is expensive. If decoding faults, the signal handler will longjmp |
| * out before it calls us recursively. |
| */ |
| instr_init(dcontext, &instr); |
| IF_ARM({ |
| /* Be sure to use the interrupted mode and not the last-dispatch mode */ |
| dr_set_isa_mode(dcontext, get_pc_mode_from_cpsr(sc), &old_mode); |
| }); |
| TRY_EXCEPT(dcontext, { decode(dcontext, instr_cache_pc, &instr); }, |
| { |
| return NULL; /* instr_cache_pc was unreadable */ |
| }); |
| IF_ARM(dr_set_isa_mode(dcontext, old_mode, NULL)); |
| |
| if (!instr_valid(&instr)) { |
| LOG(THREAD, LOG_ALL, 2, |
| "WARNING: got SIGSEGV for invalid instr at cache pc " PFX "\n", |
| instr_cache_pc); |
| ASSERT_NOT_REACHED(); |
| instr_free(dcontext, &instr); |
| return NULL; |
| } |
| |
| ucontext_to_mcontext(&mc, uc); |
| ASSERT(write != NULL); |
| |
| /* i#1009: If si_addr is plausibly one of the memory operands of the |
| * faulting instruction, assume the target was si_addr. If none of the |
| * memops match, fall back to checking page protections, which can be racy. |
| * For si_addr == NULL, we fall back to the protection check because it's |
| * too likely to be a valid memop and we can live with a race on a page that |
| * is typically unmapped. |
| */ |
| if (si->si_code == SEGV_ACCERR && si->si_addr != NULL) { |
| for (memopidx = 0; instr_compute_address_ex_priv(&instr, &mc, memopidx, &target, |
| write, &memoppos); |
| memopidx++) { |
| /* i#1045: check whether operand and si_addr overlap */ |
| memop = *write ? instr_get_dst(&instr, memoppos) |
| : instr_get_src(&instr, memoppos); |
| memopsize = opnd_size_in_bytes(opnd_get_size(memop)); |
| LOG(THREAD, LOG_ALL, 2, "memory operand %u has address " PFX " and size %u\n", |
| memopidx, target, memopsize); |
| if ((byte *)si->si_addr >= target && |
| (byte *)si->si_addr < target + memopsize) { |
| target = (byte *)si->si_addr; |
| found_target = true; |
| break; |
| } |
| } |
| } |
| |
| /* For fcache faults, use all_memory_areas, which is faster but acquires |
| * locks. If it's possible we're in DR, go to the OS to avoid deadlock. |
| */ |
| if (DYNAMO_OPTION(use_all_memory_areas)) { |
| use_allmem = safe_is_in_fcache(dcontext, instr_cache_pc, (byte *)sc->SC_XSP); |
| } |
| if (!found_target) { |
| if (si->si_addr != NULL) { |
| LOG(THREAD, LOG_ALL, 3, "%s: falling back to racy protection checks\n", |
| __FUNCTION__); |
| } |
| /* i#115/PR 394984: consider all memops */ |
| for (memopidx = 0; |
| instr_compute_address_ex_priv(&instr, &mc, memopidx, &target, write, NULL); |
| memopidx++) { |
| if (use_allmem) { |
| in_maps = get_memory_info(target, NULL, NULL, &prot); |
| } else { |
| in_maps = get_memory_info_from_os(target, NULL, NULL, &prot); |
| } |
| if ((!in_maps || !TEST(MEMPROT_READ, prot)) || |
| (*write && !TEST(MEMPROT_WRITE, prot))) { |
| found_target = true; |
| break; |
| } |
| } |
| } |
| |
| if (!found_target) { |
| /* probably an NX fault: how tell whether kernel enforcing? */ |
| in_maps = get_memory_info_from_os(instr_cache_pc, NULL, NULL, &prot); |
| if (!in_maps || !TEST(MEMPROT_EXEC, prot)) { |
| target = instr_cache_pc; |
| found_target = true; |
| } |
| } |
| |
| /* we may still not find target, e.g. for SYS_kill(SIGSEGV) */ |
| if (!found_target) |
| target = NULL; |
| DOLOG(2, LOG_ALL, { |
| LOG(THREAD, LOG_ALL, 2, |
| "For SIGSEGV at cache pc " PFX ", computed target %s " PFX "\n", |
| instr_cache_pc, *write ? "write" : "read", target); |
| d_r_loginst(dcontext, 2, &instr, "\tfaulting instr"); |
| }); |
| instr_free(dcontext, &instr); |
| return target; |
| } |
| |
| /* If native_state is true, assumes the fault is not in the cache and thus |
| * does not need translation but rather should always be re-executed. |
| */ |
| static bool |
| check_for_modified_code(dcontext_t *dcontext, cache_pc instr_cache_pc, |
| kernel_ucontext_t *uc, byte *target, bool native_state) |
| { |
| /* special case: we expect a seg fault for executable regions |
| * that were writable and marked read-only by us. |
| * have to figure out the target address! |
| * unfortunately the OS doesn't tell us, nor whether it's a write. |
| * FIXME: if sent from SYS_kill(SIGSEGV), the pc will be post-syscall, |
| * and if that post-syscall instr is a write that could have faulted, |
| * how can we tell the difference? |
| */ |
| if (was_executable_area_writable(target)) { |
| /* translate instr_cache_pc to original app pc |
| * DO NOT use translate_sigcontext, don't want to change the |
| * signal frame or else we'll lose control when we try to |
| * return to signal pc! |
| */ |
| app_pc next_pc, translated_pc = NULL; |
| fragment_t *f = NULL; |
| fragment_t wrapper; |
| ASSERT((cache_pc)SIGCXT_FROM_UCXT(uc)->SC_XIP == instr_cache_pc); |
| if (!native_state) { |
| /* For safe recreation we need to either be couldbelinking or hold |
| * the initexit lock (to keep someone from flushing current |
| * fragment), the initexit lock is easier |
| */ |
| d_r_mutex_lock(&thread_initexit_lock); |
| /* cache the fragment since pclookup is expensive for coarse units (i#658) */ |
| f = fragment_pclookup(dcontext, instr_cache_pc, &wrapper); |
| translated_pc = recreate_app_pc(dcontext, instr_cache_pc, f); |
| ASSERT(translated_pc != NULL); |
| d_r_mutex_unlock(&thread_initexit_lock); |
| } |
| |
| next_pc = |
| handle_modified_code(dcontext, instr_cache_pc, translated_pc, target, f); |
| |
| if (!native_state) { |
| /* going to exit from middle of fragment (at the write) so will mess up |
| * trace building |
| */ |
| if (is_building_trace(dcontext)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsquashing trace-in-progress\n"); |
| trace_abort(dcontext); |
| } |
| } |
| |
| if (next_pc == NULL) { |
| /* re-execute the write -- just have master_signal_handler return */ |
| return true; |
| } else { |
| ASSERT(!native_state); |
| /* Do not resume execution in cache, go back to d_r_dispatch. */ |
| transfer_from_sig_handler_to_fcache_return( |
| dcontext, uc, NULL, SIGSEGV, next_pc, |
| (linkstub_t *)get_selfmod_linkstub(), false); |
| /* now have master_signal_handler return */ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| #ifndef HAVE_SIGALTSTACK |
| /* The exact layout of this struct is relied on in master_signal_handler() |
| * in x86.asm. |
| */ |
| struct clone_and_swap_args { |
| byte *stack; |
| byte *tos; |
| }; |
| |
| /* Helper function for swapping handler to dstack */ |
| bool |
| sig_should_swap_stack(struct clone_and_swap_args *args, kernel_ucontext_t *ucxt) |
| { |
| byte *cur_esp; |
| dcontext_t *dcontext = get_thread_private_dcontext(); |
| if (dcontext == NULL) |
| return false; |
| GET_STACK_PTR(cur_esp); |
| if (!is_on_dstack(dcontext, cur_esp)) { |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); |
| /* Pass back the proper args to clone_and_swap_stack: we want to |
| * copy to dstack from the tos at the signal interruption point. |
| */ |
| args->stack = dcontext->dstack; |
| /* leave room for fpstate */ |
| args->stack -= signal_frame_extra_size(true); |
| args->stack = (byte *)ALIGN_BACKWARD(args->stack, XSTATE_ALIGNMENT); |
| args->tos = (byte *)sc->SC_XSP; |
| return true; |
| } else |
| return false; |
| } |
| #endif |
| |
| /* Helper that takes over the current thread signaled via SUSPEND_SIGNAL. Kept |
| * separate mostly to keep the priv_mcontext_t allocation out of |
| * master_signal_handler_C. |
| * If it returns, it returns false, and the signal should be squashed. |
| */ |
| static bool |
| sig_take_over(kernel_ucontext_t *uc) |
| { |
| priv_mcontext_t mc; |
| ucontext_to_mcontext(&mc, uc); |
| /* We don't want our own blocked signals: we want the app's, stored in the frame. */ |
| if (!os_thread_take_over(&mc, SIGMASK_FROM_UCXT(uc))) |
| return false; |
| ASSERT_NOT_REACHED(); /* shouldn't return */ |
| return true; /* make compiler happy */ |
| } |
| |
| static bool |
| is_safe_read_ucxt(kernel_ucontext_t *ucxt) |
| { |
| app_pc pc = (app_pc)SIGCXT_FROM_UCXT(ucxt)->SC_XIP; |
| return is_safe_read_pc(pc); |
| } |
| |
| /* the master signal handler |
| * WARNING: behavior varies with different versions of the kernel! |
| * sigaction support was only added with 2.2 |
| */ |
| #ifndef X86_32 |
| /* stub in x86.asm passes our xsp to us */ |
| # ifdef MACOS |
| void |
| master_signal_handler_C(handler_t handler, int style, int sig, kernel_siginfo_t *siginfo, |
| kernel_ucontext_t *ucxt, byte *xsp) |
| # else |
| void |
| master_signal_handler_C(int sig, kernel_siginfo_t *siginfo, kernel_ucontext_t *ucxt, |
| byte *xsp) |
| # endif |
| #else |
| /* On ia32, adding a parameter disturbs the frame we're trying to capture, so we |
| * add an intermediate frame and read the normal params off the stack directly. |
| */ |
| void |
| master_signal_handler_C(byte *xsp) |
| #endif |
| { |
| #ifdef MACOS64 |
| /* The kernel aligns to 16 after setting up the frame, so we instead compute |
| * from the siginfo pointer. |
| * XXX: 32-bit does the same thing: how was it working?!? |
| */ |
| sigframe_rt_t *frame = (sigframe_rt_t *)((byte *)siginfo - sizeof(frame->mc)); |
| /* The kernel's method of aligning overshoots. */ |
| # define KERNEL_ALIGN_BACK(val, align) (((val)-align) & -(align)) |
| /* If this assert fails, we may be seeing an AVX512 frame. */ |
| ASSERT(KERNEL_ALIGN_BACK((ptr_uint_t)frame, 16) - 8 == (ptr_uint_t)xsp && |
| "AVX512 frames not yet supported"); |
| #else |
| sigframe_rt_t *frame = (sigframe_rt_t *)xsp; |
| #endif |
| #ifdef X86_32 |
| /* Read the normal arguments from the frame. */ |
| int sig = frame->sig; |
| kernel_siginfo_t *siginfo = frame->pinfo; |
| kernel_ucontext_t *ucxt = frame->puc; |
| #endif /* !X64 */ |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); |
| thread_record_t *tr; |
| #ifdef DEBUG |
| uint level = 2; |
| # if !defined(HAVE_MEMINFO) |
| /* avoid logging every single TRY probe fault */ |
| if (!dynamo_initialized) |
| level = 5; |
| # endif |
| #endif |
| bool local; |
| #if defined(MACOS) && !defined(X64) |
| /* The kernel clears fs, so we have to re-instate our selector, if |
| * it was set in the first place. |
| */ |
| if (sc->__ss.__fs != 0) |
| tls_reinstate_selector(sc->__ss.__fs); |
| #endif |
| #ifdef X86 |
| /* i#2089: For is_thread_tls_initialized() we need a safe_read path that does not |
| * do any logging or call get_thread_private_dcontext() as those will recurse. |
| * This path is global so there's no SELF_PROTECT_LOCAL and we also bypass |
| * the ENTERING_DR() for this short path. |
| */ |
| if (sig == SIGSEGV && sc->SC_XIP == (ptr_uint_t)safe_read_tls_magic) { |
| sc->SC_RETURN_REG = 0; |
| sc->SC_XIP = (reg_t)safe_read_tls_magic_recover; |
| return; |
| } else if (sig == SIGSEGV && sc->SC_XIP == (ptr_uint_t)safe_read_tls_self) { |
| sc->SC_RETURN_REG = 0; |
| sc->SC_XIP = (reg_t)safe_read_tls_self_recover; |
| return; |
| } else if (sig == SIGSEGV && sc->SC_XIP == (ptr_uint_t)safe_read_tls_app_self) { |
| sc->SC_RETURN_REG = 0; |
| sc->SC_XIP = (reg_t)safe_read_tls_app_self_recover; |
| return; |
| } |
| #endif |
| |
| dcontext_t *dcontext = get_thread_private_dcontext(); |
| |
| #ifdef MACOS |
| # ifdef X64 |
| ASSERT((YMM_ENABLED() && ucxt->uc_mcsize == sizeof(_STRUCT_MCONTEXT_AVX64)) || |
| (!YMM_ENABLED() && ucxt->uc_mcsize == sizeof(_STRUCT_MCONTEXT64))); |
| # else |
| ASSERT((YMM_ENABLED() && ucxt->uc_mcsize == sizeof(_STRUCT_MCONTEXT_AVX32)) || |
| (!YMM_ENABLED() && ucxt->uc_mcsize == sizeof(_STRUCT_MCONTEXT))); |
| # endif |
| #endif |
| |
| /* i#350: To support safe_read or TRY_EXCEPT without a dcontext, use the |
| * global dcontext |
| * when handling safe_read faults. This lets us pass the check for a |
| * dcontext below and causes us to use the global log. |
| */ |
| if (dcontext == NULL && (sig == SIGSEGV || sig == SIGBUS) && |
| (is_safe_read_ucxt(ucxt) || |
| (!dynamo_initialized && global_try_except.try_except_state != NULL))) { |
| dcontext = GLOBAL_DCONTEXT; |
| } |
| |
| if (dynamo_exited && d_r_get_num_threads() > 1 && sig == SIGSEGV) { |
| /* PR 470957: this is almost certainly a race so just squelch it. |
| * We live w/ the risk that it was holding a lock our release-build |
| * exit code needs. |
| */ |
| exit_thread_syscall(1); |
| } |
| /* FIXME: ensure the path for recording a pending signal does not grab any DR locks |
| * that could have been interrupted |
| * e.g., synchronize_dynamic_options grabs the stats_lock! |
| */ |
| if (sig == SUSPEND_SIGNAL) { |
| if (proc_get_vendor() == VENDOR_AMD) { |
| /* i#3356: Work around an AMD processor bug where it does not clear the |
| * hidden gs base when the gs selector is written. Pre-4.7 Linux kernels |
| * leave the prior thread's base in place on a switch due to this. |
| * We can thus come here and get the wrong dcontext on attach; worse, |
| * we can get NULL here but the wrong one later during init. It's |
| * safest to just set a non-zero value (the kernel ignores zero) for all |
| * unknown threads here. There are no problems for non-attach takeover. |
| */ |
| if (dcontext == NULL || dcontext->owning_thread != get_sys_thread_id()) { |
| /* tls_thread_preinit() further rules out a temp-native dcontext |
| * and avoids clobbering it, to preserve the thread_lookup() case |
| * below (which we do not want to run first as we could swap to |
| * the incorrect dcontext midway through it). |
| */ |
| if (!tls_thread_preinit()) { |
| SYSLOG_INTERNAL_ERROR_ONCE("ERROR: Failed to work around AMD context " |
| "switch bug #3356: crashes or " |
| "hangs may ensue..."); |
| } |
| dcontext = NULL; |
| } |
| } |
| } |
| if (dcontext == NULL && |
| /* Check for a temporarily-native thread we're synch-ing with. */ |
| (sig == SUSPEND_SIGNAL |
| #ifdef X86 |
| || |
| (INTERNAL_OPTION(safe_read_tls_init) && |
| /* Check for whether this is a thread with its invalid sentinel magic set. |
| * In this case, we assume that it is either a thread that is currently |
| * temporarily-native via API like DR_EMIT_GO_NATIVE, or a thread in the |
| * clone window. We know by inspection of our own code that it is safe to |
| * call thread_lookup for either case thread makes a clone or was just |
| * cloned. i.e. thread_lookup requires a lock that must not be held by the |
| * calling thread (i#2921). |
| * XXX: what is ARM doing, any special case w/ dcontext == NULL? |
| */ |
| /* Don't do this if we're detaching as we risk a safe-read fault after |
| * we've removed our handler (i#3535). |
| */ |
| detacher_tid == INVALID_THREAD_ID && safe_read_tls_magic() == TLS_MAGIC_INVALID) |
| #endif |
| )) { |
| tr = thread_lookup(get_sys_thread_id()); |
| if (tr != NULL) |
| dcontext = tr->dcontext; |
| } |
| if (dcontext == NULL || |
| (dcontext != GLOBAL_DCONTEXT && |
| (dcontext->signal_field == NULL || |
| !((thread_sig_info_t *)dcontext->signal_field)->fully_initialized))) { |
| /* XXX i#26: this could be a signal arbitrarily sent to this thread. |
| * We could try to route it to another thread, using a global queue |
| * of pending signals. But what if it was targeted to this thread |
| * via SYS_{tgkill,tkill}? Can we tell the difference, even if |
| * we watch the kill syscalls: could come from another process? |
| * For now we use our native thread signal delivery mechanism. |
| */ |
| if (sig_is_alarm_signal(sig)) { |
| /* Assuming an alarm during thread exit or init (xref PR 596127, |
| * i#359). Delivering it sometimes works, but we could be mid-init |
| * where it's not easy and we could crash b/c of in-between state |
| * (observed for alarm mid-signal_thread_init where info->app_sigaction |
| * is still NULL). |
| */ |
| return; |
| } else if (sig == SUSPEND_SIGNAL && dcontext == NULL) { |
| /* We sent SUSPEND_SIGNAL to a thread we don't control (no |
| * dcontext), which means we want to take over. |
| */ |
| ASSERT(!doing_detach); |
| if (!sig_take_over(ucxt)) |
| return; |
| ASSERT_NOT_REACHED(); /* else, shouldn't return */ |
| } else { |
| LOG(GLOBAL, LOG_ASYNCH, 1, |
| "signal with no siginfo (tid=%d, sig=%d): attempting to deliver to " |
| "native thread\n", |
| get_sys_thread_id(), sig); |
| DOLOG(1, LOG_ASYNCH, { dump_sigcontext(GLOBAL_DCONTEXT, sc); }); |
| } |
| |
| /* For can_always_delay[sig] we could just return and drop it, but we |
| * try to perturb the app behavior less with a native signal frame: |
| */ |
| execute_native_handler(dcontext, sig, frame); |
| return; |
| } |
| |
| /* we may be entering dynamo from code cache! */ |
| /* Note that this is unsafe if -single_thread_in_DR => we grab a lock => |
| * hang if signal interrupts DR: but we don't really support that option |
| */ |
| ENTERING_DR(); |
| if (dcontext == GLOBAL_DCONTEXT) { |
| local = false; |
| tr = thread_lookup(get_sys_thread_id()); |
| } else { |
| tr = dcontext->thread_record; |
| local = local_heap_protected(dcontext); |
| if (local) |
| SELF_PROTECT_LOCAL(dcontext, WRITABLE); |
| } |
| /* i#1921: For proper native execution with re-takeover we need to propagate |
| * signals to app handlers while native. For now we do not support re-takeover |
| * and we give up our handlers via signal_remove_handlers(). |
| */ |
| ASSERT(tr == NULL || tr->under_dynamo_control || IS_CLIENT_THREAD(dcontext) || |
| sig == SUSPEND_SIGNAL); |
| |
| LOG(THREAD, LOG_ASYNCH, level, |
| "\nmaster_signal_handler: thread=%d, sig=%d, xsp=" PFX ", retaddr=" PFX "\n", |
| get_sys_thread_id(), sig, xsp, *((byte **)xsp)); |
| LOG(THREAD, LOG_ASYNCH, level + 1, |
| "siginfo: sig = %d, pid = %d, status = %d, errno = %d, si_code = %d\n", |
| siginfo->si_signo, siginfo->si_pid, siginfo->si_status, siginfo->si_errno, |
| siginfo->si_code); |
| DOLOG(level + 1, LOG_ASYNCH, { dump_sigcontext(dcontext, sc); }); |
| |
| #if defined(X86_32) && !defined(VMX86_SERVER) && defined(LINUX) |
| /* FIXME case 6700: 2.6.9 (FC3) kernel sets up our frame with a pretcode |
| * of 0x440. This happens if our restorer is unspecified (though 2.6.9 |
| * src code shows setting the restorer to a default value in that case...) |
| * or if we explicitly point at dynamorio_sigreturn. I couldn't figure |
| * out why it kept putting 0x440 there. So we fix the issue w/ this |
| * hardcoded return. |
| * This hack causes vmkernel to kill the process on sigreturn due to |
| * vmkernel's non-standard sigreturn semantics. PR 404712. |
| */ |
| *((byte **)xsp) = (byte *)dynamorio_sigreturn; |
| #endif |
| |
| /* N.B.: |
| * ucontext_t is defined in two different places. The one we get |
| * included is /usr/include/sys/ucontext.h, which would have us |
| * doing this: |
| * void *pc = (void *) ucxt->uc_mcontext.gregs[EIP]; |
| * However, EIP is not defined for us (used to be in older |
| * RedHat version) unless we define __USE_GNU, which we don't want to do |
| * for other reasons, so we'd have to also say: |
| * #define EIP 14 |
| * Instead we go by the ucontext_t definition in |
| * /usr/include/asm/ucontext.h, which has it containing a sigcontext struct, |
| * defined in /usr/include/asm/sigcontext.h. This is the definition used |
| * by the kernel. The two definitions are field-for-field |
| * identical except that the sys one has an fpstate struct at the end -- |
| * but the next field in the frame is an fpstate. The only mystery |
| * is why the rt frame is declared as ucontext instead of sigcontext. |
| * The kernel's version of ucontext must be the asm one! |
| * And the sys one grabs the next field of the frame. |
| * Also note that mcontext_t.fpregs == sigcontext.fpstate is NULL if |
| * floating point operations have not been used (lazy fp state saving). |
| * Also, sigset_t has different sizes according to kernel (8 bytes) vs. |
| * glibc (128 bytes?). |
| */ |
| |
| switch (sig) { |
| |
| case SIGBUS: /* PR 313665: look for DR crashes on unaligned memory or mmap bounds */ |
| case SIGSEGV: { |
| /* Older kernels do NOT fill out the signal-specific fields of siginfo, |
| * except for SIGCHLD. Thus we cannot do this: |
| * void *pc = (void*) siginfo->si_addr; |
| * Thus we must use the third argument, which is a ucontext_t (see above) |
| */ |
| void *pc = (void *)sc->SC_XIP; |
| bool syscall_signal = false; /* signal came from syscall? */ |
| bool is_write = false; |
| byte *target; |
| bool is_DR_exception = false; |
| |
| #ifdef SIDELINE |
| if (dcontext == NULL) { |
| SYSLOG_INTERNAL_ERROR("seg fault in sideline thread -- NULL dcontext!"); |
| ASSERT_NOT_REACHED(); |
| } |
| #endif |
| if (is_safe_read_ucxt(ucxt) || |
| (!dynamo_initialized && global_try_except.try_except_state != NULL) || |
| dcontext->try_except.try_except_state != NULL) { |
| /* handle our own TRY/EXCEPT */ |
| try_except_context_t *try_cxt; |
| #ifdef HAVE_MEMINFO |
| /* our probe produces many of these every run */ |
| /* since we use for safe_*, making a _ONCE */ |
| SYSLOG_INTERNAL_WARNING_ONCE("(1+x) Handling our fault in a TRY at " PFX, pc); |
| #endif |
| LOG(THREAD, LOG_ALL, level, "TRY fault at " PFX "\n", pc); |
| if (TEST(DUMPCORE_TRY_EXCEPT, DYNAMO_OPTION(dumpcore_mask))) |
| os_dump_core("try/except fault"); |
| |
| if (is_safe_read_ucxt(ucxt)) { |
| sc->SC_XIP = (reg_t)safe_read_resume_pc(); |
| /* Break out to log the normal return from the signal handler. |
| */ |
| break; |
| } |
| try_cxt = (dcontext != NULL) ? dcontext->try_except.try_except_state |
| : global_try_except.try_except_state; |
| ASSERT(try_cxt != NULL); |
| |
| /* The exception interception code did an ENTER so we must EXIT here */ |
| EXITING_DR(); |
| /* Since we have no sigreturn we have to restore the mask |
| * manually, just like siglongjmp(). i#226/PR 492568: we rely |
| * on the kernel storing the prior mask in ucxt, so we do not |
| * need to store it on every setjmp. |
| */ |
| /* Verify that there's no scenario where the mask gets changed prior |
| * to a fault inside a try. This relies on dr_setjmp_sigmask() filling |
| * in the mask, which we only bother to do in debug build. |
| */ |
| ASSERT(memcmp(&try_cxt->context.sigmask, &ucxt->uc_sigmask, |
| sizeof(ucxt->uc_sigmask)) == 0); |
| sigprocmask_syscall(SIG_SETMASK, SIGMASK_FROM_UCXT(ucxt), NULL, |
| sizeof(ucxt->uc_sigmask)); |
| DR_LONGJMP(&try_cxt->context, LONGJMP_EXCEPTION); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| target = compute_memory_target(dcontext, pc, ucxt, siginfo, &is_write); |
| |
| #ifdef CLIENT_INTERFACE |
| if (CLIENTS_EXIST() && is_in_client_lib(pc)) { |
| /* i#1354: client might write to a page we made read-only. |
| * If so, handle the fault and re-execute it, if it's safe to do so |
| * (we document these criteria under DR_MEMPROT_PRETEND_WRITE). |
| */ |
| if (is_write && !is_couldbelinking(dcontext) && OWN_NO_LOCKS(dcontext) && |
| check_for_modified_code(dcontext, pc, ucxt, target, true /*native*/)) |
| break; |
| abort_on_fault(dcontext, DUMPCORE_CLIENT_EXCEPTION, pc, target, sig, frame, |
| exception_label_client, (sig == SIGSEGV) ? "SEGV" : "BUS", |
| " client library"); |
| ASSERT_NOT_REACHED(); |
| } |
| #endif |
| |
| /* For !HAVE_MEMINFO, we cannot compute the target until |
| * after the try/except check b/c compute_memory_target() |
| * calls get_memory_info_from_os() which does a probe: and the |
| * try/except could be from a probe itself. A try/except that |
| * triggers a stack overflow should recover on the longjmp, so |
| * this order should be fine. |
| */ |
| |
| /* FIXME: share code with Windows callback.c */ |
| /* FIXME PR 205795: in_fcache and is_dynamo_address do grab locks! */ |
| if ((is_on_dstack(dcontext, (byte *)sc->SC_XSP) |
| /* PR 302951: clean call arg processing => pass to app/client. |
| * Rather than call the risky in_fcache we check whereami. */ |
| IF_CLIENT_INTERFACE(&&(dcontext->whereami != DR_WHERE_FCACHE))) || |
| is_on_alt_stack(dcontext, (byte *)sc->SC_XSP) || |
| is_on_initstack((byte *)sc->SC_XSP)) { |
| /* Checks here need to cover everything that record_pending_signal() |
| * thinks is non-fcache, non-gencode: else that routine will kill |
| * process since can't delay or re-execute (i#195/PR 453964). |
| */ |
| is_DR_exception = true; |
| } else if (!safe_is_in_fcache(dcontext, pc, (byte *)sc->SC_XSP) && |
| (in_generated_routine(dcontext, pc) || |
| is_at_do_syscall(dcontext, pc, (byte *)sc->SC_XSP) || |
| is_dynamo_address(pc))) { |
| #ifdef CLIENT_INTERFACE |
| if (!in_generated_routine(dcontext, pc) && |
| !is_at_do_syscall(dcontext, pc, (byte *)sc->SC_XSP)) { |
| /* PR 451074: client needs a chance to handle exceptions in its |
| * own gencode. client_exception_event() won't return if client |
| * wants to re-execute faulting instr. |
| */ |
| sigcontext_t sc_interrupted = *get_sigcontext_from_rt_frame(frame); |
| dr_signal_action_t action = send_signal_to_client( |
| dcontext, sig, frame, sc, target, false /*!blocked*/, NULL); |
| if (action != DR_SIGNAL_DELIVER && /* for delivery, continue below */ |
| !handle_client_action_from_cache(dcontext, sig, action, frame, sc, |
| &sc_interrupted, |
| false /*!blocked*/)) { |
| /* client handled fault */ |
| break; |
| } |
| } |
| #endif |
| is_DR_exception = true; |
| } |
| if (is_DR_exception) { |
| /* kill(getpid(), SIGSEGV) looks just like a SIGSEGV in the store of eax |
| * to mcontext after the syscall instr in do_syscall -- try to distinguish: |
| */ |
| if (is_sys_kill(dcontext, pc, (byte *)sc->SC_XSP, siginfo)) { |
| LOG(THREAD, LOG_ALL, 2, |
| "assuming SIGSEGV at post-do-syscall is kill, not our write fault\n"); |
| syscall_signal = true; |
| } |
| if (!syscall_signal) { |
| if (check_in_last_thread_vm_area(dcontext, target)) { |
| /* See comments in callback.c as well. |
| * FIXME: try to share code |
| */ |
| SYSLOG_INTERNAL_WARNING("(decode) exception in last area, " |
| "DR pc=" PFX ", app pc=" PFX, |
| pc, target); |
| STATS_INC(num_exceptions_decode); |
| if (is_building_trace(dcontext)) { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "intercept_exception: " |
| "squashing old trace\n"); |
| trace_abort(dcontext); |
| } |
| /* we do get faults when not building a bb: e.g., |
| * ret_after_call_check does decoding (case 9396) */ |
| if (dcontext->bb_build_info != NULL) { |
| /* must have been building a bb at the time */ |
| bb_build_abort(dcontext, true /*clean vm area*/, true /*unlock*/); |
| } |
| /* Since we have no sigreturn we have to restore the mask manually */ |
| unblock_all_signals(NULL); |
| /* Let's pass it back to the application - memory is unreadable */ |
| if (TEST(DUMPCORE_FORGE_UNREAD_EXEC, DYNAMO_OPTION(dumpcore_mask))) |
| os_dump_core("Warning: Racy app execution (decode unreadable)"); |
| os_forge_exception(target, UNREADABLE_MEMORY_EXECUTION_EXCEPTION); |
| ASSERT_NOT_REACHED(); |
| } else { |
| abort_on_DR_fault(dcontext, pc, target, sig, frame, |
| (sig == SIGSEGV) ? "SEGV" : "BUS", |
| in_generated_routine(dcontext, pc) ? " generated" |
| : ""); |
| } |
| } |
| } |
| /* if get here, pass the signal to the app */ |
| |
| ASSERT(pc != 0); /* shouldn't get here */ |
| if (sig == SIGSEGV && !syscall_signal /*only for in-cache signals*/) { |
| /* special case: we expect a seg fault for executable regions |
| * that were writable and marked read-only by us. |
| */ |
| if (is_write && |
| check_for_modified_code(dcontext, pc, ucxt, target, false /*!native*/)) { |
| /* it was our signal, so don't pass to app -- return now */ |
| break; |
| } |
| } |
| /* pass it to the application (or client) */ |
| if (dcontext->currently_stopped) { |
| execute_native_handler(dcontext, sig, frame); |
| break; |
| } |
| LOG(THREAD, LOG_ALL, 1, |
| "** Received SIG%s at cache pc " PFX " in thread " TIDFMT "\n", |
| (sig == SIGSEGV) ? "SEGV" : "BUS", pc, d_r_get_thread_id()); |
| ASSERT(syscall_signal || safe_is_in_fcache(dcontext, pc, (byte *)sc->SC_XSP)); |
| /* we do not call trace_abort() here since we may need to |
| * translate from a temp private bb (i#376): but all paths |
| * that deliver the signal or redirect will call it |
| */ |
| record_pending_signal(dcontext, sig, ucxt, frame, false _IF_CLIENT(target)); |
| break; |
| } |
| |
| /* PR 212090: the signal we use to suspend threads */ |
| case SUSPEND_SIGNAL: |
| if (handle_suspend_signal(dcontext, ucxt, frame)) { |
| /* i#1921: see comment above */ |
| ASSERT(tr == NULL || tr->under_dynamo_control || IS_CLIENT_THREAD(dcontext)); |
| record_pending_signal(dcontext, sig, ucxt, frame, false _IF_CLIENT(NULL)); |
| } |
| /* else, don't deliver to app */ |
| break; |
| |
| /* i#61/PR 211530: the signal we use for nudges */ |
| case NUDGESIG_SIGNUM: |
| if (handle_nudge_signal(dcontext, siginfo, ucxt)) |
| record_pending_signal(dcontext, sig, ucxt, frame, false _IF_CLIENT(NULL)); |
| /* else, don't deliver to app */ |
| break; |
| |
| case SIGALRM: |
| case SIGVTALRM: |
| case SIGPROF: |
| if (handle_alarm(dcontext, sig, ucxt)) |
| record_pending_signal(dcontext, sig, ucxt, frame, false _IF_CLIENT(NULL)); |
| /* else, don't deliver to app */ |
| break; |
| |
| #ifdef SIDELINE |
| case SIGCHLD: { |
| int status = siginfo->si_status; |
| if (siginfo->si_pid == 0) { |
| /* FIXME: with older versions of linux the sigchld fields of |
| * siginfo are not filled in properly! |
| * This is my attempt to handle that, pid seems to be 0 |
| */ |
| break; |
| } |
| if (status != 0) { |
| LOG(THREAD, LOG_ALL, 0, "*** Child thread died with error %d\n", status); |
| ASSERT_NOT_REACHED(); |
| } |
| break; |
| } |
| #endif |
| |
| default: { |
| record_pending_signal(dcontext, sig, ucxt, frame, false _IF_CLIENT(NULL)); |
| break; |
| } |
| } /* end switch */ |
| |
| LOG(THREAD, LOG_ASYNCH, level, |
| "\tmaster_signal_handler %d returning now to " PFX "\n\n", sig, sc->SC_XIP); |
| |
| /* Ensure we didn't get the app's sigstack into our frame. On Mac, the kernel |
| * doesn't use the frame's uc_stack, so we limit this to Linux. |
| * The pointers may be different if a thread is on its way to exit, and the app's |
| * sigstack was already restored (i#3369). |
| */ |
| IF_LINUX(ASSERT(dcontext == NULL || dcontext == GLOBAL_DCONTEXT || dynamo_exited || |
| dcontext->is_exiting || |
| frame->uc.uc_stack.ss_sp == |
| ((thread_sig_info_t *)dcontext->signal_field)->sigstack.ss_sp)); |
| |
| /* restore protections */ |
| if (local) |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| EXITING_DR(); |
| } |
| |
| static bool |
| execute_handler_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *our_frame, |
| sigcontext_t *sc_orig, |
| fragment_t *f _IF_CLIENT(byte *access_address)) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| /* we want to modify the sc in DR's frame */ |
| kernel_ucontext_t *uc = get_ucontext_from_rt_frame(our_frame); |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); |
| kernel_sigset_t blocked; |
| /* Need to get xsp now before get new dcontext. |
| * This is the translated xsp, so we avoid PR 306410 (cleancall arg fault |
| * on dstack => handler run on dstack) that Windows hit. |
| */ |
| byte *xsp = get_sigstack_frame_ptr(dcontext, info, sig, |
| our_frame/* take xsp from (translated) |
| * interruption point */); |
| |
| #ifdef CLIENT_INTERFACE |
| sigcontext_t sc_interrupted = *sc; |
| dr_signal_action_t action = send_signal_to_client( |
| dcontext, sig, our_frame, sc_orig, access_address, false /*not blocked*/, f); |
| if (!handle_client_action_from_cache(dcontext, sig, action, our_frame, sc_orig, |
| &sc_interrupted, false /*!blocked*/)) |
| return false; |
| #else |
| if (info->app_sigaction[sig] == NULL || |
| info->app_sigaction[sig]->handler == (handler_t)SIG_DFL) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\taction is SIG_DFL\n"); |
| if (execute_default_from_cache(dcontext, sig, our_frame, sc_orig, false)) { |
| /* if we haven't terminated, restore original (untranslated) sc |
| * on request. |
| * Note, this also restores SIMD regs, if client translated them! |
| */ |
| restore_orig_sigcontext(our_frame, sc_orig); |
| } |
| return false; |
| } |
| ASSERT(info->app_sigaction[sig] != NULL && |
| info->app_sigaction[sig]->handler != (handler_t)SIG_IGN && |
| info->app_sigaction[sig]->handler != (handler_t)SIG_DFL); |
| #endif |
| |
| LOG(THREAD, LOG_ASYNCH, 2, "execute_handler_from_cache for signal %d\n", sig); |
| RSTATS_INC(num_signals); |
| |
| /* now that we know it's not a client-involved fault, dump as app fault */ |
| report_app_problem(dcontext, APPFAULT_FAULT, (byte *)sc->SC_XIP, (byte *)sc->SC_FP, |
| "\nSignal %d delivered to application handler.\n", sig); |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "\txsp is " PFX "\n", xsp); |
| |
| /* copy frame to appropriate stack and convert to non-rt if necessary */ |
| copy_frame_to_stack(dcontext, info, sig, our_frame, (void *)xsp, false /*!pending*/); |
| LOG(THREAD, LOG_ASYNCH, 3, "\tcopied frame from " PFX " to " PFX "\n", our_frame, |
| xsp); |
| sigcontext_t *app_sc = get_sigcontext_from_app_frame(info, sig, (void *)xsp); |
| |
| /* Because of difficulties determining when/if a signal handler |
| * returns, we do what the kernel does: abandon all of our current |
| * state, copy what we might need to the handler frame if we come back, |
| * and then it's ok if the handler doesn't return. |
| * If it does, we start interpreting afresh when we see sigreturn(). |
| * This routine assumes anything needed to return has been put in the |
| * frame (only needed for signals queued up while in dynamo), and goes |
| * ahead and trashes the current dcontext. |
| */ |
| |
| /* if we were building a trace, kill it */ |
| if (is_building_trace(dcontext)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsquashing trace-in-progress\n"); |
| trace_abort(dcontext); |
| } |
| |
| /* add to set of blocked signals those in sigaction mask */ |
| blocked = info->app_sigaction[sig]->mask; |
| /* SA_NOMASK says whether to block sig itself or not */ |
| if ((info->app_sigaction[sig]->flags & SA_NOMASK) == 0) |
| kernel_sigaddset(&blocked, sig); |
| set_blocked(dcontext, &blocked, false /*relative: OR these in*/); |
| |
| /* Doesn't matter what most app registers are, signal handler doesn't |
| * expect anything except the frame on the stack. We do need to set xsp, |
| * only because if app wants special signal stack we need to point xsp |
| * there. (If no special signal stack, this is a nop.) |
| */ |
| sc->SC_XSP = (ptr_uint_t)xsp; |
| /* Set up args to handler: int sig, kernel_siginfo_t *siginfo, |
| * kernel_ucontext_t *ucxt. |
| */ |
| #ifdef X86_64 |
| sc->SC_XDI = sig; |
| sc->SC_XSI = (reg_t) & ((sigframe_rt_t *)xsp)->info; |
| sc->SC_XDX = (reg_t) & ((sigframe_rt_t *)xsp)->uc; |
| #elif defined(AARCHXX) |
| sc->SC_R0 = sig; |
| if (IS_RT_FOR_APP(info, sig)) { |
| sc->SC_R1 = (reg_t) & ((sigframe_rt_t *)xsp)->info; |
| sc->SC_R2 = (reg_t) & ((sigframe_rt_t *)xsp)->uc; |
| } |
| if (sig_has_restorer(info, sig)) |
| sc->SC_LR = (reg_t)info->app_sigaction[sig]->restorer; |
| else |
| sc->SC_LR = (reg_t)dynamorio_sigreturn; |
| # ifndef AARCH64 |
| /* We're going to our fcache_return gencode which uses DEFAULT_ISA_MODE */ |
| set_pc_mode_in_cpsr(sc, DEFAULT_ISA_MODE); |
| # endif |
| #endif |
| /* Set our sigreturn context (NOT for the app: we already copied the |
| * translated context to the app stack) to point to fcache_return! |
| * Then we'll go back through kernel, appear in fcache_return, |
| * and go through d_r_dispatch & interp, without messing up DR stack. |
| */ |
| transfer_from_sig_handler_to_fcache_return( |
| dcontext, uc, app_sc, sig, |
| /* Make sure handler is next thing we execute */ |
| (app_pc)SIGACT_PRIMARY_HANDLER(info->app_sigaction[sig]), |
| (linkstub_t *)get_asynch_linkstub(), true); |
| |
| if ((info->app_sigaction[sig]->flags & SA_ONESHOT) != 0) { |
| /* clear handler now -- can't delete memory since sigreturn, |
| * others may look at sigaction struct, so we just set to default |
| */ |
| info->app_sigaction[sig]->handler = (handler_t)SIG_DFL; |
| } |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "\tset next_tag to handler " PFX ", xsp to " PFX "\n", |
| SIGACT_PRIMARY_HANDLER(info->app_sigaction[sig]), xsp); |
| return true; |
| } |
| |
| static bool |
| execute_handler_from_dispatch(dcontext_t *dcontext, int sig) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| byte *xsp = get_sigstack_frame_ptr(dcontext, info, sig, NULL); |
| sigframe_rt_t *frame = &(info->sigpending[sig]->rt_frame); |
| priv_mcontext_t *mcontext = get_mcontext(dcontext); |
| sigcontext_t *sc; |
| kernel_ucontext_t *uc; |
| kernel_sigset_t blocked; |
| |
| #ifdef CLIENT_INTERFACE |
| dr_signal_action_t action; |
| #else |
| if (info->app_sigaction[sig] == NULL || |
| info->app_sigaction[sig]->handler == (handler_t)SIG_DFL) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\taction is SIG_DFL\n"); |
| execute_default_from_dispatch(dcontext, sig, frame); |
| return true; |
| } |
| ASSERT(info->app_sigaction[sig] != NULL && |
| info->app_sigaction[sig]->handler != (handler_t)SIG_IGN && |
| info->app_sigaction[sig]->handler != (handler_t)SIG_DFL); |
| #endif |
| |
| LOG(THREAD, LOG_ASYNCH, 2, "execute_handler_from_dispatch for signal %d\n", sig); |
| RSTATS_INC(num_signals); |
| |
| /* modify the rtframe before copying to stack so we can pass final |
| * version to client, and propagate its mods |
| */ |
| uc = get_ucontext_from_rt_frame(frame); |
| sc = SIGCXT_FROM_UCXT(uc); |
| |
| /* Because of difficulties determining when/if a signal handler |
| * returns, we do what the kernel does: abandon all of our current |
| * state, copy what we might need to the handler frame if we come back, |
| * and then it's ok if the handler doesn't return. |
| * If it does, we start interpreting afresh when we see sigreturn(). |
| */ |
| |
| #ifdef DEBUG |
| if (d_r_stats->loglevel >= 3 && (d_r_stats->logmask & LOG_ASYNCH) != 0) { |
| LOG(THREAD, LOG_ASYNCH, 3, "original sigcontext " PFX ":\n", sc); |
| dump_sigcontext(dcontext, sc); |
| } |
| #endif |
| if (info->sigpending[sig]->use_sigcontext) { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "%s: using sigcontext, not mcontext (syscall restart)\n", __FUNCTION__); |
| } else { |
| /* copy currently-interrupted-context to frame's context, so we can |
| * abandon the currently-interrupted context. |
| */ |
| mcontext_to_ucontext(uc, mcontext); |
| } |
| /* Sigreturn needs the target ISA mode to be set in the T bit in cpsr. |
| * Since we came from d_r_dispatch, the post-signal target's mode is in dcontext. |
| */ |
| IF_ARM(set_pc_mode_in_cpsr(sc, dr_get_isa_mode(dcontext))); |
| /* mcontext does not contain fp or mmx or xmm state, which may have |
| * changed since the frame was created (while finishing up interrupted |
| * fragment prior to returning to d_r_dispatch). Since DR does not touch |
| * this state except for xmm on x64, we go ahead and copy the |
| * current state into the frame, and then touch up xmm for x64. |
| */ |
| /* FIXME: should this be done for all pending as soon as reach |
| * d_r_dispatch? what if get two asynch inside same frag prior to exiting |
| * cache? have issues with fpstate, but also prob with next_tag? FIXME |
| */ |
| /* FIXME: we should clear fpstate for app handler itself as that's |
| * how our own handler is executed. |
| */ |
| #if defined(LINUX) && defined(X86) |
| ASSERT(sc->fpstate != NULL); /* not doing i#641 yet */ |
| save_fpstate(dcontext, frame); |
| #endif /* LINUX && X86 */ |
| #ifdef DEBUG |
| if (d_r_stats->loglevel >= 3 && (d_r_stats->logmask & LOG_ASYNCH) != 0) { |
| LOG(THREAD, LOG_ASYNCH, 3, "new sigcontext " PFX ":\n", sc); |
| dump_sigcontext(dcontext, sc); |
| LOG(THREAD, LOG_ASYNCH, 3, "\n"); |
| } |
| IF_AARCHXX(ASSERT(get_sigcxt_stolen_reg(sc) != (reg_t)*get_dr_tls_base_addr())); |
| #endif |
| /* FIXME: other state? debug regs? |
| * if no syscall allowed between master_ (when frame created) and |
| * receiving, then don't have to worry about debug regs, etc. |
| * check for syscall when record pending, if it exists, try to |
| * receive in pre_system_call or something? what if ignorable? FIXME! |
| */ |
| |
| if (!info->sigpending[sig]->use_sigcontext) { |
| /* for the pc we want the app pc not the cache pc */ |
| sc->SC_XIP = (ptr_uint_t)dcontext->next_tag; |
| LOG(THREAD, LOG_ASYNCH, 3, "\tset frame's eip to " PFX "\n", sc->SC_XIP); |
| } |
| |
| #ifdef CLIENT_INTERFACE |
| sigcontext_t sc_interrupted = *sc; |
| action = send_signal_to_client(dcontext, sig, frame, NULL, |
| info->sigpending[sig]->access_address, |
| false /*not blocked*/, NULL); |
| /* in order to pass to the client, we come all the way here for signals |
| * the app has no handler for |
| */ |
| if (action == DR_SIGNAL_REDIRECT) { |
| /* send_signal_to_client copied mcontext into frame's sc */ |
| priv_mcontext_t *mcontext = get_mcontext(dcontext); |
| ucontext_to_mcontext(mcontext, uc); |
| if (is_building_trace(dcontext)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsquashing trace-in-progress\n"); |
| trace_abort(dcontext); |
| } |
| sig_full_cxt_t sc_interrupted_full = { &sc_interrupted, NULL /*not provided*/ }; |
| instrument_kernel_xfer(dcontext, DR_XFER_CLIENT_REDIRECT, sc_interrupted_full, |
| NULL, NULL, mcontext->pc, mcontext->xsp, osc_empty, |
| mcontext, sig); |
| /* We're going back to dispatch and won't use the sigcontext. |
| * canonicalize_pc_target() will set the dcontext ISA mode for us. |
| */ |
| dcontext->next_tag = canonicalize_pc_target(dcontext, mcontext->pc); |
| return true; /* don't try another signal */ |
| } else if (action == DR_SIGNAL_SUPPRESS || |
| (info->app_sigaction[sig] != NULL && |
| info->app_sigaction[sig]->handler == (handler_t)SIG_IGN)) { |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: not delivering!\n", |
| (action == DR_SIGNAL_SUPPRESS) ? "client suppressing signal" |
| : "app signal handler is SIG_IGN"); |
| return false; |
| } else if (action == DR_SIGNAL_BYPASS || |
| (info->app_sigaction[sig] == NULL || |
| info->app_sigaction[sig]->handler == (handler_t)SIG_DFL)) { |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: executing default action\n", |
| (action == DR_SIGNAL_BYPASS) ? "client forcing default" |
| : "app signal handler is SIG_DFL"); |
| if (info->sigpending[sig]->use_sigcontext) { |
| /* after the default action we want to go to the sigcontext */ |
| dcontext->next_tag = canonicalize_pc_target(dcontext, (app_pc)sc->SC_XIP); |
| ucontext_to_mcontext(get_mcontext(dcontext), uc); |
| IF_ARM(dr_set_isa_mode(dcontext, get_pc_mode_from_cpsr(sc), NULL)); |
| } |
| execute_default_from_dispatch(dcontext, sig, frame); |
| return true; |
| } |
| CLIENT_ASSERT(action == DR_SIGNAL_DELIVER, "invalid signal event return value"); |
| #endif |
| |
| /* now that we've made all our changes and given the client a |
| * chance to make changes, copy the frame to the appropriate stack |
| * location and convert to non-rt if necessary |
| */ |
| copy_frame_to_stack(dcontext, info, sig, frame, xsp, true /*pending*/); |
| /* now point at the app's frame */ |
| sc = get_sigcontext_from_app_frame(info, sig, (void *)xsp); |
| |
| ASSERT(info->app_sigaction[sig] != NULL); |
| |
| /* add to set of blocked signals those in sigaction mask */ |
| blocked = info->app_sigaction[sig]->mask; |
| /* SA_NOMASK says whether to block sig itself or not */ |
| if ((info->app_sigaction[sig]->flags & SA_NOMASK) == 0) |
| kernel_sigaddset(&blocked, sig); |
| set_blocked(dcontext, &blocked, false /*relative: OR these in*/); |
| |
| /* if we were building a trace, kill it */ |
| if (is_building_trace(dcontext)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsquashing trace-in-progress\n"); |
| trace_abort(dcontext); |
| } |
| |
| /* Doesn't matter what most app registers are, signal handler doesn't |
| * expect anything except the frame on the stack. We do need to set xsp. |
| */ |
| mcontext->xsp = (ptr_uint_t)xsp; |
| /* Set up args to handler: int sig, kernel_siginfo_t *siginfo, |
| * kernel_ucontext_t *ucxt. |
| */ |
| #ifdef MACOS64 |
| mcontext->xdi = (reg_t)info->app_sigaction[sig]->handler; |
| int infostyle = TEST(SA_SIGINFO, info->app_sigaction[sig]->flags) |
| ? SIGHAND_STYLE_UC_FLAVOR |
| : SIGHAND_STYLE_UC_TRAD; |
| mcontext->xsi = infostyle; |
| mcontext->xdx = sig; |
| mcontext->xcx = (reg_t) & ((sigframe_rt_t *)xsp)->info; |
| mcontext->r8 = (reg_t) & ((sigframe_rt_t *)xsp)->uc; |
| #elif defined(X86_64) |
| mcontext->xdi = sig; |
| mcontext->xsi = (reg_t) & ((sigframe_rt_t *)xsp)->info; |
| mcontext->xdx = (reg_t) & ((sigframe_rt_t *)xsp)->uc; |
| #elif defined(AARCHXX) |
| mcontext->r0 = sig; |
| if (IS_RT_FOR_APP(info, sig)) { |
| mcontext->r1 = (reg_t) & ((sigframe_rt_t *)xsp)->info; |
| mcontext->r2 = (reg_t) & ((sigframe_rt_t *)xsp)->uc; |
| } |
| if (sig_has_restorer(info, sig)) |
| mcontext->lr = (reg_t)info->app_sigaction[sig]->restorer; |
| else |
| mcontext->lr = (reg_t)dynamorio_sigreturn; |
| #endif |
| #ifdef X86 |
| /* Clear eflags DF (signal handler should match function entry ABI) */ |
| mcontext->xflags &= ~EFLAGS_DF; |
| #endif |
| /* Make sure handler is next thing we execute */ |
| mcontext->pc = (app_pc)SIGACT_PRIMARY_HANDLER(info->app_sigaction[sig]); |
| |
| if ((info->app_sigaction[sig]->flags & SA_ONESHOT) != 0) { |
| /* clear handler now -- can't delete memory since sigreturn, |
| * others may look at sigaction struct, so we just set to default |
| */ |
| info->app_sigaction[sig]->handler = (handler_t)SIG_DFL; |
| } |
| #ifdef CLIENT_INTERFACE |
| sig_full_cxt_t sc_full = { sc, NULL /*not provided*/ }; |
| /* i#4041: Provide the actually-interrupted mid-rseq PC to this event. */ |
| ptr_uint_t official_xl8 = sc->SC_XIP; |
| sc->SC_XIP = |
| (ptr_uint_t)translate_last_direct_translation(dcontext, (app_pc)official_xl8); |
| instrument_kernel_xfer(dcontext, DR_XFER_SIGNAL_DELIVERY, sc_full, NULL, NULL, |
| mcontext->pc, mcontext->xsp, osc_empty, mcontext, sig); |
| dcontext->next_tag = canonicalize_pc_target(dcontext, mcontext->pc); |
| sc->SC_XIP = official_xl8; |
| #endif |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "\tset xsp to " PFX "\n", xsp); |
| return true; |
| } |
| |
| /* Sends a signal to a currently-native thread. dcontext can be NULL. */ |
| static void |
| execute_native_handler(dcontext_t *dcontext, int sig, sigframe_rt_t *our_frame) |
| { |
| /* If dcontext is NULL, we use a synthetic info struct where we fill in the |
| * info we need: the app's sigaction and sigstack settings. |
| */ |
| thread_sig_info_t synthetic = {}; |
| kernel_sigaction_t *sigact_array[SIGARRAY_SIZE] = {}; |
| bool intercept_array[SIGARRAY_SIZE]; |
| kernel_sigaction_t sigact_struct; |
| thread_sig_info_t *info; |
| LOG(THREAD, LOG_ASYNCH, 2, "%s for signal %d in thread " TIDFMT "\n", __FUNCTION__, |
| sig, get_sys_thread_id()); |
| if (dcontext != NULL) { |
| info = (thread_sig_info_t *)dcontext->signal_field; |
| } else { |
| if (atomic_read_bool(&multiple_handlers_present)) { |
| /* See i#1921 comment up top: we don't handle this. */ |
| report_unhandleable_signal_and_exit(sig, "multiple native handlers"); |
| ASSERT_NOT_REACHED(); |
| } |
| info = &synthetic; |
| synthetic.we_intercept = intercept_array; |
| synthetic.we_intercept[sig] = true; |
| synthetic.app_sigaction = sigact_array; |
| synthetic.app_sigaction[sig] = &sigact_struct; |
| d_r_read_lock(&detached_sigact_lock); |
| memcpy(&sigact_struct, &detached_sigact[sig], sizeof(sigact_struct)); |
| d_r_read_unlock(&detached_sigact_lock); |
| #ifdef HAVE_SIGALTSTACK |
| IF_DEBUG(int rc =) |
| sigaltstack_syscall(NULL, &synthetic.app_sigstack); |
| ASSERT(rc == 0); |
| #endif |
| } |
| kernel_ucontext_t *uc = get_ucontext_from_rt_frame(our_frame); |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); |
| kernel_sigset_t blocked; |
| byte *xsp = get_sigstack_frame_ptr(dcontext, info, sig, our_frame); |
| |
| /* We do not send the signal to clients as it is not a managed thread and we |
| * cannot really take any action except send it to the native handler. |
| */ |
| |
| if (info->app_sigaction[sig] == NULL || |
| info->app_sigaction[sig]->handler == (handler_t)SIG_DFL) { |
| /* TODO i#1921: We want to call: |
| * execute_default_from_cache(dcontext, sig, our_frame, sc, false) |
| * But we need to pass in our sythentic info. |
| * And not all that callee's code will work with a NULL dcontext yet: it needs |
| * some work. |
| * Plus it calls handle_free() which we need to arrange for. |
| * For now we bail. |
| */ |
| if (can_always_delay[sig]) { |
| /* We are dropping asynchronous signals during detach. The thread may |
| * already have lost its TLS (xref i#3535). A safe read may result in a |
| * crash if DR's SIGSEGV handler is removed before the safe read's |
| * SIGSEGV is delivered. |
| * |
| * Note that besides dropping potentially important signals, there is |
| * still a small race window if the signal gets delivered after the |
| * detach has finished, i.e. doing detach is false. This is an issue in |
| * particular if the app has started re-attaching. |
| * |
| * Signals that are not clearly asynchronous may hit corner case(s) of |
| * i#3535. (xref i#26). |
| */ |
| if (doing_detach) { |
| DOLOG(1, LOG_ASYNCH, { dump_sigcontext(GLOBAL_DCONTEXT, sc); }); |
| SYSLOG_INTERNAL_ERROR( |
| "Signal in native thread during detach with default action is not " |
| "fully supported: dropping and continuing (tid=%d, sig=%d)", |
| get_sys_thread_id(), sig); |
| } |
| return; /* Just drop the signal. */ |
| } |
| if (default_action[sig] == DEFAULT_IGNORE) |
| return; |
| report_unhandleable_signal_and_exit(sig, "default action in native thread"); |
| ASSERT_NOT_REACHED(); |
| return; |
| } |
| ASSERT(info->app_sigaction[sig] != NULL && |
| info->app_sigaction[sig]->handler != (handler_t)SIG_IGN && |
| info->app_sigaction[sig]->handler != (handler_t)SIG_DFL); |
| |
| RSTATS_INC(num_signals); |
| RSTATS_INC(num_native_signals); |
| report_app_problem(dcontext, APPFAULT_FAULT, (byte *)sc->SC_XIP, (byte *)sc->SC_FP, |
| "\nSignal %d delivered to application handler.\n", sig); |
| |
| copy_frame_to_stack(dcontext, info, sig, our_frame, (void *)xsp, false /*!pending*/); |
| |
| blocked = info->app_sigaction[sig]->mask; |
| if (!TEST(SA_NOMASK, (info->app_sigaction[sig]->flags))) |
| kernel_sigaddset(&blocked, sig); |
| DOLOG(2, LOG_ASYNCH, { |
| LOG(THREAD, LOG_ASYNCH, 3, "Pre-signal blocked signals, stored in our frame:\n"); |
| dump_sigset(dcontext, (kernel_sigset_t *)&our_frame->uc.uc_sigmask); |
| }); |
| for (int i = 1; i <= MAX_SIGNUM; i++) { |
| if (kernel_sigismember(&blocked, i)) { |
| kernel_sigaddset((kernel_sigset_t *)&our_frame->uc.uc_sigmask, i); |
| } |
| } |
| DOLOG(2, LOG_ASYNCH, { |
| LOG(THREAD, LOG_ASYNCH, 3, "Blocked signals within app handler will be:\n"); |
| dump_sigset(dcontext, (kernel_sigset_t *)&our_frame->uc.uc_sigmask); |
| }); |
| |
| /* Now edit the resumption point when master_signal_handler returns to go |
| * straight to the app handler. |
| */ |
| sc->SC_XSP = (ptr_uint_t)xsp; |
| /* Set up args to handler. */ |
| #ifdef X86_64 |
| sc->SC_XDI = sig; |
| sc->SC_XSI = (reg_t) & ((sigframe_rt_t *)xsp)->info; |
| sc->SC_XDX = (reg_t) & ((sigframe_rt_t *)xsp)->uc; |
| #elif defined(AARCHXX) |
| sc->SC_R0 = sig; |
| if (IS_RT_FOR_APP(info, sig)) { |
| sc->SC_R1 = (reg_t) & ((sigframe_rt_t *)xsp)->info; |
| sc->SC_R2 = (reg_t) & ((sigframe_rt_t *)xsp)->uc; |
| } |
| if (sig_has_restorer(info, sig)) |
| sc->SC_LR = (reg_t)info->app_sigaction[sig]->restorer; |
| else |
| sc->SC_LR = (reg_t)dynamorio_sigreturn; |
| #endif |
| sc->SC_XIP = (reg_t)SIGACT_PRIMARY_HANDLER(info->app_sigaction[sig]); |
| |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: set pc to handler %p with xsp=%p\n", __FUNCTION__, |
| SIGACT_PRIMARY_HANDLER(info->app_sigaction[sig]), xsp); |
| |
| if (TEST(SA_ONESHOT, detached_sigact[sig].flags)) { |
| /* XXX: When we remove DR's handler from the final thread we'll ignore this |
| * delivery: but we can't safely access that data struct here. The best we |
| * can do is avoid delivering twice ourselves. |
| */ |
| d_r_write_lock(&detached_sigact_lock); |
| memset(&detached_sigact[sig], 0, sizeof(detached_sigact[sig])); |
| d_r_write_unlock(&detached_sigact_lock); |
| } |
| } |
| |
| /* The arg to SYS_kill, i.e., the signal number, should be in dcontext->sys_param0 */ |
| /* This routine unblocks signals, but the caller must set the handler to default. */ |
| static void |
| terminate_via_kill(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| ASSERT(dcontext == get_thread_private_dcontext()); |
| /* Enure signal_thread_exit() will not re-block */ |
| memset(&info->app_sigblocked, 0, sizeof(info->app_sigblocked)); |
| |
| /* FIXME PR 541760: there can be multiple thread groups and thus |
| * this may not exit all threads in the address space |
| */ |
| block_cleanup_and_terminate( |
| dcontext, SYS_kill, |
| /* Pass -pid in case main thread has exited |
| * in which case will get -ESRCH |
| */ |
| IF_VMX86(os_in_vmkernel_userworld() ? -(int)get_process_id() :) get_process_id(), |
| dcontext->sys_param0, true, 0, 0); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| bool |
| is_currently_on_sigaltstack(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| byte *cur_esp; |
| GET_STACK_PTR(cur_esp); |
| return (cur_esp >= (byte *)info->sigstack.ss_sp && |
| cur_esp < (byte *)info->sigstack.ss_sp + info->sigstack.ss_size); |
| } |
| |
| static void |
| terminate_via_kill_from_anywhere(dcontext_t *dcontext, int sig) |
| { |
| dcontext->sys_param0 = sig; /* store arg to SYS_kill */ |
| if (is_currently_on_sigaltstack(dcontext)) { |
| /* We can't clean up our sigstack properly when we're on it |
| * (i#1160) so we terminate on the dstack. |
| */ |
| call_switch_stack(dcontext, dcontext->dstack, |
| (void (*)(void *))terminate_via_kill, NULL /*!d_r_initstack */, |
| false /*no return */); |
| } else { |
| terminate_via_kill(dcontext); |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* xref os_request_fatal_coredump() */ |
| void |
| os_terminate_via_signal(dcontext_t *dcontext, terminate_flags_t flags, int sig) |
| { |
| if (signal_is_interceptable(sig)) { |
| bool set_action = false; |
| #if defined(STATIC_LIBRARY) && defined(LINUX) |
| if (INTERNAL_OPTION(invoke_app_on_crash)) { |
| /* We come here for asserts. Faults already bypass this routine. */ |
| dcontext_t *my_dc = get_thread_private_dcontext(); |
| if (my_dc != NULL) { |
| thread_sig_info_t *info = (thread_sig_info_t *)my_dc->signal_field; |
| if (info != NULL && info->app_sigaction[sig] != NULL && |
| IS_RT_FOR_APP(info, sig)) { |
| set_action = true; |
| sigaction_syscall(sig, info->app_sigaction[sig], NULL); |
| } |
| } |
| } |
| #endif |
| if (!set_action) { |
| DEBUG_DECLARE(bool res =) |
| set_default_signal_action(sig); |
| ASSERT(res); |
| } |
| } |
| if (TEST(TERMINATE_CLEANUP, flags)) { |
| /* we enter from several different places, so rewind until top-level kstat */ |
| KSTOP_REWIND_UNTIL(thread_measured); |
| ASSERT(dcontext != NULL); |
| dcontext->sys_param0 = sig; |
| /* XXX: the comment in the else below implies some systems have SYS_kill |
| * of SIGSEGV w/ no handler on oneself actually return. |
| * cleanup_and_terminate won't return to us and will use global_do_syscall |
| * to invoke SYS_kill, which in debug will do an inf loop (good!) but |
| * in release will do SYS_exit_group -- oh well, the systems I'm testing |
| * on do an immediate exit. |
| */ |
| terminate_via_kill_from_anywhere(dcontext, sig); |
| } else { |
| /* general clean up is unsafe: just remove .1config file */ |
| d_r_config_exit(); |
| dynamorio_syscall(SYS_kill, 2, get_process_id(), sig); |
| /* We try both the SYS_kill and the immediate crash since on some platforms |
| * the SIGKILL is delayed and on others the *-1 is hanging(?): should investigate |
| */ |
| if (sig == SIGSEGV) /* make doubly-sure */ |
| *((int *)PTR_UINT_MINUS_1) = 0; |
| while (true) { |
| /* in case signal delivery is delayed we wait...forever */ |
| os_thread_yield(); |
| } |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| static bool |
| execute_default_action(dcontext_t *dcontext, int sig, sigframe_rt_t *frame, |
| sigcontext_t *sc_orig, bool from_dispatch, bool forged) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| sigcontext_t *sc = get_sigcontext_from_rt_frame(frame); |
| byte *pc = (byte *)sc->SC_XIP; |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "execute_default_action for signal %d\n", sig); |
| |
| /* should only come here for signals we catch, or signal with ONESHOT |
| * that didn't sigreturn |
| */ |
| ASSERT(info->we_intercept[sig] || |
| (info->app_sigaction[sig]->flags & SA_ONESHOT) != 0); |
| |
| if (info->app_sigaction[sig] != NULL && |
| (info->app_sigaction[sig]->flags & SA_ONESHOT) != 0) { |
| if (!info->we_intercept[sig]) { |
| handler_free(dcontext, info->app_sigaction[sig], sizeof(kernel_sigaction_t)); |
| info->app_sigaction[sig] = NULL; |
| } |
| } |
| |
| /* FIXME PR 205310: we can't always perfectly emulate the default |
| * behavior. To execute the default action, we have to un-register our |
| * handler, if we have one, for signals whose default action is not |
| * ignore or that will just be re-raised upon returning to the |
| * interrupted context -- FIXME: are any of the ignores repeated? |
| * SIGURG? |
| * |
| * If called from execute_handler_from_cache(), our master_signal_handler() |
| * is going to return directly to the translated context: which means we |
| * go native to re-execute the instr, which if it does in fact generate |
| * the signal again means we have a nice transparent core dump. |
| * |
| * If called from execute_handler_from_dispatch(), we need to generate |
| * the signal ourselves. |
| */ |
| if (default_action[sig] != DEFAULT_IGNORE) { |
| DEBUG_DECLARE(bool ok =) |
| set_default_signal_action(sig); |
| ASSERT(ok); |
| |
| /* FIXME: to avoid races w/ shared handlers should set a flag to |
| * prevent another thread from re-enabling. |
| * Perhaps worse: what if this signal arrives for another thread |
| * in the meantime (and the default is not terminate)? |
| */ |
| if (info->shared_app_sigaction) { |
| LOG(THREAD, LOG_ASYNCH, 1, |
| "WARNING: having to install SIG_DFL for thread " TIDFMT ", but will be " |
| "shared!\n", |
| d_r_get_thread_id()); |
| } |
| if (default_action[sig] == DEFAULT_TERMINATE || |
| default_action[sig] == DEFAULT_TERMINATE_CORE) { |
| report_app_problem(dcontext, APPFAULT_CRASH, pc, (byte *)sc->SC_FP, |
| "\nSignal %d delivered to application as default " |
| "action.\n", |
| sig); |
| /* App may call sigaction to set handler SIG_DFL (unnecessary but legal), |
| * in which case DR will put a handler in info->app_sigaction[sig]. |
| * We must clear it, otherwise, signal_thread_exit may cleanup the |
| * handler and set it to SIG_IGN instead. |
| */ |
| if (info->app_sigaction[sig] != NULL) { |
| ASSERT(info->we_intercept[sig]); |
| handler_free(dcontext, info->app_sigaction[sig], |
| sizeof(kernel_sigaction_t)); |
| info->app_sigaction[sig] = NULL; |
| } |
| /* N.B.: we don't have to restore our handler because the |
| * default action is for the process (entire thread group for NPTL) to die! |
| */ |
| if (from_dispatch || can_always_delay[sig] || forged || |
| is_sys_kill(dcontext, pc, (byte *)sc->SC_XSP, &frame->info)) { |
| /* This must have come from SYS_kill rather than raised by |
| * a faulting instruction. Thus we can't go re-execute the |
| * instr in order to re-raise the signal (if from_dispatch, |
| * we delayed and can't re-execute anyway). Instead we |
| * re-generate via SYS_kill. An alternative, if we don't |
| * care about generating a core dump, is to use SYS_exit |
| * and pass the right exit code to indicate the signal |
| * number: that would avoid races w/ the sigaction. |
| * |
| * FIXME: should have app make the syscall to get a more |
| * transparent core dump! |
| */ |
| LOG(THREAD, LOG_ASYNCH, 1, "Terminating via kill\n"); |
| if (!from_dispatch && !forged) |
| KSTOP_NOT_MATCHING_NOT_PROPAGATED(fcache_default); |
| KSTOP_NOT_MATCHING_NOT_PROPAGATED(dispatch_num_exits); |
| if (is_couldbelinking(dcontext)) /* won't be for SYS_kill (i#1159) */ |
| enter_nolinking(dcontext, NULL, false); |
| /* we could be on sigstack so call this version: */ |
| terminate_via_kill_from_anywhere(dcontext, sig); |
| ASSERT_NOT_REACHED(); |
| } else { |
| /* We assume that re-executing the interrupted instr will |
| * re-raise the fault. We could easily be wrong: |
| * xref PR 363811 infinite loop due to memory we |
| * thought was unreadable and thus thought would raise |
| * a signal; xref PR 368277 to improve is_sys_kill(), and the |
| * "forged" parameter that puts us in the if() above. |
| * FIXME PR 205310: we should check whether we come out of |
| * the cache when we expected to terminate! |
| * |
| * An alternative is to abandon transparent core dumps and |
| * do the same explicit SYS_kill we do for from_dispatch. |
| * That would let us clean up DR as well. |
| * FIXME: currently we do not clean up DR for a synchronous |
| * signal death, but we do for asynch. |
| */ |
| /* i#552: cleanup and raise client exit event */ |
| int instr_sz = 0; |
| thread_sig_info_t *info; |
| /* We are on the sigstack now, so assign it to NULL to avoid being |
| * freed during process exit cleanup |
| */ |
| info = (thread_sig_info_t *)dcontext->signal_field; |
| info->sigstack.ss_sp = NULL; |
| /* We enter from several different places, so rewind until |
| * top-level kstat. |
| */ |
| KSTOP_REWIND_UNTIL(thread_measured); |
| /* We try to raise the same signal in app's context so a correct |
| * coredump can be generated. However, the client might change |
| * the code in a way that the corresponding app code won't |
| * raise the signal, so we first check if the app instr is the |
| * same as instr in the cache, and raise the signal (by return). |
| * Otherwise, we kill the process instead. |
| * XXX: if the PC is unreadable we'll just crash here...should check |
| * for readability safely. |
| */ |
| ASSERT(sc_orig != NULL); |
| instr_sz = decode_sizeof(dcontext, (byte *)sc_orig->SC_XIP, |
| NULL _IF_X86_64(NULL)); |
| if (instr_sz != 0 && |
| pc != NULL && /* avoid crash on xl8 failure (i#1699) */ |
| instr_sz == decode_sizeof(dcontext, pc, NULL _IF_X86_64(NULL)) && |
| memcmp(pc, (byte *)sc_orig->SC_XIP, instr_sz) == 0) { |
| /* the app instr matches the cache instr; cleanup and raise the |
| * the signal in the app context |
| */ |
| LOG(THREAD, LOG_ASYNCH, 1, "Raising signal by re-executing\n"); |
| dynamo_process_exit(); |
| /* we cannot re-enter the cache, which is freed by now */ |
| ASSERT(!from_dispatch); |
| return false; |
| } else { |
| /* mismatch, cleanup and terminate */ |
| LOG(THREAD, LOG_ASYNCH, 1, "Terminating via kill\n"); |
| dcontext->sys_param0 = sig; |
| terminate_via_kill(dcontext); |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| } else { |
| /* FIXME PR 297033: in order to intercept DEFAULT_STOP / |
| * DEFAULT_CONTINUE signals, we need to set sigcontext to point |
| * to some kind of regain-control routine, so that when our |
| * thread gets to run again we can reset our handler. So far |
| * we have no signals that fall here that we intercept. |
| */ |
| CLIENT_ASSERT(false, "STOP/CONT signals not supported"); |
| } |
| #if defined(DEBUG) && defined(INTERNAL) |
| if (sig == SIGSEGV && !dynamo_exited) { |
| /* pc should be an app pc at this point (it was translated) -- |
| * check for bad cases here |
| */ |
| if (safe_is_in_fcache(dcontext, pc, (byte *)sc->SC_XSP)) { |
| fragment_t wrapper; |
| fragment_t *f; |
| LOG(THREAD, LOG_ALL, 1, |
| "Received SIGSEGV at pc " PFX " in thread " TIDFMT "\n", pc, |
| d_r_get_thread_id()); |
| f = fragment_pclookup(dcontext, pc, &wrapper); |
| if (f) |
| disassemble_fragment(dcontext, f, false); |
| ASSERT_NOT_REACHED(); |
| } else if (in_generated_routine(dcontext, pc)) { |
| LOG(THREAD, LOG_ALL, 1, |
| "Received SIGSEGV at generated non-code-cache pc " PFX "\n", pc); |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| #endif |
| } |
| |
| /* now continue at the interruption point and re-raise the signal */ |
| return true; |
| } |
| |
| static bool |
| execute_default_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *frame, |
| sigcontext_t *sc_orig, bool forged) |
| { |
| return execute_default_action(dcontext, sig, frame, sc_orig, false, forged); |
| } |
| |
| static void |
| execute_default_from_dispatch(dcontext_t *dcontext, int sig, sigframe_rt_t *frame) |
| { |
| execute_default_action(dcontext, sig, frame, NULL, true, false); |
| } |
| |
| void |
| receive_pending_signal(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| sigpending_t *temp; |
| int sig; |
| LOG(THREAD, LOG_ASYNCH, 3, "receive_pending_signal\n"); |
| if (info->interrupted != NULL) { |
| relink_interrupted_fragment(dcontext, info); |
| } |
| /* grab first pending signal |
| * XXX: start with real-time ones? |
| */ |
| /* "lock" the array to prevent a new signal that interrupts this bit of |
| * code from prepended or deleting from the array while we're accessing it |
| */ |
| info->accessing_sigpending = true; |
| /* barrier to prevent compiler from moving the above write below the loop */ |
| __asm__ __volatile__("" : : : "memory"); |
| if (!info->multiple_pending_units && |
| info->num_pending + 2 >= DYNAMO_OPTION(max_pending_signals)) { |
| /* We're close to the limit: proactively get a new unit while it's safe |
| * to acquire locks. We do that by pushing over the edge. |
| * We assume that filling up a 2nd unit is too pathological to plan for. |
| */ |
| info->multiple_pending_units = true; |
| SYSLOG_INTERNAL_WARNING("many pending signals: asking for 2nd special unit"); |
| sigpending_t *temp1 = special_heap_alloc(info->sigheap); |
| sigpending_t *temp2 = special_heap_alloc(info->sigheap); |
| sigpending_t *temp3 = special_heap_alloc(info->sigheap); |
| special_heap_free(info->sigheap, temp1); |
| special_heap_free(info->sigheap, temp2); |
| special_heap_free(info->sigheap, temp3); |
| } |
| for (sig = 1; sig <= MAX_SIGNUM; sig++) { |
| if (info->sigpending[sig] != NULL) { |
| bool executing = true; |
| /* We do not re-check whether blocked if it was unblocked at |
| * receive time, to properly handle sigsuspend (i#1340). |
| */ |
| if (!info->sigpending[sig]->unblocked && |
| kernel_sigismember(&info->app_sigblocked, sig)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsignal %d is blocked!\n", sig); |
| continue; |
| } |
| LOG(THREAD, LOG_ASYNCH, 3, "\treceiving signal %d\n", sig); |
| /* execute_handler_from_dispatch()'s call to copy_frame_to_stack() is |
| * allowed to remove the front entry from info->sigpending[sig] and |
| * jump to d_r_dispatch. |
| */ |
| executing = execute_handler_from_dispatch(dcontext, sig); |
| temp = info->sigpending[sig]; |
| info->sigpending[sig] = temp->next; |
| special_heap_free(info->sigheap, temp); |
| info->num_pending--; |
| |
| /* only one signal at a time! */ |
| if (executing) { |
| /* Make negative so our fcache_enter check makes progress but |
| * our C code still considers there to be pending signals. |
| */ |
| dcontext->signals_pending = -1; |
| break; |
| } |
| } |
| } |
| /* barrier to prevent compiler from moving the below write above the loop */ |
| __asm__ __volatile__("" : : : "memory"); |
| info->accessing_sigpending = false; |
| |
| /* we only clear this on a call to us where we find NO pending signals */ |
| if (sig > MAX_SIGNUM) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tclearing signals_pending flag\n"); |
| dcontext->signals_pending = 0; |
| } |
| } |
| |
| /* Returns false if should NOT issue syscall. */ |
| bool |
| #ifdef LINUX |
| handle_sigreturn(dcontext_t *dcontext, bool rt) |
| #else |
| handle_sigreturn(dcontext_t *dcontext, void *ucxt_param, int style) |
| #endif |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| sigcontext_t *sc = NULL; /* initialize to satisfy Mac clang */ |
| kernel_ucontext_t *ucxt = NULL; |
| int sig = 0; |
| app_pc next_pc; |
| #if defined(DEBUG) || !defined(MACOS64) |
| /* xsp was put in mcontext prior to pre_system_call() */ |
| reg_t xsp = get_mcontext(dcontext)->xsp; |
| #endif |
| #ifdef MACOS |
| bool rt = true; |
| #endif |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "%ssigreturn()\n", rt ? "rt_" : ""); |
| LOG(THREAD, LOG_ASYNCH, 3, "\txsp is " PFX "\n", xsp); |
| |
| #ifdef PROGRAM_SHEPHERDING |
| /* if (!sig_has_restorer, region was never added to exec list, |
| * allowed as pattern only and kicked off at first write via |
| * selfmod detection or otherwise if vsyscall, so no worries |
| * about having to remove it here |
| */ |
| #endif |
| |
| /* The easiest way to set all the non-GPR state that DR does not separately |
| * preserve is to actually execute the sigreturn syscall, so we set up to do |
| * that. We do not want to change DR's signal state, however, so we set it |
| * back to DR's values after processing the state for the app. |
| */ |
| kernel_sigset_t our_mask; |
| sigprocmask_syscall(SIG_SETMASK, NULL, &our_mask, sizeof(our_mask)); |
| |
| /* get sigframe: it's the top thing on the stack, except the ret |
| * popped off pretcode. |
| * WARNING: handler for tcsh's window_change (SIGWINCH) clobbers its |
| * signal # arg, so don't use frame->sig! (kernel doesn't look at sig |
| * so app can get away with it) |
| */ |
| if (rt) { |
| #ifdef LINUX |
| sigframe_rt_t *frame = (sigframe_rt_t *)(xsp IF_X86(-sizeof(char *))); |
| /* use si_signo instead of sig, less likely to be clobbered by app */ |
| sig = frame->info.si_signo; |
| # ifdef X86_32 |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsignal was %d (did == param %d)\n", sig, |
| frame->sig); |
| if (frame->sig != sig) |
| LOG(THREAD, LOG_ASYNCH, 1, "WARNING: app sig handler clobbered sig param\n"); |
| # endif |
| sc = get_sigcontext_from_app_frame(info, sig, (void *)frame); |
| ucxt = &frame->uc; |
| /* Check again for the magic words. See the i#3812 comment above. */ |
| IF_X86(ASSERT( |
| (sc->fpstate->sw_reserved.magic1 == 0 && |
| sc->fpstate->sw_reserved.extended_size == sizeof(kernel_fpstate_t)) || |
| (sc->fpstate->sw_reserved.magic1 == FP_XSTATE_MAGIC1 && |
| *(int *)((byte *)sc->fpstate + sc->fpstate->sw_reserved.extended_size - |
| FP_XSTATE_MAGIC2_SIZE) == FP_XSTATE_MAGIC2))); |
| #elif defined(MACOS) |
| ucxt = (kernel_ucontext_t *)ucxt_param; |
| if (ucxt == NULL) { |
| /* On Mac the kernel seems to store state on whether the process is |
| * on the altstack, so longjmp calls _sigunaltstack() which issues a |
| * sigreturn syscall telling the kernel about the altstack change, |
| * with a NULL context. |
| */ |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsigunalstack sigreturn: no context\n"); |
| return true; |
| } |
| # ifdef X64 |
| kernel_siginfo_t *siginfo = (kernel_siginfo_t *)ucxt - 1; |
| sig = siginfo->si_signo; |
| # else |
| /* The initial frame fields on the stack are messed up due to |
| * params to handler from tramp, so use params to syscall. |
| * XXX: we don't have signal # though: so we have to rely on app |
| * not clobbering the sig param field. |
| */ |
| sig = *(int *)xsp; |
| # endif |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsignal was %d\n", sig); |
| sc = SIGCXT_FROM_UCXT(ucxt); |
| #endif |
| ASSERT(sig > 0 && sig <= MAX_SIGNUM && IS_RT_FOR_APP(info, sig)); |
| /* Re-set sigstack from the value stored in the frame. Silently ignore failure, |
| * just like the kernel does. |
| */ |
| uint ignored; |
| /* The kernel checks for being on the stack *after* swapping stacks, so pass |
| * sc->SC_XSP as the current stack. |
| */ |
| handle_sigaltstack(dcontext, &ucxt->uc_stack, NULL, sc->SC_XSP, &ignored); |
| /* Restore DR's so sigreturn syscall won't change it. */ |
| ucxt->uc_stack = info->sigstack; |
| |
| /* FIXME: what if handler called sigaction and requested rt |
| * when itself was non-rt? |
| */ |
| |
| /* Discard blocked signals, re-set from prev mask stored in frame. */ |
| set_blocked(dcontext, SIGMASK_FROM_UCXT(ucxt), true /*absolute*/); |
| /* Restore DR's so sigreturn syscall won't change it. */ |
| *SIGMASK_FROM_UCXT(ucxt) = our_mask; |
| } |
| #if defined(LINUX) && !defined(X64) |
| else { |
| /* FIXME: libc's restorer pops prior to calling sigreturn, I have |
| * no idea why, but kernel asks for xsp-8 not xsp-4...weird! |
| */ |
| kernel_sigset_t prevset; |
| sigframe_plain_t *frame = (sigframe_plain_t *)(xsp IF_X86(-8)); |
| /* We don't trust frame->sig (app sometimes clobbers it), and for |
| * plain frame there's no other place that sig is stored, |
| * so as a hack we added a new frame! |
| * FIXME: this means we won't support nonstandard use of SYS_sigreturn, |
| * e.g., as NtContinue, if frame didn't come from a real signal and so |
| * wasn't copied to stack by us. |
| */ |
| sig = frame->sig_noclobber; |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsignal was %d (did == param %d)\n", sig, |
| IF_X86_ELSE(frame->sig, 0)); |
| # ifdef X86_32 |
| if (frame->sig != sig) |
| LOG(THREAD, LOG_ASYNCH, 1, "WARNING: app sig handler clobbered sig param\n"); |
| # endif |
| ASSERT(sig > 0 && sig <= MAX_SIGNUM && !IS_RT_FOR_APP(info, sig)); |
| sc = get_sigcontext_from_app_frame(info, sig, (void *)frame); |
| /* discard blocked signals, re-set from prev mask stored in frame */ |
| prevset.sig[0] = frame->IF_X86_ELSE(sc.oldmask, uc.uc_mcontext.oldmask); |
| if (_NSIG_WORDS > 1) { |
| memcpy(&prevset.sig[1], &frame->IF_X86_ELSE(extramask, uc.sigset_ex), |
| sizeof(prevset.sig[1])); |
| } |
| # ifdef ARM |
| ucxt = &frame->uc; /* we leave ucxt NULL for x86: not needed there */ |
| # endif |
| set_blocked(dcontext, &prevset, true /*absolute*/); |
| /* Restore DR's so sigreturn syscall won't change it. */ |
| convert_rt_mask_to_nonrt(frame, &our_mask); |
| } |
| #endif /* LINUX */ |
| |
| /* Make sure we deliver pending signals that are now unblocked. |
| */ |
| check_signals_pending(dcontext, info); |
| |
| /* if we were building a trace, kill it */ |
| if (is_building_trace(dcontext)) { |
| LOG(THREAD, LOG_ASYNCH, 3, "\tsquashing trace-in-progress\n"); |
| trace_abort(dcontext); |
| } |
| |
| /* Defensively check for NULL. |
| * XXX i#3182: It did happen but it is not clear how. |
| */ |
| if (info->app_sigaction[sig] != NULL && |
| TEST(SA_ONESHOT, info->app_sigaction[sig]->flags)) { |
| ASSERT(info->app_sigaction[sig]->handler == (handler_t)SIG_DFL); |
| if (!info->we_intercept[sig]) { |
| /* let kernel do default independent of us */ |
| handler_free(dcontext, info->app_sigaction[sig], sizeof(kernel_sigaction_t)); |
| info->app_sigaction[sig] = NULL; |
| } |
| } |
| |
| ASSERT(!safe_is_in_fcache(dcontext, (app_pc)sc->SC_XIP, (byte *)sc->SC_XSP)); |
| |
| #ifdef CLIENT_INTERFACE |
| sig_full_cxt_t sc_full = { sc, NULL /*not provided*/ }; |
| get_mcontext(dcontext)->pc = dcontext->next_tag; |
| instrument_kernel_xfer(dcontext, DR_XFER_SIGNAL_RETURN, osc_empty, NULL, |
| get_mcontext(dcontext), (app_pc)sc->SC_XIP, sc->SC_XSP, |
| sc_full, NULL, sig); |
| #endif |
| |
| #ifdef DEBUG |
| if (d_r_stats->loglevel >= 3 && (d_r_stats->logmask & LOG_ASYNCH) != 0) { |
| LOG(THREAD, LOG_ASYNCH, 3, "returning-to sigcontext " PFX ":\n", sc); |
| dump_sigcontext(dcontext, sc); |
| } |
| #endif |
| |
| /* XXX i#1206: if we interrupted a non-ignorable syscall to run the app's |
| * handler, and we set up to restart the syscall, we'll come here with the |
| * translated syscall pc -- thus we can't distinguish from a signal interrupting |
| * the prior app instr. So we can't simply point at do_syscall and call |
| * set_at_syscall -- we have to re-interpret the syscall and re-run the |
| * pre-syscall handler. Hopefully all our pre-syscall handlers can handle that. |
| */ |
| |
| /* set up for d_r_dispatch */ |
| /* we have to use a different slot since next_tag ends up holding the do_syscall |
| * entry when entered from d_r_dispatch (we're called from |
| * pre_syscall, prior to entering cache) |
| */ |
| dcontext->asynch_target = canonicalize_pc_target( |
| dcontext, (app_pc)(sc->SC_XIP IF_ARM(| (TEST(EFLAGS_T, sc->SC_XFLAGS) ? 1 : 0)))); |
| next_pc = dcontext->asynch_target; |
| |
| #ifdef VMX86_SERVER |
| /* PR 404712: kernel only restores gp regs so we do it ourselves and avoid |
| * complexities of kernel's non-linux-like sigreturn semantics |
| */ |
| sig_full_cxt_t sc_full = { sc, NULL }; /* non-ARM so NULL ok */ |
| sigcontext_to_mcontext(get_mcontext(dcontext), &sc_full, DR_MC_ALL); |
| #else |
| /* HACK to get eax put into mcontext AFTER do_syscall */ |
| dcontext->next_tag = (app_pc)sc->IF_X86_ELSE(SC_XAX, SC_R0); |
| /* use special linkstub so we know why we came out of the cache */ |
| sc->IF_X86_ELSE(SC_XAX, SC_R0) = (ptr_uint_t)get_asynch_linkstub(); |
| |
| /* set our sigreturn context to point to fcache_return */ |
| /* We don't need PC_AS_JMP_TGT b/c the kernel uses EFLAGS_T for the mode */ |
| sc->SC_XIP = (ptr_uint_t)fcache_return_routine(dcontext); |
| |
| /* if we overlaid inner frame on nested signal, will end up with this |
| * error -- disable in release build since this is often app's fault (stack |
| * too small) |
| * FIXME: how make this transparent? what ends up happening is that we |
| * get a segfault when we start interpreting d_r_dispatch, we want to make it |
| * look like whatever would happen to the app... |
| */ |
| ASSERT((app_pc)sc->SC_XIP != next_pc); |
| # ifdef AARCHXX |
| ASSERT(get_sigcxt_stolen_reg(sc) != (reg_t)*get_dr_tls_base_addr()); |
| /* We're called from DR and are not yet in the cache, so we want to set the |
| * mcontext slot, not the TLS slot, to set the stolen reg value. |
| */ |
| set_stolen_reg_val(get_mcontext(dcontext), get_sigcxt_stolen_reg(sc)); |
| /* The linkstub expects DR's TLS to be in the actual register. */ |
| set_sigcxt_stolen_reg(sc, (reg_t)*get_dr_tls_base_addr()); |
| # ifdef AARCH64 |
| /* On entry to the do_syscall gencode, we save X1 into TLS_REG1_SLOT. |
| * Then the sigreturn would redirect the flow to the fcache_return gencode. |
| * In fcache_return it recovers the values of x0 and x1 from TLS_SLOT 0 and 1. |
| */ |
| get_mcontext(dcontext)->r1 = sc->regs[1]; |
| # else |
| /* We're going to our fcache_return gencode which uses DEFAULT_ISA_MODE */ |
| set_pc_mode_in_cpsr(sc, DEFAULT_ISA_MODE); |
| # endif |
| # endif |
| #endif |
| |
| LOG(THREAD, LOG_ASYNCH, 3, "set next tag to " PFX ", sc->SC_XIP to " PFX "\n", |
| next_pc, sc->SC_XIP); |
| |
| return IF_VMX86_ELSE(false, true); |
| } |
| |
| bool |
| is_signal_restorer_code(byte *pc, size_t *len) |
| { |
| /* is this a sigreturn pattern placed by kernel on the stack or vsyscall page? |
| * for non-rt frame: |
| * 0x58 popl %eax |
| * 0xb8 <sysnum> movl SYS_sigreturn, %eax |
| * 0xcd 0x80 int 0x80 |
| * for rt frame: |
| * 0xb8 <sysnum> movl SYS_rt_sigreturn, %eax |
| * 0xcd 0x80 int 0x80 |
| */ |
| /* optimized we only need two uint reads, but we have to do |
| * some little-endian byte-order reverses to get the right result |
| */ |
| #define reverse(x) \ |
| ((((x)&0xff) << 24) | (((x)&0xff00) << 8) | (((x)&0xff0000) >> 8) | \ |
| (((x)&0xff000000) >> 24)) |
| #ifdef MACOS |
| # define SYS_RT_SIGRET SYS_sigreturn |
| #else |
| # define SYS_RT_SIGRET SYS_rt_sigreturn |
| #endif |
| #ifndef X64 |
| /* 58 b8 s4 s3 s2 s1 cd 80 */ |
| static const uint non_rt_1w = reverse(0x58b80000 | (reverse(SYS_sigreturn) >> 16)); |
| static const uint non_rt_2w = reverse((reverse(SYS_sigreturn) << 16) | 0xcd80); |
| #endif |
| /* b8 s4 s3 s2 s1 cd 80 XX */ |
| static const uint rt_1w = reverse(0xb8000000 | (reverse(SYS_RT_SIGRET) >> 8)); |
| static const uint rt_2w = reverse((reverse(SYS_RT_SIGRET) << 24) | 0x00cd8000); |
| /* test rt first as it's the most common |
| * only 7 bytes here so we ignore the last one (becomes msb since little-endian) |
| */ |
| if (*((uint *)pc) == rt_1w && (*((uint *)(pc + 4)) & 0x00ffffff) == rt_2w) { |
| if (len != NULL) |
| *len = 7; |
| return true; |
| } |
| #ifndef X64 |
| if (*((uint *)pc) == non_rt_1w && *((uint *)(pc + 4)) == non_rt_2w) { |
| if (len != NULL) |
| *len = 8; |
| return true; |
| } |
| #endif |
| return false; |
| } |
| |
| void |
| os_forge_exception(app_pc target_pc, dr_exception_type_t type) |
| { |
| /* PR 205136: |
| * We want to deliver now, and the caller expects us not to return. |
| * We have two alternatives: |
| * 1) Emulate stack frame, and call transfer_to_dispatch() for delivery. We |
| * may not know how to fill out every field of the frame (cr2, etc.). Plus, |
| * we have problems w/ default actions (PR 205310) but we have to solve |
| * those long-term anyway. We also have to create different frames based on |
| * whether app intercepts via rt or not. |
| * 2) Call SYS_tgkill from a special location that our handler can |
| * recognize and know it's a signal meant for the app and that the |
| * interrupted DR can be discarded. We'd then essentially repeat 1, |
| * but modifying the kernel-generated frame. We'd have to always |
| * intercept SIGILL. |
| * I'm going with #1 for now b/c the common case is simpler. |
| */ |
| dcontext_t *dcontext = get_thread_private_dcontext(); |
| #if defined(LINUX) && defined(X86) |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| #endif |
| char frame_no_xstate[sizeof(sigframe_rt_t)]; |
| sigframe_rt_t *frame = (sigframe_rt_t *)frame_no_xstate; |
| int sig; |
| dr_where_am_i_t cur_whereami = dcontext->whereami; |
| kernel_ucontext_t *uc = get_ucontext_from_rt_frame(frame); |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); |
| switch (type) { |
| case ILLEGAL_INSTRUCTION_EXCEPTION: sig = SIGILL; break; |
| case UNREADABLE_MEMORY_EXECUTION_EXCEPTION: sig = SIGSEGV; break; |
| case SINGLE_STEP_EXCEPTION: ASSERT_NOT_IMPLEMENTED(false); /* FIXME: i#2144 */ |
| case IN_PAGE_ERROR_EXCEPTION: /* fall-through: Windows only */ |
| default: |
| ASSERT_NOT_REACHED(); |
| sig = SIGSEGV; |
| break; |
| } |
| |
| LOG(GLOBAL, LOG_ASYNCH, 1, "os_forge_exception sig=%d\n", sig); |
| |
| /* Since we always delay delivery, we always want an rt frame. we'll convert |
| * to a plain frame on delivery. |
| */ |
| memset(frame, 0, sizeof(*frame)); |
| frame->info.si_signo = sig; |
| /* Set si_code to match what would happen natively. We also need this to |
| * avoid the !is_sys_kill() check in record_pending_signal() to avoid an |
| * infinite loop (i#3171). |
| */ |
| frame->info.si_code = IF_LINUX_ELSE(SI_KERNEL, 0); |
| frame->info.si_addr = target_pc; |
| #ifdef X86_32 |
| frame->sig = sig; |
| frame->pinfo = &frame->info; |
| frame->puc = (void *)&frame->uc; |
| #endif |
| #if defined(LINUX) && defined(X86) |
| /* We use a TLS buffer to avoid too much stack space here. */ |
| sc->fpstate = (kernel_fpstate_t *)get_and_initialize_xstate_buffer(dcontext); |
| #endif |
| mcontext_to_ucontext(uc, get_mcontext(dcontext)); |
| sc->SC_XIP = (reg_t)target_pc; |
| /* We'll fill in fpstate at delivery time. |
| * We fill in segment registers to their current values and assume they won't |
| * change and that these are the right values. |
| * |
| * FIXME i#2095: restore the app's segment register value(s). |
| * |
| * XXX: it seems to work w/o filling in the other state: |
| * I'm leaving cr2 and other fields all zero. |
| * If this gets problematic we could switch to approach #2. |
| */ |
| thread_set_segment_registers(sc); |
| #if defined(X86) && defined(LINUX) |
| if (sig_has_restorer(info, sig)) |
| frame->pretcode = (char *)info->app_sigaction[sig]->restorer; |
| else |
| frame->pretcode = (char *)dynamorio_sigreturn; |
| #endif |
| |
| /* We assume that we do not need to translate the context when forged. |
| * If we did, we'd move this below enter_nolinking() (and update |
| * record_pending_signal() to do the translation). |
| */ |
| record_pending_signal(dcontext, sig, &frame->uc, frame, |
| true /*forged*/ |
| _IF_CLIENT(NULL)); |
| |
| /* For most callers this is not necessary and we only do it to match |
| * the Windows usage model: but for forging from our own handler, |
| * this is good b/c it resets us to the base of dstack. |
| */ |
| /* tell d_r_dispatch() why we're coming there */ |
| dcontext->whereami = DR_WHERE_TRAMPOLINE; |
| KSTART(dispatch_num_exits); |
| set_last_exit(dcontext, (linkstub_t *)get_asynch_linkstub()); |
| if (is_couldbelinking(dcontext)) |
| enter_nolinking(dcontext, NULL, false); |
| transfer_to_dispatch( |
| dcontext, get_mcontext(dcontext), |
| cur_whereami != DR_WHERE_FCACHE && cur_whereami != DR_WHERE_SIGNAL_HANDLER |
| /*full_DR_state*/); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void |
| os_request_fatal_coredump(const char *msg) |
| { |
| /* To enable getting a coredump just make sure that rlimits are |
| * not preventing getting one, e.g. ulimit -c unlimited |
| */ |
| SYSLOG_INTERNAL_ERROR("Crashing the process deliberately for a core dump!"); |
| os_terminate_via_signal(NULL, 0 /*no cleanup*/, SIGSEGV); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void |
| os_request_live_coredump(const char *msg) |
| { |
| #ifdef VMX86_SERVER |
| if (os_in_vmkernel_userworld()) { |
| vmk_request_live_coredump(msg); |
| return; |
| } |
| #endif |
| LOG(GLOBAL, LOG_ASYNCH, 1, |
| "LiveCoreDump unsupported (PR 365105). " |
| "Continuing execution without a core.\n"); |
| return; |
| } |
| |
| void |
| os_dump_core(const char *msg) |
| { |
| /* FIXME Case 3408: fork stack dump crashes on 2.6 kernel, so moving the getchar |
| * ahead to aid in debugging */ |
| if (TEST(DUMPCORE_WAIT_FOR_DEBUGGER, dynamo_options.dumpcore_mask)) { |
| SYSLOG_INTERNAL_ERROR("looping so you can use gdb to attach to pid %s", |
| get_application_pid()); |
| IF_CLIENT_INTERFACE(SYSLOG(SYSLOG_CRITICAL, WAITING_FOR_DEBUGGER, 2, |
| get_application_name(), get_application_pid())); |
| /* getchar() can hit our own vsyscall hook (from PR 212570); typically we |
| * want to attach and not continue anyway, so doing an infinite loop: |
| */ |
| while (true) |
| os_thread_yield(); |
| } |
| |
| if (DYNAMO_OPTION(live_dump)) { |
| os_request_live_coredump(msg); |
| } |
| |
| if (TEST(DUMPCORE_INCLUDE_STACKDUMP, dynamo_options.dumpcore_mask)) { |
| /* fork, dump core, then use gdb to get a stack dump |
| * we can get into an infinite loop if there's a seg fault |
| * in the process of doing this -- so we have a do-once test, |
| * and if it failed we do the no-symbols dr callstack dump |
| */ |
| static bool tried_stackdump = false; |
| if (!tried_stackdump) { |
| tried_stackdump = true; |
| d_r_stackdump(); |
| } else { |
| static bool tried_calldump = false; |
| if (!tried_calldump) { |
| tried_calldump = true; |
| dump_dr_callstack(STDERR); |
| } |
| } |
| } |
| |
| if (!DYNAMO_OPTION(live_dump)) { |
| os_request_fatal_coredump(msg); |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| #ifdef RETURN_AFTER_CALL |
| bool |
| at_known_exception(dcontext_t *dcontext, app_pc target_pc, app_pc source_fragment) |
| { |
| /* There is a known exception in signal restorers and the Linux |
| * dynamic symbol resoulution. |
| * The latter we assume it is the only other recurring known exception, |
| * so the first time we pattern match to help make sure it is indeed |
| * _dl_runtime_resolve (since with LD_BIND_NOW it will never be called). |
| * After that we compare with the known value. |
| */ |
| |
| static app_pc known_exception = 0; |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| |
| LOG(THREAD, LOG_INTERP, 1, "RCT: testing for KNOWN exception " PFX " " PFX "\n", |
| target_pc, source_fragment); |
| |
| /* Check if this is a signal return. |
| FIXME: we should really get that from the frame itself. |
| Since currently grabbing restorer only when copying a frame, |
| this will work with nested signals only if they all have same restorer |
| (I haven't seen restorers other than the one in libc) |
| */ |
| if (target_pc == info->signal_restorer_retaddr) { |
| LOG(THREAD, LOG_INTERP, 1, |
| "RCT: KNOWN exception this is a signal restorer --ok \n"); |
| STATS_INC(ret_after_call_signal_restorer); |
| return true; |
| } |
| |
| if (source_fragment == known_exception) { |
| LOG(THREAD, LOG_INTERP, 1, |
| "RCT: KNOWN exception again _dl_runtime_resolve --ok\n"); |
| return true; |
| } |
| |
| if (known_exception == 0) { |
| int ret_imm; |
| return at_dl_runtime_resolve_ret(dcontext, source_fragment, &ret_imm); |
| } |
| return false; |
| } |
| #endif /* RETURN_AFTER_CALL */ |
| |
| /*************************************************************************** |
| * ITIMERS |
| * |
| * We support combining an app itimer with a DR itimer for each of the 3 types |
| * (PR 204556). |
| */ |
| |
| static inline uint64 |
| timeval_to_usec(struct timeval *t1) |
| { |
| return ((uint64)(t1->tv_sec)) * 1000000 + t1->tv_usec; |
| } |
| |
| static inline void |
| usec_to_timeval(uint64 usec, struct timeval *t1) |
| { |
| t1->tv_sec = (long)usec / 1000000; |
| t1->tv_usec = (long)usec % 1000000; |
| } |
| |
| static void |
| init_itimer(dcontext_t *dcontext, bool first) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int i; |
| ASSERT(info != NULL); |
| ASSERT(!info->shared_itimer); /* else inherit */ |
| LOG(THREAD, LOG_ASYNCH, 2, "thread has private itimers%s\n", |
| os_itimers_thread_shared() ? " (for now)" : ""); |
| if (os_itimers_thread_shared()) { |
| /* we have to allocate now even if no itimer is installed until later, |
| * so that all child threads point to the same data |
| */ |
| info->itimer = (thread_itimer_info_t(*)[NUM_ITIMERS])global_heap_alloc( |
| sizeof(*info->itimer) HEAPACCT(ACCT_OTHER)); |
| } else { |
| /* for simplicity and parallel w/ shared we allocate proactively */ |
| info->itimer = (thread_itimer_info_t(*)[NUM_ITIMERS])heap_alloc( |
| dcontext, sizeof(*info->itimer) HEAPACCT(ACCT_OTHER)); |
| } |
| memset(info->itimer, 0, sizeof(*info->itimer)); |
| for (i = 0; i < NUM_ITIMERS; i++) { |
| ASSIGN_INIT_RECURSIVE_LOCK_FREE((*info->itimer)[i].lock, shared_itimer_lock); |
| } |
| if (first) { |
| /* see if app has set up an itimer before we were loaded */ |
| struct itimerval prev; |
| int rc; |
| int which; |
| for (which = 0; which < NUM_ITIMERS; which++) { |
| rc = getitimer_syscall(which, &prev); |
| ASSERT(rc == SUCCESS); |
| (*info->itimer)[which].app.interval = timeval_to_usec(&prev.it_interval); |
| (*info->itimer)[which].app.value = timeval_to_usec(&prev.it_value); |
| } |
| } |
| } |
| |
| /* Up to caller to hold lock for shared itimers */ |
| static bool |
| set_actual_itimer(dcontext_t *dcontext, int which, thread_sig_info_t *info, bool enable) |
| { |
| struct itimerval val; |
| int rc; |
| ASSERT(info != NULL && info->itimer != NULL); |
| ASSERT(which >= 0 && which < NUM_ITIMERS); |
| if (enable) { |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "installing itimer %d interval=" INT64_FORMAT_STRING |
| ", value=" INT64_FORMAT_STRING "\n", |
| which, (*info->itimer)[which].actual.interval, |
| (*info->itimer)[which].actual.value); |
| /* i#2907: we have no signal handlers until we start the app (i#2335) |
| * so we can't set up an itimer until then. |
| */ |
| ASSERT(dynamo_initialized); |
| ASSERT(!info->shared_itimer || |
| self_owns_recursive_lock(&(*info->itimer)[which].lock)); |
| usec_to_timeval((*info->itimer)[which].actual.interval, &val.it_interval); |
| usec_to_timeval((*info->itimer)[which].actual.value, &val.it_value); |
| } else { |
| LOG(THREAD, LOG_ASYNCH, 2, "disabling itimer %d\n", which); |
| memset(&val, 0, sizeof(val)); |
| (*info->itimer)[which].actual.value = 0; |
| (*info->itimer)[which].actual.interval = 0; |
| } |
| rc = setitimer_syscall(which, &val, NULL); |
| return (rc == SUCCESS); |
| } |
| |
| /* Caller should hold lock */ |
| static bool |
| itimer_new_settings(dcontext_t *dcontext, int which, bool app_changed) |
| { |
| struct itimerval val; |
| bool res = true; |
| int rc; |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| ASSERT(info != NULL && info->itimer != NULL); |
| ASSERT(which >= 0 && which < NUM_ITIMERS); |
| ASSERT(!info->shared_itimer || |
| self_owns_recursive_lock(&(*info->itimer)[which].lock)); |
| /* the general strategy is to set the actual value to the smaller, |
| * update the larger on each signal, and when the larger becomes |
| * smaller do a one-time swap for the remaining |
| */ |
| if ((*info->itimer)[which].dr.interval > 0 && |
| ((*info->itimer)[which].app.interval == 0 || |
| (*info->itimer)[which].dr.interval < (*info->itimer)[which].app.interval)) |
| (*info->itimer)[which].actual.interval = (*info->itimer)[which].dr.interval; |
| else |
| (*info->itimer)[which].actual.interval = (*info->itimer)[which].app.interval; |
| |
| if ((*info->itimer)[which].actual.value > 0) { |
| if ((*info->itimer)[which].actual.interval == 0 && |
| (*info->itimer)[which].dr.value == 0 && |
| (*info->itimer)[which].app.value == 0) { |
| (*info->itimer)[which].actual.value = 0; |
| res = set_actual_itimer(dcontext, which, info, false /*disabled*/); |
| } else { |
| /* one of app or us has an in-flight timer which we should not interrupt. |
| * but, we already set the new requested value (for app or us), so we |
| * need to update the actual value so we subtract properly. |
| */ |
| rc = getitimer_syscall(which, &val); |
| ASSERT(rc == SUCCESS); |
| uint64 left = timeval_to_usec(&val.it_value); |
| if (!app_changed && |
| (*info->itimer)[which].actual.value == (*info->itimer)[which].app.value) |
| (*info->itimer)[which].app.value = left; |
| if (app_changed && |
| (*info->itimer)[which].actual.value == (*info->itimer)[which].dr.value) |
| (*info->itimer)[which].dr.value = left; |
| (*info->itimer)[which].actual.value = left; |
| } |
| } else { |
| if ((*info->itimer)[which].dr.value > 0 && |
| ((*info->itimer)[which].app.value == 0 || |
| (*info->itimer)[which].dr.value < (*info->itimer)[which].app.value)) |
| (*info->itimer)[which].actual.value = (*info->itimer)[which].dr.value; |
| else { |
| (*info->itimer)[which].actual.value = (*info->itimer)[which].app.value; |
| } |
| res = set_actual_itimer(dcontext, which, info, true /*enable*/); |
| } |
| return res; |
| } |
| |
| bool |
| set_itimer_callback(dcontext_t *dcontext, int which, uint millisec, |
| void (*func)(dcontext_t *, priv_mcontext_t *), |
| void (*func_api)(dcontext_t *, dr_mcontext_t *)) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| bool rc; |
| if (which < 0 || which >= NUM_ITIMERS) { |
| CLIENT_ASSERT(false, "invalid itimer type"); |
| return false; |
| } |
| if (func == NULL && func_api == NULL && millisec != 0) { |
| CLIENT_ASSERT(false, "invalid function"); |
| return false; |
| } |
| ASSERT(info != NULL && info->itimer != NULL); |
| if (info->shared_itimer) |
| acquire_recursive_lock(&(*info->itimer)[which].lock); |
| (*info->itimer)[which].dr.interval = ((uint64)millisec) * 1000; |
| (*info->itimer)[which].dr.value = (*info->itimer)[which].dr.interval; |
| (*info->itimer)[which].cb = func; |
| (*info->itimer)[which].cb_api = func_api; |
| if (!dynamo_initialized) { |
| /* i#2907: we have no signal handlers until we start the app (i#2335) |
| * so we can't set up an itimer until then. start_itimer() called |
| * from os_thread_under_dynamo() will enable it. |
| */ |
| LOG(THREAD, LOG_ASYNCH, 2, "delaying itimer until attach\n"); |
| rc = true; |
| } else |
| rc = itimer_new_settings(dcontext, which, false /*us*/); |
| if (info->shared_itimer) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| return rc; |
| } |
| |
| uint |
| get_itimer_frequency(dcontext_t *dcontext, int which) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| uint ms = 0; |
| if (which < 0 || which >= NUM_ITIMERS) { |
| CLIENT_ASSERT(false, "invalid itimer type"); |
| return 0; |
| } |
| ASSERT(info != NULL && info->itimer != NULL); |
| if (info->shared_itimer) |
| acquire_recursive_lock(&(*info->itimer)[which].lock); |
| ms = (*info->itimer)[which].dr.interval / 1000; |
| if (info->shared_itimer) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| return ms; |
| } |
| |
| static int |
| signal_to_itimer_type(int sig) |
| { |
| if (sig == SIGALRM) |
| return ITIMER_REAL; |
| else if (sig == SIGVTALRM) |
| return ITIMER_VIRTUAL; |
| else if (sig == SIGPROF) |
| return ITIMER_PROF; |
| else |
| return -1; |
| } |
| |
| static bool |
| alarm_signal_has_DR_only_itimer(dcontext_t *dcontext, int signal) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| int which = signal_to_itimer_type(signal); |
| if (which == -1) |
| return false; |
| if (info->shared_itimer) |
| acquire_recursive_lock(&(*info->itimer)[which].lock); |
| bool DR_only = |
| ((*info->itimer)[which].dr.value > 0 || (*info->itimer)[which].dr.interval > 0) && |
| (*info->itimer)[which].app.value == 0 && (*info->itimer)[which].app.interval == 0; |
| if (info->shared_itimer) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| return DR_only; |
| } |
| |
| static bool |
| handle_alarm(dcontext_t *dcontext, int sig, kernel_ucontext_t *ucxt) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| ASSERT(info != NULL && info->itimer != NULL); |
| int which = 0; |
| bool invoke_cb = false, pass_to_app = false, reset_timer_manually = false; |
| bool should_release_lock = false; |
| |
| /* i#471: suppress alarms coming in after exit */ |
| if (dynamo_exited) |
| return pass_to_app; |
| |
| which = signal_to_itimer_type(sig); |
| ASSERT(which != -1); |
| LOG(THREAD, LOG_ASYNCH, 2, "received alarm %d @" PFX "\n", which, |
| SIGCXT_FROM_UCXT(ucxt)->SC_XIP); |
| |
| /* This alarm could have interrupted an app thread making an itimer syscall, |
| * which is why we don't want to block on a lock here. |
| * It can't interrupt this same thread handling a prior alarm (b/c we block |
| * the signal in our handler). It could arrive in thread B while thread A |
| * is still handling a prior alarm if the alarm frequency is high and the |
| * processing is slow, which is why we split the locks to be per-itimer-type. |
| * We also avoid new thread setup code acquiring these itimer locks by using |
| * atomic increments instead for the refcounts. Xref i#2993. |
| */ |
| if (info->shared_itimer) { |
| #ifdef DEADLOCK_AVOIDANCE |
| /* i#2061: in debug build we can get an alarm while in deadlock handling |
| * code that holds innermost_lock. We just drop such alarms. |
| */ |
| if (OWN_MUTEX(&innermost_lock)) |
| return pass_to_app; |
| #endif |
| if (self_owns_recursive_lock(&(*info->itimer)[which].lock)) { |
| /* What can we do? We just go ahead and hope conflicting writes work out. |
| * We don't re-acquire in case app was in middle of acquiring. |
| */ |
| } else { |
| #define ALARM_LOCK_MAX_TRIES 4 |
| int i; |
| for (i = 0; i < ALARM_LOCK_MAX_TRIES; ++i) { |
| if (try_recursive_lock(&(*info->itimer)[which].lock)) { |
| should_release_lock = true; |
| break; |
| } |
| os_thread_yield(); |
| } |
| if (!should_release_lock) { |
| /* Heuristic: if fail N times then assume interrupted lock routine |
| * while processing an app syscall (see above: we ruled out other |
| * scenarios). What can we do? Just continue and hope conflicting |
| * writes work out. |
| */ |
| } |
| } |
| } |
| if ((*info->itimer)[which].app.value > 0) { |
| /* Alarm could have been on its way when app value changed */ |
| if ((*info->itimer)[which].app.value >= (*info->itimer)[which].actual.value) { |
| (*info->itimer)[which].app.value -= (*info->itimer)[which].actual.value; |
| LOG(THREAD, LOG_ASYNCH, 2, "\tapp value is now %d\n", |
| (*info->itimer)[which].app.value); |
| if ((*info->itimer)[which].app.value == 0) { |
| pass_to_app = true; |
| (*info->itimer)[which].app.value = (*info->itimer)[which].app.interval; |
| } else |
| reset_timer_manually = true; |
| } |
| } |
| if ((*info->itimer)[which].dr.value > 0) { |
| /* Alarm could have been on its way when DR value changed */ |
| if ((*info->itimer)[which].dr.value >= (*info->itimer)[which].actual.value) { |
| (*info->itimer)[which].dr.value -= (*info->itimer)[which].actual.value; |
| LOG(THREAD, LOG_ASYNCH, 2, "\tdr value is now %d\n", |
| (*info->itimer)[which].dr.value); |
| if ((*info->itimer)[which].dr.value == 0) { |
| invoke_cb = true; |
| (*info->itimer)[which].dr.value = (*info->itimer)[which].dr.interval; |
| } else |
| reset_timer_manually = true; |
| } |
| } |
| /* for efficiency we let the kernel reset the value to interval if |
| * there's only one timer |
| */ |
| if (reset_timer_manually) { |
| (*info->itimer)[which].actual.value = 0; |
| itimer_new_settings(dcontext, which, true /*doesn't matter: actual.value==0*/); |
| } else |
| (*info->itimer)[which].actual.value = (*info->itimer)[which].actual.interval; |
| |
| if (invoke_cb) { |
| /* invoke after setting new itimer value */ |
| /* we save stack space by allocating superset dr_mcontext_t */ |
| dr_mcontext_t dmc; |
| dr_mcontext_init(&dmc); |
| priv_mcontext_t *mc = dr_mcontext_as_priv_mcontext(&dmc); |
| ucontext_to_mcontext(mc, ucxt); |
| void (*cb)(dcontext_t *, priv_mcontext_t *) = (*info->itimer)[which].cb; |
| void (*cb_api)(dcontext_t *, dr_mcontext_t *) = (*info->itimer)[which].cb_api; |
| |
| if (which == ITIMER_VIRTUAL && info->shared_itimer && should_release_lock) { |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| should_release_lock = false; |
| } |
| |
| if (cb != NULL) { |
| cb(dcontext, mc); |
| } else { |
| cb_api(dcontext, &dmc); |
| } |
| } |
| if (info->shared_itimer && should_release_lock) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| return pass_to_app; |
| } |
| |
| /* Starts itimer if stopped, or increases refcount of existing itimer if already |
| * started. It is *not* safe to call this more than once for the same thread, |
| * since it will inflate the refcount and prevent cleanup. |
| */ |
| void |
| start_itimer(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| ASSERT(info != NULL && info->itimer != NULL); |
| bool start = false; |
| if (info->shared_itimer) { |
| /* i#2993: We avoid acquiring the lock as an alarm signal can arrive during |
| * the lock routine (esp in debug build) and cause problems. |
| */ |
| int new_count = |
| atomic_add_exchange_int((volatile int *)info->shared_itimer_underDR, 1); |
| start = (new_count == 1); |
| } else |
| start = true; |
| if (start) { |
| /* Enable all DR itimers b/c at least one thread in this set of threads |
| * sharing itimers is under DR control |
| */ |
| int which; |
| LOG(THREAD, LOG_ASYNCH, 2, "starting DR itimers from thread " TIDFMT "\n", |
| d_r_get_thread_id()); |
| for (which = 0; which < NUM_ITIMERS; which++) { |
| if (info->shared_itimer) |
| acquire_recursive_lock(&(*info->itimer)[which].lock); |
| /* May have already been set up with the start delayed (i#2907). */ |
| if ((*info->itimer)[which].dr.interval > 0) { |
| (*info->itimer)[which].dr.value = (*info->itimer)[which].dr.interval; |
| itimer_new_settings(dcontext, which, false /*!app*/); |
| } |
| if (info->shared_itimer) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| } |
| } |
| } |
| |
| /* Decrements the itimer refcount, and turns off the itimer once there are no |
| * more threads listening for it. It is not safe to call this more than once on |
| * the same thread. |
| */ |
| void |
| stop_itimer(dcontext_t *dcontext) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| ASSERT(info != NULL && info->itimer != NULL); |
| bool stop = false; |
| if (info->shared_itimer) { |
| ASSERT(*info->shared_itimer_underDR > 0); |
| int new_count = |
| atomic_add_exchange_int((volatile int *)info->shared_itimer_underDR, -1); |
| stop = (new_count == 0); |
| } else |
| stop = true; |
| if (stop) { |
| /* Disable all DR itimers b/c this set of threads sharing this |
| * itimer is now completely native |
| */ |
| int which; |
| LOG(THREAD, LOG_ASYNCH, 2, "stopping DR itimers from thread " TIDFMT "\n", |
| d_r_get_thread_id()); |
| for (which = 0; which < NUM_ITIMERS; which++) { |
| if (info->shared_itimer) |
| acquire_recursive_lock(&(*info->itimer)[which].lock); |
| if ((*info->itimer)[which].dr.value > 0) { |
| (*info->itimer)[which].dr.value = 0; |
| if ((*info->itimer)[which].app.value > 0) { |
| (*info->itimer)[which].actual.interval = |
| (*info->itimer)[which].app.interval; |
| } else |
| set_actual_itimer(dcontext, which, info, false /*disable*/); |
| } |
| if (info->shared_itimer) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| } |
| } |
| } |
| |
| /* handle app itimer syscalls */ |
| /* handle_pre_alarm also calls this function and passes NULL as prev_timer */ |
| void |
| handle_pre_setitimer(dcontext_t *dcontext, int which, const struct itimerval *new_timer, |
| struct itimerval *prev_timer) |
| { |
| if (new_timer == NULL || which < 0 || which >= NUM_ITIMERS) |
| return; |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| ASSERT(info != NULL && info->itimer != NULL); |
| struct itimerval val; |
| if (d_r_safe_read(new_timer, sizeof(val), &val)) { |
| if (info->shared_itimer) |
| acquire_recursive_lock(&(*info->itimer)[which].lock); |
| /* save a copy in case the syscall fails */ |
| (*info->itimer)[which].app_saved = (*info->itimer)[which].app; |
| (*info->itimer)[which].app.interval = timeval_to_usec(&val.it_interval); |
| (*info->itimer)[which].app.value = timeval_to_usec(&val.it_value); |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "app setitimer type=%d interval=" SZFMT " value=" SZFMT "\n", which, |
| (*info->itimer)[which].app.interval, (*info->itimer)[which].app.value); |
| itimer_new_settings(dcontext, which, true /*app*/); |
| if (info->shared_itimer) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| } |
| } |
| |
| void |
| handle_post_setitimer(dcontext_t *dcontext, bool success, int which, |
| const struct itimerval *new_timer, struct itimerval *prev_timer) |
| { |
| if (new_timer == NULL || which < 0 || which >= NUM_ITIMERS) { |
| ASSERT(new_timer == NULL || !success); |
| return; |
| } |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| ASSERT(info != NULL && info->itimer != NULL); |
| ASSERT(which >= 0 && which < NUM_ITIMERS); |
| if (!success && new_timer != NULL) { |
| if (info->shared_itimer) |
| acquire_recursive_lock(&(*info->itimer)[which].lock); |
| /* restore saved pre-syscall settings */ |
| (*info->itimer)[which].app = (*info->itimer)[which].app_saved; |
| itimer_new_settings(dcontext, which, true /*app*/); |
| if (info->shared_itimer) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| } |
| if (success && prev_timer != NULL) |
| handle_post_getitimer(dcontext, success, which, prev_timer); |
| } |
| |
| void |
| handle_post_getitimer(dcontext_t *dcontext, bool success, int which, |
| struct itimerval *cur_timer) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| ASSERT(info != NULL && info->itimer != NULL); |
| if (success) { |
| /* write succeeded for kernel but we're user and can have races */ |
| struct timeval val; |
| DEBUG_DECLARE(bool ok;) |
| ASSERT(which >= 0 && which < NUM_ITIMERS); |
| ASSERT(cur_timer != NULL); |
| if (info->shared_itimer) |
| acquire_recursive_lock(&(*info->itimer)[which].lock); |
| usec_to_timeval((*info->itimer)[which].app.interval, &val); |
| IF_DEBUG(ok =) |
| safe_write_ex(&cur_timer->it_interval, sizeof(val), &val, NULL); |
| ASSERT(ok); |
| if (d_r_safe_read(&cur_timer->it_value, sizeof(val), &val)) { |
| /* subtract the difference between last-asked-for value |
| * and current value to reflect elapsed time |
| */ |
| uint64 left = (*info->itimer)[which].app.value - |
| ((*info->itimer)[which].actual.value - timeval_to_usec(&val)); |
| usec_to_timeval(left, &val); |
| IF_DEBUG(ok =) |
| safe_write_ex(&cur_timer->it_value, sizeof(val), &val, NULL); |
| ASSERT(ok); |
| } else |
| ASSERT_NOT_REACHED(); |
| if (info->shared_itimer) |
| release_recursive_lock(&(*info->itimer)[which].lock); |
| } |
| } |
| |
| /* handle app alarm syscall */ |
| /* alarm uses the same itimer and could be defined in terms of setitimer */ |
| void |
| handle_pre_alarm(dcontext_t *dcontext, unsigned int sec) |
| { |
| struct itimerval val; |
| val.it_interval.tv_usec = 0; |
| val.it_interval.tv_sec = 0; |
| val.it_value.tv_usec = 0; |
| val.it_value.tv_sec = sec; |
| handle_pre_setitimer(dcontext, ITIMER_REAL, &val, NULL); |
| } |
| |
| void |
| handle_post_alarm(dcontext_t *dcontext, bool success, unsigned int sec) |
| { |
| /* alarm is always successful, so do nothing in post */ |
| ASSERT(success); |
| return; |
| } |
| |
| /*************************************************************************** |
| * Internal DR communication |
| */ |
| |
| typedef struct _sig_detach_info_t { |
| KSYNCH_TYPE *detached; |
| byte *sigframe_xsp; |
| #ifdef HAVE_SIGALTSTACK |
| stack_t *app_sigstack; |
| #endif |
| } sig_detach_info_t; |
| |
| static void |
| notify_and_jmp_without_stack(KSYNCH_TYPE *notify_var, byte *continuation, byte *xsp) |
| { |
| if (ksynch_kernel_support()) { |
| /* Can't use dstack once we signal so in asm we do: |
| * futex/semaphore = 1; |
| * %xsp = xsp; |
| * dynamorio_condvar_wake_and_jmp(notify_var, continuation); |
| */ |
| #ifdef MACOS |
| ASSERT(sizeof(notify_var->sem) == 4); |
| #endif |
| /* We clobber xsp last just in case the compiler uses xsp to fetch the |
| * variables we're loading into registers. |
| */ |
| #ifdef DR_HOST_NOT_TARGET |
| ASSERT_NOT_REACHED(); |
| #elif defined(X86) |
| # ifndef MACOS |
| /* i#2632: recent clang for 32-bit annoyingly won't do the right thing for |
| * "jmp dynamorio_condvar_wake_and_jmp" and leaves relocs so we ensure it's PIC. |
| * We do this first as it may end up clobbering a scratch reg like xax. |
| */ |
| void (*asm_jmp_tgt)() = dynamorio_condvar_wake_and_jmp; |
| asm("mov %0, %%" ASM_XDX : : "m"(asm_jmp_tgt)); |
| # endif |
| asm("mov %0, %%" ASM_XAX : : "m"(notify_var)); |
| asm("mov %0, %%" ASM_XCX : : "m"(continuation)); |
| asm("mov %0, %%" ASM_XSP : : "m"(xsp)); /* Clobber xsp last (see above). */ |
| # ifdef MACOS |
| asm("movl $1,4(%" ASM_XAX ")"); |
| asm("jmp _dynamorio_condvar_wake_and_jmp"); |
| # else |
| asm("movl $1,(%" ASM_XAX ")"); |
| asm("jmp *%" ASM_XDX); |
| # endif |
| #elif defined(AARCHXX) |
| asm("ldr " ASM_R0 ", %0" : : "m"(notify_var)); |
| asm("mov " ASM_R1 ", #1"); |
| asm("str " ASM_R1 ",[" ASM_R0 "]"); |
| asm("ldr " ASM_R1 ", %0" : : "m"(continuation)); |
| asm("ldr " ASM_R2 ", %0" : : "m"(xsp)); |
| asm("mov " ASM_XSP ", " ASM_R2); /* Clobber xsp last (see above). */ |
| asm("b dynamorio_condvar_wake_and_jmp"); |
| #endif |
| } else { |
| ksynch_set_value(notify_var, 1); |
| #ifdef DR_HOST_NOT_TARGET |
| ASSERT_NOT_REACHED(); |
| #elif defined(X86) |
| asm("mov %0, %%" ASM_XAX : : "m"(continuation)); |
| asm("mov %0, %%" ASM_XSP : : "m"(xsp)); /* Clobber xsp last (see above). */ |
| asm("jmp *%" ASM_XAX); |
| #elif defined(AARCHXX) |
| asm("ldr " ASM_R0 ", %0" : : "m"(continuation)); |
| asm("ldr " ASM_R1 ", %0" : : "m"(xsp)); /* Clobber xsp last (see above). */ |
| asm("mov " ASM_XSP ", " ASM_R1); |
| asm(ASM_INDJMP " " ASM_R0); |
| #endif /* X86/ARM */ |
| } |
| } |
| |
| /* Go native from detach. This is executed on the app stack. */ |
| static void |
| sig_detach_go_native(sig_detach_info_t *info) |
| { |
| byte *xsp = info->sigframe_xsp; |
| |
| #ifdef HAVE_SIGALTSTACK |
| /* Restore the app signal stack, though sigreturn will overwrite this with the |
| * uc_stack in the frame's ucontext anyway (which we already set for the app). |
| */ |
| DEBUG_DECLARE(int rc =) |
| sigaltstack_syscall(info->app_sigstack, NULL); |
| ASSERT(rc == 0); |
| #endif |
| |
| #ifdef X86 |
| /* Skip pretcode */ |
| xsp += sizeof(char *); |
| #endif |
| notify_and_jmp_without_stack(info->detached, (byte *)dynamorio_sigreturn, xsp); |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* Sets this (slave) thread to detach by directly returning from the signal. */ |
| static void |
| sig_detach(dcontext_t *dcontext, sigframe_rt_t *frame, KSYNCH_TYPE *detached) |
| { |
| thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; |
| byte *xsp; |
| sig_detach_info_t detach_info; |
| |
| LOG(THREAD, LOG_ASYNCH, 1, "%s: detaching\n", __FUNCTION__); |
| |
| if (!atomic_read_bool(&multiple_handlers_present)) { |
| /* Save the app's handlers (we only support one handler setups) for |
| * execute_native_handler(). |
| */ |
| d_r_read_lock(&detached_sigact_lock); |
| for (int i = 0; i < SIGARRAY_SIZE; ++i) { |
| if (info->app_sigaction[i] != NULL && |
| detached_sigact[i].handler == (handler_t)0) { |
| d_r_read_unlock(&detached_sigact_lock); |
| d_r_write_lock(&detached_sigact_lock); |
| if (detached_sigact[i].handler == (handler_t)0) { |
| memcpy(&detached_sigact[i], info->app_sigaction[i], |
| sizeof(detached_sigact[i])); |
| } |
| d_r_write_unlock(&detached_sigact_lock); |
| d_r_read_lock(&detached_sigact_lock); |
| } |
| } |
| d_r_read_unlock(&detached_sigact_lock); |
| } |
| |
| /* Update the mask of the signal frame so that the later sigreturn will |
| * restore the app signal mask. |
| */ |
| memcpy(&frame->uc.uc_sigmask, &info->app_sigblocked, sizeof(info->app_sigblocked)); |
| |
| /* Copy the signal frame to the app stack. |
| * XXX: We live with the transparency risk of storing the signal frame on |
| * the app stack: we assume the app stack is writable where we need it to be, |
| * and that we're not clobbering any app data beyond TOS. |
| */ |
| xsp = get_sigstack_frame_ptr(dcontext, info, SUSPEND_SIGNAL, frame); |
| copy_frame_to_stack(dcontext, info, SUSPEND_SIGNAL, frame, xsp, false /*!pending*/); |
| |
| #ifdef HAVE_SIGALTSTACK |
| /* Make sure the frame's sigstack reflects the app stack. |
| * copy_frame_to_stack() should have done this for us. |
| */ |
| ASSERT(((sigframe_rt_t *)xsp)->uc.uc_stack.ss_sp == info->app_sigstack.ss_sp); |
| #endif |
| |
| /* Restore app segment registers. */ |
| os_thread_not_under_dynamo(dcontext); |
| os_tls_thread_exit(dcontext->local_state); |
| |
| #ifdef HAVE_SIGALTSTACK |
| /* We can't restore the app's sigstack here as that will invalidate the |
| * sigstack we're currently on. |
| */ |
| detach_info.app_sigstack = &info->app_sigstack; |
| #endif |
| detach_info.detached = detached; |
| detach_info.sigframe_xsp = xsp; |
| |
| call_switch_stack(&detach_info, xsp, (void (*)(void *))sig_detach_go_native, |
| false /*free_initstack*/, false /*do not return*/); |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* Returns whether to pass on to app */ |
| static bool |
| handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt, sigframe_rt_t *frame) |
| { |
| os_thread_data_t *ostd = (os_thread_data_t *)dcontext->os_field; |
| kernel_sigset_t prevmask; |
| sig_full_cxt_t sc_full; |
| ASSERT(ostd != NULL); |
| |
| if (ostd->terminate) { |
| /* PR 297902: exit this thread, without using the dstack */ |
| /* For MacOS, we need a stack as 32-bit syscalls take args on the stack. |
| * We go ahead and use it for x86 too for simpler sysenter return. |
| * We don't have a lot of options: we're terminating, so we go ahead |
| * and use the app stack. |
| */ |
| byte *app_xsp; |
| if (IS_CLIENT_THREAD(dcontext)) |
| app_xsp = (byte *)SIGCXT_FROM_UCXT(ucxt)->SC_XSP; |
| else |
| app_xsp = (byte *)get_mcontext(dcontext)->xsp; |
| LOG(THREAD, LOG_ASYNCH, 2, "handle_suspend_signal: exiting\n"); |
| ASSERT(app_xsp != NULL); |
| notify_and_jmp_without_stack(&ostd->terminated, (byte *)dynamorio_sys_exit, |
| app_xsp); |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| if (!doing_detach && is_thread_currently_native(dcontext->thread_record) && |
| !IS_CLIENT_THREAD(dcontext) IF_APP_EXPORTS(&&!dr_api_exit)) { |
| if (!sig_take_over(ucxt)) |
| return false; |
| ASSERT_NOT_REACHED(); /* else, shouldn't return */ |
| } |
| |
| /* If suspend_count is 0, we are not trying to suspend this thread |
| * (os_thread_resume() may have already decremented suspend_count to 0, but |
| * os_thread_suspend() will not send a signal until this thread unsets |
| * ostd->suspended, so not having a lock around the suspend_count read is |
| * ok), so pass signal to app. |
| * If we are trying or have already suspended this thread, our own |
| * os_thread_suspend() will not send a 2nd suspend signal until we are |
| * completely resumed, so we can distinguish app uses of SUSPEND_SIGNAL. We |
| * can't have a race between the read and write of suspended_sigcxt b/c |
| * signals are blocked. It's fine to have a race and reorder the app's |
| * signal w/ DR's. |
| */ |
| if (ostd->suspend_count == 0) |
| return true; /* pass to app */ |
| ASSERT(ostd->suspended_sigcxt == NULL); |
| |
| /* XXX: we're not setting DR_WHERE_SIGNAL_HANDLER in enough places. |
| * It's trickier than other whereamis b/c we want to resume the |
| * prior whereami when we return from the handler, but there are |
| * complex control paths that do not always return. |
| * We try to at least do it for the ksynch_wait here. |
| */ |
| dr_where_am_i_t prior_whereami = dcontext->whereami; |
| dcontext->whereami = DR_WHERE_SIGNAL_HANDLER; |
| |
| sig_full_initialize(&sc_full, ucxt); |
| ostd->suspended_sigcxt = &sc_full; |
| |
| LOG(THREAD, LOG_ASYNCH, 2, "handle_suspend_signal: suspended now\n"); |
| /* We cannot use mutexes here as we have interrupted DR at an |
| * arbitrary point! Thus we can't use the event_t routines. |
| * However, the existing synch and check above prevent any |
| * re-entrance here, and our cond vars target just a single thread, |
| * so we can get away w/o a mutex. |
| */ |
| /* Notify os_thread_suspend that it can now return, as this thread is |
| * officially suspended now and is ready for thread_{get,set}_mcontext. |
| */ |
| ASSERT(ksynch_get_value(&ostd->suspended) == 0); |
| ksynch_set_value(&ostd->suspended, 1); |
| ksynch_wake_all(&ostd->suspended); |
| |
| /* We're sitting on our sigaltstack w/ all signals blocked. We're |
| * going to stay here but unblock all signals so we don't lose any |
| * delivered while we're waiting. We're at a safe enough point (now |
| * that we've set ostd->suspended: i#5779) to re-enter |
| * master_signal_handler(). We use a mutex in thread_{suspend,resume} to |
| * prevent our own re-suspension signal from arriving before we've |
| * re-blocked on the resume. |
| */ |
| sigprocmask_syscall(SIG_SETMASK, SIGMASK_FROM_UCXT(ucxt), &prevmask, |
| sizeof(ucxt->uc_sigmask)); |
| |
| /* i#96/PR 295561: use futex(2) if available */ |
| while (ksynch_get_value(&ostd->wakeup) == 0) { |
| /* Waits only if the wakeup flag is not set as 1. Return value |
| * doesn't matter because the flag will be re-checked. |
| */ |
| ksynch_wait(&ostd->wakeup, 0, 0); |
| if (ksynch_get_value(&ostd->wakeup) == 0) { |
| /* If it still has to wait, give up the cpu. */ |
| os_thread_yield(); |
| } |
| } |
| LOG(THREAD, LOG_ASYNCH, 2, "handle_suspend_signal: awake now\n"); |
| |
| /* re-block so our exit from master_signal_handler is not interrupted */ |
| sigprocmask_syscall(SIG_SETMASK, &prevmask, NULL, sizeof(prevmask)); |
| ostd->suspended_sigcxt = NULL; |
| |
| /* Notify os_thread_resume that it can return now, which (assuming |
| * suspend_count is back to 0) means it's then safe to re-suspend. |
| */ |
| ksynch_set_value(&ostd->suspended, 0); /*reset prior to signalling os_thread_resume*/ |
| ksynch_set_value(&ostd->resumed, 1); |
| ksynch_wake_all(&ostd->resumed); |
| |
| dcontext->whereami = prior_whereami; |
| |
| if (ostd->retakeover) { |
| ostd->retakeover = false; |
| sig_take_over(ucxt); /* shouldn't return for this case */ |
| ASSERT_NOT_REACHED(); |
| } else if (ostd->do_detach) { |
| ostd->do_detach = false; |
| sig_detach(dcontext, frame, &ostd->detached); /* no return */ |
| ASSERT_NOT_REACHED(); |
| } |
| |
| return false; /* do not pass to app */ |
| } |
| |
| /* PR 206278: for try/except we need to save the signal mask */ |
| void |
| dr_setjmp_sigmask(dr_jmp_buf_t *buf) |
| { |
| /* i#226/PR 492568: we rely on the kernel storing the prior mask in the |
| * signal frame, so we do not need to store it on every setjmp, which |
| * can be a performance hit. |
| */ |
| #ifdef DEBUG |
| sigprocmask_syscall(SIG_SETMASK, NULL, &buf->sigmask, sizeof(buf->sigmask)); |
| #endif |
| } |
| |
| /* i#61/PR 211530: nudge on Linux. |
| * Determines whether this is a nudge signal, and if so queues up a nudge, |
| * or is an app signal. Returns whether to pass the signal on to the app. |
| */ |
| static bool |
| handle_nudge_signal(dcontext_t *dcontext, kernel_siginfo_t *siginfo, |
| kernel_ucontext_t *ucxt) |
| { |
| sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); |
| nudge_arg_t *arg = (nudge_arg_t *)siginfo; |
| instr_t instr; |
| char buf[MAX_INSTR_LENGTH]; |
| |
| /* Distinguish a nudge from an app signal. An app using libc sigqueue() |
| * will never have its signal mistaken as libc does not expose the kernel_siginfo_t |
| * and always passes 0 for si_errno, so we're only worried beyond our |
| * si_code check about an app using a raw syscall that is deliberately |
| * trying to fool us. |
| * While there is a lot of padding space in kernel_siginfo_t, the kernel doesn't |
| * copy it through on SYS_rt_sigqueueinfo so we don't have room for any |
| * dedicated magic numbers. The client id could function as a magic |
| * number for client nudges, but I don't think we want to kill the app |
| * if an external nudger types the client id wrong. |
| */ |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: sig=%d code=%d errno=%d\n", __FUNCTION__, |
| siginfo->si_signo, siginfo->si_code, siginfo->si_errno); |
| if (siginfo->si_signo != |
| NUDGESIG_SIGNUM |
| /* PR 477454: remove the IF_NOT_VMX86 once we have nudge-arg support */ |
| IF_NOT_VMX86(|| siginfo->si_code != SI_QUEUE || siginfo->si_errno == 0)) { |
| return true; /* pass to app */ |
| } |
| #if defined(CLIENT_INTERFACE) && !defined(VMX86_SERVER) |
| DODEBUG({ |
| if (TEST(NUDGE_GENERIC(client), arg->nudge_action_mask) && |
| !is_valid_client_id(arg->client_id)) { |
| SYSLOG_INTERNAL_WARNING("received client nudge for invalid id=0x%x", |
| arg->client_id); |
| } |
| }); |
| #endif |
| if (dynamo_exited || !dynamo_initialized || dcontext == NULL) { |
| /* Ignore the nudge: too early, or too late. |
| * Xref Windows handling of such cases in nudge.c: old case 5702, etc. |
| * We do this before the illegal-instr check b/c it's unsafe to decode |
| * if too early or too late. |
| */ |
| SYSLOG_INTERNAL_WARNING("too-early or too-late nudge: ignoring"); |
| return false; /* do not pass to app */ |
| } |
| |
| /* As a further check, try to detect whether this was raised synchronously |
| * from a real illegal instr: though si_code for that should not be |
| * SI_QUEUE. It's possible a nudge happened to come at a bad instr before |
| * it faulted, or maybe the instr after a syscall or other wait spot is |
| * illegal, but we'll live with that risk. |
| */ |
| ASSERT(NUDGESIG_SIGNUM == SIGILL); /* else this check makes no sense */ |
| instr_init(dcontext, &instr); |
| if (d_r_safe_read((byte *)sc->SC_XIP, sizeof(buf), buf) && |
| (decode(dcontext, (byte *)buf, &instr) == NULL || |
| /* check for ud2 (xref PR 523161) */ |
| instr_is_undefined(&instr))) { |
| LOG(THREAD, LOG_ASYNCH, 2, "%s: real illegal instr @" PFX "\n", __FUNCTION__, |
| sc->SC_XIP); |
| DOLOG(2, LOG_ASYNCH, |
| { disassemble_with_bytes(dcontext, (byte *)sc->SC_XIP, THREAD); }); |
| instr_free(dcontext, &instr); |
| return true; /* pass to app */ |
| } |
| instr_free(dcontext, &instr); |
| |
| #ifdef VMX86_SERVER |
| /* Treat as a client nudge until we have PR 477454 */ |
| if (siginfo->si_errno == 0) { |
| arg->version = NUDGE_ARG_CURRENT_VERSION; |
| arg->flags = 0; |
| arg->nudge_action_mask = NUDGE_GENERIC(client); |
| arg->client_id = 0; |
| arg->client_arg = 0; |
| } |
| #endif |
| |
| LOG(THREAD, LOG_ASYNCH, 1, |
| "received nudge version=%u flags=0x%x mask=0x%x id=0x%08x " |
| "arg=0x" ZHEX64_FORMAT_STRING "\n", |
| arg->version, arg->flags, arg->nudge_action_mask, arg->client_id, |
| arg->client_arg); |
| SYSLOG_INTERNAL_INFO("received nudge mask=0x%x id=0x%08x arg=0x" ZHEX64_FORMAT_STRING, |
| arg->nudge_action_mask, arg->client_id, arg->client_arg); |
| |
| /* We need to handle the nudge at a safe, nolinking spot */ |
| if (safe_is_in_fcache(dcontext, (byte *)sc->SC_XIP, (byte *)sc->SC_XSP) && |
| dcontext->interrupted_for_nudge == NULL) { |
| /* We unlink the interrupted fragment and skip any inlined syscalls to |
| * bound the nudge delivery time. If we already unlinked one we assume |
| * that's sufficient. |
| */ |
| fragment_t wrapper; |
| fragment_t *f = fragment_pclookup(dcontext, (byte *)sc->SC_XIP, &wrapper); |
| if (f != NULL) { |
| if (unlink_fragment_for_signal(dcontext, f, (byte *)sc->SC_XIP)) |
| dcontext->interrupted_for_nudge = f; |
| } |
| } |
| |
| /* No lock is needed since thread-private and this signal is blocked now */ |
| nudge_add_pending(dcontext, arg); |
| |
| return false; /* do not pass to app */ |
| } |