aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/hipe/hipe_x86_signal.c
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /erts/emulator/hipe/hipe_x86_signal.c
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'erts/emulator/hipe/hipe_x86_signal.c')
-rw-r--r--erts/emulator/hipe/hipe_x86_signal.c355
1 files changed, 355 insertions, 0 deletions
diff --git a/erts/emulator/hipe/hipe_x86_signal.c b/erts/emulator/hipe/hipe_x86_signal.c
new file mode 100644
index 0000000000..a4fff4ce31
--- /dev/null
+++ b/erts/emulator/hipe/hipe_x86_signal.c
@@ -0,0 +1,355 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2001-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+/* $Id$
+ * hipe_x86_signal.c
+ *
+ * Erlang code compiled to x86 native code uses the x86 %esp as its
+ * stack pointer. This improves performance in several ways:
+ * - It permits the use of the x86 call and ret instructions, which
+ * reduces code volume and improves branch prediction.
+ * - It avoids stealing a gp register to act as a stack pointer.
+ *
+ * Unix signal handlers are by default delivered onto the current
+ * stack, i.e. %esp. This is a problem since our native-code stacks
+ * are small and may not have room for the Unix signal handler.
+ *
+ * There is a way to redirect signal handlers to an "alternate" signal
+ * stack by using the SA_ONSTACK flag with the sigaction() library call.
+ * Unfortunately, this has to be specified explicitly for each signal,
+ * and it is difficult to enforce given the presence of libraries.
+ *
+ * Our solution is to override the C library's signal handler setup
+ * procedure with our own which enforces the SA_ONSTACK flag.
+ *
+ * XXX: This code only supports Linux with glibc-2.1 or above,
+ * and Solaris 8.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef ERTS_SMP
+#include "sys.h"
+#include "erl_alloc.h"
+#endif
+#include "hipe_signal.h"
+
+#if __GLIBC__ == 2 && (__GLIBC_MINOR__ >= 3)
+/* See comment below for glibc 2.2. */
+#ifndef __USE_GNU
+#define __USE_GNU /* to un-hide RTLD_NEXT */
+#endif
+#include <dlfcn.h>
+static int (*__next_sigaction)(int, const struct sigaction*, struct sigaction*);
+#define init_done() (__next_sigaction != 0)
+extern int __sigaction(int, const struct sigaction*, struct sigaction*);
+#define __SIGACTION __sigaction
+static void do_init(void)
+{
+ __next_sigaction = dlsym(RTLD_NEXT, "__sigaction");
+ if (__next_sigaction != 0)
+ return;
+ perror("dlsym");
+ abort();
+}
+#define INIT() do { if (!init_done()) do_init(); } while (0)
+#endif /* glibc 2.3 */
+
+#if __GLIBC__ == 2 && (__GLIBC_MINOR__ == 2 /*|| __GLIBC_MINOR__ == 3*/)
+/*
+ * __libc_sigaction() is the core routine.
+ * Without libpthread, sigaction() and __sigaction() are both aliases
+ * for __libc_sigaction().
+ * libpthread redefines __sigaction() as a non-trivial wrapper around
+ * __libc_sigaction(), and makes sigaction() an alias for __sigaction().
+ * glibc has internal calls to both sigaction() and __sigaction().
+ *
+ * Overriding __libc_sigaction() would be ideal, but doing so breaks
+ * libpthread (threads hang).
+ *
+ * Overriding __sigaction(), using dlsym RTLD_NEXT to find glibc's
+ * version of __sigaction(), works with glibc-2.2.4 and 2.2.5.
+ * Unfortunately, this solution doesn't work with earlier versions,
+ * including glibc-2.2.2 and glibc-2.1.92 (2.2 despite its name):
+ * 2.2.2 SIGSEGVs in dlsym RTLD_NEXT (known glibc bug), and 2.1.92
+ * SIGSEGVs inexplicably in two test cases in the HiPE test suite.
+ *
+ * Instead we only override sigaction() and call __sigaction()
+ * directly. This should work for HiPE/x86 as long as only the Posix
+ * signal interface is used, i.e. there are no calls to simulated
+ * old BSD or SysV interfaces.
+ * glibc's internal calls to __sigaction() appear to be mostly safe.
+ * hipe_signal_init() fixes some unsafe ones, e.g. the SIGPROF handler.
+ *
+ * Tested with glibc-2.1.92 on RedHat 7.0, glibc-2.2.2 on RedHat 7.1,
+ * glibc-2.2.4 on RedHat 7.2, and glibc-2.2.5 on RedHat 7.3.
+ */
+#if 0
+/* works with 2.2.5 and 2.2.4, but not 2.2.2 or 2.1.92 */
+#define __USE_GNU /* to un-hide RTLD_NEXT */
+#include <dlfcn.h>
+static int (*__next_sigaction)(int, const struct sigaction*, struct sigaction*);
+#define init_done() (__next_sigaction != 0)
+#define __SIGACTION __sigaction
+static void do_init(void)
+{
+ __next_sigaction = dlsym(RTLD_NEXT, "__sigaction");
+ if (__next_sigaction != 0)
+ return;
+ perror("dlsym");
+ abort();
+}
+#define INIT() do { if (!init_done()) do_init(); } while (0)
+#else
+/* semi-works with all 2.2 versions so far */
+extern int __sigaction(int, const struct sigaction*, struct sigaction*);
+#define __next_sigaction __sigaction /* pthreads-aware version */
+#undef __SIGACTION /* we can't override __sigaction() */
+#define INIT() do{}while(0)
+#endif
+#endif /* glibc 2.2 */
+
+#if __GLIBC__ == 2 && __GLIBC_MINOR__ == 1
+/*
+ * __sigaction() is the core routine.
+ * Without libpthread, sigaction() is an alias for __sigaction().
+ * libpthread redefines sigaction() as a non-trivial wrapper around
+ * __sigaction().
+ * glibc has internal calls to both sigaction() and __sigaction().
+ *
+ * Overriding __sigaction() would be ideal, but doing so breaks
+ * libpthread (threads hang). Instead we override sigaction() and
+ * use dlsym RTLD_NEXT to find glibc's version of sigaction().
+ * glibc's internal calls to __sigaction() appear to be mostly safe.
+ * hipe_signal_init() fixes some unsafe ones, e.g. the SIGPROF handler.
+ *
+ * Tested with glibc-2.1.3 on RedHat 6.2.
+ */
+#include <dlfcn.h>
+static int (*__next_sigaction)(int, const struct sigaction*, struct sigaction*);
+#define init_done() (__next_sigaction != 0)
+#undef __SIGACTION
+static void do_init(void)
+{
+ __next_sigaction = dlsym(RTLD_NEXT, "sigaction");
+ if (__next_sigaction != 0)
+ return;
+ perror("dlsym");
+ abort();
+}
+#define INIT() do { if (!init_done()) do_init(); } while (0)
+#endif /* glibc 2.1 */
+
+/* Is there no standard identifier for Darwin/MacOSX ? */
+#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__)
+#define __DARWIN__ 1
+#endif
+
+#if defined(__DARWIN__)
+/*
+ * Assumes Mac OS X >= 10.3 (dlsym operations not available in 10.2 and
+ * earlier).
+ *
+ * The code below assumes that is is part of the main image (earlier
+ * in the load order than libSystem and certainly before any dylib
+ * that might use sigaction) -- a standard RTLD_NEXT caveat.
+ *
+ * _sigaction lives in /usr/lib/libSystem.B.dylib and can be found
+ * with the standard dlsym(RTLD_NEXT) call. The proviso on Mac OS X
+ * being that the symbol for dlsym doesn't include a leading '_'.
+ *
+ * The other _sigaction, _sigaction_no_bind I don't understand the purpose
+ * of and don't modify.
+ */
+#include <dlfcn.h>
+static int (*__next_sigaction)(int, const struct sigaction*, struct sigaction*);
+#define init_done() (__next_sigaction != 0)
+#define __SIGACTION _sigaction
+static void do_init(void)
+{
+ __next_sigaction = dlsym(RTLD_NEXT, "sigaction");
+ if (__next_sigaction != 0)
+ return;
+ perror("dlsym_darwin");
+ abort();
+}
+#define _NSIG NSIG
+#define INIT() do { if (!init_done()) do_init(); } while (0)
+#endif /* __DARWIN__ */
+
+#if !defined(__GLIBC__) && !defined(__DARWIN__)
+/*
+ * Assume Solaris/x86 2.8.
+ * There is a number of sigaction() procedures in libc:
+ * * sigaction(): weak reference to _sigaction().
+ * * _sigaction(): apparently a simple wrapper around __sigaction().
+ * * __sigaction(): apparently the procedure doing the actual system call.
+ * * _libc_sigaction(): apparently some thread-related wrapper, which ends
+ * up calling __sigaction().
+ * The threads library redefines sigaction() and _sigaction() to its
+ * own wrapper, which checks for and restricts access to threads-related
+ * signals. The wrapper appears to eventually call libc's __sigaction().
+ *
+ * We catch and override _sigaction() since overriding __sigaction()
+ * causes fatal errors in some cases.
+ *
+ * When linked with thread support, there are calls to sigaction() before
+ * our init routine has had a chance to find _sigaction()'s address.
+ * This forces us to initialise at the first call.
+ */
+#include <dlfcn.h>
+static int (*__next_sigaction)(int, const struct sigaction*, struct sigaction*);
+#define init_done() (__next_sigaction != 0)
+#define __SIGACTION _sigaction
+static void do_init(void)
+{
+ __next_sigaction = dlsym(RTLD_NEXT, "_sigaction");
+ if (__next_sigaction != 0)
+ return;
+ perror("dlsym");
+ abort();
+}
+#define _NSIG NSIG
+#define INIT() do { if (!init_done()) do_init(); } while (0)
+#endif /* not glibc or darwin */
+
+/*
+ * This is our wrapper for sigaction(). sigaction() can be called before
+ * hipe_signal_init() has been executed, especially when threads support
+ * has been linked with the executable. Therefore, we must initialise
+ * __next_sigaction() dynamically, the first time it's needed.
+ */
+static int my_sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
+{
+ struct sigaction newact;
+
+ INIT();
+
+ if (act &&
+ act->sa_handler != SIG_DFL &&
+ act->sa_handler != SIG_IGN &&
+ !(act->sa_flags & SA_ONSTACK)) {
+ newact = *act;
+ newact.sa_flags |= SA_ONSTACK;
+ act = &newact;
+ }
+ return __next_sigaction(signum, act, oldact);
+}
+
+/*
+ * This overrides the C library's core sigaction() procedure, catching
+ * all its internal calls.
+ */
+#ifdef __SIGACTION
+int __SIGACTION(int signum, const struct sigaction *act, struct sigaction *oldact)
+{
+ return my_sigaction(signum, act, oldact);
+}
+#endif
+
+/*
+ * This catches the application's own sigaction() calls.
+ */
+#if !defined(__DARWIN__)
+int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
+{
+ return my_sigaction(signum, act, oldact);
+}
+#endif
+
+/*
+ * Set alternate signal stack for the invoking thread.
+ */
+static void hipe_sigaltstack(void *ss_sp)
+{
+ stack_t ss;
+
+ ss.ss_sp = ss_sp;
+ ss.ss_flags = SS_ONSTACK;
+ ss.ss_size = SIGSTKSZ;
+ if (sigaltstack(&ss, NULL) < 0) {
+ /* might be a broken pre-2.4 Linux kernel, try harder */
+ ss.ss_flags = 0;
+ if (sigaltstack(&ss, NULL) < 0) {
+ perror("sigaltstack");
+ abort();
+ }
+ }
+}
+
+#ifdef ERTS_SMP
+/*
+ * Set up alternate signal stack for an Erlang process scheduler thread.
+ */
+void hipe_thread_signal_init(void)
+{
+ hipe_sigaltstack(erts_alloc(ERTS_ALC_T_HIPE, SIGSTKSZ));
+}
+#endif
+
+/*
+ * Set up alternate signal stack for the main thread,
+ * unless this is a multithreaded runtime system.
+ */
+static void hipe_sigaltstack_init(void)
+{
+#if !defined(ERTS_SMP)
+ static unsigned long my_sigstack[SIGSTKSZ/sizeof(long)];
+ hipe_sigaltstack(my_sigstack);
+#endif
+}
+
+/*
+ * 1. Set up alternate signal stack for the main thread.
+ * 2. Add SA_ONSTACK to existing user-defined signal handlers.
+ */
+void hipe_signal_init(void)
+{
+ struct sigaction sa;
+ int i;
+
+ INIT();
+
+ hipe_sigaltstack_init();
+
+ for (i = 1; i < _NSIG; ++i) {
+ if (sigaction(i, NULL, &sa)) {
+ /* This will fail with EINVAL on Solaris if 'i' is one of the
+ thread library's private signals. We DO catch the initial
+ setup of these signals, so things MAY be OK anyway. */
+ continue;
+ }
+ if (sa.sa_handler == SIG_DFL ||
+ sa.sa_handler == SIG_IGN ||
+ (sa.sa_flags & SA_ONSTACK))
+ continue;
+ sa.sa_flags |= SA_ONSTACK;
+ if (sigaction(i, &sa, NULL)) {
+#ifdef SIGCANCEL
+ /* Solaris 9 x86 refuses to let us modify SIGCANCEL. */
+ if (i == SIGCANCEL)
+ continue;
+#endif
+ perror("sigaction");
+ abort();
+ }
+ }
+}