root/tests/lib/libc/sys/t_ptrace_threads_wait.h
/*      $NetBSD: t_ptrace_threads_wait.h,v 1.3 2025/05/02 02:24:44 riastradh Exp $      */

/*-
 * Copyright (c) 2016, 2017, 2018, 2019, 2020 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * 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.
 */

#define TRACE_THREADS_NUM 100

static volatile int done;
pthread_mutex_t trace_threads_mtx = PTHREAD_MUTEX_INITIALIZER;

static void *
trace_threads_cb(void *arg __unused)
{

        pthread_mutex_lock(&trace_threads_mtx);
        done++;
        pthread_mutex_unlock(&trace_threads_mtx);

        while (done < TRACE_THREADS_NUM)
                sched_yield();

        return NULL;
}

static void
trace_threads(bool trace_create, bool trace_exit, bool masked)
{
        const int sigval = SIGSTOP;
        pid_t child, wpid;
#if defined(TWAIT_HAVE_STATUS)
        int status;
#endif
        ptrace_state_t state;
        const int slen = sizeof(state);
        ptrace_event_t event;
        const int elen = sizeof(event);
        struct ptrace_siginfo info;

        sigset_t intmask;

        pthread_t t[TRACE_THREADS_NUM];
        int rv;
        size_t n;
        lwpid_t lid;

        /* Track created and exited threads */
        struct lwp_event_count traced_lwps[__arraycount(t)] = {{0, 0}};

        DPRINTF("Before forking process PID=%d\n", getpid());
        SYSCALL_REQUIRE((child = fork()) != -1);
        if (child == 0) {
                DPRINTF("Before calling PT_TRACE_ME from child %d\n", getpid());
                FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);

                if (masked) {
                        sigemptyset(&intmask);
                        sigaddset(&intmask, SIGTRAP);
                        sigprocmask(SIG_BLOCK, &intmask, NULL);
                }

                DPRINTF("Before raising %s from child\n", strsignal(sigval));
                FORKEE_ASSERT(raise(sigval) == 0);

                for (n = 0; n < __arraycount(t); n++) {
                        rv = pthread_create(&t[n], NULL, trace_threads_cb,
                            NULL);
                        FORKEE_ASSERT(rv == 0);
                }

                for (n = 0; n < __arraycount(t); n++) {
                        rv = pthread_join(t[n], NULL);
                        FORKEE_ASSERT(rv == 0);
                }

                /*
                 * There is race between _exit() and pthread_join() detaching
                 * a thread. For simplicity kill the process after detecting
                 * LWP events.
                 */
                while (true)
                        continue;

                FORKEE_ASSERT(0 && "Not reached");
        }
        DPRINTF("Parent process PID=%d, child's PID=%d\n", getpid(), child);

        DPRINTF("Before calling %s() for the child\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_stopped(status, sigval);

        DPRINTF("Before calling ptrace(2) with PT_GET_SIGINFO for child\n");
        SYSCALL_REQUIRE(
            ptrace(PT_GET_SIGINFO, child, &info, sizeof(info)) != -1);

        DPRINTF("Signal traced to lwpid=%d\n", info.psi_lwpid);
        DPRINTF("Signal properties: si_signo=%#x si_code=%#x si_errno=%#x\n",
            info.psi_siginfo.si_signo, info.psi_siginfo.si_code,
            info.psi_siginfo.si_errno);

        ATF_REQUIRE_EQ(info.psi_siginfo.si_signo, sigval);
        ATF_REQUIRE_EQ(info.psi_siginfo.si_code, SI_LWP);

        DPRINTF("Set LWP event mask for the child %d\n", child);
        memset(&event, 0, sizeof(event));
        if (trace_create)
                event.pe_set_event |= PTRACE_LWP_CREATE;
        if (trace_exit)
                event.pe_set_event |= PTRACE_LWP_EXIT;
        SYSCALL_REQUIRE(ptrace(PT_SET_EVENT_MASK, child, &event, elen) != -1);

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        for (n = 0; n < (trace_create ? __arraycount(t) : 0); n++) {
                DPRINTF("Before calling %s() for the child - expected stopped "
                    "SIGTRAP\n", TWAIT_FNAME);
                TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0),
                    child);

                validate_status_stopped(status, SIGTRAP);

                DPRINTF("Before calling ptrace(2) with PT_GET_SIGINFO for "
                    "child\n");
                SYSCALL_REQUIRE(
                    ptrace(PT_GET_SIGINFO, child, &info, sizeof(info)) != -1);

                DPRINTF("Signal traced to lwpid=%d\n", info.psi_lwpid);
                DPRINTF("Signal properties: si_signo=%#x si_code=%#x "
                    "si_errno=%#x\n",
                    info.psi_siginfo.si_signo, info.psi_siginfo.si_code,
                    info.psi_siginfo.si_errno);

                ATF_REQUIRE_EQ(info.psi_siginfo.si_signo, SIGTRAP);
                ATF_REQUIRE_EQ(info.psi_siginfo.si_code, TRAP_LWP);

                SYSCALL_REQUIRE(
                    ptrace(PT_GET_PROCESS_STATE, child, &state, slen) != -1);

                ATF_REQUIRE_EQ_MSG(state.pe_report_event, PTRACE_LWP_CREATE,
                    "%d != %d", state.pe_report_event, PTRACE_LWP_CREATE);

                lid = state.pe_lwp;
                DPRINTF("Reported PTRACE_LWP_CREATE event with lid %d\n", lid);

                *FIND_EVENT_COUNT(traced_lwps, lid) += 1;

                DPRINTF("Before resuming the child process where it left off "
                    "and without signal to be sent\n");
                SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
        }

        for (n = 0; n < (trace_exit ? __arraycount(t) : 0); n++) {
                DPRINTF("Before calling %s() for the child - expected stopped "
                    "SIGTRAP\n", TWAIT_FNAME);
                TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0),
                    child);

                validate_status_stopped(status, SIGTRAP);

                DPRINTF("Before calling ptrace(2) with PT_GET_SIGINFO for "
                    "child\n");
                SYSCALL_REQUIRE(
                    ptrace(PT_GET_SIGINFO, child, &info, sizeof(info)) != -1);

                DPRINTF("Signal traced to lwpid=%d\n", info.psi_lwpid);
                DPRINTF("Signal properties: si_signo=%#x si_code=%#x "
                    "si_errno=%#x\n",
                    info.psi_siginfo.si_signo, info.psi_siginfo.si_code,
                    info.psi_siginfo.si_errno);

                ATF_REQUIRE_EQ(info.psi_siginfo.si_signo, SIGTRAP);
                ATF_REQUIRE_EQ(info.psi_siginfo.si_code, TRAP_LWP);

                SYSCALL_REQUIRE(
                    ptrace(PT_GET_PROCESS_STATE, child, &state, slen) != -1);

                ATF_REQUIRE_EQ_MSG(state.pe_report_event, PTRACE_LWP_EXIT,
                    "%d != %d", state.pe_report_event, PTRACE_LWP_EXIT);

                lid = state.pe_lwp;
                DPRINTF("Reported PTRACE_LWP_EXIT event with lid %d\n", lid);

                if (trace_create) {
                        int *count = FIND_EVENT_COUNT(traced_lwps, lid);
                        ATF_REQUIRE_EQ(*count, 1);
                        *count = 0;
                }

                DPRINTF("Before resuming the child process where it left off "
                    "and without signal to be sent\n");
                SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
        }

        kill(child, SIGKILL);

        DPRINTF("Before calling %s() for the child - expected exited\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_signaled(status, SIGKILL, 0);

        DPRINTF("Before calling %s() for the child - expected no process\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0));
}

#define TRACE_THREADS(test, trace_create, trace_exit, mask)             \
ATF_TC(test);                                                           \
ATF_TC_HEAD(test, tc)                                                   \
{                                                                       \
        atf_tc_set_md_var(tc, "descr",                                  \
            "Verify spawning threads with%s tracing LWP create and"     \
            "with%s tracing LWP exit", trace_create ? "" : "out",       \
            trace_exit ? "" : "out");                                   \
}                                                                       \
                                                                        \
ATF_TC_BODY(test, tc)                                                   \
{                                                                       \
                                                                        \
        trace_threads(trace_create, trace_exit, mask);                  \
}

TRACE_THREADS(trace_thread_nolwpevents, false, false, false)
TRACE_THREADS(trace_thread_lwpexit, false, true, false)
TRACE_THREADS(trace_thread_lwpcreate, true, false, false)
TRACE_THREADS(trace_thread_lwpcreate_and_exit, true, true, false)

TRACE_THREADS(trace_thread_lwpexit_masked_sigtrap, false, true, true)
TRACE_THREADS(trace_thread_lwpcreate_masked_sigtrap, true, false, true)
TRACE_THREADS(trace_thread_lwpcreate_and_exit_masked_sigtrap, true, true, true)

/// ----------------------------------------------------------------------------

static void *
thread_and_exec_thread_cb(void *arg __unused)
{

        execlp("/bin/echo", "/bin/echo", NULL);

        abort();
}

static void
threads_and_exec(void)
{
        const int sigval = SIGSTOP;
        pid_t child, wpid;
#if defined(TWAIT_HAVE_STATUS)
        int status;
#endif
        ptrace_state_t state;
        const int slen = sizeof(state);
        ptrace_event_t event;
        const int elen = sizeof(event);
        struct ptrace_siginfo info;

        pthread_t t;
        lwpid_t lid;

        DPRINTF("Before forking process PID=%d\n", getpid());
        SYSCALL_REQUIRE((child = fork()) != -1);
        if (child == 0) {
                DPRINTF("Before calling PT_TRACE_ME from child %d\n", getpid());
                FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);

                DPRINTF("Before raising %s from child\n", strsignal(sigval));
                FORKEE_ASSERT(raise(sigval) == 0);

                FORKEE_PTHREAD(pthread_create(&t, NULL,
                    thread_and_exec_thread_cb, NULL));

                for (;;)
                        continue;

                FORKEE_ASSERT(0 && "Not reached");
        }
        DPRINTF("Parent process PID=%d, child's PID=%d\n", getpid(), child);

        DPRINTF("Before calling %s() for the child\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_stopped(status, sigval);

        DPRINTF("Before calling ptrace(2) with PT_GET_SIGINFO for child\n");
        SYSCALL_REQUIRE(
            ptrace(PT_GET_SIGINFO, child, &info, sizeof(info)) != -1);

        DPRINTF("Signal traced to lwpid=%d\n", info.psi_lwpid);
        DPRINTF("Signal properties: si_signo=%#x si_code=%#x si_errno=%#x\n",
            info.psi_siginfo.si_signo, info.psi_siginfo.si_code,
            info.psi_siginfo.si_errno);

        ATF_REQUIRE_EQ(info.psi_siginfo.si_signo, sigval);
        ATF_REQUIRE_EQ(info.psi_siginfo.si_code, SI_LWP);

        DPRINTF("Set LWP event mask for the child %d\n", child);
        memset(&event, 0, sizeof(event));
        event.pe_set_event |= PTRACE_LWP_CREATE | PTRACE_LWP_EXIT;
        SYSCALL_REQUIRE(ptrace(PT_SET_EVENT_MASK, child, &event, elen) != -1);

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected stopped "
            "SIGTRAP\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0),
            child);

        validate_status_stopped(status, SIGTRAP);

        DPRINTF("Before calling ptrace(2) with PT_GET_SIGINFO for "
            "child\n");
        SYSCALL_REQUIRE(
            ptrace(PT_GET_SIGINFO, child, &info, sizeof(info)) != -1);

        DPRINTF("Signal traced to lwpid=%d\n", info.psi_lwpid);
        DPRINTF("Signal properties: si_signo=%#x si_code=%#x "
            "si_errno=%#x\n",
            info.psi_siginfo.si_signo, info.psi_siginfo.si_code,
            info.psi_siginfo.si_errno);

        ATF_REQUIRE_EQ(info.psi_siginfo.si_signo, SIGTRAP);
        ATF_REQUIRE_EQ(info.psi_siginfo.si_code, TRAP_LWP);

        SYSCALL_REQUIRE(
            ptrace(PT_GET_PROCESS_STATE, child, &state, slen) != -1);

        ATF_REQUIRE_EQ_MSG(state.pe_report_event, PTRACE_LWP_CREATE,
            "%d != %d", state.pe_report_event, PTRACE_LWP_CREATE);

        lid = state.pe_lwp;
        DPRINTF("Reported PTRACE_LWP_CREATE event with lid %d\n", lid);

        DPRINTF("Before resuming the child process where it left off "
            "and without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected stopped "
            "SIGTRAP\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0),
            child);

        validate_status_stopped(status, SIGTRAP);

        DPRINTF("Before calling ptrace(2) with PT_GET_SIGINFO for "
            "child\n");
        SYSCALL_REQUIRE(
            ptrace(PT_GET_SIGINFO, child, &info, sizeof(info)) != -1);

        DPRINTF("Signal traced to lwpid=%d\n", info.psi_lwpid);
        DPRINTF("Signal properties: si_signo=%#x si_code=%#x "
            "si_errno=%#x\n",
            info.psi_siginfo.si_signo, info.psi_siginfo.si_code,
            info.psi_siginfo.si_errno);

        ATF_REQUIRE_EQ(info.psi_siginfo.si_signo, SIGTRAP);
        ATF_REQUIRE_EQ(info.psi_siginfo.si_code, TRAP_LWP);

        SYSCALL_REQUIRE(
            ptrace(PT_GET_PROCESS_STATE, child, &state, slen) != -1);

        ATF_REQUIRE_EQ_MSG(state.pe_report_event, PTRACE_LWP_EXIT,
            "%d != %d", state.pe_report_event, PTRACE_LWP_EXIT);

        lid = state.pe_lwp;
        DPRINTF("Reported PTRACE_LWP_EXIT event with lid %d\n", lid);

        DPRINTF("Before resuming the child process where it left off "
            "and without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected stopped "
            "SIGTRAP\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0),
            child);

        validate_status_stopped(status, SIGTRAP);

        DPRINTF("Before calling ptrace(2) with PT_GET_SIGINFO for "
            "child\n");
        SYSCALL_REQUIRE(
            ptrace(PT_GET_SIGINFO, child, &info, sizeof(info)) != -1);

        DPRINTF("Signal traced to lwpid=%d\n", info.psi_lwpid);
        DPRINTF("Signal properties: si_signo=%#x si_code=%#x "
            "si_errno=%#x\n",
            info.psi_siginfo.si_signo, info.psi_siginfo.si_code,
            info.psi_siginfo.si_errno);

        ATF_REQUIRE_EQ(info.psi_siginfo.si_signo, SIGTRAP);
        ATF_REQUIRE_EQ(info.psi_siginfo.si_code, TRAP_EXEC);

        SYSCALL_REQUIRE(ptrace(PT_KILL, child, NULL, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected exited\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_signaled(status, SIGKILL, 0);

        DPRINTF("Before calling %s() for the child - expected no process\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0));
}

ATF_TC(threads_and_exec);
ATF_TC_HEAD(threads_and_exec, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Verify that multithreaded application on exec() will report "
            "LWP_EXIT events");
}

ATF_TC_BODY(threads_and_exec, tc)
{

        threads_and_exec();
}

/// ----------------------------------------------------------------------------

ATF_TC(suspend_no_deadlock);
ATF_TC_HEAD(suspend_no_deadlock, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Verify that the while the only thread within a process is "
            "suspended, the whole process cannot be unstopped");
}

ATF_TC_BODY(suspend_no_deadlock, tc)
{
        const int exitval = 5;
        const int sigval = SIGSTOP;
        pid_t child, wpid;
#if defined(TWAIT_HAVE_STATUS)
        int status;
#endif
        struct ptrace_siginfo psi;

        DPRINTF("Before forking process PID=%d\n", getpid());
        SYSCALL_REQUIRE((child = fork()) != -1);
        if (child == 0) {
                DPRINTF("Before calling PT_TRACE_ME from child %d\n", getpid());
                FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);

                DPRINTF("Before raising %s from child\n", strsignal(sigval));
                FORKEE_ASSERT(raise(sigval) == 0);

                DPRINTF("Before exiting of the child process\n");
                _exit(exitval);
        }
        DPRINTF("Parent process PID=%d, child's PID=%d\n", getpid(), child);

        DPRINTF("Before calling %s() for the child\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_stopped(status, sigval);

        DPRINTF("Before reading siginfo and lwpid_t\n");
        SYSCALL_REQUIRE(ptrace(PT_GET_SIGINFO, child, &psi, sizeof(psi)) != -1);

        DPRINTF("Before suspending LWP %d\n", psi.psi_lwpid);
        SYSCALL_REQUIRE(ptrace(PT_SUSPEND, child, NULL, psi.psi_lwpid) != -1);

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        ATF_REQUIRE_ERRNO(EDEADLK,
            ptrace(PT_CONTINUE, child, (void *)1, 0) == -1);

        DPRINTF("Before resuming LWP %d\n", psi.psi_lwpid);
        SYSCALL_REQUIRE(ptrace(PT_RESUME, child, NULL, psi.psi_lwpid) != -1);

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected exited\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_exited(status, exitval);

        DPRINTF("Before calling %s() for the child - expected no process\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0));
}

/// ----------------------------------------------------------------------------

static pthread_barrier_t barrier1_resume;
static pthread_barrier_t barrier2_resume;

static void *
resume_thread(void *arg)
{

        raise(SIGUSR1);

        pthread_barrier_wait(&barrier1_resume);

        /* Debugger will suspend the process here */

        pthread_barrier_wait(&barrier2_resume);

        raise(SIGUSR2);

        return infinite_thread(arg);
}

ATF_TC(resume);
ATF_TC_HEAD(resume, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Verify that a thread can be suspended by a debugger and later "
            "resumed by the debugger");
}

ATF_TC_BODY(resume, tc)
{
        const int sigval = SIGSTOP;
        pid_t child, wpid;
#if defined(TWAIT_HAVE_STATUS)
        int status;
#endif
        lwpid_t lid;
        struct ptrace_siginfo psi;
        pthread_t t;

        DPRINTF("Before forking process PID=%d\n", getpid());
        SYSCALL_REQUIRE((child = fork()) != -1);
        if (child == 0) {
                DPRINTF("Before calling PT_TRACE_ME from child %d\n", getpid());
                FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);

                pthread_barrier_init(&barrier1_resume, NULL, 2);
                pthread_barrier_init(&barrier2_resume, NULL, 2);

                DPRINTF("Before raising %s from child\n", strsignal(sigval));
                FORKEE_ASSERT(raise(sigval) == 0);

                DPRINTF("Before creating new thread in child\n");
                FORKEE_PTHREAD(pthread_create(&t, NULL, resume_thread, NULL));

                pthread_barrier_wait(&barrier1_resume);

                pthread_barrier_wait(&barrier2_resume);

                infinite_thread(NULL);
        }
        DPRINTF("Parent process PID=%d, child's PID=%d\n", getpid(), child);

        DPRINTF("Before calling %s() for the child\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_stopped(status, sigval);

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected stopped "
            "SIGUSR1\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_stopped(status, SIGUSR1);

        DPRINTF("Before reading siginfo and lwpid_t\n");
        SYSCALL_REQUIRE(ptrace(PT_GET_SIGINFO, child, &psi, sizeof(psi)) != -1);

        DPRINTF("Before suspending LWP %d\n", psi.psi_lwpid);
        SYSCALL_REQUIRE(ptrace(PT_SUSPEND, child, NULL, psi.psi_lwpid) != -1);

        lid = psi.psi_lwpid;

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        DPRINTF("Before suspending the parent for 1 second, we expect no signals\n");
        SYSCALL_REQUIRE(sleep(1) == 0);

#if defined(TWAIT_HAVE_OPTIONS)
        DPRINTF("Before calling %s() for the child - expected no status\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, WNOHANG), 0);
#endif

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_STOP, child, NULL, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected stopped "
            "SIGSTOP\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_stopped(status, SIGSTOP);

        DPRINTF("Before resuming LWP %d\n", lid);
        SYSCALL_REQUIRE(ptrace(PT_RESUME, child, NULL, lid) != -1);

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected stopped "
            "SIGUSR2\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_stopped(status, SIGUSR2);

        DPRINTF("Before resuming the child process where it left off and "
            "without signal to be sent\n");
        SYSCALL_REQUIRE(ptrace(PT_KILL, child, (void *)1, 0) != -1);

        DPRINTF("Before calling %s() for the child - expected exited\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_signaled(status, SIGKILL, 0);

        DPRINTF("Before calling %s() for the child - expected no process\n",
            TWAIT_FNAME);
        TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0));
}

/// ----------------------------------------------------------------------------

#if defined(TWAIT_HAVE_STATUS)

#define THREAD_CONCURRENT_BREAKPOINT_NUM 50
#define THREAD_CONCURRENT_SIGNALS_NUM 50
#define THREAD_CONCURRENT_WATCHPOINT_NUM 50

/* List of signals to use for the test */
const int thread_concurrent_signals_list[] = {
        SIGIO,
        SIGXCPU,
        SIGXFSZ,
        SIGVTALRM,
        SIGPROF,
        SIGWINCH,
        SIGINFO,
        SIGUSR1,
        SIGUSR2
};

enum thread_concurrent_signal_handling {
        /* the signal is discarded by debugger */
        TCSH_DISCARD,
        /* the handler is set to SIG_IGN */
        TCSH_SIG_IGN,
        /* an actual handler is used */
        TCSH_HANDLER
};

static pthread_barrier_t thread_concurrent_barrier;
static pthread_key_t thread_concurrent_key;
static uint32_t thread_concurrent_watchpoint_var = 0;

static void *
thread_concurrent_breakpoint_thread(void *arg)
{
        static volatile int watchme = 1;
        pthread_barrier_wait(&thread_concurrent_barrier);
        DPRINTF("Before entering breakpoint func from LWP %d\n", _lwp_self());
        check_happy(watchme);
        return NULL;
}

static void
thread_concurrent_sig_handler(int sig)
{
        void *tls_val = pthread_getspecific(thread_concurrent_key);
        DPRINTF("Before increment, LWP %d tls_val=%p\n", _lwp_self(), tls_val);
        FORKEE_PTHREAD(pthread_setspecific(thread_concurrent_key,
            (void*)((uintptr_t)tls_val + 1)));
}

static void *
thread_concurrent_signals_thread(void *arg)
{
        int sigval = thread_concurrent_signals_list[
            _lwp_self() % __arraycount(thread_concurrent_signals_list)];
        enum thread_concurrent_signal_handling *signal_handle = arg;
        void *tls_val;

        pthread_barrier_wait(&thread_concurrent_barrier);
        DPRINTF("Before raising %s from LWP %d\n", strsignal(sigval),
                _lwp_self());
        pthread_kill(pthread_self(), sigval);
        if (*signal_handle == TCSH_HANDLER) {
            tls_val = pthread_getspecific(thread_concurrent_key);
            DPRINTF("After raising, LWP %d tls_val=%p\n", _lwp_self(), tls_val);
            FORKEE_ASSERT(tls_val == (void*)1);
        }
        return NULL;
}

static void *
thread_concurrent_watchpoint_thread(void *arg)
{
        pthread_barrier_wait(&thread_concurrent_barrier);
        DPRINTF("Before modifying var from LWP %d\n", _lwp_self());
        thread_concurrent_watchpoint_var = 1;
        return NULL;
}

#if defined(__i386__) || defined(__x86_64__)
enum thread_concurrent_sigtrap_event {
        TCSE_UNKNOWN,
        TCSE_BREAKPOINT,
        TCSE_WATCHPOINT
};

static void
thread_concurrent_lwp_setup(pid_t child, lwpid_t lwpid);
static enum thread_concurrent_sigtrap_event
thread_concurrent_handle_sigtrap(pid_t child, ptrace_siginfo_t *info);
#endif

static void
thread_concurrent_test(enum thread_concurrent_signal_handling signal_handle,
    int breakpoint_threads, int signal_threads, int watchpoint_threads)
{
        const int exitval = 5;
        const int sigval = SIGSTOP;
        pid_t child, wpid;
        int status;
        struct lwp_event_count signal_counts[THREAD_CONCURRENT_SIGNALS_NUM]
            = {{0, 0}};
        struct lwp_event_count bp_counts[THREAD_CONCURRENT_BREAKPOINT_NUM]
            = {{0, 0}};
        struct lwp_event_count wp_counts[THREAD_CONCURRENT_BREAKPOINT_NUM]
            = {{0, 0}};
        ptrace_event_t event;
        int i;

#if defined(HAVE_DBREGS)
        if (!can_we_set_dbregs()) {
                atf_tc_skip("Either run this test as root or set sysctl(3) "
                            "security.models.extensions.user_set_dbregs to 1");
        }
#endif

        atf_tc_skip("PR kern/54960");

        /* Protect against out-of-bounds array access. */
        ATF_REQUIRE(breakpoint_threads <= THREAD_CONCURRENT_BREAKPOINT_NUM);
        ATF_REQUIRE(signal_threads <= THREAD_CONCURRENT_SIGNALS_NUM);
        ATF_REQUIRE(watchpoint_threads <= THREAD_CONCURRENT_WATCHPOINT_NUM);

        DPRINTF("Before forking process PID=%d\n", getpid());
        SYSCALL_REQUIRE((child = fork()) != -1);
        if (child == 0) {
                pthread_t bp_threads[THREAD_CONCURRENT_BREAKPOINT_NUM];
                pthread_t sig_threads[THREAD_CONCURRENT_SIGNALS_NUM];
                pthread_t wp_threads[THREAD_CONCURRENT_WATCHPOINT_NUM];

                DPRINTF("Before calling PT_TRACE_ME from child %d\n", getpid());
                FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);

                DPRINTF("Before raising %s from child\n", strsignal(sigval));
                FORKEE_ASSERT(raise(sigval) == 0);

                if (signal_handle != TCSH_DISCARD) {
                        struct sigaction sa;
                        unsigned int j;

                        memset(&sa, 0, sizeof(sa));
                        if (signal_handle == TCSH_SIG_IGN)
                                sa.sa_handler = SIG_IGN;
                        else
                                sa.sa_handler = thread_concurrent_sig_handler;
                        sigemptyset(&sa.sa_mask);

                        for (j = 0;
                            j < __arraycount(thread_concurrent_signals_list);
                            j++)
                                FORKEE_ASSERT(sigaction(
                                    thread_concurrent_signals_list[j], &sa, NULL)
                                    != -1);
                }

                DPRINTF("Before starting threads from the child\n");
                FORKEE_PTHREAD(pthread_barrier_init(
                    &thread_concurrent_barrier, NULL,
                    breakpoint_threads + signal_threads + watchpoint_threads));
                FORKEE_PTHREAD(pthread_key_create(&thread_concurrent_key,
                        NULL));

                for (i = 0; i < signal_threads; i++) {
                        FORKEE_PTHREAD(pthread_create(&sig_threads[i], NULL,
                            thread_concurrent_signals_thread,
                            &signal_handle));
                }
                for (i = 0; i < breakpoint_threads; i++) {
                        FORKEE_PTHREAD(pthread_create(&bp_threads[i], NULL,
                            thread_concurrent_breakpoint_thread, NULL));
                }
                for (i = 0; i < watchpoint_threads; i++) {
                        FORKEE_PTHREAD(pthread_create(&wp_threads[i], NULL,
                            thread_concurrent_watchpoint_thread, NULL));
                }

                DPRINTF("Before joining threads from the child\n");
                for (i = 0; i < watchpoint_threads; i++) {
                        FORKEE_PTHREAD(pthread_join(wp_threads[i], NULL));
                }
                for (i = 0; i < breakpoint_threads; i++) {
                        FORKEE_PTHREAD(pthread_join(bp_threads[i], NULL));
                }
                for (i = 0; i < signal_threads; i++) {
                        FORKEE_PTHREAD(pthread_join(sig_threads[i], NULL));
                }

                FORKEE_PTHREAD(pthread_key_delete(thread_concurrent_key));
                FORKEE_PTHREAD(pthread_barrier_destroy(
                    &thread_concurrent_barrier));

                DPRINTF("Before exiting of the child process\n");
                _exit(exitval);
        }
        DPRINTF("Parent process PID=%d, child's PID=%d\n", getpid(), child);

        DPRINTF("Before calling %s() for the child\n", TWAIT_FNAME);
        TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);

        validate_status_stopped(status, sigval);

        DPRINTF("Set LWP event mask for the child process\n");
        memset(&event, 0, sizeof(event));
        event.pe_set_event |= PTRACE_LWP_CREATE;
        SYSCALL_REQUIRE(ptrace(PT_SET_EVENT_MASK, child, &event, sizeof(event))
            != -1);

        DPRINTF("Before resuming the child process where it left off\n");
        SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);

        DPRINTF("Before entering signal collection loop\n");
        while (1) {
                ptrace_siginfo_t info;

                DPRINTF("Before calling %s() for the child\n", TWAIT_FNAME);
                TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0),
                    child);
                if (WIFEXITED(status))
                        break;
                /* Note: we use validate_status_stopped() to get nice error
                 * message.  Signal is irrelevant since it won't be reached.
                 */
                else if (!WIFSTOPPED(status))
                        validate_status_stopped(status, 0);

                DPRINTF("Before calling PT_GET_SIGINFO\n");
                SYSCALL_REQUIRE(ptrace(PT_GET_SIGINFO, child, &info,
                    sizeof(info)) != -1);

                DPRINTF("Received signal %d from LWP %d (wait: %d)\n",
                    info.psi_siginfo.si_signo, info.psi_lwpid,
                    WSTOPSIG(status));

                ATF_CHECK_EQ_MSG(info.psi_siginfo.si_signo, WSTOPSIG(status),
                    "lwp=%d, WSTOPSIG=%d, psi_siginfo=%d", info.psi_lwpid,
                    WSTOPSIG(status), info.psi_siginfo.si_signo);

                if (WSTOPSIG(status) != SIGTRAP) {
                        int expected_sig =
                            thread_concurrent_signals_list[info.psi_lwpid %
                            __arraycount(thread_concurrent_signals_list)];
                        ATF_CHECK_EQ_MSG(WSTOPSIG(status), expected_sig,
                                "lwp=%d, expected %d, got %d", info.psi_lwpid,
                                expected_sig, WSTOPSIG(status));

                        *FIND_EVENT_COUNT(signal_counts, info.psi_lwpid) += 1;
                } else if (info.psi_siginfo.si_code == TRAP_LWP) {
#if defined(__i386__) || defined(__x86_64__)
                        thread_concurrent_lwp_setup(child, info.psi_lwpid);
#endif
                } else {
#if defined(__i386__) || defined(__x86_64__)
                        switch (thread_concurrent_handle_sigtrap(child, &info)) {
                                case TCSE_UNKNOWN:
                                        /* already reported inside the function */
                                        break;
                                case TCSE_BREAKPOINT:
                                        *FIND_EVENT_COUNT(bp_counts,
                                            info.psi_lwpid) += 1;
                                        break;
                                case TCSE_WATCHPOINT:
                                        *FIND_EVENT_COUNT(wp_counts,
                                            info.psi_lwpid) += 1;
                                        break;
                        }
#else
                        ATF_CHECK_MSG(0, "Unexpected SIGTRAP, si_code=%d\n",
                            info.psi_siginfo.si_code);
#endif
                }

                DPRINTF("Before resuming the child process\n");
                SYSCALL_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1,
                     signal_handle != TCSH_DISCARD && WSTOPSIG(status) != SIGTRAP
                     ? WSTOPSIG(status) : 0) != -1);
        }

        for (i = 0; i < signal_threads; i++)
                ATF_CHECK_EQ_MSG(signal_counts[i].lec_count, 1,
                    "signal_counts[%d].lec_count=%d; lec_lwp=%d",
                    i, signal_counts[i].lec_count, signal_counts[i].lec_lwp);
        for (i = signal_threads; i < THREAD_CONCURRENT_SIGNALS_NUM; i++)
                ATF_CHECK_EQ_MSG(signal_counts[i].lec_count, 0,
                    "extraneous signal_counts[%d].lec_count=%d; lec_lwp=%d",
                    i, signal_counts[i].lec_count, signal_counts[i].lec_lwp);

        for (i = 0; i < breakpoint_threads; i++)
                ATF_CHECK_EQ_MSG(bp_counts[i].lec_count, 1,
                    "bp_counts[%d].lec_count=%d; lec_lwp=%d",
                    i, bp_counts[i].lec_count, bp_counts[i].lec_lwp);
        for (i = breakpoint_threads; i < THREAD_CONCURRENT_BREAKPOINT_NUM; i++)
                ATF_CHECK_EQ_MSG(bp_counts[i].lec_count, 0,
                    "extraneous bp_counts[%d].lec_count=%d; lec_lwp=%d",
                    i, bp_counts[i].lec_count, bp_counts[i].lec_lwp);

        for (i = 0; i < watchpoint_threads; i++)
                ATF_CHECK_EQ_MSG(wp_counts[i].lec_count, 1,
                    "wp_counts[%d].lec_count=%d; lec_lwp=%d",
                    i, wp_counts[i].lec_count, wp_counts[i].lec_lwp);
        for (i = watchpoint_threads; i < THREAD_CONCURRENT_WATCHPOINT_NUM; i++)
                ATF_CHECK_EQ_MSG(wp_counts[i].lec_count, 0,
                    "extraneous wp_counts[%d].lec_count=%d; lec_lwp=%d",
                    i, wp_counts[i].lec_count, wp_counts[i].lec_lwp);

        validate_status_exited(status, exitval);
}

