summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sysdeps/unix/sysv/linux/Makefile10
-rw-r--r--sysdeps/unix/sysv/linux/tst-rseq-nptl.c256
-rw-r--r--sysdeps/unix/sysv/linux/tst-rseq.c64
-rw-r--r--sysdeps/unix/sysv/linux/tst-rseq.h59
4 files changed, 388 insertions, 1 deletions
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index e855db2cb9..2ee83e88d7 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -100,7 +100,11 @@ tests += tst-clone tst-clone2 tst-clone3 tst-fanotify tst-personality \
test-errno-linux tst-memfd_create tst-mlock2 tst-pkey \
tst-rlimit-infinity tst-ofdlocks tst-gettid tst-gettid-kill \
tst-tgkill
-tests-internal += tst-ofdlocks-compat tst-sigcontext-get_pc
+
+# tst-rseq is an internal test because it requires a definition of __NR_rseq
+# from the internal system call list.
+tests-internal += tst-ofdlocks-compat tst-sigcontext-get_pc \
+ tst-rseq
CFLAGS-tst-sigcontext-get_pc.c = -fasynchronous-unwind-tables
@@ -301,4 +305,8 @@ endif
ifeq ($(subdir),nptl)
tests += tst-align-clone tst-getpid1
+
+# tst-rseq-nptl is an internal test because it requires a definition of
+# __NR_rseq from the internal system call list.
+tests-internal += tst-rseq-nptl
endif
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-nptl.c b/sysdeps/unix/sysv/linux/tst-rseq-nptl.c
new file mode 100644
index 0000000000..5e788dcfa9
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-nptl.c
@@ -0,0 +1,256 @@
+/* Restartable Sequences NPTL test.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/* These tests validate that rseq is registered from various execution
+ contexts (main thread, destructor, other threads, other threads created
+ from destructor, forked process (without exec), pthread_atfork handlers,
+ pthread setspecific destructors, signal handlers, atexit handlers).
+
+ See the Linux kernel selftests for extensive rseq stress-tests. */
+
+#include <stdio.h>
+#include <support/check.h>
+#include <support/xthread.h>
+#include <sys/rseq.h>
+#include <unistd.h>
+
+#ifdef RSEQ_SIG
+# include <array_length.h>
+# include <errno.h>
+# include <error.h>
+# include <pthread.h>
+# include <signal.h>
+# include <stdlib.h>
+# include <string.h>
+# include <support/namespace.h>
+# include <support/xsignal.h>
+# include <syscall.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include "tst-rseq.h"
+
+static pthread_key_t rseq_test_key;
+
+static void
+atfork_prepare (void)
+{
+ if (!rseq_thread_registered ())
+ {
+ printf ("error: rseq not registered in pthread atfork prepare\n");
+ support_record_failure ();
+ }
+}
+
+static void
+atfork_parent (void)
+{
+ if (!rseq_thread_registered ())
+ {
+ printf ("error: rseq not registered in pthread atfork parent\n");
+ support_record_failure ();
+ }
+}
+
+static void
+atfork_child (void)
+{
+ if (!rseq_thread_registered ())
+ {
+ printf ("error: rseq not registered in pthread atfork child\n");
+ support_record_failure ();
+ }
+}
+
+static void
+rseq_key_destructor (void *arg)
+{
+ /* Cannot use deferred failure reporting after main returns. */
+ if (!rseq_thread_registered ())
+ FAIL_EXIT1 ("rseq not registered in pthread key destructor");
+}
+
+static void
+atexit_handler (void)
+{
+ /* Cannot use deferred failure reporting after main returns. */
+ if (!rseq_thread_registered ())
+ FAIL_EXIT1 ("rseq not registered in atexit handler");
+}
+
+static void
+do_rseq_main_test (void)
+{
+ TEST_COMPARE (atexit (atexit_handler), 0);
+ rseq_test_key = xpthread_key_create (rseq_key_destructor);
+ TEST_COMPARE (pthread_atfork (atfork_prepare, atfork_parent, atfork_child), 0);
+ xraise (SIGUSR1);
+ TEST_COMPARE (pthread_setspecific (rseq_test_key, (void *) 1l), 0);
+ TEST_VERIFY_EXIT (rseq_thread_registered ());
+}
+
+static void
+cancel_routine (void *arg)
+{
+ if (!rseq_thread_registered ())
+ {
+ printf ("error: rseq not registered in cancel routine\n");
+ support_record_failure ();
+ }
+}
+
+static pthread_barrier_t cancel_thread_barrier;
+static pthread_cond_t cancel_thread_cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t cancel_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void
+test_cancel_thread (void)
+{
+ pthread_cleanup_push (cancel_routine, NULL);
+ (void) xpthread_barrier_wait (&cancel_thread_barrier);
+ /* Wait forever until cancellation. */
+ xpthread_cond_wait (&cancel_thread_cond, &cancel_thread_mutex);
+ pthread_cleanup_pop (0);
+}
+
+static void *
+thread_function (void * arg)
+{
+ int i = (int) (intptr_t) arg;
+
+ xraise (SIGUSR1);
+ if (i == 0)
+ test_cancel_thread ();
+ TEST_COMPARE (pthread_setspecific (rseq_test_key, (void *) 1l), 0);
+ return rseq_thread_registered () ? NULL : (void *) 1l;
+}
+
+static void
+sighandler (int sig)
+{
+ if (!rseq_thread_registered ())
+ {
+ printf ("error: rseq not registered in signal handler\n");
+ support_record_failure ();
+ }
+}
+
+static void
+setup_signals (void)
+{
+ struct sigaction sa;
+
+ sigemptyset (&sa.sa_mask);
+ sigaddset (&sa.sa_mask, SIGUSR1);
+ sa.sa_flags = 0;
+ sa.sa_handler = sighandler;
+ xsigaction (SIGUSR1, &sa, NULL);
+}
+
+static int
+do_rseq_threads_test (int nr_threads)
+{
+ pthread_t th[nr_threads];
+ int i;
+ int result = 0;
+
+ xpthread_barrier_init (&cancel_thread_barrier, NULL, 2);
+
+ for (i = 0; i < nr_threads; ++i)
+ th[i] = xpthread_create (NULL, thread_function,
+ (void *) (intptr_t) i);
+
+ (void) xpthread_barrier_wait (&cancel_thread_barrier);
+
+ xpthread_cancel (th[0]);
+
+ for (i = 0; i < nr_threads; ++i)
+ {
+ void *v;
+
+ v = xpthread_join (th[i]);
+ if (i != 0 && v != NULL)
+ {
+ printf ("error: join %d successful, but child failed\n", i);
+ result = 1;
+ }
+ else if (i == 0 && v == NULL)
+ {
+ printf ("error: join %d successful, child did not fail as expected\n", i);
+ result = 1;
+ }
+ }
+
+ xpthread_barrier_destroy (&cancel_thread_barrier);
+
+ return result;
+}
+
+static void
+subprocess_callback (void *closure)
+{
+ do_rseq_main_test ();
+}
+
+static void
+do_rseq_fork_test (void)
+{
+ support_isolate_in_subprocess (subprocess_callback, NULL);
+}
+
+static int
+do_rseq_test (void)
+{
+ int t[] = { 1, 2, 6, 5, 4, 3, 50 };
+ int i, result = 0;
+
+ if (!rseq_available ())
+ FAIL_UNSUPPORTED ("kernel does not support rseq, skipping test");
+ setup_signals ();
+ xraise (SIGUSR1);
+ do_rseq_main_test ();
+ for (i = 0; i < array_length (t); i++)
+ if (do_rseq_threads_test (t[i]))
+ result = 1;
+ do_rseq_fork_test ();
+ return result;
+}
+
+static void __attribute__ ((destructor))
+do_rseq_destructor_test (void)
+{
+ /* Cannot use deferred failure reporting after main returns. */
+ if (do_rseq_test ())
+ FAIL_EXIT1 ("rseq not registered within destructor");
+ xpthread_key_delete (rseq_test_key);
+}
+
+#else /* RSEQ_SIG */
+static int
+do_rseq_test (void)
+{
+ FAIL_UNSUPPORTED ("glibc does not define RSEQ_SIG, skipping test");
+ return 0;
+}
+#endif /* RSEQ_SIG */
+
+static int
+do_test (void)
+{
+ return do_rseq_test ();
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/tst-rseq.c b/sysdeps/unix/sysv/linux/tst-rseq.c
new file mode 100644
index 0000000000..aa902fb26a
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq.c
@@ -0,0 +1,64 @@
+/* Restartable Sequences single-threaded tests.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/* These tests validate that rseq is registered from main in an executable
+ not linked against libpthread. */
+
+#include <support/check.h>
+#include <stdio.h>
+#include <sys/rseq.h>
+#include <unistd.h>
+
+#ifdef RSEQ_SIG
+# include <errno.h>
+# include <error.h>
+# include <stdlib.h>
+# include <string.h>
+# include <syscall.h>
+# include "tst-rseq.h"
+
+static void
+do_rseq_main_test (void)
+{
+ TEST_VERIFY_EXIT (rseq_thread_registered ());
+}
+
+static void
+do_rseq_test (void)
+{
+ if (!rseq_available ())
+ {
+ FAIL_UNSUPPORTED ("kernel does not support rseq, skipping test");
+ }
+ do_rseq_main_test ();
+}
+#else /* RSEQ_SIG */
+static void
+do_rseq_test (void)
+{
+ FAIL_UNSUPPORTED ("glibc does not define RSEQ_SIG, skipping test");
+}
+#endif /* RSEQ_SIG */
+
+static int
+do_test (void)
+{
+ do_rseq_test ();
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/tst-rseq.h b/sysdeps/unix/sysv/linux/tst-rseq.h
new file mode 100644
index 0000000000..c2cb211f56
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq.h
@@ -0,0 +1,59 @@
+/* Restartable Sequences tests header.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <error.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <support/check.h>
+#include <syscall.h>
+#include <sys/rseq.h>
+
+static inline bool
+rseq_thread_registered (void)
+{
+ int32_t v;
+
+ __atomic_load (&__rseq_abi.cpu_id, &v, __ATOMIC_RELAXED);
+ return v >= 0;
+}
+
+static inline int
+sys_rseq (struct rseq *rseq_abi, uint32_t rseq_len, int flags, uint32_t sig)
+{
+ return syscall (__NR_rseq, rseq_abi, rseq_len, flags, sig);
+}
+
+static inline bool
+rseq_available (void)
+{
+ int rc;
+
+ rc = sys_rseq (NULL, 0, 0, 0);
+ if (rc != -1)
+ FAIL_EXIT1 ("Unexpected rseq return value %d", rc);
+ switch (errno)
+ {
+ case ENOSYS:
+ return false;
+ case EINVAL:
+ /* rseq is implemented, but detected an invalid rseq_len parameter. */
+ return true;
+ default:
+ FAIL_EXIT1 ("Unexpected rseq error %s", strerror (errno));
+ }
+}