]> Savannah Git Hosting - gnulib.git/commitdiff
sigsegv: Add tests.
authorBruno Haible <bruno@clisp.org>
Sun, 16 May 2021 16:29:48 +0000 (18:29 +0200)
committerBruno Haible <bruno@clisp.org>
Sun, 16 May 2021 17:02:56 +0000 (19:02 +0200)
* tests/test-sigsegv-catch-segv1.c: New file, from GNU libsigsegv with
modifications.
* tests/test-sigsegv-catch-segv2.c: Likewise.
* tests/test-sigsegv-catch-stackoverflow1.c: Likewise.
* tests/test-sigsegv-catch-stackoverflow2.c: Likewise.
* tests/altstack-util.h: Likewise.
* tests/mmap-anon-util.h: Likewise.
* modules/sigsegv-tests: New file.

ChangeLog
modules/sigsegv-tests [new file with mode: 0644]
tests/altstack-util.h [new file with mode: 0644]
tests/mmap-anon-util.h [new file with mode: 0644]
tests/test-sigsegv-catch-segv1.c [new file with mode: 0644]
tests/test-sigsegv-catch-segv2.c [new file with mode: 0644]
tests/test-sigsegv-catch-stackoverflow1.c [new file with mode: 0644]
tests/test-sigsegv-catch-stackoverflow2.c [new file with mode: 0644]

index c939655da530ea6e4efd2d726e0777464b973aaf..88f7944268a7c40eb5eb6f62a757dba0b1f84bb0 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2021-05-16  Bruno Haible  <bruno@clisp.org>
 
+       sigsegv: Add tests.
+       * tests/test-sigsegv-catch-segv1.c: New file, from GNU libsigsegv with
+       modifications.
+       * tests/test-sigsegv-catch-segv2.c: Likewise.
+       * tests/test-sigsegv-catch-stackoverflow1.c: Likewise.
+       * tests/test-sigsegv-catch-stackoverflow2.c: Likewise.
+       * tests/altstack-util.h: Likewise.
+       * tests/mmap-anon-util.h: Likewise.
+       * modules/sigsegv-tests: New file.
+
        sigsegv: New module.
        * lib/sigsegv.in.h: New file, from GNU libsigsegv with modifications.
        * lib/sigsegv.c: Likewise.