#define THREAD_CONCURRENT_TEST(test, sig_hdl, bps, sigs, wps, descr)    \
ATF_TC(test);                                                           \
ATF_TC_HEAD(test, tc)                                                   \
{                                                                       \
        atf_tc_set_md_var(tc, "descr", descr);                          \
}                                                                       \
                                                                        \
ATF_TC_BODY(test, tc)                                                   \
{                                                                       \
        thread_concurrent_test(sig_hdl, bps, sigs, wps);                \
}

THREAD_CONCURRENT_TEST(thread_concurrent_signals, TCSH_DISCARD,
    0, THREAD_CONCURRENT_SIGNALS_NUM, 0,
    "Verify that concurrent signals issued to a single thread are reported "
    "correctly");
THREAD_CONCURRENT_TEST(thread_concurrent_signals_sig_ign, TCSH_SIG_IGN,
    0, THREAD_CONCURRENT_SIGNALS_NUM, 0,
    "Verify that concurrent signals issued to a single thread are reported "
    "correctly and passed back to SIG_IGN handler");
THREAD_CONCURRENT_TEST(thread_concurrent_signals_handler, TCSH_HANDLER,
    0, THREAD_CONCURRENT_SIGNALS_NUM, 0,
    "Verify that concurrent signals issued to a single thread are reported "
    "correctly and passed back to a handler function");

