* interrupt-handling magic
*/
* This software is part of the SBCL system. See the README file for
* more information.
*
* This software is derived from the CMU CL system, which was
* written at Carnegie Mellon University and released into the
* public domain. The software is in the public domain and is
* provided with absolutely no warranty. See the COPYING and CREDITS
* files for more information.
*/
*
* In the case of most signals, when Lisp asks us to handle the
* signal, the outermost handler (the one actually passed to UNIX) is
* either interrupt_handle_now(..) or maybe_now_maybe_later(..).
* In that case, the Lisp-level handler is stored in interrupt_handlers[..]
* and interrupt_low_level_handlers[..] is cleared.
*
* However, some signals need special handling, e.g.
*
* o the SIGSEGV (for e.g. Linux) or SIGBUS (for e.g. FreeBSD) used by the
* garbage collector to detect violations of write protection,
* because some cases of such signals (e.g. GC-related violations of
* write protection) are handled at C level and never passed on to
* Lisp. For such signals, we still store any Lisp-level handler
* in interrupt_handlers[..], but for the outermost handle we use
* the value from interrupt_low_level_handlers[..], instead of the
* ordinary interrupt_handle_now(..) or interrupt_handle_later(..).
*
* o the SIGTRAP which Lisp code may uses to handle breakpoints,
* pseudo-atomic sections, and some classes of error (e.g. "function
* not defined"). This never goes anywhere near the Lisp handlers at all.
* See src/code/signal.lisp
*
* - WHN 20000728, dan 20010128 */
#include "sbcl.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#ifndef LISP_FEATURE_WIN32
#include <sys/wait.h>
#endif
#include <errno.h>
#include "runtime.h"
#include "arch.h"
#include "os.h"
#include "interrupt.h"
#include "globals.h"
#include "lispregs.h"
#include "validate.h"
#include "interr.h"
#include "gc.h"
#include "alloc.h"
#include "dynbind.h"
#include "getallocptr.h"
#include "genesis/fdefn.h"
#include "genesis/simple-fun.h"
#include "genesis/cons.h"
#include "genesis/vector.h"
#ifdef ATOMIC_LOGGING
#include "atomiclog.inc"
uword_t *eventdata;
int n_logevents;
#endif
#ifdef ADDRESS_SANITIZER
#include <sanitizer/asan_interface.h>
#endif
* This is a workaround for some slightly silly Linux/GNU Libc
* behaviour: glibc defines sigset_t to support 1024 signals, which is
* more than the kernel. This is usually not a problem, but becomes
* one when we want to save a signal mask from a ucontext, and restore
* it later into another ucontext: the ucontext is allocated on the
* stack by the kernel, so copying a libc-sized sigset_t into it will
* overflow and cause other data on the stack to be corrupted */
#ifdef LISP_FEATURE_WIN32
# define REAL_SIGSET_SIZE_BYTES (4)
#else
* In fact it is *not* a multiple of 8 - it it 65 on x86-64-linux */
# define REAL_SIGSET_SIZE_BYTES ((NSIG/8))
#endif
#ifdef LISP_FEATURE_NETBSD
#define OS_SA_NODEFER 0
#else
#define OS_SA_NODEFER SA_NODEFER
#endif
static inline void
sigcopyset(sigset_t *new, sigset_t *old)
{
#ifdef ADDRESS_SANITIZER
sigemptyset(new);
#endif
memcpy(new, old, REAL_SIGSET_SIZE_BYTES);
}
* be handled in a high-level way? (Early in cold init, the answer is
* 'no', because Lisp is still too brain-dead to handle anything.
* After sufficient initialization has been completed, the answer
* becomes 'yes'.) */
boolean internal_errors_enabled = 0;
#ifndef LISP_FEATURE_WIN32
static
void (*interrupt_low_level_handlers[NSIG]) (int, siginfo_t*, os_context_t*);
#endif
lispobj lisp_sig_handlers[NSIG];
* FPU control word from the context, as after the signal is delivered
* we appear to have a null FPU control word. */
#if defined(RESTORE_FP_CONTROL_FROM_CONTEXT)
#define RESTORE_FP_CONTROL_WORD(context,void_context) \
os_context_t *context = arch_os_get_context(&void_context); \
os_restore_fp_control(context);
#else
#define RESTORE_FP_CONTROL_WORD(context,void_context) \
os_context_t *context = arch_os_get_context(&void_context);
#endif
* because NSIG is the maximum capacity of a sigset, whereas SIGRTMAX is a
* possibly smaller value indicating the number of signal numbers that could
* really be in use. For systems which have distinctly different values
* (discounting the deliberate off-by-one nature), sigismember() may return -1
* for signal numbers in excess of SIGRTMAX, and we would generally take
* -1 to mean boolean 'true' unless it is carefully checked for.
* The following program illustrates the issue:
* #include <signal.h>
* #include <stdio.h>
* void main() {
* sigset_t ss;
* sigemptyset(&ss);
* printf("%d %d %d\n", SIGRTMAX, NSIG, sigismember(&ss, 41));
* }
* ./sigismembertest => 40 65 -1
*/
#ifdef SIGRTMAX
# define MAX_SIGNUM SIGRTMAX
#else
# define MAX_SIGNUM (NSIG-1)
#endif
static void sigmask_logior(sigset_t *dest, const sigset_t *source)
{
int i;
for(i = 1; i <= MAX_SIGNUM; i++) {
if (sigismember(source, i)) sigaddset(dest, i);
}
}
static void sigmask_logandc(sigset_t *dest, const sigset_t *source)
{
int i;
for(i = 1; i <= MAX_SIGNUM; i++) {
if (sigismember(source, i)) sigdelset(dest, i);
}
}
* Non-targetted, truly asynchronous signals can be delivered to
* basically any thread, but invoking Lisp handlers in such foreign
* threads is really bad, so let's resignal it.
*
* This should at least bring attention to the problem, but it cannot
* work for SIGSEGV and similar. It is good enough for timers, and
* maybe all deferrables. */
#if defined LISP_FEATURE_DARWIN && defined LISP_FEATURE_SB_THREAD
pthread_key_t foreign_thread_ever_lispified;
int sigwait_bug_mitigation_count;
#endif
#ifdef LISP_FEATURE_WIN32
#define resignal_to_lisp_thread(dummy1,dummy2) {}
#else
static void
resignal_to_lisp_thread(int signal, os_context_t *context)
{
#if defined LISP_FEATURE_DARWIN && defined LISP_FEATURE_SB_THREAD
if (signal == SIG_STOP_FOR_GC && pthread_getspecific(foreign_thread_ever_lispified)) {
__sync_fetch_and_add(&sigwait_bug_mitigation_count, 1);
return;
}
#endif
if (!sigismember(&deferrable_sigset,signal)) {
corruption_warning_and_maybe_lose
#ifdef LISP_FEATURE_SB_THREAD
("Received signal %d @ %lx in non-lisp"THREAD_ID_LABEL", resignaling to a lisp thread.",
signal, *os_context_pc_addr(context), THREAD_ID_VALUE);
#else
("Received signal %d in non-lisp thread, resignaling to a lisp thread.", signal);
#endif
}
sigset_t sigset;
sigemptyset(&sigset);
int i;
for(i = 1; i < NSIG; i++) {
if (!ARE_SAME_HANDLER(interrupt_low_level_handlers[i], SIG_DFL)
|| lisp_sig_handlers[i]) {
sigaddset(&sigset, i);
}
}
thread_sigmask(SIG_BLOCK, &sigset, 0);
sigmask_logior(os_context_sigmask_addr(context), &sigset);
kill(getpid(), signal);
}
#endif
#if INSTALL_SIG_MEMORY_FAULT_HANDLER && defined(THREAD_SANITIZER)
* 'sa_mask' given to sigaction(). This is courtesy of an interceptor -
* https://github.com/llvm-mirror/compiler-rt/blob/bcc227ee4af1ef3e63033b35dcb1d5627a3b2941/lib/tsan/rtl/tsan_interceptors.cc#L1972
*
* So among other things, SIGSEGV is blocked on receipt of any random signal
* of interest (SIGPROF, SIGALRM, SIGPIPE, ...) that might call Lisp code.
* Therefore, if any handler re-enters Lisp, there is a high likelihood
* of SIGSEGV being delivered while blocked. Unfortunately, the OS treats
* blocked SIGSEGV exactly as if the specified disposition were SIG_DFL,
* which results in process termination and a core dump.
*
* Our trick is to unblock SIGSEGV early in every handler,
* so not to face sudden death if it happens to invoke Lisp.
*/
# define UNBLOCK_SIGSEGV() \
{ sigset_t mask; sigemptyset(&mask); \
sigaddset(&mask, SIG_MEMORY_FAULT); \
thread_sigmask(SIG_UNBLOCK, &mask, 0); }
#else
# define UNBLOCK_SIGSEGV() {}
#endif
* simple-base-string and won't move, this is slightly ok */
__attribute__((unused)) static char* cur_thread_name()
{
struct thread* th = get_sb_vm_thread();
struct thread_instance *lispthread =
(void*)(th->lisp_thread - INSTANCE_POINTER_LOWTAG);
struct vector* name = VECTOR(lispthread->name);
if (widetag_of(&name->header) == SIMPLE_BASE_STRING_WIDETAG) return (char*)name->data;
return "?";
}
* called from one of:
*
* interrupt_handle_now_handler
* maybe_now_maybe_later
* low_level_handle_now_handler
*
* This gives us a single point of control (or three) over errno, fp
* control word, and fixing up signal context on sparc.
*
* The SPARC/Linux platform doesn't quite do signals the way we want
* them done. The third argument in the handler isn't filled in by the
* kernel properly, so we fix it up ourselves in the
* arch_os_get_context(..) function. -- CSR, 2002-07-23
*/
#ifdef ATOMIC_LOGGING
void dump_eventlog()
{
int i = 0;
uword_t *e = eventdata;
while (i<n_logevents) {
char *fmt = (char*)e[i+1];
switch (e[i]) {
default: printf("busted event log"); return;
case 2: printf(fmt); break;
case 3: printf(fmt,e[i+2]); break;
case 4: printf(fmt,e[i+2],e[i+3]); break;
case 5: printf(fmt,e[i+2],e[i+3],e[i+4]); break;
case 6: printf(fmt,e[i+2],e[i+3],e[i+4],e[i+5]); break;
case 7: printf(fmt,e[i+2],e[i+3],e[i+4],e[i+5],e[i+6]); break;
}
putchar('\n');
i += e[i];
}
}
static void record_signal(int sig, void* context)
{
event3("sig%d @%p in %d", sig, (void*)*os_context_pc_addr(context),
(int)get_sb_vm_thread()->os_kernel_tid);
}
#define RECORD_SIGNAL(sig,ctxt) if(sig!=SIGSEGV)record_signal(sig,ctxt);
#else
#define RECORD_SIGNAL(sig,ctxt)
#endif
#if defined(SHARED_LIBRARY) || defined(LISP_FEATURE_WIN32)
# define should_handle_in_this_thread(c) (1)
#else
# define should_handle_in_this_thread(c) lisp_thread_p(c)
#endif
#define SAVE_ERRNO(signal,context,void_context) \
{ \
int _saved_errno = errno; \
RECORD_SIGNAL(signal,void_context); \
UNBLOCK_SIGSEGV(); \
RESTORE_FP_CONTROL_WORD(context,void_context); \
if (should_handle_in_this_thread(context)) {
#define RESTORE_ERRNO \
} else resignal_to_lisp_thread(signal,void_context); \
errno = _saved_errno; \
}
static void run_deferred_handler(struct interrupt_data *data,
os_context_t *context);
void
sigset_tostring(const sigset_t *sigset, char* result, int result_length)
{
int i;
int len = 0;
for(i = 1; i <= MAX_SIGNUM; i++)
if (sigismember(sigset, i)) {
if (len > result_length - 5) {
strcpy(result, "too many to list");
return;
}
len += sprintf(result+len, "%s%d", len?",":"", i);
}
result[len] = 0;
}
#ifdef LISP_FEATURE_SB_SAFEPOINT
static void sigaddset_deferrable(sigset_t *s) {
sigaddset(s, SIGURG);
}
static void sigaddset_async(sigset_t *s) {
#else
static void sigaddset_deferrable(sigset_t *s) {
#endif
sigaddset(s, SIGHUP);
sigaddset(s, SIGINT);
sigaddset(s, SIGTERM);
sigaddset(s, SIGQUIT);
sigaddset(s, SIGALRM);
sigaddset(s, SIGURG);
sigaddset(s, SIGTSTP);
sigaddset(s, SIGCHLD);
#ifdef SIGIO
sigaddset(s, SIGIO);
#else
sigaddset(s, SIGPOLL);
#endif
#ifndef LISP_FEATURE_BACKTRACE_ON_SIGNAL
sigaddset(s, SIGXCPU);
#endif
sigaddset(s, SIGXFSZ);
#if !(defined SIG_STOP_FOR_GC && SIG_STOP_FOR_GC == SIGVTALRM)
sigaddset(s, SIGVTALRM);
#endif
#if !(defined SIG_STOP_FOR_GC && SIG_STOP_FOR_GC == SIGWINCH)
sigaddset(s, SIGWINCH);
#endif
}
static void
sigaddset_gc(sigset_t __attribute__((unused)) *sigset)
{
#ifdef THREADS_USING_GCSIGNAL
sigaddset(sigset,SIG_STOP_FOR_GC);
#endif
}
void
sigaddset_blockable(sigset_t *sigset)
{
#ifdef LISP_FEATURE_SB_SAFEPOINT
sigaddset_async(sigset);
#else
sigaddset_deferrable(sigset);
sigaddset_gc(sigset);
#endif
sigaddset(sigset, SIGPIPE);
sigaddset(sigset, SIGPROF);
}
sigset_t deferrable_sigset;
sigset_t blockable_sigset;
sigset_t thread_start_sigset;
* We always use SIGUSR2 as SIG_STOP_FOR_GC, though in days past it may have
* varied by OS. Also, long ago, there was a different signal to resume after
* suspension, but now we use a semaphore for that, which is technically
* on shaky ground, but seems to work. e.g. consider an implementation of
* of sem_wait that requires a call to malloc; it could fail badly for us */
sigset_t gc_sigset;
* and fail if there is a mixture. Explicitly ignore SIGALRM which we now
* allow to be always blocked in a thread and/or manipulated.
* Also don't bother with ones guarded by #ifdef in sigaddset_deferrable
* (SIGIO, SIGPOLL, SIGXCPU).
* The intent is to perform a best-effort check that the runtime's assumptions
* are not egregiously violated, not to enforce proper use of each and every signal.
* (Who would add a SIGTSTP handler that is not completely async safe anyway?)
*/
boolean
deferrables_blocked_p(sigset_t *sigset)
{
sigset_t current;
if (sigset == 0) {
thread_sigmask(SIG_BLOCK, 0, ¤t);
sigset = ¤t;
}
#ifdef LISP_FEATURE_SB_SAFEPOINT
* All other deferrable signals remain permanently in a blocked state.
* Therefore the answer to the question of whether deferrables
* are blocked is simply whether SIGURG is blocked */
return sigismember(sigset, SIGURG);
#else
* which not only doesn't rely on signal deferral, but wants to manipulate the
* blocked/unblocked bit completely independently of SBCL's requirements.
* Such usage would have needed to either modify the global deferrable_sigset
* at runtime, or locally patch it out of sigaddset_deferrable.
* I'd prefer to remove external access to deferrable_sigset which suggests that
* this predicate should be insensitive to whether SIGPROF is deferrable.
* The actual deferral mechanmism still works, because remember, this test does
* not affect behavior of correct code - it is just to decide whether we understand
* the signal mask to be in a valid state, but it was overly restrictive.
*
* Also SIGXCPU and SIGPWR are conspicuously absent. SB-THREAD:INTERRUPT-THREAD
* used SIGPWR long ago, but I don't know why it was never in deferrable_sigset.
*/
const int expected_mask = 0x3ff
#if (defined SIG_STOP_FOR_GC && SIG_STOP_FOR_GC == SIGVTALRM)
- (1<<1)
#endif
#if (defined SIG_STOP_FOR_GC && SIG_STOP_FOR_GC == SIGWINCH)
- (1<<0)
#endif
;
int mask = (sigismember(sigset, SIGHUP) << 9)
| (sigismember(sigset, SIGINT) << 8)
| (sigismember(sigset, SIGTERM) << 7)
| (sigismember(sigset, SIGQUIT) << 6)
| (sigismember(sigset, SIGURG) << 5)
| (sigismember(sigset, SIGTSTP) << 4)
| (sigismember(sigset, SIGCHLD) << 3)
| (sigismember(sigset, SIGXFSZ) << 2)
#if !(defined SIG_STOP_FOR_GC && SIG_STOP_FOR_GC == SIGVTALRM)
| (sigismember(sigset, SIGVTALRM) << 1)
#endif
#if !(defined SIG_STOP_FOR_GC && SIG_STOP_FOR_GC == SIGWINCH)
| (sigismember(sigset, SIGWINCH) << 0)
#endif
;
if (mask == expected_mask) return 1;
if (!mask) return 0;
char buf[3*64];
sigset_tostring(sigset, buf, sizeof buf);
lose("deferrable signals partially blocked: {%s}", buf);
#endif
}
void
check_deferrables_unblocked_or_lose(sigset_t *sigset)
{
if (deferrables_blocked_p(sigset))
lose("deferrables blocked");
}
void
check_deferrables_blocked_or_lose(sigset_t *sigset)
{
if (!deferrables_blocked_p(sigset))
lose("deferrables unblocked");
}
static void assert_blockables_blocked()
{
#if !defined(LISP_FEATURE_WIN32)
* tracks the sigmask and checks it explicitly, some functions are
* still required to keep the mask set up properly. (After all, the
* goal of the sigmask emulation is to not have to change all the
* call sites in the first place.)
*
* However, this does not hold for all signals equally: While
* deferrables matter ("is interrupt-thread okay?"), it is not worth
* having to set up blockables properly (which include the
* non-existing GC signals).
*
* Yet, as the original comment explains it:
* Adjusting FREE-INTERRUPT-CONTEXT-INDEX* and other aspecs of
* fake_foreign_function_call machinery are sometimes useful here[...].
*
* So we merely skip this assertion.
* -- DFL, trying to expand on a comment by AK.
*/
sigset_t mask;
thread_sigmask(SIG_BLOCK, 0, &mask);
* (1) stop-for-GC
* (2) other blockable asynchronous signals
* (3) other blockable synchronous signals (SIGPIPE)
* If the representative bit is in the mask, say the whole set is.
* Since this is just a check of an invariant which correct execution
* will always adhere to, there is not much additional advantage to
* looking at all the signal bits, unlike the situation where the
* mask test is used as a predicate to decide on control flow.
*/
if (!(
#ifdef THREADS_USING_GCSIGNAL
sigismember(&mask, SIG_STOP_FOR_GC) &&
#endif
sigismember(&mask, SIGHUP) &&
sigismember(&mask, SIGPIPE)))
lose("blockables unblocked");
#endif
}
#ifndef LISP_FEATURE_SB_SAFEPOINT
void
check_gc_signals_unblocked_or_lose(sigset_t *sigset)
{
sigset_t current;
if (!sigset) {
thread_sigmask(SIG_BLOCK, 0, ¤t);
sigset = ¤t;
}
if (sigismember(sigset, SIG_STOP_FOR_GC))
lose("gc signals blocked");
}
#endif
void
block_deferrable_signals(sigset_t *old)
{
thread_sigmask(SIG_BLOCK, &deferrable_sigset, old);
}
void
block_blockable_signals(sigset_t *old)
{
thread_sigmask(SIG_BLOCK, &blockable_sigset, old);
}
void
unblock_deferrable_signals(sigset_t *where)
{
if (interrupt_handler_pending_p())
lose("unblock_deferrable_signals: losing proposition");
#ifndef LISP_FEATURE_SB_SAFEPOINT
check_gc_signals_unblocked_or_lose(where);
#endif
sigset_t localmask, *sigset;
if (get_sb_vm_thread()->state_word.user_thread_p) {
sigset = &deferrable_sigset;
} else {
* Actually, if we get here in the finalizer thread, things are
* in bad shape - stack exhaustion or something */
localmask = deferrable_sigset;
sigdelset(&localmask, SIGALRM);
sigset = &localmask;
}
if (where)
sigmask_logandc(where, sigset);
else
thread_sigmask(SIG_UNBLOCK, sigset, 0);
}
#ifndef LISP_FEATURE_SB_SAFEPOINT
void unblock_gc_signals(void) {
thread_sigmask(SIG_UNBLOCK, &gc_sigset, 0);
}
#endif
void
unblock_signals_in_context_and_maybe_warn(os_context_t *context)
{
sigset_t *sigset = os_context_sigmask_addr(context);
#ifndef LISP_FEATURE_SB_SAFEPOINT
if (sigismember(sigset, SIG_STOP_FOR_GC)) {
corruption_warning_and_maybe_lose(
"Enabling blocked gc signals to allow returning to Lisp without risking\n\
gc deadlocks. Since GC signals are only blocked in signal handlers when \n\
they are not safe to interrupt at all, this is a pretty severe occurrence.\n");
sigdelset(sigset, SIG_STOP_FOR_GC);
}
#endif
if (!interrupt_handler_pending_p()) {
unblock_deferrable_signals(sigset);
}
}
inline static void
check_interrupts_enabled_or_lose(os_context_t *context)
{
__attribute__((unused)) struct thread *thread = get_sb_vm_thread();
if (read_TLS(INTERRUPTS_ENABLED,thread) == NIL)
lose("interrupts not enabled");
if (arch_pseudo_atomic_atomic(context))
lose ("in pseudo atomic section");
}
* handler, because that means that deferabbles are already blocked.
* The purpose is to avoid losing the pending gc signal if a
* deferrable interrupt async unwinds between clearing the pseudo
* atomic and trapping to GC.*/
#ifndef LISP_FEATURE_SB_SAFEPOINT
void
maybe_save_gc_mask_and_block_deferrables(sigset_t *sigset)
{
#ifndef LISP_FEATURE_WIN32
struct thread *thread = get_sb_vm_thread();
struct interrupt_data *data = &thread_interrupt_data(thread);
sigset_t oldset;
* blocked. Let's make sure we are not interrupted. */
block_blockable_signals(&oldset);
#ifndef LISP_FEATURE_SB_THREAD
* block. */
if (data->gc_blocked_deferrables)
lose("gc_blocked_deferrables already true");
#endif
if ((!data->pending_handler) &&
(!data->gc_blocked_deferrables)) {
FSHOW_SIGNAL((stderr,"/setting gc_blocked_deferrables\n"));
data->gc_blocked_deferrables = 1;
if (sigset) {
sigcopyset(&data->pending_mask, sigset);
sigaddset_deferrable(sigset);
thread_sigmask(SIG_SETMASK,&oldset,0);
return;
} else {
* unblock gc signals. In the end, this is equivalent to
* blocking the deferrables. */
sigcopyset(&data->pending_mask, &oldset);
unblock_gc_signals();
return;
}
}
thread_sigmask(SIG_SETMASK,&oldset,0);
#endif
}
#endif
* enabled, without the protection of *GC-INHIBIT* T and there is gc
* (or stop for gc) pending, but we haven't trapped yet? */
int
in_leaving_without_gcing_race_p(struct thread __attribute__((unused)) *thread)
{
return ((read_TLS(IN_WITHOUT_GCING,thread) != NIL) &&
(read_TLS(INTERRUPTS_ENABLED,thread) != NIL) &&
(read_TLS(GC_INHIBIT,thread) == NIL) &&
((read_TLS(GC_PENDING,thread) != NIL)
#if defined(LISP_FEATURE_SB_THREAD)
|| (read_TLS(STOP_FOR_GC_PENDING,thread) != NIL)
#endif
));
}
static void
check_interrupt_context_or_lose(os_context_t *context)
{
struct thread *thread = get_sb_vm_thread();
struct interrupt_data *data = &thread_interrupt_data(thread);
int interrupt_deferred_p = (data->pending_handler != 0);
int interrupt_pending = (read_TLS(INTERRUPT_PENDING,thread) != NIL);
sigset_t *sigset = os_context_sigmask_addr(context);
* handle_allocation_trap. */
#if defined(LISP_FEATURE_GENCGC) && !GENCGC_IS_PRECISE
int interrupts_enabled = (read_TLS(INTERRUPTS_ENABLED,thread) != NIL);
int gc_inhibit = (read_TLS(GC_INHIBIT,thread) != NIL);
int gc_pending = (read_TLS(GC_PENDING,thread) == T);
int pseudo_atomic_interrupted = get_pseudo_atomic_interrupted(thread);
int in_race_p = in_leaving_without_gcing_race_p(thread);
int safepoint_active = 0;
#if defined(LISP_FEATURE_SB_SAFEPOINT)
* we're already holding it (thread_register_gc_trigger() is
* called from PA, gc_stop_the_world() and gc_start_the_world()
* are called from WITHOUT-GCING, all other takers of the lock
* have deferrables blocked). */
if (!(interrupt_pending || pseudo_atomic_interrupted || gc_inhibit)) {
WITH_GC_STATE_LOCK {
safepoint_active = gc_cycle_active();
}
}
#endif
* section and trapping, a SIG_STOP_FOR_GC would see the next
* check fail, for this reason sig_stop_for_gc handler does not
* call this function. */
if (interrupt_deferred_p) {
if (!(!interrupts_enabled || pseudo_atomic_interrupted || in_race_p))
lose("Stray deferred interrupt.");
}
if (gc_pending)
if (!(pseudo_atomic_interrupted || gc_inhibit || in_race_p || safepoint_active))
lose("GC_PENDING, but why?");
#if defined(LISP_FEATURE_SB_THREAD)
{
int stop_for_gc_pending =
(read_TLS(STOP_FOR_GC_PENDING,thread) != NIL);
if (stop_for_gc_pending)
if (!(pseudo_atomic_interrupted || gc_inhibit || in_race_p || safepoint_active))
lose("STOP_FOR_GC_PENDING, but why?");
if (pseudo_atomic_interrupted)
if (!(gc_pending || stop_for_gc_pending || interrupt_deferred_p))
lose("pseudo_atomic_interrupted, but why?");
}
#else
if (pseudo_atomic_interrupted)
if (!(gc_pending || interrupt_deferred_p))
lose("pseudo_atomic_interrupted, but why?");
#endif
#endif
if (interrupt_pending && !interrupt_deferred_p)
lose("INTERRUPT_PENDING but not pending handler.");
if ((data->gc_blocked_deferrables) && interrupt_pending)
lose("gc_blocked_deferrables and interrupt pending.");
if (data->gc_blocked_deferrables)
check_deferrables_blocked_or_lose(sigset);
if (interrupt_pending || interrupt_deferred_p ||
data->gc_blocked_deferrables)
check_deferrables_blocked_or_lose(sigset);
else {
check_deferrables_unblocked_or_lose(sigset);
#ifndef LISP_FEATURE_SB_SAFEPOINT
* that run lisp code. */
check_gc_signals_unblocked_or_lose(sigset);
#endif
}
}
* utility routines used by various signal handlers
*/
#ifdef LISP_FEATURE_ARM64
static void
build_fake_control_stack_frames(struct thread __attribute__((unused)) *th,
os_context_t __attribute__((unused)) *context)
{
lispobj oldcont;
lispobj* csp = (lispobj *)(uword_t) (*os_context_register_addr(context, reg_CSP)) + 2;
access_control_frame_pointer(th) = (lispobj *)(uword_t) csp;
oldcont = (lispobj)(*os_context_register_addr(context, reg_CFP));
access_control_frame_pointer(th)[1] = *os_context_pc_addr(context);
access_control_frame_pointer(th)[0] = oldcont;
access_control_stack_pointer(th) = csp + 2;
}
#else
static void
build_fake_control_stack_frames(struct thread __attribute__((unused)) *th,
os_context_t __attribute__((unused)) *context)
{
#ifndef LISP_FEATURE_C_STACK_IS_CONTROL_STACK
lispobj oldcont;
#if !defined(LISP_FEATURE_ARM) && !defined(LISP_FEATURE_ARM64)
access_control_frame_pointer(th) =
(lispobj *)(uword_t)
(*os_context_register_addr(context, reg_CSP));
if ((lispobj *)(uword_t)
(*os_context_register_addr(context, reg_CFP))
== access_control_frame_pointer(th)) {
* frame isn't built yet. */
if (functionp(*os_context_register_addr(context, reg_CODE))) {
* build it for them. */
access_control_frame_pointer(th)[0] =
*os_context_register_addr(context, reg_OCFP);
access_control_frame_pointer(th)[1] =
*os_context_register_addr(context, reg_LRA);
access_control_frame_pointer(th) += 2;
oldcont = (lispobj)(*os_context_register_addr(context, reg_CFP));
}
else {
* partial frame wasn't there. */
oldcont = (lispobj)(*os_context_register_addr(context, reg_OCFP));
}
} else
#elif defined (LISP_FEATURE_ARM)
access_control_frame_pointer(th) = (lispobj*) SymbolValue(CONTROL_STACK_POINTER, th);
#elif defined (LISP_FEATURE_ARM64)
access_control_frame_pointer(th) =
(lispobj *)(uword_t) (*os_context_register_addr(context, reg_CSP)) + 2;
#endif
* allocate a stack frame due to stack arguments. */
* "Can anything strange happen during return?" */
{
oldcont = (lispobj)(*os_context_register_addr(context, reg_CFP));
}
access_control_stack_pointer(th) = access_control_frame_pointer(th) + 3;
access_control_frame_pointer(th)[0] = oldcont;
#ifdef reg_CODE
access_control_frame_pointer(th)[1] = NIL;
access_control_frame_pointer(th)[2] =
(lispobj)(*os_context_register_addr(context, reg_CODE));
#else
access_control_frame_pointer(th)[1] = *os_context_pc_addr(context);
#endif
#endif
}
#endif
* frames. */
void fake_foreign_function_call_noassert(os_context_t *context)
{
int context_index;
struct thread *thread=get_sb_vm_thread();
#ifdef reg_ALLOC
#ifdef LISP_FEATURE_SB_THREAD
thread->pseudo_atomic_bits =
#else
dynamic_space_free_pointer =
(lispobj *)(uword_t)
#endif
(*os_context_register_addr(context, reg_ALLOC));
#if defined(LISP_FEATURE_MIPS)
if ((sword_t)dynamic_space_free_pointer & 1) {
lose("dead in fake_foreign_function_call, context = %x", context);
}
#endif
#endif
#ifdef reg_BSP
set_binding_stack_pointer(thread,
*os_context_register_addr(context, reg_BSP));
#endif
#if defined(LISP_FEATURE_ARM)
bind_variable(INTERRUPTED_CONTROL_STACK_POINTER,
SymbolValue(CONTROL_STACK_POINTER, thread),
thread);
#endif
* and save the context in the context array. */
context_index =
fixnum_value(read_TLS(FREE_INTERRUPT_CONTEXT_INDEX,thread));
if (context_index >= MAX_INTERRUPTS)
lose("maximum interrupt nesting depth (%d) exceeded", MAX_INTERRUPTS);
bind_variable(FREE_INTERRUPT_CONTEXT_INDEX,
make_fixnum(context_index + 1),thread);
nth_interrupt_context(context_index, thread) = context;
build_fake_control_stack_frames(thread, context);
#if !defined(LISP_FEATURE_X86) && !defined(LISP_FEATURE_X86_64) && \
!(defined(LISP_FEATURE_ARM64) && defined(LISP_FEATURE_SB_THREAD))
* all, so leave them to believe that they are never in foreign
* code.
And ARM64 uses control_stack_pointer, which is set in
build_fake_control_stack_frames. */
foreign_function_call_active_p(thread) = 1;
#endif
}
void fake_foreign_function_call(os_context_t *context)
{
assert_blockables_blocked();
fake_foreign_function_call_noassert(context);
}
* the usual signal mask will be restored from the context when the handler
* finishes. Otherwise, be careful */
void
undo_fake_foreign_function_call(os_context_t __attribute__((unused)) *context)
{
struct thread *thread=get_sb_vm_thread();
block_blockable_signals(0);
foreign_function_call_active_p(thread) = 0;
#ifdef LISP_FEATURE_SB_THREAD
nth_interrupt_context(fixnum_value(read_TLS(FREE_INTERRUPT_CONTEXT_INDEX,thread)) - 1, thread) = NULL;
#endif
unbind(thread);
#if defined(LISP_FEATURE_ARM)
SetSymbolValue(CONTROL_STACK_POINTER,
SymbolValue(INTERRUPTED_CONTROL_STACK_POINTER,
thread),
thread);
unbind(thread);
#endif
#if defined(reg_ALLOC) && !defined(LISP_FEATURE_SB_THREAD)
*os_context_register_addr(context, reg_ALLOC) =
(uword_t) dynamic_space_free_pointer
| (*os_context_register_addr(context, reg_ALLOC)
& LOWTAG_MASK);
((uword_t)(*os_context_register_addr(context, reg_ALLOC))
& ~LOWTAG_MASK)
| ((uword_t) dynamic_space_free_pointer & LOWTAG_MASK);
*/
#endif
#if defined(reg_ALLOC) && defined(LISP_FEATURE_SB_THREAD)
* into the context (p-a-bits for p-a, and dynamic space free
* pointer for ROOM). */
*os_context_register_addr(context, reg_ALLOC) =
(uword_t) dynamic_space_free_pointer
| (thread->pseudo_atomic_bits & LOWTAG_MASK);
* not updating them. */
thread->pseudo_atomic_bits = 0;
#endif
}
* signalling an internal error */
void
interrupt_internal_error(os_context_t *context, boolean continuable)
{
DX_ALLOC_SAP(context_sap, context);
fake_foreign_function_call(context);
if (!internal_errors_enabled) {
describe_internal_error(context);
* before the Lisp error handling mechanism is set up. */
lose("internal error too early in init, can't recover");
}
thread_sigmask(SIG_SETMASK, os_context_sigmask_addr(context), 0);
#if defined(LISP_FEATURE_LINUX) && defined(LISP_FEATURE_MIPS)
{
sigset_t newset;
sigemptyset(&newset);
sigaddset(&newset, SIGTRAP);
thread_sigmask(SIG_UNBLOCK, &newset, 0);
}
#endif
SHOW("in interrupt_internal_error");
#if QSHOW == 2
* error, so that even if the Lisp error handler gets badly
* confused, we have a chance to determine what's going on. */
describe_internal_error(context);
#endif
funcall2(StaticSymbolFunction(INTERNAL_ERROR), context_sap,
continuable ? T : NIL);
undo_fake_foreign_function_call(context);
if (continuable)
arch_skip_instruction(context);
}
boolean
interrupt_handler_pending_p(void)
{
struct interrupt_data *data = &thread_interrupt_data(get_sb_vm_thread());
return (data->pending_handler != 0);
}
void
interrupt_handle_pending(os_context_t *context)
{
#ifdef ADDRESS_SANITIZER
__asan_unpoison_memory_region(context, sizeof *context);
#endif
* occurs within pseudo-atomic, it will be deferred, and we'll
* trap to here at the end of the pseudo-atomic block. Second, if
* the GC (in alloc()) decides that a GC is required, it will set
* *GC-PENDING* and pseudo-atomic-interrupted if not *GC-INHIBIT*,
* and alloc() is always called from within pseudo-atomic, and
* thus we end up here again. Third, when calling GC-ON or at the
* end of a WITHOUT-GCING, MAYBE-HANDLE-PENDING-GC will trap to
* here if there is a pending GC. Fourth, ahem, at the end of
* WITHOUT-INTERRUPTS (bar complications with nesting).
*
* A fourth way happens with safepoints: In addition to a stop for
* GC that is pending, there are thruptions. Both mechanisms are
* mostly signal-free, yet also of an asynchronous nature, so it makes
* sense to let interrupt_handle_pending take care of running them:
* It gets run precisely at those places where it is safe to process
* pending asynchronous tasks. */
struct thread *thread = get_sb_vm_thread();
struct interrupt_data *data = &thread_interrupt_data(thread);
if (arch_pseudo_atomic_atomic(context)) {
lose("Handling pending interrupt in pseudo atomic.");
}
FSHOW_SIGNAL((stderr, "/entering interrupt_handle_pending\n"));
assert_blockables_blocked();
#ifndef LISP_FEATURE_SB_SAFEPOINT
* (On safepoint builds, there is no gc_blocked_deferrables nor
* SIG_STOP_FOR_GC.)
*/
* handler, then the pending mask was saved and
* gc_blocked_deferrables set. Hence, there can be no pending
* handler and it's safe to restore the pending mask.
*
* Note, that if gc_blocked_deferrables is false we may still have
* to GC. In this case, we are coming out of a WITHOUT-GCING or a
* pseudo atomic was interrupt be a deferrable first. */
if (data->gc_blocked_deferrables) {
if (data->pending_handler)
lose("GC blocked deferrables but still got a pending handler.");
if (read_TLS(GC_INHIBIT,thread)!=NIL)
lose("GC blocked deferrables while GC is inhibited.");
* one that interrupted us during the critical section) into
* the os_context for the signal we're currently in the
* handler for. This should ensure that when we return from
* the handler the blocked signals are unblocked. */
sigcopyset(os_context_sigmask_addr(context), &data->pending_mask);
data->gc_blocked_deferrables = 0;
}
#endif
if (read_TLS(GC_INHIBIT,thread)==NIL) {
void *original_pending_handler = data->pending_handler;
#ifdef LISP_FEATURE_SB_SAFEPOINT
if (read_TLS(STOP_FOR_GC_PENDING,thread) != NIL
|| (read_TLS(THRUPTION_PENDING,thread) != NIL
&& read_TLS(INTERRUPTS_ENABLED, thread) != NIL)) {
fake_foreign_function_call(context);
thread_in_lisp_raised(context);
undo_fake_foreign_function_call(context);
}
#elif defined(LISP_FEATURE_SB_THREAD)
if (read_TLS(STOP_FOR_GC_PENDING,thread) != NIL) {
* the signal handler if it actually stops us. */
arch_clear_pseudo_atomic_interrupted(context);
sig_stop_for_gc_handler(SIG_STOP_FOR_GC,NULL,context);
} else
#endif
* used to be used in SUB-GC as part of the mechanism to
* supress recursive gcs.*/
if (read_TLS(GC_PENDING,thread) == T) {
* pending handler we don't want to run. Second, we are
* going to clear pseudo atomic interrupted to avoid
* spurious trapping on every allocation in SUB_GC and
* having a pending handler with interrupts enabled and
* without pseudo atomic interrupted breaks an
* invariant. */
if (data->pending_handler) {
bind_variable(ALLOW_WITH_INTERRUPTS, NIL, thread);
bind_variable(INTERRUPTS_ENABLED, NIL, thread);
}
arch_clear_pseudo_atomic_interrupted(context);
* is doing a gc already we will get a SIG_STOP_FOR_GC and
* that will clear it.
*
* If there is a pending handler or gc was triggerred in a
* signal handler then maybe_gc won't run POST_GC and will
* return normally. */
if (!maybe_gc(context))
lose("GC not inhibited but maybe_gc did not GC.");
if (data->pending_handler) {
unbind(thread);
unbind(thread);
}
} else if (read_TLS(GC_PENDING,thread) != NIL) {
* GC-PENDING is not NIL then we cannot trap on pseudo
* atomic due to GC (see if(GC_PENDING) logic in
* cheneygc.c an gengcgc.c), plus there is a outer
* WITHOUT-INTERRUPTS SUB_GC, so how did we end up
* here? */
lose("Trapping to run pending handler while GC in progress.");
}
assert_blockables_blocked();
* that should be handled on the spot. */
if (read_TLS(GC_PENDING,thread) != NIL)
lose("GC_PENDING after doing gc.");
#ifdef THREADS_USING_GCSIGNAL
if (read_TLS(STOP_FOR_GC_PENDING,thread) != NIL)
lose("STOP_FOR_GC_PENDING after doing gc.");
#endif
* that's already pending. Second, that there is no interrupt
* lossage: if original_pending_handler was NULL then even if
* an interrupt arrived during GC (POST-GC, really) it was
* handled. */
if (original_pending_handler != data->pending_handler)
lose("pending handler changed in gc: %p -> %p, signal = %d.",
original_pending_handler, data->pending_handler, data->pending_signal);
}
#ifndef LISP_FEATURE_WIN32
* had to be executed or because Lisp is a bit too eager to call
* DO-PENDING-INTERRUPT. */
if ((read_TLS(INTERRUPTS_ENABLED,thread) != NIL) &&
(data->pending_handler)) {
* INTERRUPT_PENDING and pseudo atomic interrupted. It's safe
* because we checked above that there is no GC pending. */
write_TLS(INTERRUPT_PENDING, NIL, thread);
arch_clear_pseudo_atomic_interrupted(context);
sigcopyset(os_context_sigmask_addr(context), &data->pending_mask);
run_deferred_handler(data, context);
}
#ifdef LISP_FEATURE_SB_SAFEPOINT
if (read_TLS(THRUPTION_PENDING,thread)==T)
* thruption pending, but a signal had been deferred. The
* pitstop at the top of this function could only take care
* of GC, and skipped the thruption, so we need to try again
* now that INTERRUPT_PENDING and the sigmask have been
* reset. */
while (check_pending_thruptions(context))
;
#endif
#endif
#ifdef LISP_FEATURE_GENCGC
if (get_pseudo_atomic_interrupted(thread))
lose("pseudo_atomic_interrupted after interrupt_handle_pending");
#endif
* without never actually doing anything, the tests in Lisp for
* when to call receive-pending-interrupt are not exact. */
FSHOW_SIGNAL((stderr, "/exiting interrupt_handle_pending\n"));
}
void
interrupt_handle_now(int signal, siginfo_t *info, os_context_t *context)
{
boolean were_in_lisp;
lispobj handler = lisp_sig_handlers[signal];
if (!functionp(handler)) return;
assert_blockables_blocked();
if (sigismember(&deferrable_sigset,signal))
check_interrupts_enabled_or_lose(context);
were_in_lisp = !foreign_function_call_active_p(get_sb_vm_thread());
if (were_in_lisp)
{
fake_foreign_function_call_noassert(context);
}
* return-elsewhere world (the original context will no longer
* be available; should we copy it or was nobody using it anyway?)
* then we should convert this to return-elsewhere */
#if !defined(LISP_FEATURE_SB_SAFEPOINT) && defined(LISP_FEATURE_C_STACK_IS_CONTROL_STACK)
* allow signals again when it sees fit. */
* Not a problem for the conservative GC. */
unblock_gc_signals();
#endif
WITH_GC_AT_SAFEPOINTS_ONLY()
{
DX_ALLOC_SAP(context_sap, context);
DX_ALLOC_SAP(info_sap, info);
FSHOW_SIGNAL((stderr,"/calling Lisp-level handler\n"));
funcall3(handler,
make_fixnum(signal),
info_sap,
context_sap);
}
if (were_in_lisp)
{
undo_fake_foreign_function_call(context);
}
}
* are that some signal was deferred during the section. Note that as
* far as C or the kernel is concerned we dealt with the signal
* already; we're just doing the Lisp-level processing now that we
* put off then */
static void
run_deferred_handler(struct interrupt_data *data, os_context_t *context)
{
* interrupt may hit, overwrite interrupt_data, so reset the
* pending handler before calling it. Trust the handler to finish
* with the siginfo before enabling interrupts. */
void (*pending_handler) (int, siginfo_t*, os_context_t*) =
data->pending_handler;
data->pending_handler=0;
FSHOW_SIGNAL((stderr, "/running deferred handler %p\n", pending_handler));
(*pending_handler)(data->pending_signal,&(data->pending_info), context);
}
#ifndef LISP_FEATURE_WIN32
static void
store_signal_data_for_later (struct interrupt_data *data, void *handler,
int signal,
siginfo_t *info, os_context_t *context)
{
if (!context || !handler || data->pending_handler)
lose("can't defer signal: context=%p handler=%p pending=%p",
context, handler, data->pending_handler);
data->pending_handler = handler;
data->pending_signal = signal;
if (info)
memcpy(&data->pending_info, info, sizeof *info);
else
memset(&data->pending_info, 0, sizeof *info);
* interrupted) is copied to be restored when run_deferred_handler
* happens. Then the usually-blocked signals are added to the mask
* in the context so that we are running with blocked signals when
* the handler returns */
sigcopyset(&data->pending_mask, os_context_sigmask_addr(context));
sigaddset_deferrable(os_context_sigmask_addr(context));
}
static boolean
can_handle_now(void *handler, struct interrupt_data *data,
int signal, siginfo_t *info, os_context_t *context)
{
#ifdef DEBUG
assert_blockables_blocked();
#endif
struct thread *thread = get_sb_vm_thread();
if (read_TLS(INTERRUPT_PENDING,thread) != NIL)
lose("interrupt already pending");
if (thread_interrupt_data(thread).pending_handler)
lose("there is a pending handler already (PA)");
if (data->gc_blocked_deferrables)
lose("can_handle_now: gc_blocked_deferrables true");
int answer = 1;
* not PSEDUO_ATOMIC_INTERRUPTED. This is important for a pseudo
* atomic section inside a WITHOUT-INTERRUPTS.
*
* Also, if in_leaving_without_gcing_race_p then
* interrupt_handle_pending is going to be called soon, so
* stashing the signal away is safe.
*/
if ((read_TLS(INTERRUPTS_ENABLED,thread) == NIL) ||
in_leaving_without_gcing_race_p(thread)) {
FSHOW_SIGNAL((stderr,
"/can_handle_now(%p,%d): deferred (RACE=%d)\n",
handler,signal,
in_leaving_without_gcing_race_p(thread)));
store_signal_data_for_later(data,handler,signal,info,context);
write_TLS(INTERRUPT_PENDING, T,thread);
answer = 0;
}
* actually use its argument for anything on x86, so this branch
* may succeed even when context is null (gencgc alloc()) */
else if (arch_pseudo_atomic_atomic(context)) {
FSHOW_SIGNAL((stderr,
"/can_handle_now(%p,%d): deferred(PA)\n",
handler,signal));
store_signal_data_for_later(data,handler,signal,info,context);
arch_set_pseudo_atomic_interrupted(context);
answer = 0;
}
check_interrupt_context_or_lose(context);
return answer;
}
static void
maybe_now_maybe_later(int signal, siginfo_t *info, void *void_context)
{
SAVE_ERRNO(signal,context,void_context);
struct thread *thread = get_sb_vm_thread();
struct interrupt_data *data = &thread_interrupt_data(thread);
if (can_handle_now(interrupt_handle_now, data, signal, info, context))
interrupt_handle_now(signal, info, context);
RESTORE_ERRNO;
}
#endif
#ifdef LISP_FEATURE_GC_METRICS
pthread_cond_t gcmetrics_condvar = PTHREAD_COND_INITIALIZER;
pthread_mutex_t gcmetrics_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
#ifdef THREADS_USING_GCSIGNAL
void
sig_stop_for_gc_handler(int __attribute__((unused)) signal,
siginfo_t __attribute__((unused)) *info,
os_context_t *context)
{
struct thread *thread=get_sb_vm_thread();
boolean was_in_lisp;
* pseudo atomic until gc is finally allowed. */
if (read_TLS(GC_INHIBIT,thread) != NIL) {
FSHOW_SIGNAL((stderr, "sig_stop_for_gc deferred (*GC-INHIBIT*)\n"));
write_TLS(STOP_FOR_GC_PENDING,T,thread);
return;
} else if (arch_pseudo_atomic_atomic(context)) {
FSHOW_SIGNAL((stderr,"sig_stop_for_gc deferred (PA)\n"));
write_TLS(STOP_FOR_GC_PENDING,T,thread);
arch_set_pseudo_atomic_interrupted(context);
maybe_save_gc_mask_and_block_deferrables
(os_context_sigmask_addr(context));
return;
}
FSHOW_SIGNAL((stderr, "/sig_stop_for_gc_handler\n"));
was_in_lisp = !foreign_function_call_active_p(get_sb_vm_thread());
if (was_in_lisp) {
fake_foreign_function_call(context);
}
write_TLS(GC_PENDING,NIL,thread);
write_TLS(STOP_FOR_GC_PENDING,NIL,thread);
* pseudo_atomic_interrupted and gc_blocked_deferrables are set,
* deferrables are blocked then pseudo_atomic_atomic is cleared,
* but a SIG_STOP_FOR_GC arrives before trapping to
* interrupt_handle_pending. Here, GC_PENDING is cleared but
* pseudo_atomic_interrupted is not and we go on running with
* pseudo_atomic_interrupted but without a pending interrupt or
* GC. GC_BLOCKED_DEFERRABLES is also left at 1. So let's tidy it
* up. */
if (thread_interrupt_data(thread).gc_blocked_deferrables) {
FSHOW_SIGNAL((stderr,"cleaning up after gc_blocked_deferrables\n"));
clear_pseudo_atomic_interrupted(thread);
struct interrupt_data *interrupt_data = &thread_interrupt_data(thread);
sigcopyset(os_context_sigmask_addr(context), &interrupt_data->pending_mask);
interrupt_data->gc_blocked_deferrables = 0;
}
* for now, and nobody else touches it, the sole exception being that GC
* sets it to RUNNING. The loads inside thread_wait_until_not()
* are slightly more interesting from that perspective */
if (thread->state_word.state != STATE_RUNNING)
lose("stop_for_gc: bad thread state: %x", (int)thread->state_word.state);
* occurs below at thread_wait_until_not(STATE_STOPPED). Note that sem_post()
* is expressly permitted in signal handlers, and set_thread_state uses it */
set_thread_state(thread, STATE_STOPPED, 0);
FSHOW_SIGNAL((stderr,"suspended\n"));
* the unused portion of the control stack to reduce conservatism.
* On the platforms with threads and exact gc it is
* actually a must. */
scrub_control_stack();
* Normally the way to implement a "suspend" operation is to issue any blocking
* syscall such as sigsuspend() or select(). Apparently every OS + C runtime that
* we wish to support has no problem with sem_wait() here in the signal handler. */
#ifdef LISP_FEATURE_GC_METRICS
int my_state;
{
struct timespec t_beginwait, t_endwait, t_runtime;
clock_gettime(CLOCK_MONOTONIC, &t_beginwait);
my_state = thread_wait_until_not(STATE_STOPPED, thread);
clock_gettime(CLOCK_MONOTONIC, &t_endwait);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t_runtime);
long elapsed = ((t_endwait.tv_sec - t_beginwait.tv_sec)*1000000000L
+ (t_endwait.tv_nsec - t_beginwait.tv_nsec)) / 1000;
struct extra_thread_data *data = thread_extra_data(thread);
if (elapsed > data->worst_gc_wait) data->worst_gc_wait = elapsed;
data->sum_gc_wait += elapsed;
data->avg_gc_wait = data->sum_gc_wait / ++data->n_gc_wait;
data->on_cpu_time = t_runtime.tv_sec * 1000000 + t_runtime.tv_nsec / 1000;
pthread_cond_broadcast(&gcmetrics_condvar);
}
#else
int my_state = thread_wait_until_not(STATE_STOPPED, thread);
#endif
FSHOW_SIGNAL((stderr,"resumed\n"));
* its own state, hence it must be running.
* (If we tried to observe a different thread, it could appear to change from
* STOPPED to DEAD, skipping RUNNING, because if you blink you might miss it) */
if (my_state != STATE_RUNNING)
lose("stop_for_gc: bad state on wakeup: %x", my_state);
if (was_in_lisp) {
undo_fake_foreign_function_call(context);
}
}
#endif
void
interrupt_handle_now_handler(int signal, siginfo_t *info, void *void_context)
{
SAVE_ERRNO(signal,context,void_context);
#ifndef LISP_FEATURE_WIN32
if ((signal == SIGILL) || (signal == SIGBUS)
#if !(defined LISP_FEATURE_LINUX || defined LISP_FEATURE_ANDROID || defined LISP_FEATURE_HAIKU)
|| (signal == SIGEMT)
#endif
)
corruption_warning_and_maybe_lose("Signal %d received (PC: %p)", signal,
*os_context_pc_addr(context));
#endif
interrupt_handle_now(signal, info, context);
RESTORE_ERRNO;
}
* returns, it will call function instead of whatever it was doing
* previously
*/
#if (defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64))
extern int *context_eflags_addr(os_context_t *context);
#endif
extern lispobj call_into_lisp(lispobj fun, lispobj *args, int nargs);
extern void post_signal_tramp(void);
extern void call_into_lisp_tramp(void);
void
arrange_return_to_c_function(os_context_t *context,
call_into_lisp_lookalike funptr,
lispobj function)
{
#ifndef LISP_FEATURE_SB_SAFEPOINT
check_gc_signals_unblocked_or_lose(os_context_sigmask_addr(context));
#endif
#if !(defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64))
void * fun=native_pointer(function);
void *code = &(((struct simple_fun *) fun)->insts);
#endif
* user's backtrace makes (as much) sense (as usual) */
* function only call Lisp code that signals an error, it's not
* much of a problem. In other words, running out of the control
* stack between a syscall and (GET-ERRNO) may clobber errno if
* something fails during signalling or in the handler. But I
* can't see what can go wrong as long as there is no CONTINUE
* like restart on them. */
#ifdef LISP_FEATURE_X86
* registers, called call_into_lisp, then restored GP registers and
* returned. It would look something like this:
push ebp
mov ebp esp
pushfl
pushal
push $0
push $0
pushl {address of function to call}
call 0x8058db0 <call_into_lisp>
addl $12,%esp
popal
popfl
leave
ret
* What we do here is set up the stack that call_into_lisp would
* expect to see if it had been called by this code, and frob the
* signal context so that signal return goes directly to call_into_lisp,
* and when that function (and the lisp function it invoked) returns,
* it returns to the second half of this imaginary function which
* restores all registers and returns to C
* For this to work, the latter part of the imaginary function
* must obviously exist in reality. That would be post_signal_tramp
*/
#ifndef LISP_FEATURE_DARWIN
uint32_t *sp=(uint32_t *)*os_context_register_addr(context,reg_ESP);
#endif
#if defined(LISP_FEATURE_DARWIN)
uint32_t *register_save_area = (uint32_t *)os_allocate(0x40);
FSHOW_SIGNAL((stderr, "/arrange_return_to_lisp_function: preparing to go to function %x, sp: %x\n", function,
*os_context_register_addr(context,reg_ESP)));
FSHOW_SIGNAL((stderr, "/arrange_return_to_lisp_function: context: %x, &context %x\n", context, &context));
* 2. copy register state into register_save_block
* 3. put a pointer to register_save_block in a register in the context
* 4. set the context's EIP to point to a trampoline which:
* a. builds the fake stack frame from the block
* b. frees the block
* c. calls the function
*/
*register_save_area = *os_context_pc_addr(context);
*(register_save_area + 1) = function;
*(register_save_area + 2) = *os_context_register_addr(context,reg_EDI);
*(register_save_area + 3) = *os_context_register_addr(context,reg_ESI);
*(register_save_area + 4) = *os_context_register_addr(context,reg_EDX);
*(register_save_area + 5) = *os_context_register_addr(context,reg_ECX);
*(register_save_area + 6) = *os_context_register_addr(context,reg_EBX);
*(register_save_area + 7) = *os_context_register_addr(context,reg_EAX);
*(register_save_area + 8) = *context_eflags_addr(context);
*os_context_pc_addr(context) =
(os_context_register_t) funptr;
*os_context_register_addr(context,reg_ECX) =
(os_context_register_t) register_save_area;
#else
*(sp-15) = (uint32_t)post_signal_tramp;
*(sp-14) = function;
*(sp-13) = 0;
*(sp-12) = 0;
*(sp-11)=*os_context_register_addr(context,reg_EDI);
*(sp-10)=*os_context_register_addr(context,reg_ESI);
*(sp-9)=*os_context_register_addr(context,reg_ESP)-8;
*(sp-8)=0;
*(sp-7)=*os_context_register_addr(context,reg_EBX);
*(sp-6)=*os_context_register_addr(context,reg_EDX);
*(sp-5)=*os_context_register_addr(context,reg_ECX);
*(sp-4)=*os_context_register_addr(context,reg_EAX);
*(sp-3)=*context_eflags_addr(context);
*(sp-2)=*os_context_register_addr(context,reg_EBP);
*(sp-1)=*os_context_pc_addr(context);
#endif
#elif defined(LISP_FEATURE_X86_64)
uword_t *sp=(uword_t *)*os_context_register_addr(context,reg_RSP);
*(sp-18) = (uint64_t)post_signal_tramp;
*(sp-17)=*os_context_register_addr(context,reg_R15);
*(sp-16)=*os_context_register_addr(context,reg_R14);
*(sp-15)=*os_context_register_addr(context,reg_R13);
*(sp-14)=*os_context_register_addr(context,reg_R12);
*(sp-13)=*os_context_register_addr(context,reg_R11);
*(sp-12)=*os_context_register_addr(context,reg_R10);
*(sp-11)=*os_context_register_addr(context,reg_R9);
*(sp-10)=*os_context_register_addr(context,reg_R8);
*(sp-9)=*os_context_register_addr(context,reg_RDI);
*(sp-8)=*os_context_register_addr(context,reg_RSI);
*(sp-7)=*os_context_register_addr(context,reg_RBX);
*(sp-6)=*os_context_register_addr(context,reg_RDX);
*(sp-5)=*os_context_register_addr(context,reg_RCX);
*(sp-4)=*os_context_register_addr(context,reg_RAX);
*(sp-3)=*context_eflags_addr(context);
*(sp-2)=*os_context_register_addr(context,reg_RBP);
*(sp-1)=*os_context_pc_addr(context);
*os_context_register_addr(context,reg_RDI) =
(os_context_register_t)function;
*os_context_register_addr(context,reg_RSI) = 0;
*os_context_register_addr(context,reg_RDX) = 0;
#else
struct thread *th=get_sb_vm_thread();
build_fake_control_stack_frames(th,context);
#endif
#ifdef LISP_FEATURE_X86
#if !defined(LISP_FEATURE_DARWIN)
*os_context_pc_addr(context) = (os_context_register_t)funptr;
*os_context_register_addr(context,reg_ECX) = 0;
*os_context_register_addr(context,reg_EBP) = (os_context_register_t)(sp-2);
#ifdef __NetBSD__
*os_context_register_addr(context,reg_UESP) =
(os_context_register_t)(sp-15);
#else
*os_context_register_addr(context,reg_ESP) = (os_context_register_t)(sp-15);
#endif
#endif
#elif defined(LISP_FEATURE_X86_64)
*os_context_pc_addr(context) = (os_context_register_t)funptr;
*os_context_register_addr(context,reg_RCX) = 0;
*os_context_register_addr(context,reg_RBP) = (os_context_register_t)(sp-2);
*os_context_register_addr(context,reg_RSP) = (os_context_register_t)(sp-18);
#else
non-x86 ports */
*os_context_pc_addr(context) = (os_context_register_t)(unsigned long)code;
*os_context_register_addr(context,reg_NARGS) = 0;
#ifdef reg_LIP
*os_context_register_addr(context,reg_LIP) =
(os_context_register_t)(unsigned long)code;
#endif
*os_context_register_addr(context,reg_CFP) =
(os_context_register_t)(unsigned long)access_control_frame_pointer(th);
#endif
#ifdef ARCH_HAS_NPC_REGISTER
*os_context_npc_addr(context) =
4 + *os_context_pc_addr(context);
#endif
#if defined(LISP_FEATURE_SPARC) || defined(LISP_FEATURE_ARM) || defined(LISP_FEATURE_RISCV)
*os_context_register_addr(context,reg_CODE) =
(os_context_register_t)((char*)fun + FUN_POINTER_LOWTAG);
#endif
FSHOW((stderr, "/arranged return to Lisp function (0x%lx)\n",
(long)function));
}
void
arrange_return_to_lisp_function(os_context_t *context, lispobj function)
{
#if defined(LISP_FEATURE_DARWIN) && defined(LISP_FEATURE_X86)
arrange_return_to_c_function(context,
(call_into_lisp_lookalike)call_into_lisp_tramp,
function);
#else
arrange_return_to_c_function(context, call_into_lisp, function);
#endif
}
#if !(defined(LISP_FEATURE_X86_64) || defined(LISP_FEATURE_ARM) || defined(LISP_FEATURE_ARM64))
* variables should work for functions as well, but on PPC/Darwin
* we get bus error at bogus addresses instead, hence this workaround,
* that has the added benefit of automatically discriminating between
* functions and variables.
*/
void
undefined_alien_function(void)
{
funcall0(StaticSymbolFunction(UNDEFINED_ALIEN_FUN_ERROR));
}
#endif
void lower_thread_control_stack_guard_page(struct thread *th)
{
protect_control_stack_guard_page(0, th);
protect_control_stack_return_guard_page(1, th);
th->state_word.control_stack_guard_page_protected = 0;
fprintf(stderr, "INFO: Control stack guard page unprotected\n");
}
void reset_thread_control_stack_guard_page(struct thread *th)
{
memset(CONTROL_STACK_GUARD_PAGE(th), 0, os_vm_page_size);
protect_control_stack_guard_page(1, th);
protect_control_stack_return_guard_page(0, th);
th->state_word.control_stack_guard_page_protected = 1;
fprintf(stderr, "INFO: Control stack guard page reprotected\n");
}
boolean
handle_guard_page_triggered(os_context_t *context,os_vm_address_t addr)
{
struct thread *th=get_sb_vm_thread();
if(addr >= CONTROL_STACK_HARD_GUARD_PAGE(th) &&
addr < CONTROL_STACK_HARD_GUARD_PAGE(th) + os_vm_page_size) {
fake_foreign_function_call(context);
lose("Control stack exhausted, fault: %p, PC: %p",
addr, (void*)*os_context_pc_addr(context));
}
else if(addr >= CONTROL_STACK_GUARD_PAGE(th) &&
addr < CONTROL_STACK_GUARD_PAGE(th) + os_vm_page_size) {
* protection so the error handler has some headroom, protect the
* previous page so that we can catch returns from the guard page
* and restore it. */
if (gc_active_p) {
fake_foreign_function_call(context);
lose("Control stack exhausted with gc_active_p, fault: %p, PC: %p",
addr, (void*)*os_context_pc_addr(context));
}
if (arch_pseudo_atomic_atomic(context)) {
fake_foreign_function_call(context);
lose("Control stack exhausted while pseudo-atomic, fault: %p, PC: %p",
addr, (void*)*os_context_pc_addr(context));
}
if (lose_on_corruption_p) {
fake_foreign_function_call(context);
lose("Control stack exhausted, fault: %p, PC: %p",
addr, (void*)*os_context_pc_addr(context));
}
if (!th->state_word.control_stack_guard_page_protected)
lose("control_stack_guard_page_protected NIL");
lower_thread_control_stack_guard_page(th);
#ifdef LISP_FEATURE_C_STACK_IS_CONTROL_STACK
* exhausted in a signal handler. */
unblock_signals_in_context_and_maybe_warn(context);
#endif
arrange_return_to_lisp_function
(context, StaticSymbolFunction(CONTROL_STACK_EXHAUSTED_ERROR));
return 1;
}
else if(addr >= CONTROL_STACK_RETURN_GUARD_PAGE(th) &&
addr < CONTROL_STACK_RETURN_GUARD_PAGE(th) + os_vm_page_size) {
* unprotect this one. This works even if we somehow missed
* the return-guard-page, and hit it on our way to new
* exhaustion instead. */
if (th->state_word.control_stack_guard_page_protected)
lose("control_stack_guard_page_protected not NIL");
reset_thread_control_stack_guard_page(th);
return 1;
}
else if(addr >= BINDING_STACK_HARD_GUARD_PAGE(th) &&
addr < BINDING_STACK_HARD_GUARD_PAGE(th) + os_vm_page_size) {
lose("Binding stack exhausted");
}
else if(addr >= BINDING_STACK_GUARD_PAGE(th) &&
addr < BINDING_STACK_GUARD_PAGE(th) + os_vm_page_size) {
protect_binding_stack_guard_page(0, NULL);
protect_binding_stack_return_guard_page(1, NULL);
if (lose_on_corruption_p) {
fake_foreign_function_call(context);
lose("Binding stack exhausted");
}
fprintf(stderr, "INFO: Binding stack guard page unprotected\n");
* exhausted in a signal handler. */
unblock_signals_in_context_and_maybe_warn(context);
arrange_return_to_lisp_function
(context, StaticSymbolFunction(BINDING_STACK_EXHAUSTED_ERROR));
return 1;
}
else if(addr >= BINDING_STACK_RETURN_GUARD_PAGE(th) &&
addr < BINDING_STACK_RETURN_GUARD_PAGE(th) + os_vm_page_size) {
protect_binding_stack_guard_page(1, NULL);
protect_binding_stack_return_guard_page(0, NULL);
fprintf(stderr, "INFO: Binding stack guard page reprotected\n");
return 1;
}
else if(addr >= ALIEN_STACK_HARD_GUARD_PAGE(th) &&
addr < ALIEN_STACK_HARD_GUARD_PAGE(th) + os_vm_page_size) {
lose("Alien stack exhausted");
}
else if(addr >= ALIEN_STACK_GUARD_PAGE(th) &&
addr < ALIEN_STACK_GUARD_PAGE(th) + os_vm_page_size) {
protect_alien_stack_guard_page(0, NULL);
protect_alien_stack_return_guard_page(1, NULL);
fprintf(stderr, "INFO: Alien stack guard page unprotected\n");
* exhausted in a signal handler. */
unblock_signals_in_context_and_maybe_warn(context);
arrange_return_to_lisp_function
(context, StaticSymbolFunction(ALIEN_STACK_EXHAUSTED_ERROR));
return 1;
}
else if(addr >= ALIEN_STACK_RETURN_GUARD_PAGE(th) &&
addr < ALIEN_STACK_RETURN_GUARD_PAGE(th) + os_vm_page_size) {
protect_alien_stack_guard_page(1, NULL);
protect_alien_stack_return_guard_page(0, NULL);
fprintf(stderr, "INFO: Alien stack guard page reprotected\n");
return 1;
}
else if (addr >= undefined_alien_address &&
addr < undefined_alien_address + os_vm_page_size) {
arrange_return_to_lisp_function
(context, StaticSymbolFunction(UNDEFINED_ALIEN_VARIABLE_ERROR));
return 1;
}
else return 0;
}
#ifndef LISP_FEATURE_WIN32
extern void restore_sbcl_signals () {
int signal;
for (signal = 0; signal < NSIG; signal++) {
interrupt_handler_t handler = interrupt_low_level_handlers[signal];
if ((void*)handler != (void*)SIG_DFL) {
ll_install_handler(signal, handler);
}
}
}
static void
low_level_handle_now_handler(int signal, siginfo_t *info, void *void_context)
{
SAVE_ERRNO(signal,context,void_context);
(*interrupt_low_level_handlers[signal])(signal, info, context);
RESTORE_ERRNO;
}
* SIG{SEGV, ILL, TRAP, FPE, ABRT}. Low-level handlers might or might not
* involve calling Lisp.
* As well there are two asynchronous signals installed via this function:
* - STOP_FOR_GC is low-level, but might defer the signal through
* an intricate bunch of decisions about the state of the world.
* - SIGURG without :SB-SAFEPOINT is a high-level (Lisp) handler,
* but with :SB-SAFEPOINT is low-level handler that uses different
* criteria for when to defer. */
void
ll_install_handler (int signal, interrupt_handler_t handler)
{
struct sigaction sa;
if (0 > signal || signal >= NSIG
#ifdef LISP_FEATURE_SB_SAFEPOINT
* but we don't actually want to defer it, at least not here.
* (It might get deferred until a safepoint). And if we put it only
* into blockable_sigset, we'd have to special-case it around thread
* creation at least. */
|| (signal != SIGURG && sigismember(&deferrable_sigset,signal))
#else
|| sigismember(&deferrable_sigset,signal)
#endif
|| (void*)handler == (void*)SIG_DFL)
lose("ll_install_handler: bad args: sig=%d, fn=%p", signal, handler);
sa.sa_sigaction = low_level_handle_now_handler;
sa.sa_mask = blockable_sigset;
sa.sa_flags = SA_SIGINFO | SA_RESTART | OS_SA_NODEFER;
#ifdef LISP_FEATURE_C_STACK_IS_CONTROL_STACK
if (signal==SIG_MEMORY_FAULT) sa.sa_flags |= SA_ONSTACK;
#endif
sigaction(signal, &sa, NULL);
interrupt_low_level_handlers[signal] = handler;
}
#endif
extern void sigprof_handler(int, siginfo_t*, void*);
void install_handler(int signal, lispobj handler)
{
#ifndef LISP_FEATURE_WIN32
struct sigaction sa;
memset(&sa, 0, sizeof sa);
if (interrupt_low_level_handlers[signal]) {
lisp_sig_handlers[signal] = functionp(handler) ? handler : 0;
} else if (signal == SIGPROF) {
if (handler) sa.sa_sigaction = sigprof_handler;
else sa.sa_handler = SIG_DFL;
sa.sa_mask = blockable_sigset;
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigaction(signal, &sa, NULL);
return;
} else {
if (handler==0 || handler==1) {
sa.sa_handler = handler ? SIG_IGN : SIG_DFL;
sigaction(signal, &sa, NULL);
lisp_sig_handlers[signal] = 0;
return;
}
if (sigismember(&deferrable_sigset, signal))
sa.sa_sigaction = maybe_now_maybe_later;
else
sa.sa_sigaction = interrupt_handle_now_handler;
sa.sa_mask = blockable_sigset;
sa.sa_flags = SA_SIGINFO | SA_RESTART | OS_SA_NODEFER;
lisp_sig_handlers[signal] = handler;
sigaction(signal, &sa, NULL);
}
#endif
}
* the altstack. */
void
sigabrt_handler(int __attribute__((unused)) signal,
siginfo_t __attribute__((unused)) *info,
os_context_t *context)
{
* shouldn't return. */
fake_foreign_function_call(context);
lose("SIGABRT received.");
}
void
interrupt_init(void)
{
#ifdef ATOMIC_LOGGING
eventdata = calloc(EVENTBUFMAX, N_WORD_BYTES);
#endif
int __attribute__((unused)) i;
SHOW("entering interrupt_init()");
sigemptyset(&deferrable_sigset);
sigemptyset(&blockable_sigset);
sigemptyset(&gc_sigset);
sigaddset_deferrable(&deferrable_sigset);
sigaddset_blockable(&blockable_sigset);
sigaddset_gc(&gc_sigset);
sigaddset_deferrable(&thread_start_sigset);
current_thread set up yet, which can be a thread-local variable,
and sigprof_handler will try to allocate it, but thread-local
initialization is not guaranteed to be async safe. */
sigaddset(&thread_start_sigset, SIGPROF);
#ifdef LISP_FEATURE_BACKTRACE_ON_SIGNAL
void backtrace_lisp_threads(int, siginfo_t*, os_context_t*);
ll_install_handler(SIGXCPU, backtrace_lisp_threads);
#endif
#ifndef LISP_FEATURE_WIN32
ll_install_handler(SIGABRT, sigabrt_handler);
#endif
}
#ifndef LISP_FEATURE_WIN32
int
siginfo_code(siginfo_t *info)
{
return info->si_code;
}
void
lisp_memory_fault_error(os_context_t *context, os_vm_address_t addr)
{
fake_foreign_function_call(context);
#ifdef ARCH_HAS_STACK_POINTER
char* pc = (char*)*os_context_pc_addr(context);
struct code* code = (struct code*)component_ptr_from_pc(pc);
unsigned int offset = code ? pc - (char*)code : 0;
if (offset)
corruption_warning_and_maybe_lose(
"Memory fault at %p (pc=%p [code %p+0x%X ID 0x%x], fp=%p, sp=%p)" THREAD_ID_LABEL,
addr, pc, code, offset, code_serialno(code),
os_context_frame_pointer(context),
*os_context_sp_addr(context), THREAD_ID_VALUE);
else
corruption_warning_and_maybe_lose(
"Memory fault at %p (pc=%p, fp=%p, sp=%p)" THREAD_ID_LABEL,
addr, pc, os_context_frame_pointer(context),
*os_context_sp_addr(context), THREAD_ID_VALUE);
#else
corruption_warning_and_maybe_lose("Memory fault at %p (pc=%p)",
addr, *os_context_pc_addr(context));
#endif
#ifdef LISP_FEATURE_C_STACK_IS_CONTROL_STACK
* signal emulation on macOS. It almost makes one want to cry.
* We're not actually on an alternate stack at this point.
* Instead of telling the emulated sigsegv (which needn't have been
* emulated at all) to return to an intruction which executes a
* sigtrap (also emulated), we should just go straight where
* we need to go and hand it the original context rather than
* having to track the context through two bogus signals */
# if !(defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64))
# error memory fault emulation needs validating for this architecture
# endif
* we need to return from this signal handler. But when we get to
* Lisp we'd like to have a signal context (with correct values in
* it) to present to the debugger, along with knowledge of what
* the faulting address was. To get a signal context on the main
* stack, we arrange to return to a trap instruction. To get the
* correct program counter in the context, we save it on the stack
* here and restore it to the context in the trap handler. To
* pass the fault address, we save it on the stack here and pick
* it up in the trap handler. And the stack pointer manipulation
* works as long as the on-stack side only pops items in its trap
* handler. */
extern void memory_fault_emulation_trap(void);
undo_fake_foreign_function_call(context);
void **sp = (void **)*os_context_sp_addr(context);
*--sp = (void *)*os_context_pc_addr(context);
*--sp = addr;
# ifdef LISP_FEATURE_X86
*((void **)os_context_register_addr(context, reg_ESP)) = sp;
# else
*((void **)os_context_sp_addr(context)) = sp;
# endif
*os_context_pc_addr(context) =
(os_context_register_t)memory_fault_emulation_trap;
* memory_fault_emulation_trap (in target-assem.S), which will
* trap, and the handler calls the function below, where we
* restore our state to parallel what a non-x86oid would have, and
* then run the common code for handling the error in Lisp. */
}
void
handle_memory_fault_emulation_trap(os_context_t *context)
{
void **sp = (void **)*os_context_sp_addr(context);
void *addr = *sp++;
*os_context_pc_addr(context) = (os_context_register_t)*sp++;
# ifdef LISP_FEATURE_X86
*((void **)os_context_register_addr(context, reg_ESP)) = sp;
# else
*os_context_sp_addr(context) = (os_context_register_t)sp;
# endif
fake_foreign_function_call(context);
#endif
* On real computers, we're still in lisp_memory_fault_error(). */
thread_sigmask(SIG_SETMASK, os_context_sigmask_addr(context), 0);
DX_ALLOC_SAP(context_sap, context);
DX_ALLOC_SAP(fault_address_sap, addr);
funcall2(StaticSymbolFunction(MEMORY_FAULT_ERROR),
context_sap, fault_address_sap);
undo_fake_foreign_function_call(context);
}
#endif
static void
unhandled_trap_error(os_context_t *context)
{
DX_ALLOC_SAP(context_sap, context);
fake_foreign_function_call(context);
thread_sigmask(SIG_SETMASK, os_context_sigmask_addr(context), 0);
funcall1(StaticSymbolFunction(UNHANDLED_TRAP_ERROR), context_sap);
lose("UNHANDLED-TRAP-ERROR fell through");
}
* case is highly architecture dependent, but the overall shape is
* this. */
void
handle_trap(os_context_t *context, int trap)
{
if (trap >= trap_Error) {
trap = trap_Error;
}
switch(trap) {
#ifndef LISP_FEATURE_WIN32
case trap_PendingInterrupt:
FSHOW((stderr, "/<trap pending interrupt>\n"));
arch_skip_instruction(context);
interrupt_handle_pending(context);
break;
#endif
case trap_Error:
case trap_Cerror:
#ifdef trap_InvalidArgCount
case trap_InvalidArgCount:
#endif
#ifdef trap_UninitializedLoad
# define CONTINUABLE_P (trap==trap_Cerror || trap==trap_UninitializedLoad)
case trap_UninitializedLoad:
#else
# define CONTINUABLE_P (trap==trap_Cerror)
#endif
FSHOW((stderr, "/<trap error/cerror %d>\n", trap));
interrupt_internal_error(context, CONTINUABLE_P);
break;
case trap_Breakpoint:
arch_handle_breakpoint(context);
break;
case trap_FunEndBreakpoint:
arch_handle_fun_end_breakpoint(context);
break;
#ifdef trap_AfterBreakpoint
case trap_AfterBreakpoint:
arch_handle_after_breakpoint(context);
break;
#endif
#ifdef trap_SingleStepAround
case trap_SingleStepAround:
case trap_SingleStepBefore:
arch_handle_single_step_trap(context, trap);
break;
#endif
#ifdef trap_GlobalSafepoint
case trap_GlobalSafepoint:
fake_foreign_function_call(context);
thread_in_lisp_raised(context);
undo_fake_foreign_function_call(context);
arch_skip_instruction(context);
break;
case trap_CspSafepoint:
fake_foreign_function_call(context);
thread_in_safety_transition(context);
undo_fake_foreign_function_call(context);
arch_skip_instruction(context);
break;
#endif
#if defined(LISP_FEATURE_C_STACK_IS_CONTROL_STACK) && !defined(LISP_FEATURE_WIN32)
case trap_MemoryFaultEmulation:
handle_memory_fault_emulation_trap(context);
break;
#endif
case trap_Halt:
fake_foreign_function_call(context);
lose("%%PRIMITIVE HALT called; the party is over.");
default:
unhandled_trap_error(context);
}
}
#ifndef LISP_FEATURE_WIN32
int sb_toggle_sigprof(os_context_t* context, int block) {
if (context) {
gc_assert(!block);
context = nth_interrupt_context(0, get_sb_vm_thread());
gc_assert(context);
sigset_t *mask = os_context_sigmask_addr(context);
int was_blocked = sigismember(mask, SIGPROF);
if (block) sigaddset(mask, SIGPROF); else sigdelset(mask, SIGPROF);
return was_blocked;
} else {
sigset_t sigset, old;
sigemptyset(&sigset);
sigaddset(&sigset, SIGPROF);
thread_sigmask(block ? SIG_BLOCK : SIG_UNBLOCK, &sigset, &old);
return sigismember(&old, SIGPROF);
}
}
#endif