/* Handle an exception by invoking the user's fault handler and/or forwarding the duty to the previously installed handlers. */ kern_return_t catch_exception_raise (mach_port_t exception_port, mach_port_t thread, mach_port_t task, exception_type_t exception, exception_data_t code, mach_msg_type_number_t code_count) { #ifdef SIGSEGV_EXC_STATE_TYPE SIGSEGV_EXC_STATE_TYPE exc_state; #endif SIGSEGV_THREAD_STATE_TYPE thread_state; mach_msg_type_number_t state_count; unsigned long addr; unsigned long sp; #ifdef DEBUG_EXCEPTION_HANDLING fprintf (stderr, "Exception: 0x%x Code: 0x%x 0x%x in catch....\n", exception, code_count > 0 ? code[0] : -1, code_count > 1 ? code[1] : -1); #endif /* See http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/thread_get_state.html. */ #ifdef SIGSEGV_EXC_STATE_TYPE state_count = SIGSEGV_EXC_STATE_COUNT; if (thread_get_state (thread, SIGSEGV_EXC_STATE_FLAVOR, (void *) &exc_state, &state_count) != KERN_SUCCESS) { /* The thread is supposed to be suspended while the exception handler is called. This shouldn't fail. */ #ifdef DEBUG_EXCEPTION_HANDLING fprintf (stderr, "thread_get_state failed for exception state\n"); #endif return KERN_FAILURE; } #endif /* It turns out any Darwin kernel starting at 10.2 contains a "fast" path to determine the address of a fault: it is located into code[1]. MacOS X exception delivery is really slow, so we also pass code and make getting the EXC_STATE conditional. */ addr = (unsigned long) (SIGSEGV_FAULT_ADDRESS (code, exc_state)); /* It gets worse if we want to retrieve the machine registers, so we call the user handler before detecting if the exception is really a stack fault. */ if (call_user_handler ((void *) addr, 0)) return KERN_SUCCESS; /* See http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/thread_get_state.html. */ state_count = SIGSEGV_THREAD_STATE_COUNT; if (thread_get_state (thread, SIGSEGV_THREAD_STATE_FLAVOR, (void *) &thread_state, &state_count) != KERN_SUCCESS) { /* The thread is supposed to be suspended while the exception handler is called. This shouldn't fail. */ #ifdef DEBUG_EXCEPTION_HANDLING fprintf (stderr, "thread_get_state failed for thread state\n"); #endif return KERN_FAILURE; } sp = (unsigned long) (SIGSEGV_STACK_POINTER (thread_state)); /* Got the thread's state. Now extract the address that caused the fault and invoke the user's handler. */ save_thread_state = thread_state; /* If the fault address is near the stack pointer, it's a stack overflow. Otherwise, treat it like a normal SIGSEGV. */ if (addr <= sp + 4096 && sp <= addr + 4096) { unsigned long new_safe_esp; #ifdef DEBUG_EXCEPTION_HANDLING fprintf (stderr, "Treating as stack overflow, sp = 0x%lx\n", (char *) sp); #endif new_safe_esp = #if STACK_DIRECTION < 0 stk_extra_stack + stk_extra_stack_size - 256; #else stk_extra_stack + 256; #endif #ifdef __i386__ new_safe_esp &= -16; /* align */ new_safe_esp -= 4; /* make room for (unused) return address slot */ #endif SIGSEGV_STACK_POINTER (thread_state) = new_safe_esp; /* Continue handling this fault in the faulting thread. (We cannot longjmp while in the exception handling thread, so we need to mimic what signals do!) */ SIGSEGV_PROGRAM_COUNTER (thread_state) = (unsigned long) altstack_handler; } else { if (call_user_handler ((void *) addr, 1)) return KERN_SUCCESS; SIGSEGV_PROGRAM_COUNTER (thread_state) = (unsigned long) terminating_handler; } /* See http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/thread_set_state.html. */ if (thread_set_state (thread, SIGSEGV_THREAD_STATE_FLAVOR, (void *) &thread_state, state_count) != KERN_SUCCESS) { #ifdef DEBUG_EXCEPTION_HANDLING fprintf (stderr, "thread_set_state failed for altstack state\n"); #endif return KERN_FAILURE; } return KERN_SUCCESS; }
void sigacthandler(int sig, siginfo_t *sip, void *uvp) { ucontext_t *ucp = uvp; ulwp_t *self = curthread; /* * Do this in case we took a signal while in a cancelable system call. * It does no harm if we were not in such a system call. */ self->ul_sp = 0; if (sig != SIGCANCEL) self->ul_cancel_async = self->ul_save_async; /* * If this thread has performed a longjmp() from a signal handler * back to main level some time in the past, it has left the kernel * thinking that it is still in the signal context. We repair this * possible damage by setting ucp->uc_link to NULL if we know that * we are actually executing at main level (self->ul_siglink == NULL). * See the code for setjmp()/longjmp() for more details. */ if (self->ul_siglink == NULL) ucp->uc_link = NULL; /* * If we are not in a critical region and are * not deferring signals, take the signal now. */ if ((self->ul_critical + self->ul_sigdefer) == 0) { call_user_handler(sig, sip, ucp); /* * On the surface, the following call seems redundant * because call_user_handler() cannot return. However, * we don't want to return from here because the compiler * might recycle our frame. We want to keep it on the * stack to assist debuggers such as pstack in identifying * signal frames. The call to thr_panic() serves to prevent * tail-call optimisation here. */ thr_panic("sigacthandler(): call_user_handler() returned"); } /* * We are in a critical region or we are deferring signals. When * we emerge from the region we will call take_deferred_signal(). */ ASSERT(self->ul_cursig == 0); self->ul_cursig = (char)sig; if (sip != NULL) (void) memcpy(&self->ul_siginfo, sip, sizeof (siginfo_t)); else self->ul_siginfo.si_signo = 0; /* * Make sure that if we return to a call to __lwp_park() * or ___lwp_cond_wait() that it returns right away * (giving us a spurious wakeup but not a deadlock). */ set_parking_flag(self, 0); /* * Return to the previous context with all signals blocked. * We will restore the signal mask in take_deferred_signal(). * Note that we are calling the system call trap here, not * the setcontext() wrapper. We don't want to change the * thread's ul_sigmask by this operation. */ ucp->uc_sigmask = maskset; (void) __setcontext(ucp); thr_panic("sigacthandler(): __setcontext() returned"); }