#if defined(__i386__) || defined(__x86_64__)
THREAD_CONCURRENT_TEST(thread_concurrent_breakpoints, TCSH_DISCARD,
    THREAD_CONCURRENT_BREAKPOINT_NUM, 0, 0,
    "Verify that concurrent breakpoints are reported correctly");
THREAD_CONCURRENT_TEST(thread_concurrent_watchpoints, TCSH_DISCARD,
    0, 0, THREAD_CONCURRENT_WATCHPOINT_NUM,
    "Verify that concurrent breakpoints are reported correctly");
THREAD_CONCURRENT_TEST(thread_concurrent_bp_wp, TCSH_DISCARD,
    THREAD_CONCURRENT_BREAKPOINT_NUM, 0, THREAD_CONCURRENT_WATCHPOINT_NUM,
    "Verify that concurrent breakpoints and watchpoints are reported "
    "correctly");

THREAD_CONCURRENT_TEST(thread_concurrent_bp_sig, TCSH_DISCARD,
    THREAD_CONCURRENT_BREAKPOINT_NUM, THREAD_CONCURRENT_SIGNALS_NUM, 0,
    "Verify that concurrent breakpoints and signals are reported correctly");
THREAD_CONCURRENT_TEST(thread_concurrent_bp_sig_sig_ign, TCSH_SIG_IGN,
    THREAD_CONCURRENT_BREAKPOINT_NUM, THREAD_CONCURRENT_SIGNALS_NUM, 0,
    "Verify that concurrent breakpoints and signals are reported correctly "
    "and passed back to SIG_IGN handler");
