[PATCH glibc 3/3] rseq registration tests (v10)

Florian Weimer fweimer@redhat.com
Wed May 20 10:52:51 GMT 2020


* Mathieu Desnoyers via Libc-alpha:

> - Include atomic.h from tst-rseq.c for use of atomic_load_relaxed.
>   Move tst-rseq.c to internal tests within Makefile due to its use of
>   atomic.h.

You could use __atomic builtins to avoid that, or <stdatomic.h>.  Both
are fine in tests.  I suggest to do that, so that the test can be built
more easily outside of glibc.

However, this needs to remain an internal test because the test needs a
defintiion of __NR_rseq from the internal system call list.  Regular
tests use the installed kernel headers, which may not define __NR_rseq.
Maybe mention this in a comment in nptl/Makefile, along with the
tests-internal change?

> 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..3e3996d686
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-rseq-nptl.c
> @@ -0,0 +1,338 @@
> +/* Restartable Sequences NPTL test.
> +
> +   Copyright (C) 2020 Free Software Foundation, Inc.

Maybe rmoeve this empty line.

> +#ifdef RSEQ_SIG
> +#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>

This should be:

#ifdef RSEQ_SIG
# include <pthread.h>
# include <stdlib.h>

And so on.

Nested preprocessor conditionals need to be indented per our style
rules.

Please also remove the redundant <syscall.h> inclusion.

> +static pthread_key_t rseq_test_key;
> +
> +static int
> +rseq_thread_registered (void)
> +{
> +  return (int32_t) atomic_load_relaxed (&__rseq_abi.cpu_id) >= 0;
> +}

The return type should be bool.

> +static void
> +rseq_key_destructor (void *arg)
> +{
> +  /* Cannot use deferred failure reporting after main () returns.  */

No () after function name.

> +  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 int
> +do_rseq_main_test (void)
> +{
> +  if (atexit (atexit_handler))
> +    FAIL_EXIT1 ("error calling atexit");
> +  rseq_test_key = xpthread_key_create (rseq_key_destructor);
> +  if (pthread_atfork (atfork_prepare, atfork_parent, atfork_child))
> +    FAIL_EXIT1 ("error calling pthread_atfork");
> +  if (raise (SIGUSR1))
> +    FAIL_EXIT1 ("error raising signal");
> +  if (pthread_setspecific (rseq_test_key, (void *) 1l))
> +    FAIL_EXIT1 ("error in pthread_setspecific");
> +  if (!rseq_thread_registered ())
> +    {
> +      FAIL_RET ("rseq not registered in main thread");
> +    }

You could use

  TEST_COMPARE (atexit (atexit_handler), 0);
  …
  TEST_VERIFY (rseq_thread_registered ());

from <support/check.h>.  The automatically generated error messages will
provide sufficient context, I think.

> +  return 0;
> +}
> +
> +static void
> +cancel_routine (void *arg)
> +{
> +  if (!rseq_thread_registered ())
> +    {
> +      printf ("rseq not registered in cancel routine\n");
> +      support_record_failure ();
> +    }
> +}

Please add "error: " to the error message, to make it clearer that it is
an error.

> +
> +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);
> +}

This can use a real barrier (pthread_barrier_t), I think.  No need for
polling then.

> +static void *
> +thread_function (void * arg)
> +{
> +  int i = (int) (intptr_t) arg;
> +
> +  if (raise (SIGUSR1))
> +    FAIL_EXIT1 ("error raising signal");

This can use xraise.

> +  if (i == 0)
> +    test_cancel_thread ();
> +  if (pthread_setspecific (rseq_test_key, (void *) 1l))
> +    FAIL_EXIT1 ("error in pthread_setspecific");

Could use TEST_COMPARE.

> +  return rseq_thread_registered () ? NULL : (void *) 1l;
> +}
> +
> +static void
> +sighandler (int sig)
> +{
> +  if (!rseq_thread_registered ())
> +    {
> +      printf ("rseq not registered in signal handler\n");
> +      support_record_failure ();
> +    }
> +}

Please add "error: ".

> +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;
> +  if (sigaction (SIGUSR1, &sa, NULL) != 0)
> +    {
> +      FAIL_EXIT1 ("sigaction failure: %s", strerror (errno));
> +    }
> +}

This could use xsigaction.

> +#define N 7
> +static const int t[N] = { 1, 2, 6, 5, 4, 3, 50 };

Should these definitions be local to do_rseq_test below?  You can avoid
defining N by using array_length from <array_length.h>.

> +static int
> +do_rseq_threads_test (int nr_threads)
> +{
> +  pthread_t th[nr_threads];
> +  int i;
> +  int result = 0;
> +
> +  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);
> +      }

Could use xpthread_create.

> +  while (!atomic_load_acquire (&cancel_thread_ready))
> +    usleep (100);

See above, could use xpthread_barrier_wait.

The present code does not wait until all threads have entered their
cancellation region, so I'm not sure if the test object is actually met
here.

> +  if (pthread_cancel (th[0]))
> +    FAIL_EXIT1 ("error in pthread_cancel");

Could use xpthread_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;
> +        }

