/*      $NetBSD: h_segv.c,v 1.15 2024/05/14 15:54:16 riastradh Exp $    */

/*-
* Copyright (c) 2017 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Christos Zoulas.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: h_segv.c,v 1.15 2024/05/14 15:54:16 riastradh Exp $");

#define __TEST_FENV

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ptrace.h>

#include <err.h>
#include <fenv.h>
#if (__arm__ && !__SOFTFP__) || __aarch64__
#include <ieeefp.h> /* only need for ARM Cortex/Neon hack */
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int flags;
#define F_RECURSE       1
#define F_HANDLE        2
#define F_MASK          4
#define F_IGNORE        8
#define F_CHECK         16

static struct {
       const char *n;
       int v;
} nv[] = {
       { "recurse",    F_RECURSE },
       { "handle",     F_HANDLE },
       { "mask",       F_MASK },
       { "ignore",     F_IGNORE },
       { "check",      F_CHECK }
};

static int sig;
static struct {
       const char *n;
       int v;
} sn[] = {
       { "segv",       SIGSEGV },
       { "trap",       SIGTRAP },
       { "ill",        SIGILL },
       { "fpe",        SIGFPE },
       { "bus",        SIGBUS }
};

static void
trigger_segv(void)
{
       volatile int *p = (int *)(intptr_t)atoi("0");

       *p = 1;
}

static void
trigger_trap(void)
{

#ifdef PTRACE_BREAKPOINT_ASM
       PTRACE_BREAKPOINT_ASM;
#else
       /* port me */
#endif
}

static void
trigger_ill(void)
{

#ifdef PTRACE_ILLEGAL_ASM
       PTRACE_ILLEGAL_ASM;
#else
       /* port me */
#endif
}

static void
check_fpe(void)
{
#if (__arm__ && !__SOFTFP__) || __aarch64__
       /*
        * Some NEON fpus do not trap on IEEE 754 FP exceptions.
        * Skip these tests if running on them and compiled for
        * hard float.
        */
       if (0 == fpsetmask(fpsetmask(FP_X_INV))) {
               printf("FPU does not implement traps on FP exceptions\n");
               exit(EXIT_FAILURE);
       }
#elif defined __riscv__
       printf("RISC-V does not support floating-point exception traps\n");
       exit(EXIT_FAILURE);
#endif
       exit(EXIT_SUCCESS);
}

volatile int ignore_result;

static void
trigger_fpe(void)
{
       volatile double a = getpid();
       volatile double b = strtol("0", NULL, 0);

#ifdef __HAVE_FENV
       feenableexcept(FE_ALL_EXCEPT);
#endif

       /*
        * Try to trigger SIGFPE either by dividing by zero (which is
        * defined to raise FE_DIVBYZERO, but may just return infinity
        * without trapping the exception) or by converting infinity to
        * integer.
        */
       ignore_result = (int)(a/b);
}

static void
trigger_bus(void)
{
       FILE *fp;
       char *p;

       /* Open an empty file for writing. */
       fp = tmpfile();
       if (fp == NULL)
               err(EXIT_FAILURE, "tmpfile");

       /*
        * Map an empty file with mmap(2) to a pointer.
        *
        * PROT_READ handles read-modify-write sequences emitted for
        * certain combinations of CPUs and compilers (e.g. Alpha AXP).
        */
       p = mmap(0, 1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fileno(fp), 0);
       if (p == MAP_FAILED)
               err(EXIT_FAILURE, "mmap");

       /* Invalid memory access causes CPU trap, translated to SIGBUS */
       *p = 'a';
}

static void
trigger(void)
{

       switch (sig) {
       case SIGSEGV:
               trigger_segv();
               break;
       case SIGTRAP:
               trigger_trap();
               break;
       case SIGILL:
               trigger_ill();
               break;
       case SIGFPE:
               trigger_fpe();
               break;
       case SIGBUS:
               trigger_bus();
               break;
       default:
               break;
       }
}

static void
foo(int s)
{
       char buf[64];
       int i = snprintf(buf, sizeof(buf), "got %d\n", s);
       write(2, buf, i);

       if (flags & F_RECURSE)
               trigger();

       exit(EXIT_SUCCESS);
}

static __dead void
usage(void)
{
       const char *pname = getprogname();

       fprintf(stderr, "Usage: %s segv|trap|ill|fpe|bus "
                       "[recurse|mask|handle|ignore|check] ...\n", pname);

       exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{

       if (argc == 1)
           usage();

       for (int i = 1; i < argc; i++) {
               size_t j;
               for (j = 0; j < __arraycount(nv); j++) {
                       if (strcmp(nv[j].n, argv[i]) == 0) {
                               flags |= nv[j].v;
                               goto consumed;
                       }
               }
               for (j = 0; j < __arraycount(sn); j++) {
                       if (strcmp(sn[j].n, argv[i]) == 0) {
                               sig = sn[j].v;
                               goto consumed;
                       }
               }

               usage();

       consumed:
               continue;
       }

       if (flags == 0 || sig == 0)
               usage();

       if (flags & F_CHECK && sig != SIGFPE) {
               fprintf(stderr, "can only check for fpe support\n");
               return 1;
       }
       if (flags & F_CHECK)
               check_fpe();

       if (flags & F_HANDLE) {
               struct sigaction sa;

               sa.sa_flags = SA_RESTART;
               sa.sa_handler = foo;
               sigemptyset(&sa.sa_mask);
               if (sigaction(sig, &sa, NULL) == -1)
                       err(EXIT_FAILURE, "sigaction");
       }

       if (flags & F_MASK) {
               sigset_t set;

               sigemptyset(&set);
               sigaddset(&set, sig);
               if (sigprocmask(SIG_BLOCK, &set, NULL) == -1)
                       err(EXIT_FAILURE, "sigprocmask");
       }

       if (flags & F_IGNORE) {
               struct sigaction sa;

               memset(&sa, 0, sizeof(sa));
               sa.sa_handler = SIG_IGN;
               sigemptyset(&sa.sa_mask);
               if (sigaction(sig, &sa, NULL) == -1)
                       err(EXIT_FAILURE, "sigaction");
       }

       trigger();

       return EXIT_SUCCESS;
}