THREAD_CONCURRENT_TEST(thread_concurrent_bp_sig_handler, TCSH_HANDLER,
    THREAD_CONCURRENT_BREAKPOINT_NUM, THREAD_CONCURRENT_SIGNALS_NUM, 0,
    "Verify that concurrent breakpoints and signals are reported correctly "
    "and passed back to a handler function");

THREAD_CONCURRENT_TEST(thread_concurrent_wp_sig, TCSH_DISCARD,
    0, THREAD_CONCURRENT_SIGNALS_NUM, THREAD_CONCURRENT_WATCHPOINT_NUM,
    "Verify that concurrent watchpoints and signals are reported correctly");
THREAD_CONCURRENT_TEST(thread_concurrent_wp_sig_sig_ign, TCSH_SIG_IGN,
    0, THREAD_CONCURRENT_SIGNALS_NUM, THREAD_CONCURRENT_WATCHPOINT_NUM,
    "Verify that concurrent watchpoints and signals are reported correctly "
    "and passed back to SIG_IGN handler");
THREAD_CONCURRENT_TEST(thread_concurrent_wp_sig_handler, TCSH_HANDLER,
    0, THREAD_CONCURRENT_SIGNALS_NUM, THREAD_CONCURRENT_WATCHPOINT_NUM,
    "Verify that concurrent watchpoints and signals are reported correctly "
    "and passed back to a handler function");

