Blame string/tst-xbzero-opt.c

Packit 6c4009
/* Test that explicit_bzero block clears are not optimized out.
Packit 6c4009
   Copyright (C) 2016-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
/* This test is conceptually based on a test designed by Matthew
Packit 6c4009
   Dempsky for the OpenBSD regression suite:
Packit 6c4009
   <openbsd>/src/regress/lib/libc/explicit_bzero/explicit_bzero.c.
Packit 6c4009
   The basic idea is, we have a function that contains a
Packit 6c4009
   block-clearing operation (not necessarily explicit_bzero), after
Packit 6c4009
   which the block is dead, in the compiler-jargon sense.  Execute
Packit 6c4009
   that function while running on a user-allocated alternative
Packit 6c4009
   stack. Then we have another pointer to the memory region affected
Packit 6c4009
   by the block clear -- namely, the original allocation for the
Packit 6c4009
   alternative stack -- and can find out whether it actually happened.
Packit 6c4009
Packit 6c4009
   The OpenBSD test uses sigaltstack and SIGUSR1 to get onto an
Packit 6c4009
   alternative stack.  This causes a number of awkward problems; some
Packit 6c4009
   operating systems (e.g. Solaris and OSX) wipe the signal stack upon
Packit 6c4009
   returning to the normal stack, there's no way to be sure that other
Packit 6c4009
   processes running on the same system will not interfere, and the
Packit 6c4009
   signal stack is very small so it's not safe to call printf there.
Packit 6c4009
   This implementation instead uses the <ucontext.h> coroutine
Packit 6c4009
   interface.  The coroutine stack is still too small to safely use
Packit 6c4009
   printf, but we know the OS won't erase it, so we can do all the
Packit 6c4009
   checks and printing from the normal stack.  */
Packit 6c4009
Packit 6c4009
#define _GNU_SOURCE 1
Packit 6c4009
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <signal.h>
Packit 6c4009
#include <stdio.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <ucontext.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
Packit 6c4009
/* A byte pattern that is unlikely to occur by chance: the first 16
Packit 6c4009
   prime numbers (OEIS A000040).  */
