[RFC,v4,5/5] rseq registration tests

Message ID 20181121183936.8176-5-mathieu.desnoyers@efficios.com
State New, archived
Headers

Commit Message

Mathieu Desnoyers Nov. 21, 2018, 6:39 p.m. UTC
  These tests validate that rseq is registered from various execution
contexts (main thread, constructor, destructor, other threads, other
threads created from constructor and destructor, forked process
(without exec), pthread_atfork handlers, pthread setspecific
destructors, C++ thread and process destructors, and signal handlers).

See the Linux kernel selftests for extensive rseq stress-tests.

Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
CC: Carlos O'Donell <carlos@redhat.com>
CC: Florian Weimer <fweimer@redhat.com>
CC: Joseph Myers <joseph@codesourcery.com>
CC: Szabolcs Nagy <szabolcs.nagy@arm.com>
CC: Thomas Gleixner <tglx@linutronix.de>
CC: Ben Maurer <bmaurer@fb.com>
CC: Peter Zijlstra <peterz@infradead.org>
CC: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com>
CC: Boqun Feng <boqun.feng@gmail.com>
CC: Will Deacon <will.deacon@arm.com>
CC: Dave Watson <davejwatson@fb.com>
CC: Paul Turner <pjt@google.com>
CC: libc-alpha@sourceware.org
---
 nptl/Makefile   |   2 +-
 nptl/tst-rseq.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 347 insertions(+), 1 deletion(-)
 create mode 100644 nptl/tst-rseq.c
  

Comments

Mathieu Desnoyers Nov. 21, 2018, 6:50 p.m. UTC | #1
----- On Nov 21, 2018, at 1:39 PM, Mathieu Desnoyers mathieu.desnoyers@efficios.com wrote:

> These tests validate that rseq is registered from various execution
> contexts (main thread, constructor, destructor, other threads, other
> threads created from constructor and destructor, forked process
> (without exec), pthread_atfork handlers, pthread setspecific
> destructors, C++ thread and process destructors, and signal handlers).
> 
> See the Linux kernel selftests for extensive rseq stress-tests.

In the next round (v5), I'll also add atexit () callback testing,
which is missing here.

Thanks,

Mathieu