THREAD_CONCURRENT_TEST(thread_concurrent_bp_wp_sig, TCSH_DISCARD,
    THREAD_CONCURRENT_BREAKPOINT_NUM, THREAD_CONCURRENT_SIGNALS_NUM,
    THREAD_CONCURRENT_WATCHPOINT_NUM,
    "Verify that concurrent breakpoints, watchpoints and signals are reported "
    "correctly");
THREAD_CONCURRENT_TEST(thread_concurrent_bp_wp_sig_sig_ign, TCSH_SIG_IGN,
    THREAD_CONCURRENT_BREAKPOINT_NUM, THREAD_CONCURRENT_SIGNALS_NUM,
    THREAD_CONCURRENT_WATCHPOINT_NUM,
    "Verify that concurrent breakpoints, watchpoints and signals are reported "
    "correctly and passed back to SIG_IGN handler");
THREAD_CONCURRENT_TEST(thread_concurrent_bp_wp_sig_handler, TCSH_HANDLER,
    THREAD_CONCURRENT_BREAKPOINT_NUM, THREAD_CONCURRENT_SIGNALS_NUM,
    THREAD_CONCURRENT_WATCHPOINT_NUM,
    "Verify that concurrent breakpoints, watchpoints and signals are reported "
    "correctly and passed back to a handler function");