diff --git a/modules/sigsegv-tests b/modules/sigsegv-tests
new file mode 100644 (file)
index 0000000..ed7ed94
--- /dev/null
@@ -0,0 +1,31 @@
+Files:
+tests/test-sigsegv-catch-segv1.c
+tests/test-sigsegv-catch-segv2.c
+tests/test-sigsegv-catch-stackoverflow1.c
+tests/test-sigsegv-catch-stackoverflow2.c
+tests/altstack-util.h
+tests/mmap-anon-util.h
+m4/mmap-anon.m4
+
+Depends-on:
+stdint
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([setrlimit])
+gl_FUNC_MMAP_ANON
+
+Makefile.am:
+TESTS += \
+  test-sigsegv-catch-segv1 \
+  test-sigsegv-catch-segv2 \
+  test-sigsegv-catch-stackoverflow1 \
+  test-sigsegv-catch-stackoverflow2
+check_PROGRAMS += \
+  test-sigsegv-catch-segv1 \
+  test-sigsegv-catch-segv2 \
+  test-sigsegv-catch-stackoverflow1 \
+  test-sigsegv-catch-stackoverflow2
+test_sigsegv_catch_segv1_LDADD = $(LDADD) $(LIBSIGSEGV)
+test_sigsegv_catch_segv2_LDADD = $(LDADD) $(LIBSIGSEGV)
+test_sigsegv_catch_stackoverflow1_LDADD = $(LDADD) $(LIBSIGSEGV)
+test_sigsegv_catch_stackoverflow2_LDADD = $(LDADD) $(LIBSIGSEGV)
diff --git a/tests/altstack-util.h b/tests/altstack-util.h
new file mode 100644 (file)
index 0000000..5130645
--- /dev/null
@@ -0,0 +1,59 @@
+/* Some auxiliary stuff for defining an alternate stack.
+   Copyright (C) 2010  Eric Blake <eblake@redhat.com>
+   Copyright (C) 2010-2021  Bruno Haible <bruno@clisp.org>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <stdint.h> /* uintptr_t */
+#include <string.h> /* for memset */
+
+#ifndef SIGSTKSZ
+# define SIGSTKSZ 16384
+#endif
+
+/* glibc says: Users should use SIGSTKSZ as the size of user-supplied
+   buffers.  We want to detect stack overflow of the alternate stack
+   in a nicer manner than just crashing, so we overallocate in
+   comparison to what we hand libsigsegv.  Also, we intentionally hand
+   an unaligned pointer, to ensure the alternate stack still ends up
+   aligned.  */
+#define MYSTACK_CRUMPLE_ZONE 8192
+char mystack_storage[SIGSTKSZ + 2 * MYSTACK_CRUMPLE_ZONE + 31];
+char *mystack; /* SIGSTKSZ bytes in the middle of storage. */
+
+static void
+prepare_alternate_stack (void)
+{
+  memset (mystack_storage, 's', sizeof mystack_storage);
+  mystack = (char *) ((uintptr_t) (mystack_storage + MYSTACK_CRUMPLE_ZONE) | 31);
+}
+
+static void
+check_alternate_stack_no_overflow (void)
+{
+  unsigned int i;
+
+  for (i = MYSTACK_CRUMPLE_ZONE; i > 0; i--)
+    if (*(mystack - i) != 's')
+      {
+        printf ("Alternate stack was exceeded by %u bytes!!\n", i);
+        exit (1);
+      }
+  for (i = MYSTACK_CRUMPLE_ZONE; i > 0; i--)
+    if (*(mystack + SIGSTKSZ - 1 + i) != 's')
+      {
+        printf ("Alternate stack was exceeded by %u bytes!!\n", i);
+        exit (1);
+      }
+}
diff --git a/tests/mmap-anon-util.h b/tests/mmap-anon-util.h
new file mode 100644 (file)
index 0000000..6fb82ef
--- /dev/null
@@ -0,0 +1,97 @@
+/* Some auxiliary stuff for using mmap & friends.
+   Copyright (C) 2002-2021  Bruno Haible <bruno@clisp.org>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#if defined _WIN32 && !defined __CYGWIN__
+
+/* ------------------------ Windows ------------------------ */
+
+# define WIN32_LEAN_AND_MEAN /* avoid including junk */
+# include <windows.h>
+# include <winerror.h>
+# define PROT_NONE PAGE_NOACCESS
+# define PROT_READ PAGE_READONLY
+# define PROT_READ_WRITE PAGE_READWRITE
+
+static void *
+mmap_zeromap (void *map_addr_hint, size_t map_len)
+{
+  if (VirtualAlloc ((void *)((uintptr_t) map_addr_hint & -0x10000),
+                    (((uintptr_t) map_addr_hint + map_len - 1) | 0xffff) + 1
+                    - ((uintptr_t) map_addr_hint & -0x10000),
+                    MEM_RESERVE, PAGE_NOACCESS)
+      && VirtualAlloc (map_addr_hint, map_len, MEM_COMMIT, PAGE_READWRITE))
+    return map_addr_hint;
+  else
+    return (void *)(-1);
+}
+
+int
+munmap (void *addr, size_t len)
+{
+  if (VirtualFree (addr, len, MEM_DECOMMIT))
+    return 0;
+  else
+    return -1;
+}
+
+int
+mprotect (void *addr, size_t len, int prot)
+{
+  DWORD oldprot;
+
+  if (VirtualProtect (addr, len, prot, &oldprot))
+    return 0;
+  else
+    return -1;
+}
+
+#else
+
+/* ------------------------ Unix ------------------------ */
+
+# include <sys/types.h>
+# include <sys/mman.h>
+# include <fcntl.h>
+
+# ifndef PROT_NONE
+#  define PROT_NONE 0
+# endif
+# define PROT_READ_WRITE  (PROT_READ|PROT_WRITE)
+
+# if HAVE_MAP_ANONYMOUS
+#  define zero_fd -1
+#  define map_flags MAP_ANONYMOUS | MAP_PRIVATE
+# else
+#  ifndef MAP_FILE
+#   define MAP_FILE 0
+#  endif
+static int zero_fd;
+#  define map_flags MAP_FILE | MAP_PRIVATE
+# endif
+
+static void *
+mmap_zeromap (void *map_addr_hint, size_t map_len)
+{
+# ifdef __hpux
+  /* HP-UX 10 mmap() often fails when given a hint.  So give the OS complete
+     freedom about the address range.  */
+  return (void *) mmap ((void *) 0,    map_len, PROT_READ_WRITE, map_flags, zero_fd, 0);
+# else
+  return (void *) mmap (map_addr_hint, map_len, PROT_READ_WRITE, map_flags, zero_fd, 0);
+# endif
+}
+
+#endif
diff --git a/tests/test-sigsegv-catch-segv1.c b/tests/test-sigsegv-catch-segv1.c
new file mode 100644 (file)
index 0000000..62eef69
--- /dev/null
@@ -0,0 +1,128 @@
+/* Test that the handler is called, with the right fault address.
+   Copyright (C) 2002-2021  Bruno Haible <bruno@clisp.org>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sigsegv.h"
+
+#include <stdint.h>
+#include <stdio.h>
+
+#if HAVE_SIGSEGV_RECOVERY
+
+# include "mmap-anon-util.h"
+# include <stdlib.h>
+
+# if SIGSEGV_FAULT_ADDRESS_ALIGNMENT > 1UL
+#  include <unistd.h>
+#  define SIGSEGV_FAULT_ADDRESS_ROUNDOFF_BITS (getpagesize () - 1)
+# else
+#  define SIGSEGV_FAULT_ADDRESS_ROUNDOFF_BITS 0
+# endif
+
+uintptr_t page;
+
+volatile int handler_called = 0;
+
+int
+handler (void *fault_address, int serious)
+{
+  handler_called++;
+  if (handler_called > 10)
+    abort ();
+  if (fault_address
+      != (void *)((page + 0x678) & ~SIGSEGV_FAULT_ADDRESS_ROUNDOFF_BITS))
+    abort ();
+  if (mprotect ((void *) page, 0x4000, PROT_READ_WRITE) == 0)
+    return 1;
+  return 0;
+}
+
+void
+crasher (uintptr_t p)
+{
+  *(volatile int *) (p + 0x678) = 42;
+}
+
+int
+main ()
+{
+  int prot_unwritable;
+  void *p;
+
+  /* Preparations.  */
+# if !HAVE_MAP_ANONYMOUS
+  zero_fd = open ("/dev/zero", O_RDONLY, 0644);
+# endif
+
+# if defined __linux__ && defined __sparc__
+  /* On Linux 2.6.26/SPARC64, PROT_READ has the same effect as
+     PROT_READ | PROT_WRITE.  */
+  prot_unwritable = PROT_NONE;
+# else
+  prot_unwritable = PROT_READ;
+# endif
+
+  /* Setup some mmaped memory.  */
+  p = mmap_zeromap ((void *) 0x12340000, 0x4000);
+  if (p == (void *)(-1))
+    {
+      fprintf (stderr, "mmap_zeromap failed.\n");
+      exit (2);
+    }
+  page = (uintptr_t) p;
+
+  /* Make it read-only.  */
+  if (mprotect ((void *) page, 0x4000, prot_unwritable) < 0)
+    {
+      fprintf (stderr, "mprotect failed.\n");
+      exit (2);
+    }
+  /* Test whether it's possible to make it read-write after it was read-only.
+     This is not possible on Cygwin.  */
+  if (mprotect ((void *) page, 0x4000, PROT_READ_WRITE) < 0
+      || mprotect ((void *) page, 0x4000, prot_unwritable) < 0)
+    {
+      fprintf (stderr, "mprotect failed.\n");
+      exit (2);
+    }
+
+  /* Install the SIGSEGV handler.  */
+  sigsegv_install_handler (&handler);
+
+  /* The first write access should invoke the handler and then complete.  */
+  crasher (page);
+  /* The second write access should not invoke the handler.  */
+  crasher (page);
+
+  /* Check that the handler was called only once.  */
+  if (handler_called != 1)
+    exit (1);
+  /* Test passed!  */
+  printf ("Test passed.\n");
+  return 0;
+}
+
+#else
+
+int
+main ()
+{
+  return 77;
+}
+
+#endif
diff --git a/tests/test-sigsegv-catch-segv2.c b/tests/test-sigsegv-catch-segv2.c
new file mode 100644 (file)
index 0000000..dd28517
--- /dev/null
@@ -0,0 +1,151 @@
+/* Test that the handler can be exited multiple times.
+   Copyright (C) 2002-2021  Bruno Haible <bruno@clisp.org>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sigsegv.h"
+
+#include <stdint.h>
+#include <stdio.h>
+
+#if HAVE_SIGSEGV_RECOVERY
+
+# if defined _WIN32 && !defined __CYGWIN__
+  /* Windows doesn't have sigset_t.  */
+  typedef int sigset_t;
+#  define sigemptyset(set)
+#  define sigprocmask(how,set,oldset)
+# endif
+
+# include "mmap-anon-util.h"
+# include <stdlib.h> /* for abort, exit */
+# include <signal.h>
+# include <setjmp.h>
+
+# if SIGSEGV_FAULT_ADDRESS_ALIGNMENT > 1UL
+#  include <unistd.h>
+#  define SIGSEGV_FAULT_ADDRESS_ROUNDOFF_BITS (getpagesize () - 1)
+# else
+#  define SIGSEGV_FAULT_ADDRESS_ROUNDOFF_BITS 0
+# endif
+
+jmp_buf mainloop;
+sigset_t mainsigset;
+
+volatile int pass = 0;
+uintptr_t page;
+
+volatile int handler_called = 0;
+
+static void
+handler_continuation (void *arg1, void *arg2, void *arg3)
+{
+  longjmp (mainloop, pass);
+}
+
+int
+handler (void *fault_address, int serious)
+{
+  handler_called++;
+  if (handler_called > 10)
+    abort ();
+  if (fault_address
+      != (void *)((page + 0x678 + 8 * pass) & ~SIGSEGV_FAULT_ADDRESS_ROUNDOFF_BITS))
+    abort ();
+  pass++;
+  printf ("Fault %d caught.\n", pass);
+  sigprocmask (SIG_SETMASK, &mainsigset, NULL);
+  return sigsegv_leave_handler (handler_continuation, NULL, NULL, NULL);
+}
+
+void
+crasher (uintptr_t p)
+{
+  *(volatile int *) (p + 0x678 + 8 * pass) = 42;
+}
+
+int
+main ()
+{
+  int prot_unwritable;
+  void *p;
+  sigset_t emptyset;
+
+  /* Preparations.  */
+# if !HAVE_MAP_ANONYMOUS
+  zero_fd = open ("/dev/zero", O_RDONLY, 0644);
+# endif
+
+# if defined __linux__ && defined __sparc__
+  /* On Linux 2.6.26/SPARC64, PROT_READ has the same effect as
+     PROT_READ | PROT_WRITE.  */
+  prot_unwritable = PROT_NONE;
+# else
+  prot_unwritable = PROT_READ;
+# endif
+
+  /* Setup some mmaped memory.  */
+  p = mmap_zeromap ((void *) 0x12340000, 0x4000);
+  if (p == (void *)(-1))
+    {
+      fprintf (stderr, "mmap_zeromap failed.\n");
+      exit (2);
+    }
+  page = (uintptr_t) p;
+
+  /* Make it read-only.  */
+  if (mprotect ((void *) page, 0x4000, prot_unwritable) < 0)
+    {
+      fprintf (stderr, "mprotect failed.\n");
+      exit (2);
+    }
+
+  /* Install the SIGSEGV handler.  */
+  if (sigsegv_install_handler (&handler) < 0)
+    exit (2);
+
+  /* Save the current signal mask.  */
+  sigemptyset (&emptyset);
+  sigprocmask (SIG_BLOCK, &emptyset, &mainsigset);
+
+  /* Provoke two SIGSEGVs in a row.  */
+  switch (setjmp (mainloop))
+    {
+    case 0: case 1:
+      printf ("Doing SIGSEGV pass %d.\n", pass + 1);
+      crasher (page);
+      printf ("no SIGSEGV?!\n"); exit (1);
+    case 2:
+      break;
+    default:
+      abort ();
+    }
+
+  /* Test passed!  */
+  printf ("Test passed.\n");
+  return 0;
+}
+
+#else
+
+int
+main ()
+{
+  return 77;
+}
+
+#endif
diff --git a/tests/test-sigsegv-catch-stackoverflow1.c b/tests/test-sigsegv-catch-stackoverflow1.c
new file mode 100644 (file)
index 0000000..141dfd4
--- /dev/null
@@ -0,0 +1,148 @@
+/* Test the stack overflow handler.
+   Copyright (C) 2002-2021  Bruno Haible <bruno@clisp.org>
+   Copyright (C) 2010 Eric Blake <eblake@redhat.com>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sigsegv.h"
+
+#include <stdio.h>
+#include <limits.h>
+
+#if HAVE_STACK_OVERFLOW_RECOVERY
+
+# if defined _WIN32 && !defined __CYGWIN__
+  /* Windows doesn't have sigset_t.  */
+  typedef int sigset_t;
+#  define sigemptyset(set)
+#  define sigprocmask(how,set,oldset)
+# endif
+
+# include <stddef.h> /* needed for NULL on SunOS4 */
+# include <stdlib.h> /* for abort, exit */
+# include <signal.h>
+# include <setjmp.h>
+# if HAVE_SETRLIMIT
+#  include <sys/types.h>
+#  include <sys/time.h>
+#  include <sys/resource.h>
+# endif
+# include "altstack-util.h"
+
+jmp_buf mainloop;
+sigset_t mainsigset;
+
+volatile int pass = 0;
+
+volatile char *stack_lower_bound;
+volatile char *stack_upper_bound;
+
+static void
+stackoverflow_handler_continuation (void *arg1, void *arg2, void *arg3)
+{
+  int arg = (int) (long) arg1;
+  longjmp (mainloop, arg);
+}
+
+void
+stackoverflow_handler (int emergency, stackoverflow_context_t scp)
+{
+  char dummy;
+  volatile char *addr = &dummy;
+  if (!(addr >= stack_lower_bound && addr <= stack_upper_bound))
+    abort ();
+  pass++;
+  printf ("Stack overflow %d caught.\n", pass);
+  sigprocmask (SIG_SETMASK, &mainsigset, NULL);
+  sigsegv_leave_handler (stackoverflow_handler_continuation,
+                         (void *) (long) (emergency ? -1 : pass), NULL, NULL);
+}
+
+volatile int *
+recurse_1 (int n, volatile int *p)
+{
+  if (n < INT_MAX)
+    *recurse_1 (n + 1, p) += n;
+  return p;
+}
+
+int
+recurse (volatile int n)
+{
+  return *recurse_1 (n, &n);
+}
+
+int
+main ()
+{
+  sigset_t emptyset;
+
+# if HAVE_SETRLIMIT && defined RLIMIT_STACK
+  /* Before starting the endless recursion, try to be friendly to the user's
+     machine.  On some Linux 2.2.x systems, there is no stack limit for user
+     processes at all.  We don't want to kill such systems.  */
+  struct rlimit rl;
+  rl.rlim_cur = rl.rlim_max = 0x100000; /* 1 MB */
+  setrlimit (RLIMIT_STACK, &rl);
+# endif
+
+  /* Prepare the storage for the alternate stack.  */
+  prepare_alternate_stack ();
+
+  /* Install the stack overflow handler.  */
+  if (stackoverflow_install_handler (&stackoverflow_handler,
+                                     mystack, SIGSTKSZ)
+      < 0)
+    exit (2);
+  stack_lower_bound = mystack;
+  stack_upper_bound = mystack + SIGSTKSZ - 1;
+
+  /* Save the current signal mask.  */
+  sigemptyset (&emptyset);
+  sigprocmask (SIG_BLOCK, &emptyset, &mainsigset);
+
+  /* Provoke two stack overflows in a row.  */
+  switch (setjmp (mainloop))
+    {
+    case -1:
+      printf ("emergency exit\n"); exit (1);
+    case 0: case 1:
+      printf ("Starting recursion pass %d.\n", pass + 1);
+      recurse (0);
+      printf ("no endless recursion?!\n"); exit (1);
+    case 2:
+      break;
+    default:
+      abort ();
+    }
+
+  /* Validate that the alternate stack did not overflow.  */
+  check_alternate_stack_no_overflow ();
+
+  printf ("Test passed.\n");
+  exit (0);
+}
+
+#else
+
+int
+main ()
+{
+  return 77;
+}
+
+#endif
diff --git a/tests/test-sigsegv-catch-stackoverflow2.c b/tests/test-sigsegv-catch-stackoverflow2.c
new file mode 100644 (file)
index 0000000..1db2d44
--- /dev/null
@@ -0,0 +1,209 @@
+/* Test that stack overflow and SIGSEGV are correctly distinguished.
+   Copyright (C) 2002-2021  Bruno Haible <bruno@clisp.org>
+   Copyright (C) 2010 Eric Blake <eblake@redhat.com>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sigsegv.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <limits.h>
+
+#if HAVE_STACK_OVERFLOW_RECOVERY && HAVE_SIGSEGV_RECOVERY
+
+# if defined _WIN32 && !defined __CYGWIN__
+  /* Windows doesn't have sigset_t.  */
+  typedef int sigset_t;
+#  define sigemptyset(set)
+#  define sigprocmask(how,set,oldset)
+# endif
+
+# include "mmap-anon-util.h"
+# include <stddef.h> /* needed for NULL on SunOS4 */
+# include <stdlib.h> /* for abort, exit */
+# include <signal.h>
+# include <setjmp.h>
+# if HAVE_SETRLIMIT
+#  include <sys/types.h>
+#  include <sys/time.h>
+#  include <sys/resource.h>
+# endif
+# include "altstack-util.h"
+
+jmp_buf mainloop;
+sigset_t mainsigset;
+
+volatile int pass = 0;
+uintptr_t page;
+
+static void
+stackoverflow_handler_continuation (void *arg1, void *arg2, void *arg3)
+{
+  int arg = (int) (long) arg1;
+  longjmp (mainloop, arg);
+}
+
+void
+stackoverflow_handler (int emergency, stackoverflow_context_t scp)
+{
+  pass++;
+  if (pass <= 2)
+    printf ("Stack overflow %d caught.\n", pass);
+  else
+    {
+      printf ("Segmentation violation misdetected as stack overflow.\n");
+      exit (1);
+    }
+  sigprocmask (SIG_SETMASK, &mainsigset, NULL);
+  sigsegv_leave_handler (stackoverflow_handler_continuation,
+                         (void *) (long) (emergency ? -1 : pass), NULL, NULL);
+}
+
+int
+sigsegv_handler (void *address, int emergency)
+{
+  /* This test is necessary to distinguish stack overflow and SIGSEGV.  */
+  if (!emergency)
+    return 0;
+
+  pass++;
+  if (pass <= 2)
+    {
+      printf ("Stack overflow %d missed.\n", pass);
+      exit (1);
+    }
+  else
+    printf ("Segmentation violation correctly detected.\n");
+  sigprocmask (SIG_SETMASK, &mainsigset, NULL);
+  return sigsegv_leave_handler (stackoverflow_handler_continuation,
+                                (void *) (long) pass, NULL, NULL);
+}
+
+volatile int *
+recurse_1 (int n, volatile int *p)
+{
+  if (n < INT_MAX)
+    *recurse_1 (n + 1, p) += n;
+  return p;
+}
+
+int
+recurse (volatile int n)
+{
+  return *recurse_1 (n, &n);
+}
+
+int
+main ()
+{
+  int prot_unwritable;
+  void *p;
+  sigset_t emptyset;
+
+# if HAVE_SETRLIMIT && defined RLIMIT_STACK
+  /* Before starting the endless recursion, try to be friendly to the user's
+     machine.  On some Linux 2.2.x systems, there is no stack limit for user
+     processes at all.  We don't want to kill such systems.  */
+  struct rlimit rl;
+  rl.rlim_cur = rl.rlim_max = 0x100000; /* 1 MB */
+  setrlimit (RLIMIT_STACK, &rl);
+# endif
+
+  /* Prepare the storage for the alternate stack.  */
+  prepare_alternate_stack ();
+
+  /* Install the stack overflow handler.  */
+  if (stackoverflow_install_handler (&stackoverflow_handler,
+                                     mystack, SIGSTKSZ)
+      < 0)
+    exit (2);
+
+  /* Preparations.  */
+# if !HAVE_MAP_ANONYMOUS
+  zero_fd = open ("/dev/zero", O_RDONLY, 0644);
+# endif
+
+# if defined __linux__ && defined __sparc__
+  /* On Linux 2.6.26/SPARC64, PROT_READ has the same effect as
+     PROT_READ | PROT_WRITE.  */
+  prot_unwritable = PROT_NONE;
+# else
+  prot_unwritable = PROT_READ;
+# endif
+
+  /* Setup some mmaped memory.  */
+  p = mmap_zeromap ((void *) 0x12340000, 0x4000);
+  if (p == (void *)(-1))
+    {
+      fprintf (stderr, "mmap_zeromap failed.\n");
+      exit (2);
+    }
+  page = (uintptr_t) p;
+
+  /* Make it read-only.  */
+  if (mprotect ((void *) page, 0x4000, prot_unwritable) < 0)
+    {
+      fprintf (stderr, "mprotect failed.\n");
+      exit (2);
+    }
+
+  /* Install the SIGSEGV handler.  */
+  if (sigsegv_install_handler (&sigsegv_handler) < 0)
+    exit (2);
+
+  /* Save the current signal mask.  */
+  sigemptyset (&emptyset);
+  sigprocmask (SIG_BLOCK, &emptyset, &mainsigset);
+
+  /* Provoke two stack overflows in a row.  */
+  switch (setjmp (mainloop))
+    {
+    case -1:
+      printf ("emergency exit\n"); exit (1);
+    case 0: case 1:
+      printf ("Starting recursion pass %d.\n", pass + 1);
+      recurse (0);
+      printf ("no endless recursion?!\n"); exit (1);
+    case 2:
+      *(volatile int *) (page + 0x678) = 42;
+      break;
+    case 3:
+      *(volatile int *) 0 = 42;
+      break;
+    case 4:
+      break;
+    default:
+      abort ();
+    }
+
+  /* Validate that the alternate stack did not overflow.  */
+  check_alternate_stack_no_overflow ();
+
+  printf ("Test passed.\n");
+  exit (0);
+}
+
+#else
+
+int
+main ()
+{
+  return 77;
+}
+
+#endif