> 
> Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
> CC: Carlos O'Donell <carlos@redhat.com>
> CC: Florian Weimer <fweimer@redhat.com>
> CC: Joseph Myers <joseph@codesourcery.com>
> CC: Szabolcs Nagy <szabolcs.nagy@arm.com>
> CC: Thomas Gleixner <tglx@linutronix.de>
> CC: Ben Maurer <bmaurer@fb.com>
> CC: Peter Zijlstra <peterz@infradead.org>
> CC: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com>
> CC: Boqun Feng <boqun.feng@gmail.com>
> CC: Will Deacon <will.deacon@arm.com>
> CC: Dave Watson <davejwatson@fb.com>
> CC: Paul Turner <pjt@google.com>
> CC: libc-alpha@sourceware.org
> ---
> nptl/Makefile   |   2 +-
> nptl/tst-rseq.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 347 insertions(+), 1 deletion(-)
> create mode 100644 nptl/tst-rseq.c
> 
> diff --git a/nptl/Makefile b/nptl/Makefile
> index 3a5dc80c65..797309f7a8 100644
> --- a/nptl/Makefile
> +++ b/nptl/Makefile
> @@ -323,7 +323,7 @@ tests = tst-attr1 tst-attr2 tst-attr3 tst-default-attr \
> tests-internal := tst-rwlock19 tst-rwlock20 \
> 		  tst-sem11 tst-sem12 tst-sem13 \
> 		  tst-barrier5 tst-signal7 tst-mutex8 tst-mutex8-static \
> -		  tst-mutexpi8 tst-mutexpi8-static
> +		  tst-mutexpi8 tst-mutexpi8-static tst-rseq
> 
> xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
> 	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
> diff --git a/nptl/tst-rseq.c b/nptl/tst-rseq.c
> new file mode 100644
> index 0000000000..c55345e44d
> --- /dev/null
> +++ b/nptl/tst-rseq.c
> @@ -0,0 +1,346 @@
> +/* Copyright (C) 2018 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +   Contributed by Mathieu Desnoyers <mathieu.desnoyers@efficios.com>, 2018.
> +
> +   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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +/* These tests validate that rseq is registered from various execution
> +   contexts (main thread, constructor, destructor, other threads, other
> +   threads created from constructor and destructor, forked process
> +   (without exec), pthread_atfork handlers, pthread setspecific
> +   destructors, C++ thread and process destructors, and signal handlers).
> +
> +   See the Linux kernel selftests for extensive rseq stress-tests.  */
> +
> +#include <sys/syscall.h>
> +#include <unistd.h>
> +#include <stdio.h>
> +#include <support/check.h>
> +
> +#if defined (__linux__) && defined (__NR_rseq)
> +#define HAS_RSEQ
> +#endif
> +
> +#ifdef HAS_RSEQ
> +#include <linux/rseq.h>
> +#include <pthread.h>
> +#include <syscall.h>
> +#include <stdlib.h>
> +#include <error.h>
> +#include <errno.h>
> +#include <string.h>
> +#include <stdint.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <signal.h>
> +#include <atomic.h>
> +
> +static pthread_key_t rseq_test_key;
> +
> +extern __thread volatile struct rseq __rseq_abi
> +__attribute__ ((tls_model ("initial-exec")));
> +
> +static int
> +rseq_thread_registered (void)
> +{
> +  return __rseq_abi.cpu_id >= 0;
> +}
> +
> +static int
> +do_rseq_main_test (void)
> +{
> +  if (raise (SIGUSR1))
> +    FAIL_EXIT1 ("error raising signal");
> +  return rseq_thread_registered () ? 0 : 1;
> +}
> +
> +static void
> +cancel_routine (void *arg)
> +{
> +  if (!rseq_thread_registered ())
> +    FAIL_EXIT1 ("rseq not registered in cancel routine");
> +}
> +
> +static int cancel_thread_ready;
> +
> +static void
> +test_cancel_thread (void)
> +{
> +  pthread_cleanup_push (cancel_routine, NULL);
> +  atomic_store_release (&cancel_thread_ready, 1);
> +  for (;;)
> +    usleep (100);
> +  pthread_cleanup_pop (0);
> +}
> +
> +static void *
> +thread_function (void * arg)
> +{
> +  int i = (int) (intptr_t) arg;
> +
> +  if (raise (SIGUSR1))
> +    FAIL_EXIT1 ("error raising signal");
> +  if (i == 0)
> +    test_cancel_thread ();
> +  if (pthread_setspecific (rseq_test_key, (void *) 1l))
> +    FAIL_EXIT1 ("error in pthread_setspecific");
> +  return rseq_thread_registered () ? NULL : (void *) 1l;
> +}
> +
> +static void
> +sighandler (int sig)
> +{
> +  if (!rseq_thread_registered ())
> +    FAIL_EXIT1 ("rseq not registered in signal handler");
> +}
> +
> +static int
> +setup_signals (void)
> +{
> +  struct sigaction sa;
> +
> +  sigemptyset (&sa.sa_mask);
> +  sigaddset (&sa.sa_mask, SIGUSR1);
> +  sa.sa_flags = 0;
> +  sa.sa_handler = sighandler;
> +  if (sigaction (SIGUSR1, &sa, NULL) != 0)
> +    {
> +      FAIL_RET ("sigaction failure: %s", strerror (errno));
> +    }
> +  return 0;
> +}
> +
> +#define N 7
> +static const int t[N] = { 1, 2, 6, 5, 4, 3, 50 };
> +
> +static int
> +do_rseq_threads_test (int nr_threads)
> +{
> +  pthread_t th[nr_threads];
> +  int i;
> +  int result = 0;
> +  pthread_attr_t at;
> +
> +  if (pthread_attr_init (&at) != 0)
> +    {
> +      FAIL_RET ("attr_init failed");
> +    }
> +
> +  if (pthread_attr_setstacksize (&at, 1 * 1024 * 1024) != 0)
> +    {
> +      FAIL_RET ("attr_setstacksize failed");
> +    }
> +
> +  cancel_thread_ready = 0;
> +  for (i = 0; i < nr_threads; ++i)
> +    if (pthread_create (&th[i], NULL, thread_function,
> +                        (void *) (intptr_t) i) != 0)
> +      {
> +        FAIL_EXIT1 ("creation of thread %d failed", i);
> +      }
> +
> +  if (pthread_attr_destroy (&at) != 0)
> +    {
> +      FAIL_RET ("attr_destroy failed");
> +    }
> +
> +  while (!atomic_load_acquire (&cancel_thread_ready))
> +    usleep (100);
> +
> +  if (pthread_cancel (th[0]))
> +    FAIL_EXIT1 ("error in pthread_cancel");
> +
> +  for (i = 0; i < nr_threads; ++i)
> +    {
> +      void *v;
> +      if (pthread_join (th[i], &v) != 0)
> +        {
> +          printf ("join of thread %d failed\n", i);
> +          result = 1;
> +        }
> +      else if (i != 0 && v != NULL)
> +        {
> +          printf ("join %d successful, but child failed\n", i);
> +          result = 1;
> +        }
> +      else if (i == 0 && v == NULL)
> +        {
> +          printf ("join %d successful, child did not fail as expected\n", i);
> +          result = 1;
> +        }
> +    }
> +  return result;
> +}
> +
> +static int
> +sys_rseq (volatile struct rseq *rseq_abi, uint32_t rseq_len,
> +         int flags, uint32_t sig)
> +{
> +  return syscall (__NR_rseq, rseq_abi, rseq_len, flags, sig);
> +}
> +
> +static int
> +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 0;
> +    case EINVAL:
> +      return 1;
> +    default:
> +      FAIL_EXIT1 ("Unexpected rseq error %s", strerror (errno));
> +    }
> +}
> +
> +static int
> +do_rseq_fork_test (void)
> +{
> +  int status;
> +  pid_t pid, retpid;
> +
> +  pid = fork ();
> +  switch (pid)
> +    {
> +      case 0:
> +        if (do_rseq_main_test ())
> +          FAIL_EXIT1 ("rseq not registered in child");
> +        exit (0);
> +      case -1:
> +          FAIL_EXIT1 ("Unexpected fork error %s", strerror (errno));
> +    }
> +  retpid = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0));
> +  if (retpid != pid)
> +    {
> +      FAIL_EXIT1 ("waitpid returned %ld, expected %ld",
> +                  (long int) retpid, (long int) pid);
> +    }
> +  return WEXITSTATUS (status);
> +}
> +
> +static int
> +do_rseq_test (void)
> +{
> +  int i, result = 0;
> +
> +  if (!rseq_available ())
> +    {
> +      FAIL_UNSUPPORTED ("kernel does not support rseq, skipping test");
> +    }
> +  if (setup_signals ())
> +    FAIL_EXIT1 ("error setting up signal handler");
> +  if (raise (SIGUSR1))
> +    FAIL_EXIT1 ("error raising signal");
> +  if (do_rseq_main_test ())
> +    result = 1;
> +  for (i = 0; i < N; i++)
> +    {
> +      if (do_rseq_threads_test (t[i]))
> +        result = 1;
> +    }
> +  if (do_rseq_fork_test ())
> +    result = 1;
> +  return result;
> +}
> +
> +static void
> +atfork_prepare (void)
> +{
> +  if (!rseq_thread_registered ())
> +    FAIL_EXIT1 ("rseq not registered in pthread atfork prepare");
> +}
> +
> +static void
> +atfork_parent (void)
> +{
> +  if (!rseq_thread_registered ())
> +    FAIL_EXIT1 ("rseq not registered in pthread atfork parent");
> +}
> +
> +static void
> +atfork_child (void)
> +{
> +  if (!rseq_thread_registered ())
> +    FAIL_EXIT1 ("rseq not registered in pthread atfork child");
> +}
> +
> +static void
> +rseq_key_destructor (void *arg)
> +{
> +  if (!rseq_thread_registered ())
> +    FAIL_EXIT1 ("rseq not registered in pthread key destructor");
> +}
> +
> +static void
> +do_rseq_create_key (void)
> +{
> +  if (pthread_key_create (&rseq_test_key, rseq_key_destructor))
> +    FAIL_EXIT1 ("error in pthread_key_create");
> +}
> +
> +static void
> +do_rseq_delete_key (void)
> +{
> +  if (pthread_key_delete (rseq_test_key))
> +    FAIL_EXIT1 ("error in pthread_key_delete");
> +}
> +
> +static void __attribute__ ((constructor))
> +do_rseq_constructor_test (void)
> +{
> +  support_record_failure_init ();
> +  do_rseq_create_key ();
> +  if (pthread_atfork (atfork_prepare, atfork_parent, atfork_child))
> +    FAIL_EXIT1 ("error calling pthread_atfork");
> +  if (do_rseq_test ())
> +    FAIL_EXIT1 ("rseq not registered within constructor");
> +}
> +
> +static void __attribute__ ((destructor))
> +do_rseq_destructor_test (void)
> +{
> +  if (do_rseq_test ())
> +    FAIL_EXIT1 ("rseq not registered within destructor");
> +  do_rseq_delete_key ();
> +}
> +
> +/* Test C++ destructor called at thread and process exit.  */
> +void
> +__call_tls_dtors (void)
> +{
> +  if (!rseq_thread_registered ())
> +    FAIL_EXIT1 ("rseq not registered in C++ thread/process exit destructor");
> +}
> +#else
> +static int
> +do_rseq_test (void)
> +{
> +  FAIL_UNSUPPORTED ("kernel headers do not support rseq, skipping test");
> +  return 0;
> +}
> +#endif
> +
> +static int
> +do_test (void)
> +{
> +  return do_rseq_test ();
> +}
> +
> +#include <support/test-driver.c>
> --
> 2.17.1
  