Packit 6c4009
static const unsigned char test_pattern[16] =
Packit 6c4009
{
Packit 6c4009
  2, 3, 5, 7,  11, 13, 17, 19,  23, 29, 31, 37,  41, 43, 47, 53
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Immediately after each subtest returns, we call swapcontext to get
Packit 6c4009
   back onto the main stack.  That call might itself overwrite the
Packit 6c4009
   test pattern, so we fill a modest-sized buffer with copies of it
Packit 6c4009
   and check whether any of them survived.  */
Packit 6c4009
Packit 6c4009
#define PATTERN_SIZE (sizeof test_pattern)
Packit 6c4009
#define PATTERN_REPS 32
Packit 6c4009
#define TEST_BUFFER_SIZE (PATTERN_SIZE * PATTERN_REPS)
Packit 6c4009
Packit 6c4009
/* There are three subtests, two of which are sanity checks.
Packit 6c4009
   Each test follows this sequence:
Packit 6c4009
Packit 6c4009
     main                      coroutine
Packit 6c4009
     ----                      --------
Packit 6c4009
     advance cur_subtest
Packit 6c4009
     swap
Packit 6c4009
                               call setup function
Packit 6c4009
                                 prepare test buffer
Packit 6c4009
                                 swap
Packit 6c4009
     verify that buffer
Packit 6c4009
     was filled in
Packit 6c4009
     swap
Packit 6c4009
                                 possibly clear buffer
Packit 6c4009
                                 return
Packit 6c4009
                               swap
Packit 6c4009
     check buffer again,
Packit 6c4009
     according to test
Packit 6c4009
     expectation
Packit 6c4009
Packit 6c4009
   In the "no_clear" case, we don't do anything to the test buffer
Packit 6c4009
   between preparing it and letting it go out of scope, and we expect
Packit 6c4009
   to find it.  This confirms that the test buffer does get filled in
Packit 6c4009
   and we can find it from the stack buffer.  In the "ordinary_clear"
Packit 6c4009
   case, we clear it using memset.  Depending on the target, the
Packit 6c4009
   compiler may not be able to apply dead store elimination to the
Packit 6c4009
   memset call, so the test does not fail if the memset is not
Packit 6c4009
   eliminated.  Finally, the "explicit_clear" case uses explicit_bzero
Packit 6c4009
   and expects _not_ to find the test buffer, which is the real
Packit 6c4009
   test.  */
Packit 6c4009
Packit 6c4009
static ucontext_t uc_main, uc_co;
Packit 6c4009
Packit 6c4009
static __attribute__ ((noinline, noclone)) int
Packit 6c4009
use_test_buffer (unsigned char *buf)
Packit 6c4009
{
Packit 6c4009
  unsigned int sum = 0;
Packit 6c4009
Packit 6c4009
  for (unsigned int i = 0; i < PATTERN_REPS; i++)
Packit 6c4009
    sum += buf[i * PATTERN_SIZE];
Packit 6c4009
Packit 6c4009
  return (sum == 2 * PATTERN_REPS) ? 0 : 1;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Always check the test buffer immediately after filling it; this
Packit 6c4009
   makes externally visible side effects depend on the buffer existing
Packit 6c4009
   and having been filled in.  */
Packit 6c4009
#if defined __CET__ && !__glibc_has_attribute (__indirect_return__)
Packit 6c4009
/* Note: swapcontext returns via indirect branch when SHSTK is enabled.
Packit 6c4009
   Without indirect_return attribute, swapcontext is marked with
Packit 6c4009
   returns_twice attribute, which prevents always_inline to work.  */
Packit 6c4009
# define ALWAYS_INLINE
Packit 6c4009
#else
Packit 6c4009
# define ALWAYS_INLINE	__attribute__ ((always_inline))
Packit 6c4009
#endif
Packit 6c4009
static inline ALWAYS_INLINE void
Packit 6c4009
prepare_test_buffer (unsigned char *buf)
Packit 6c4009
{
Packit 6c4009
  for (unsigned int i = 0; i < PATTERN_REPS; i++)
Packit 6c4009
    memcpy (buf + i*PATTERN_SIZE, test_pattern, PATTERN_SIZE);
Packit 6c4009
Packit 6c4009
  if (swapcontext (&uc_co, &uc_main))
Packit 6c4009
    abort ();
Packit 6c4009
Packit 6c4009
  /* Force the compiler to really copy the pattern to buf.  */
Packit 6c4009
  if (use_test_buffer (buf))
Packit 6c4009
    abort ();
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
setup_no_clear (void)
Packit 6c4009
{
Packit 6c4009
  unsigned char buf[TEST_BUFFER_SIZE];
Packit 6c4009
  prepare_test_buffer (buf);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
setup_ordinary_clear (void)
Packit 6c4009
{
Packit 6c4009
  unsigned char buf[TEST_BUFFER_SIZE];
Packit 6c4009
  prepare_test_buffer (buf);
Packit 6c4009
  memset (buf, 0, TEST_BUFFER_SIZE);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
setup_explicit_clear (void)
Packit 6c4009
{
Packit 6c4009
  unsigned char buf[TEST_BUFFER_SIZE];
Packit 6c4009
  prepare_test_buffer (buf);
Packit 6c4009
  explicit_bzero (buf, TEST_BUFFER_SIZE);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
enum test_expectation
Packit 6c4009
  {
Packit 6c4009
    EXPECT_NONE, EXPECT_SOME, EXPECT_ALL, NO_EXPECTATIONS
Packit 6c4009
  };
Packit 6c4009
struct subtest
Packit 6c4009
{
Packit 6c4009
  void (*setup_subtest) (void);
Packit 6c4009
  const char *label;
Packit 6c4009
  enum test_expectation expected;
Packit 6c4009
};
Packit 6c4009
static const struct subtest *cur_subtest;
Packit 6c4009
Packit 6c4009
static const struct subtest subtests[] =
Packit 6c4009
{
Packit 6c4009
  { setup_no_clear,       "no clear",       EXPECT_SOME },
Packit 6c4009
  /* The memset may happen or not, depending on compiler
Packit 6c4009
     optimizations.  */
Packit 6c4009
  { setup_ordinary_clear, "ordinary clear", NO_EXPECTATIONS },
Packit 6c4009
  { setup_explicit_clear, "explicit clear", EXPECT_NONE },
Packit 6c4009
  { 0,                    0,                -1          }
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
test_coroutine (void)
Packit 6c4009
{
Packit 6c4009
  while (cur_subtest->setup_subtest)
Packit 6c4009
    {
Packit 6c4009
      cur_subtest->setup_subtest ();
Packit 6c4009
      if (swapcontext (&uc_co, &uc_main))
Packit 6c4009
	abort ();
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* All the code above this point runs on the coroutine stack.
Packit 6c4009
   All the code below this point runs on the main stack.  */
Packit 6c4009
Packit 6c4009
static int test_status;
Packit 6c4009
static unsigned char *co_stack_buffer;
Packit 6c4009
static size_t co_stack_size;
Packit 6c4009
Packit 6c4009
static unsigned int
Packit 6c4009
count_test_patterns (unsigned char *buf, size_t bufsiz)
Packit 6c4009
{
Packit 6c4009
  unsigned char *first = memmem (buf, bufsiz, test_pattern, PATTERN_SIZE);
Packit 6c4009
  if (!first)
Packit 6c4009
    return 0;
Packit 6c4009
  unsigned int cnt = 0;
Packit 6c4009
  for (unsigned int i = 0; i < PATTERN_REPS; i++)
Packit 6c4009
    {
Packit 6c4009
      unsigned char *p = first + i*PATTERN_SIZE;
Packit 6c4009
      if (p + PATTERN_SIZE - buf > bufsiz)
Packit 6c4009
	break;
Packit 6c4009
      if (memcmp (p, test_pattern, PATTERN_SIZE) == 0)
Packit 6c4009
	cnt++;
Packit 6c4009
    }
Packit 6c4009
  return cnt;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
check_test_buffer (enum test_expectation expected,
Packit 6c4009
		   const char *label, const char *stage)
Packit 6c4009
{
Packit 6c4009
  unsigned int cnt = count_test_patterns (co_stack_buffer, co_stack_size);
Packit 6c4009
  switch (expected)
Packit 6c4009
    {
Packit 6c4009
    case EXPECT_NONE:
Packit 6c4009
      if (cnt == 0)
Packit 6c4009
	printf ("PASS: %s/%s: expected 0 got %d\n", label, stage, cnt);
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  printf ("FAIL: %s/%s: expected 0 got %d\n", label, stage, cnt);
Packit 6c4009
	  test_status = 1;
Packit 6c4009
	}
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    case EXPECT_SOME:
Packit 6c4009
      if (cnt > 0)
Packit 6c4009
	printf ("PASS: %s/%s: expected some got %d\n", label, stage, cnt);
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  printf ("FAIL: %s/%s: expected some got 0\n", label, stage);
Packit 6c4009
	  test_status = 1;
Packit 6c4009
	}
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
     case EXPECT_ALL:
Packit 6c4009
      if (cnt == PATTERN_REPS)
Packit 6c4009
	printf ("PASS: %s/%s: expected %d got %d\n", label, stage,
Packit 6c4009
		PATTERN_REPS, cnt);
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  printf ("FAIL: %s/%s: expected %d got %d\n", label, stage,
Packit 6c4009
		  PATTERN_REPS, cnt);
Packit 6c4009
	  test_status = 1;
Packit 6c4009
	}
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    case NO_EXPECTATIONS:
Packit 6c4009
      printf ("INFO: %s/%s: found %d patterns%s\n", label, stage, cnt,
Packit 6c4009
	      cnt == 0 ? " (memset not eliminated)" : "");
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    default:
Packit 6c4009
      printf ("ERROR: %s/%s: invalid value for 'expected' = %d\n",
Packit 6c4009
	      label, stage, (int)expected);
Packit 6c4009
      test_status = 1;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
test_loop (void)
Packit 6c4009
{
Packit 6c4009
  cur_subtest = subtests;
Packit 6c4009
  while (cur_subtest->setup_subtest)
Packit 6c4009
    {
Packit 6c4009
      if (swapcontext (&uc_main, &uc_co))
Packit 6c4009
	abort ();
Packit 6c4009
      check_test_buffer (EXPECT_ALL, cur_subtest->label, "prepare");
Packit 6c4009
      if (swapcontext (&uc_main, &uc_co))
Packit 6c4009
	abort ();
Packit 6c4009
      check_test_buffer (cur_subtest->expected, cur_subtest->label, "test");
Packit 6c4009
      cur_subtest++;
Packit 6c4009
    }
Packit 6c4009
  /* Terminate the coroutine.  */
Packit 6c4009
  if (swapcontext (&uc_main, &uc_co))
Packit 6c4009
    abort ();
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
int
Packit 6c4009
do_test (void)
Packit 6c4009
{
Packit 6c4009
  size_t page_alignment = sysconf (_SC_PAGESIZE);
Packit 6c4009
  if (page_alignment < sizeof (void *))
Packit 6c4009
    page_alignment = sizeof (void *);
Packit 6c4009
Packit 6c4009
  co_stack_size = SIGSTKSZ + TEST_BUFFER_SIZE;
Packit 6c4009
  if (co_stack_size < page_alignment * 4)
Packit 6c4009
    co_stack_size = page_alignment * 4;
Packit 6c4009
Packit 6c4009
  void *p;
Packit 6c4009
  int err = posix_memalign (&p, page_alignment, co_stack_size);
Packit 6c4009
  if (err || !p)
Packit 6c4009
    {
Packit 6c4009
      printf ("ERROR: allocating alt stack: %s\n", strerror (err));
Packit 6c4009
      return 2;
Packit 6c4009
    }
Packit 6c4009
  co_stack_buffer = p;
Packit 6c4009
Packit 6c4009
  if (getcontext (&uc_co))
Packit 6c4009
    {
Packit 6c4009
      printf ("ERROR: allocating coroutine context: %s\n", strerror (err));
Packit 6c4009
      return 2;
Packit 6c4009
    }
Packit 6c4009
  uc_co.uc_stack.ss_sp   = co_stack_buffer;
Packit 6c4009
  uc_co.uc_stack.ss_size = co_stack_size;
Packit 6c4009
  uc_co.uc_link          = &uc_main;
Packit 6c4009
  makecontext (&uc_co, test_coroutine, 0);
Packit 6c4009
Packit 6c4009
  test_loop ();
Packit 6c4009
  return test_status;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#include <support/test-driver.c>