This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
Re: [RFC PATCH v4 5/5] rseq registration tests
- From: Mathieu Desnoyers <mathieu dot desnoyers at efficios dot com>
- To: carlos <carlos at redhat dot com>
- Cc: Florian Weimer <fweimer at redhat dot com>, Joseph Myers <joseph at codesourcery dot com>, Szabolcs Nagy <szabolcs dot nagy at arm dot com>, libc-alpha <libc-alpha at sourceware dot org>, Thomas Gleixner <tglx at linutronix dot de>, Ben Maurer <bmaurer at fb dot com>, Peter Zijlstra <peterz at infradead dot org>, "Paul E. McKenney" <paulmck at linux dot vnet dot ibm dot com>, Boqun Feng <boqun dot feng at gmail dot com>, Will Deacon <will dot deacon at arm dot com>, Dave Watson <davejwatson at fb dot com>, Paul Turner <pjt at google dot com>
- Date: Wed, 21 Nov 2018 13:50:57 -0500 (EST)
- Subject: Re: [RFC PATCH v4 5/5] rseq registration tests
- Dkim-filter: OpenDKIM Filter v2.10.3 mail.efficios.com EE283133148
- References: <20181121183936.8176-1-mathieu.desnoyers@efficios.com> <20181121183936.8176-5-mathieu.desnoyers@efficios.com>
----- 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
--
Mathieu Desnoyers
EfficiOS Inc.
http://www.efficios.com