Could use xpthread_join.

> +static 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);
> +}

(This is the only remaining place where internal headers are potentially
required, I think.)

> +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));
> +    }
> +}

Maybe add a comment to explain what EINVAL means in this context?

> +static int
> +do_rseq_fork_test (void)
> +{
> +  int status;
> +  pid_t pid, retpid;
> +
> +  pid = fork ();
> +  switch (pid)
> +    {
> +      case 0:
> +        exit (do_rseq_main_test ());
> +      case -1:
> +        FAIL_EXIT1 ("Unexpected fork error %s", strerror (errno));
> +    }

Could use xfork.

> +  retpid = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0));
> +  if (retpid != pid)
> +    {
> +      FAIL_EXIT1 ("waitpid returned %ld, expected %ld",
> +                  (long int) retpid, (long int) pid);
> +    }

Hmm.  Is the TEMP_FAILURE_RETRY really needed?  Our xwaitpid does not
have this.

> +  if (WEXITSTATUS (status))
> +    {
> +      printf ("rseq not registered in child\n");
> +      return 1;
> +    }

This whole thing looks a lot lie support_isolate_in_subprocess from
<support/namespace.h>.

> +static int
> +do_rseq_test (void)
> +{
> +  int i, result = 0;
> +
> +  if (!rseq_available ())
> +    {
> +      FAIL_UNSUPPORTED ("kernel does not support rseq, skipping test");
> +    }

Braces are not needed here.

> +  setup_signals ();
> +  if (raise (SIGUSR1))
> +    FAIL_EXIT1 ("error raising signal");

Could use xraise.

> +  if (do_rseq_main_test ())
> +    result = 1;
> +  for (i = 0; i < N; i++)
> +    {
> +      if (do_rseq_threads_test (t[i]))
> +        result = 1;
> +    }

Braces are unnecessary.

> +/* Test C++ destructor called at thread and process exit.  */
> +void
> +__call_tls_dtors (void)
> +{
> +  /* Cannot use deferred failure reporting after main () returns.  */
> +  if (!rseq_thread_registered ())
> +    FAIL_EXIT1 ("rseq not registered in C++ thread/process exit destructor");
> +}

Uhm, what is this supposed to accomplish, under the __call_tls_dtors
name in particular?  I don't think this gets ever called.

It may make sense to have a separate, smaller C++ test to cover this
(perhaps as a separate patch).

> +#else

This needs a comment on the same line.  I think it corresponds to the
earlier “#ifdef RSEQ_SIG”.

> +static int
> +do_rseq_test (void)
> +{
> +#ifndef RSEQ_SIG
> +  FAIL_UNSUPPORTED ("glibc does not define RSEQ_SIG, skipping test");
> +#endif

And with the comment, it should be obvious that the #ifndef is not
needed here. 8-)

> +  return 0;
> +}
> +#endif

Likewise: Add comment.

> diff --git a/sysdeps/unix/sysv/linux/tst-rseq.c b/sysdeps/unix/sysv/linux/tst-rseq.c
> new file mode 100644
> index 0000000000..48a61f9998
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-rseq.c
> @@ -0,0 +1,108 @@
> +/* Restartable Sequences single-threaded tests.
> +
> +   Copyright (C) 2020 Free Software Foundation, Inc.

Perhaps remove the empty line?

> +#include <sys/syscall.h>
> +#include <unistd.h>
> +#include <stdio.h>
> +#include <support/check.h>
> +#include <sys/rseq.h>
> +
> +#ifdef RSEQ_SIG
> +#include <syscall.h>

Duplicate <syscall.h>.

> +#include <stdlib.h>
> +#include <error.h>
> +#include <errno.h>
> +#include <stdint.h>
> +#include <string.h>
> +#include <atomic.h>
> +
> +static int
> +rseq_thread_registered (void)
> +{
> +  return (int32_t) atomic_load_relaxed (&__rseq_abi.cpu_id) >= 0;
> +}

(See above.)

> +static int
> +do_rseq_main_test (void)
> +{
> +  if (!rseq_thread_registered ())
> +    {
> +      FAIL_RET ("rseq not registered in main thread");
> +    }
> +  return 0;
> +}

Additional braces.

> +static 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 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));
> +    }
> +}

It looks these three functions could be moved in to a tst-rseq.h header
file (just create the file, no Makefile updates needed).

> +static int
> +do_rseq_test (void)
> +{
> +  int result = 0;
> +
> +  if (!rseq_available ())
> +    {
> +      FAIL_UNSUPPORTED ("kernel does not support rseq, skipping test");
> +    }
> +  if (do_rseq_main_test ())
> +    result = 1;
> +  return result;
> +}
> +#else
> +static int
> +do_rseq_test (void)
> +{
> +#ifndef RSEQ_SIG
> +  FAIL_UNSUPPORTED ("glibc does not define RSEQ_SIG, skipping test");
> +#endif
> +  return 0;
> +}
> +#endif

Comments on #else and #endif, see above.

C++ test could exercise the thread exit path via thread_local, without
linking against libpthread.

Thanks,
Florian



More information about the Libc-alpha mailing list