Patch

diff --git a/nptl/Makefile b/nptl/Makefile
index 3a5dc80c65..797309f7a8 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -323,7 +323,7 @@  tests = tst-attr1 tst-attr2 tst-attr3 tst-default-attr \
 tests-internal := tst-rwlock19 tst-rwlock20 \
 		  tst-sem11 tst-sem12 tst-sem13 \
 		  tst-barrier5 tst-signal7 tst-mutex8 tst-mutex8-static \
-		  tst-mutexpi8 tst-mutexpi8-static
+		  tst-mutexpi8 tst-mutexpi8-static tst-rseq
 
 xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
 	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
diff --git a/nptl/tst-rseq.c b/nptl/tst-rseq.c
new file mode 100644
index 0000000000..c55345e44d
--- /dev/null
+++ b/nptl/tst-rseq.c
@@ -0,0 +1,346 @@ 
+/* Copyright (C) 2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Mathieu Desnoyers <mathieu.desnoyers@efficios.com>, 2018.
+
+   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
+   <http://www.gnu.org/licenses/>.  */
+
+/* These tests validate that rseq is registered from various execution
+   contexts (main thread, constructor, destructor, other threads, other
+   threads created from constructor and destructor, forked process
+   (without exec), pthread_atfork handlers, pthread setspecific
+   destructors, C++ thread and process destructors, and signal handlers).
+
+   See the Linux kernel selftests for extensive rseq stress-tests.  */
+
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <support/check.h>
+
+#if defined (__linux__) && defined (__NR_rseq)
+#define HAS_RSEQ
+#endif
+
+#ifdef HAS_RSEQ
+#include <linux/rseq.h>
+#include <pthread.h>
+#include <syscall.h>
+#include <stdlib.h>
+#include <error.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <atomic.h>
+
+static pthread_key_t rseq_test_key;
+
+extern __thread volatile struct rseq __rseq_abi
+__attribute__ ((tls_model ("initial-exec")));
+
+static int
+rseq_thread_registered (void)
+{
+  return __rseq_abi.cpu_id >= 0;
+}
+
+static int
+do_rseq_main_test (void)
+{
+  if (raise (SIGUSR1))
+    FAIL_EXIT1 ("error raising signal");
+  return rseq_thread_registered () ? 0 : 1;
+}
+
+static void
+cancel_routine (void *arg)
+{
+  if (!rseq_thread_registered ())
+    FAIL_EXIT1 ("rseq not registered in cancel routine");
+}
+
+static int cancel_thread_ready;
+
+static void
+test_cancel_thread (void)
+{
+  pthread_cleanup_push (cancel_routine, NULL);
+  atomic_store_release (&cancel_thread_ready, 1);
+  for (;;)
+    usleep (100);
+  pthread_cleanup_pop (0);
+}
+
+static void *
+thread_function (void * arg)
+{
+  int i = (int) (intptr_t) arg;
+
+  if (raise (SIGUSR1))
+    FAIL_EXIT1 ("error raising signal");
+  if (i == 0)
+    test_cancel_thread ();
+  if (pthread_setspecific (rseq_test_key, (void *) 1l))
+    FAIL_EXIT1 ("error in pthread_setspecific");
+  return rseq_thread_registered () ? NULL : (void *) 1l;
+}
+
+static void
+sighandler (int sig)
+{
+  if (!rseq_thread_registered ())
+    FAIL_EXIT1 ("rseq not registered in signal handler");
+}
+
+static int
+setup_signals (void)
+{
+  struct sigaction sa;
+
+  sigemptyset (&sa.sa_mask);
+  sigaddset (&sa.sa_mask, SIGUSR1);
+  sa.sa_flags = 0;
+  sa.sa_handler = sighandler;
+  if (sigaction (SIGUSR1, &sa, NULL) != 0)
+    {
+      FAIL_RET ("sigaction failure: %s", strerror (errno));
+    }
+  return 0;
+}
+
+#define N 7
+static const int t[N] = { 1, 2, 6, 5, 4, 3, 50 };
+
+static int
+do_rseq_threads_test (int nr_threads)
+{
+  pthread_t th[nr_threads];
+  int i;
+  int result = 0;
+  pthread_attr_t at;
+
+  if (pthread_attr_init (&at) != 0)
+    {
+      FAIL_RET ("attr_init failed");
+    }
+
+  if (pthread_attr_setstacksize (&at, 1 * 1024 * 1024) != 0)
+    {
+      FAIL_RET ("attr_setstacksize failed");
+    }
+
+  cancel_thread_ready = 0;
+  for (i = 0; i < nr_threads; ++i)
+    if (pthread_create (&th[i], NULL, thread_function,
+                        (void *) (intptr_t) i) != 0)
+      {
+        FAIL_EXIT1 ("creation of thread %d failed", i);
+      }
+
+  if (pthread_attr_destroy (&at) != 0)
+    {
+      FAIL_RET ("attr_destroy failed");
+    }
+
+  while (!atomic_load_acquire (&cancel_thread_ready))
+    usleep (100);
+
+  if (pthread_cancel (th[0]))
+    FAIL_EXIT1 ("error in pthread_cancel");
+
+  for (i = 0; i < nr_threads; ++i)
+    {
+      void *v;
+      if (pthread_join (th[i], &v) != 0)
+        {
+          printf ("join of thread %d failed\n", i);
+          result = 1;
+        }
+      else if (i != 0 && v != NULL)
+        {
+          printf ("join %d successful, but child failed\n", i);
+          result = 1;
+        }
+      else if (i == 0 && v == NULL)
+        {
+          printf ("join %d successful, child did not fail as expected\n", i);
+          result = 1;
+        }
+    }
+  return result;
+}
+
+static int
+sys_rseq (volatile struct rseq *rseq_abi, uint32_t rseq_len,
+         int flags, uint32_t sig)
+{
+  return syscall (__NR_rseq, rseq_abi, rseq_len, flags, sig);
+}
+
+static int
+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 0;
+    case EINVAL:
+      return 1;
+    default:
+      FAIL_EXIT1 ("Unexpected rseq error %s", strerror (errno));
+    }
+}
+
+static int
+do_rseq_fork_test (void)
+{
+  int status;
+  pid_t pid, retpid;
+
+  pid = fork ();
+  switch (pid)
+    {
+      case 0:
+        if (do_rseq_main_test ())
+          FAIL_EXIT1 ("rseq not registered in child");
+        exit (0);
+      case -1:
+          FAIL_EXIT1 ("Unexpected fork error %s", strerror (errno));
+    }
+  retpid = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0));
+  if (retpid != pid)
+    {
+      FAIL_EXIT1 ("waitpid returned %ld, expected %ld",
+                  (long int) retpid, (long int) pid);
+    }
+  return WEXITSTATUS (status);
+}
+
+static int
+do_rseq_test (void)
+{
+  int i, result = 0;
+
+  if (!rseq_available ())
+    {
+      FAIL_UNSUPPORTED ("kernel does not support rseq, skipping test");
+    }
+  if (setup_signals ())
+    FAIL_EXIT1 ("error setting up signal handler");
+  if (raise (SIGUSR1))
+    FAIL_EXIT1 ("error raising signal");
+  if (do_rseq_main_test ())
+    result = 1;
+  for (i = 0; i < N; i++)
+    {
+      if (do_rseq_threads_test (t[i]))
+        result = 1;
+    }
+  if (do_rseq_fork_test ())
+    result = 1;
+  return result;
+}
+
+static void
+atfork_prepare (void)
+{
+  if (!rseq_thread_registered ())
+    FAIL_EXIT1 ("rseq not registered in pthread atfork prepare");
+}
+
+static void
+atfork_parent (void)
+{
+  if (!rseq_thread_registered ())
+    FAIL_EXIT1 ("rseq not registered in pthread atfork parent");
+}
+
+static void
+atfork_child (void)
+{
+  if (!rseq_thread_registered ())
+    FAIL_EXIT1 ("rseq not registered in pthread atfork child");
+}
+
+static void
+rseq_key_destructor (void *arg)
+{
+  if (!rseq_thread_registered ())
+    FAIL_EXIT1 ("rseq not registered in pthread key destructor");
+}
+
+static void
+do_rseq_create_key (void)
+{
+  if (pthread_key_create (&rseq_test_key, rseq_key_destructor))
+    FAIL_EXIT1 ("error in pthread_key_create");
+}
+
+static void
+do_rseq_delete_key (void)
+{
+  if (pthread_key_delete (rseq_test_key))
+    FAIL_EXIT1 ("error in pthread_key_delete");
+}
+
+static void __attribute__ ((constructor))
+do_rseq_constructor_test (void)
+{
+  support_record_failure_init ();
+  do_rseq_create_key ();
+  if (pthread_atfork (atfork_prepare, atfork_parent, atfork_child))
+    FAIL_EXIT1 ("error calling pthread_atfork");
+  if (do_rseq_test ())
+    FAIL_EXIT1 ("rseq not registered within constructor");
+}
+
+static void __attribute__ ((destructor))
+do_rseq_destructor_test (void)
+{
+  if (do_rseq_test ())
+    FAIL_EXIT1 ("rseq not registered within destructor");
+  do_rseq_delete_key ();
+}
+
+/* Test C++ destructor called at thread and process exit.  */
+void
+__call_tls_dtors (void)
+{
+  if (!rseq_thread_registered ())
+    FAIL_EXIT1 ("rseq not registered in C++ thread/process exit destructor");
+}
+#else
+static int
+do_rseq_test (void)
+{
+  FAIL_UNSUPPORTED ("kernel headers do not support rseq, skipping test");
+  return 0;
+}
+#endif
+
+static int
+do_test (void)
+{
+  return do_rseq_test ();
+}
+
+#include <support/test-driver.c>