#endif

#endif /*defined(TWAIT_HAVE_STATUS)*/

#define ATF_TP_ADD_TCS_PTRACE_WAIT_THREADS() \
        ATF_TP_ADD_TC(tp, trace_thread_nolwpevents); \
        ATF_TP_ADD_TC(tp, trace_thread_lwpexit); \
        ATF_TP_ADD_TC(tp, trace_thread_lwpcreate); \
        ATF_TP_ADD_TC(tp, trace_thread_lwpcreate_and_exit); \
        ATF_TP_ADD_TC(tp, trace_thread_lwpexit_masked_sigtrap); \
        ATF_TP_ADD_TC(tp, trace_thread_lwpcreate_masked_sigtrap); \
        ATF_TP_ADD_TC(tp, trace_thread_lwpcreate_and_exit_masked_sigtrap); \
        ATF_TP_ADD_TC(tp, threads_and_exec); \
        ATF_TP_ADD_TC(tp, suspend_no_deadlock); \
        ATF_TP_ADD_TC(tp, resume); \
        ATF_TP_ADD_TC_HAVE_STATUS(tp, thread_concurrent_signals); \
        ATF_TP_ADD_TC_HAVE_STATUS(tp, thread_concurrent_signals_sig_ign); \
        ATF_TP_ADD_TC_HAVE_STATUS(tp, thread_concurrent_signals_handler); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_breakpoints); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_watchpoints); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_bp_wp); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_bp_sig); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_bp_sig_sig_ign); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_bp_sig_handler); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_wp_sig); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_wp_sig_sig_ign); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_wp_sig_handler); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_bp_wp_sig); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_bp_wp_sig_sig_ign); \
        ATF_TP_ADD_TC_HAVE_STATUS_X86(tp, thread_concurrent_bp_wp_sig_handler);