/* $NetBSD: t_ptrace_wait.c,v 1.46 2016/12/08 13:32:17 kamil Exp $ */ /*- * Copyright (c) 2016 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. */ #include __RCSID("$NetBSD: t_ptrace_wait.c,v 1.46 2016/12/08 13:32:17 kamil Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ATF_REQUIRE(a) assert(a) #define ATF_REQUIRE_MSG(a,b,...) assert(a) #define ATF_REQUIRE_EQ(a,b) assert((a)==(b)) #define ATF_REQUIRE_EQ_MSG(a,b,c,...) assert((a)==(b)) #define ATF_REQUIRE_ERRNO(a,b) do{assert(errno == a); assert(b);}while(0) /* Detect plain wait(2) use-case */ #if !defined(TWAIT_WAITPID) && \ !defined(TWAIT_WAITID) && \ !defined(TWAIT_WAIT3) && \ !defined(TWAIT_WAIT4) && \ !defined(TWAIT_WAIT6) #define TWAIT_WAIT #endif /* * There are two classes of wait(2)-like functions: * - wait4(2)-like accepting pid_t, optional options parameter, struct rusage* * - wait6(2)-like accepting idtype_t, id_t, struct wrusage, mandatory options * * The TWAIT_FNAME value is to be used for convenience in debug messages. * * The TWAIT_GENERIC() macro is designed to reuse the same unmodified * code with as many wait(2)-like functions as possible. * * In a common use-case wait4(2) and wait6(2)-like function can work the almost * the same way, however there are few important differences: * wait6(2) must specify P_PID for idtype to match wpid from wait4(2). * To behave like wait4(2), wait6(2) the 'options' to wait must include * WEXITED|WTRUNCATED. * * There are two helper macros (they purpose it to mach more than one * wait(2)-like function): * The TWAIT_HAVE_STATUS - specifies whether a function can retrieve * status (as integer value). * The TWAIT_HAVE_PID - specifies whether a function can request * exact process identifier * The TWAIT_HAVE_RUSAGE - specifies whether a function can request * the struct rusage value * */ #if defined(TWAIT_WAIT) # define TWAIT_FNAME "wait" # define TWAIT_WAIT4TYPE(a,b,c,d) wait((b)) # define TWAIT_GENERIC(a,b,c) wait((b)) # define TWAIT_HAVE_STATUS 1 #elif defined(TWAIT_WAITPID) # define TWAIT_FNAME "waitpid" # define TWAIT_WAIT4TYPE(a,b,c,d) waitpid((a),(b),(c)) # define TWAIT_GENERIC(a,b,c) waitpid((a),(b),(c)) # define TWAIT_HAVE_PID 1 # define TWAIT_HAVE_STATUS 1 #elif defined(TWAIT_WAITID) # define TWAIT_FNAME "waitid" # define TWAIT_GENERIC(a,b,c) \ waitid(P_PID,(a),NULL,(c)|WEXITED|WTRAPPED) # define TWAIT_WAIT6TYPE(a,b,c,d,e,f) waitid((a),(b),(f),(d)) # define TWAIT_HAVE_PID 1 #elif defined(TWAIT_WAIT3) # define TWAIT_FNAME "wait3" # define TWAIT_WAIT4TYPE(a,b,c,d) wait3((b),(c),(d)) # define TWAIT_GENERIC(a,b,c) wait3((b),(c),NULL) # define TWAIT_HAVE_STATUS 1 # define TWAIT_HAVE_RUSAGE 1 #elif defined(TWAIT_WAIT4) # define TWAIT_FNAME "wait4" # define TWAIT_WAIT4TYPE(a,b,c,d) wait4((a),(b),(c),(d)) # define TWAIT_GENERIC(a,b,c) wait4((a),(b),(c),NULL) # define TWAIT_HAVE_PID 1 # define TWAIT_HAVE_STATUS 1 # define TWAIT_HAVE_RUSAGE 1 #elif defined(TWAIT_WAIT6) # define TWAIT_FNAME "wait6" # define TWAIT_WAIT6TYPE(a,b,c,d,e,f) wait6((a),(b),(c),(d),(e),(f)) # define TWAIT_GENERIC(a,b,c) \ wait6(P_PID,(a),(b),(c)|WEXITED|WTRAPPED,NULL,NULL) # define TWAIT_HAVE_PID 1 # define TWAIT_HAVE_STATUS 1 #endif /* * There are 3 groups of tests: * - TWAIT_GENERIC() (wait, wait2, waitpid, wait3, wait4, wait6) * - TWAIT_WAIT4TYPE() (wait2, waitpid, wait3, wait4) * - TWAIT_WAIT6TYPE() (waitid, wait6) * * Tests only in the above categories are allowed. However some tests are not * possible in the context requested functionality to be verified, therefore * there are helper macros: * - TWAIT_HAVE_PID (wait2, waitpid, waitid, wait4, wait6) * - TWAIT_HAVE_STATUS (wait, wait2, waitpid, wait3, wait4, wait6) * - TWAIT_HAVE_RUSAGE (wait3, wait4) * - TWAIT_HAVE_RETPID (wait, wait2, waitpid, wait3, wait4, wait6) * * If there is an intention to test e.g. wait6(2) specific features in the * ptrace(2) context, find the most matching group and with #ifdefs reduce * functionality of less featured than wait6(2) interface (TWAIT_WAIT6TYPE). * * For clarity never use negative preprocessor checks, like: * #if !defined(TWAIT_WAIT4) * always refer to checks for positive values. */ #define TEST_REQUIRE_EQ(x, y) \ do { \ uintmax_t vx = (x); \ uintmax_t vy = (y); \ int ret = vx == vy; \ if (!ret) \ ATF_REQUIRE_EQ_MSG(vx, vy, "%s(%ju) == %s(%ju)", \ #x, vx, #y, vy); \ } while (/*CONSTCOND*/0) /* * A child process cannot call atf functions and expect them to magically * work like in the parent. * The printf(3) messaging from a child will not work out of the box as well * without estabilishing a communication protocol with its parent. To not * overcomplicate the tests - do not log from a child and use err(3)/errx(3) * wrapped with FORKEE_ASSERT()/FORKEE_ASSERTX() as that is guaranteed to work. */ #define FORKEE_ASSERT_EQ(x, y) \ do { \ uintmax_t vx = (x); \ uintmax_t vy = (y); \ int ret = vx == vy; \ if (!ret) \ errx(EXIT_FAILURE, "%s:%d %s(): Assertion failed for: " \ "%s(%ju) == %s(%ju)", __FILE__, __LINE__, __func__, \ #x, vx, #y, vy); \ } while (/*CONSTCOND*/0) #define FORKEE_ASSERTX(x) \ do { \ int ret = (x); \ if (!ret) \ errx(EXIT_FAILURE, "%s:%d %s(): Assertion failed for: %s",\ __FILE__, __LINE__, __func__, #x); \ } while (/*CONSTCOND*/0) #define FORKEE_ASSERT(x) \ do { \ int ret = (x); \ if (!ret) \ err(EXIT_FAILURE, "%s:%d %s(): Assertion failed for: %s",\ __FILE__, __LINE__, __func__, #x); \ } while (/*CONSTCOND*/0) /* * Simplify logic for functions using general purpose registers add HAVE_GPREGS * * For platforms that do not implement all needed calls for simplicity assume * that they are unsupported at all. */ #if defined(PT_GETREGS) \ && defined(PT_SETREGS) \ && defined(PTRACE_REG_PC) \ && defined(PTRACE_REG_SET_PC) \ && defined(PTRACE_REG_SP) \ && defined(PTRACE_REG_INTRV) #define HAVE_GPREGS #endif /* Add guards for floating point registers */ #if defined(PT_GETFPREGS) \ && defined(PT_SETFPREGS) #define HAVE_FPREGS #endif /* Add guards for cpu debug registers */ #if defined(PT_GETDBREGS) \ && defined(PT_SETDBREGS) #define HAVE_DBREGS #endif /* * If waitid(2) returns because one or more processes have a state change to * report, 0 is returned. If an error is detected, a value of -1 is returned * and errno is set to indicate the error. If WNOHANG is specified and there * are no stopped, continued or exited children, 0 is returned. */ #if defined(TWAIT_WAITID) #define TWAIT_REQUIRE_SUCCESS(a,b) TEST_REQUIRE_EQ((a), 0) #define TWAIT_REQUIRE_FAILURE(a,b) ATF_REQUIRE_ERRNO((a),(b) == -1) #define FORKEE_REQUIRE_SUCCESS(a,b) FORKEE_ASSERT_EQ(a, 0) #define FORKEE_REQUIRE_FAILURE(a,b) \ FORKEE_ASSERTX(((a) == errno) && ((b) == -1)) #else #define TWAIT_REQUIRE_SUCCESS(a,b) TEST_REQUIRE_EQ((a), (b)) #define TWAIT_REQUIRE_FAILURE(a,b) ATF_REQUIRE_ERRNO((a),(b) == -1) #define FORKEE_REQUIRE_SUCCESS(a,b) FORKEE_ASSERT_EQ(a, b) #define FORKEE_REQUIRE_FAILURE(a,b) \ FORKEE_ASSERTX(((a) == errno) && ((b) == -1)) #endif /* * Helper tools to verify whether status reports exited value */ #if TWAIT_HAVE_STATUS static void __used validate_status_exited(int status, int expected) { ATF_REQUIRE_MSG(WIFEXITED(status), "Reported !exited process"); ATF_REQUIRE_MSG(!WIFCONTINUED(status), "Reported continued process"); ATF_REQUIRE_MSG(!WIFSIGNALED(status), "Reported signaled process"); ATF_REQUIRE_MSG(!WIFSTOPPED(status), "Reported stopped process"); ATF_REQUIRE_EQ_MSG(WEXITSTATUS(status), expected, "The process has exited with invalid value %d != %d", WEXITSTATUS(status), expected); } static void __used forkee_status_exited(int status, int expected) { FORKEE_ASSERTX(WIFEXITED(status)); FORKEE_ASSERTX(!WIFCONTINUED(status)); FORKEE_ASSERTX(!WIFSIGNALED(status)); FORKEE_ASSERTX(!WIFSTOPPED(status)); FORKEE_ASSERT_EQ(WEXITSTATUS(status), expected); } static void __used validate_status_continued(int status) { ATF_REQUIRE_MSG(!WIFEXITED(status), "Reported exited process"); ATF_REQUIRE_MSG(WIFCONTINUED(status), "Reported !continued process"); ATF_REQUIRE_MSG(!WIFSIGNALED(status), "Reported signaled process"); ATF_REQUIRE_MSG(!WIFSTOPPED(status), "Reported stopped process"); } static void __used forkee_status_continued(int status) { FORKEE_ASSERTX(!WIFEXITED(status)); FORKEE_ASSERTX(WIFCONTINUED(status)); FORKEE_ASSERTX(!WIFSIGNALED(status)); FORKEE_ASSERTX(!WIFSTOPPED(status)); } static void __used validate_status_signaled(int status, int expected_termsig, int expected_core) { ATF_REQUIRE_MSG(!WIFEXITED(status), "Reported exited process"); ATF_REQUIRE_MSG(!WIFCONTINUED(status), "Reported continued process"); ATF_REQUIRE_MSG(WIFSIGNALED(status), "Reported !signaled process"); ATF_REQUIRE_MSG(!WIFSTOPPED(status), "Reported stopped process"); ATF_REQUIRE_EQ_MSG(WTERMSIG(status), expected_termsig, "Unexpected signal received"); ATF_REQUIRE_EQ_MSG(WCOREDUMP(status), expected_core, "Unexpectedly core file %s generated", expected_core ? "not" : ""); } static void __used forkee_status_signaled(int status, int expected_termsig, int expected_core) { FORKEE_ASSERTX(!WIFEXITED(status)); FORKEE_ASSERTX(!WIFCONTINUED(status)); FORKEE_ASSERTX(WIFSIGNALED(status)); FORKEE_ASSERTX(!WIFSTOPPED(status)); FORKEE_ASSERT_EQ(WTERMSIG(status), expected_termsig); FORKEE_ASSERT_EQ(WCOREDUMP(status), expected_core); } static void __used validate_status_stopped(int status, int expected) { ATF_REQUIRE_MSG(!WIFEXITED(status), "Reported exited process"); ATF_REQUIRE_MSG(!WIFCONTINUED(status), "Reported continued process"); ATF_REQUIRE_MSG(!WIFSIGNALED(status), "Reported signaled process"); ATF_REQUIRE_MSG(WIFSTOPPED(status), "Reported !stopped process"); char st[128], ex[128]; strlcpy(st, strsignal(WSTOPSIG(status)), sizeof(st)); strlcpy(ex, strsignal(expected), sizeof(ex)); ATF_REQUIRE_EQ_MSG(WSTOPSIG(status), expected, "Unexpected stop signal received [%s] != [%s]", st, ex); } static void __used forkee_status_stopped(int status, int expected) { FORKEE_ASSERTX(!WIFEXITED(status)); FORKEE_ASSERTX(!WIFCONTINUED(status)); FORKEE_ASSERTX(!WIFSIGNALED(status)); FORKEE_ASSERTX(WIFSTOPPED(status)); FORKEE_ASSERT_EQ(WSTOPSIG(status), expected); } #else #define validate_status_exited(a,b) #define forkee_status_exited(a,b) #define validate_status_continued(a,b) #define forkee_status_continued(a,b) #define validate_status_signaled(a,b,c) #define forkee_status_signaled(a,b,c) #define validate_status_stopped(a,b) #define forkee_status_stopped(a,b) #endif /* This function is currently designed to be run in the main/parent process */ static void __used await_zombie(pid_t process) { struct kinfo_proc2 p; size_t len = sizeof(p); const int name[] = { [0] = CTL_KERN, [1] = KERN_PROC2, [2] = KERN_PROC_PID, [3] = process, [4] = sizeof(p), [5] = 1 }; const size_t namelen = __arraycount(name); /* Await the process becoming a zombie */ while(1) { ATF_REQUIRE(sysctl(name, namelen, &p, &len, NULL, 0) == 0); if (p.p_stat == LSZOMB) break; ATF_REQUIRE(usleep(1000) == 0); } } /* Happy number sequence -- this function is used to just consume cpu cycles */ #define HAPPY_NUMBER 1 /* If n is not happy then its sequence ends in the cycle: * 4, 16, 37, 58, 89, 145, 42, 20, 4, ... */ #define SAD_NUMBER 4 /* Calculate the sum of the squares of the digits of n */ static unsigned __used dsum(unsigned n) { unsigned sum, x; for (sum = 0; n; n /= 10) { x = n % 10; sum += x * x; } return sum; } static int __used check_happy(unsigned n) { for (;;) { unsigned total = dsum(n); if (total == HAPPY_NUMBER) return 1; if (total == SAD_NUMBER) return 0; n = total; } } #if defined(TWAIT_HAVE_PID) #define ATF_TP_ADD_TC_HAVE_PID(a,b) ATF_TP_ADD_TC(a,b) #else #define ATF_TP_ADD_TC_HAVE_PID(a,b) #endif #if defined(HAVE_GPREGS) #define ATF_TP_ADD_TC_HAVE_GPREGS(a,b) ATF_TP_ADD_TC(a,b) #else #define ATF_TP_ADD_TC_HAVE_GPREGS(a,b) #endif #if defined(HAVE_FPREGS) #define ATF_TP_ADD_TC_HAVE_FPREGS(a,b) ATF_TP_ADD_TC(a,b) #else #define ATF_TP_ADD_TC_HAVE_FPREGS(a,b) #endif #if defined(HAVE_DBREGS) #define ATF_TP_ADD_TC_HAVE_DBREGS(a,b) ATF_TP_ADD_TC(a,b) #else #define ATF_TP_ADD_TC_HAVE_DBREGS(a,b) #endif #if defined(PT_STEP) #define ATF_TP_ADD_TC_PT_STEP(a,b) ATF_TP_ADD_TC(a,b) #else #define ATF_TP_ADD_TC_PT_STEP(a,b) #endif struct msg_fds { int pfd[2]; int cfd[2]; }; #define CLOSEFD(fd) do { \ if (fd != -1) { \ close(fd); \ fd = -1; \ } \ } while (/*CONSTCOND*/ 0) static int msg_open(struct msg_fds *fds) { if (pipe(fds->pfd) == -1) return -1; if (pipe(fds->cfd) == -1) { close(fds->pfd[0]); close(fds->pfd[1]); return -1; } return 0; } static void msg_close(struct msg_fds *fds) { CLOSEFD(fds->pfd[0]); CLOSEFD(fds->pfd[1]); CLOSEFD(fds->cfd[0]); CLOSEFD(fds->cfd[1]); } static int msg_write_child(const char *info, struct msg_fds *fds, void *msg, size_t len) { ssize_t rv; CLOSEFD(fds->cfd[1]); CLOSEFD(fds->pfd[0]); printf("Send %s\n", info); rv = write(fds->pfd[1], msg, len); if (rv != (ssize_t)len) return 1; // printf("Wait %s\n", info); rv = read(fds->cfd[0], msg, len); if (rv != (ssize_t)len) return 1; return 0; } static int msg_write_parent(const char *info, struct msg_fds *fds, void *msg, size_t len) { ssize_t rv; CLOSEFD(fds->pfd[1]); CLOSEFD(fds->cfd[0]); printf("Send %s\n", info); rv = write(fds->cfd[1], msg, len); if (rv != (ssize_t)len) return 1; // printf("Wait %s\n", info); rv = read(fds->pfd[0], msg, len); if (rv != (ssize_t)len) return 1; return 0; } static int msg_read_parent(const char *info, struct msg_fds *fds, void *msg, size_t len) { ssize_t rv; CLOSEFD(fds->pfd[1]); CLOSEFD(fds->cfd[0]); printf("Wait %s\n", info); rv = read(fds->pfd[0], msg, len); if (rv != (ssize_t)len) return 1; // printf("Send %s\n", info); rv = write(fds->cfd[1], msg, len); if (rv != (ssize_t)len) return 1; return 0; } static int msg_read_child(const char *info, struct msg_fds *fds, void *msg, size_t len) { ssize_t rv; CLOSEFD(fds->cfd[1]); CLOSEFD(fds->pfd[0]); printf("Wait %s\n", info); rv = read(fds->cfd[0], msg, len); if (rv != (ssize_t)len) return 1; // printf("Send %s\n", info); rv = write(fds->pfd[1], msg, len); if (rv != (ssize_t)len) return 1; return 0; } #define PARENT_TO_CHILD(info, fds, msg) \ ATF_REQUIRE(msg_write_child(info " to child " # fds, &fds, &msg, sizeof(msg)) == 0) #define CHILD_FROM_PARENT(info, fds, msg) \ FORKEE_ASSERT(msg_read_parent(info " from parent " # fds, &fds, &msg, sizeof(msg)) == 0) #define CHILD_TO_PARENT(info, fds, msg) \ FORKEE_ASSERT(msg_write_parent(info " to parent " # fds, &fds, &msg, sizeof(msg)) == 0) #define PARENT_FROM_CHILD(info, fds, msg) \ ATF_REQUIRE(msg_read_child(info " from parent " # fds, &fds, &msg, sizeof(msg)) == 0) int fun(int i) { return i - 2; } int main(int argc, char **argv) { const int exitval = 5; const int sigval = SIGSTOP; pid_t child, wpid; #if defined(TWAIT_HAVE_STATUS) int status; #endif int watchme = 0; int rv; int i; int status7; struct ptrace_watchpoint pw; printf("Before forking process PID=%d\n", getpid()); ATF_REQUIRE((child = fork()) != -1); if (child == 0) { printf("Before calling PT_TRACE_ME from child %d\n", getpid()); FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1); printf("Before raising %s from child\n", strsignal(sigval)); FORKEE_ASSERT(raise(sigval) == 0); printf("Before changing watchme\n"); ++watchme; printf("After changing watchme\n"); fun(watchme); printf("watchme=%d\n", watchme); printf("Before exiting of the child process\n"); _exit(exitval); } printf("Parent process PID=%d, child's PID=%d\n", getpid(), child); printf("Before calling %s() for the child\n", TWAIT_FNAME); TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); validate_status_stopped(status, sigval); rv = ptrace(PT_COUNT_WATCHPOINTS, child, NULL, 0); printf("PT_COUNT_WATCHPOINTS rv=%d\n", rv); for (i = 0; i < rv; i++) { memset(&pw, 0xab, sizeof(pw)); pw.pw_index = i; errno = 0; status7 = ptrace(PT_READ_WATCHPOINT, child, &pw, sizeof(pw)); printf("GET i=%d status7=%d errno=%d pw { .pw_index=%d .pw_lwpid=%d .pw_md.md_address=%p .pw_md.md_condition=%d .pw_md.md_length=%d}\n", i, status7, errno, pw.pw_index, pw.pw_lwpid, pw.pw_md.md_address, pw.pw_md.md_condition, pw.pw_md.md_length); } pw.pw_index = 0; pw.pw_lwpid = 0; pw.pw_md.md_address = (void*)fun; pw.pw_md.md_condition = X86_HW_WATCHPOINT_DR7_CONDITION_EXECUTION; pw.pw_md.md_length = X86_HW_WATCHPOINT_DR7_LENGTH_BYTE; errno = 0; status7 = ptrace(PT_WRITE_WATCHPOINT, child, &pw, sizeof(pw)); printf("SET i=0 status7=%d errno=%d pw { .pw_index=%d .pw_lwpid=%d .pw_md.md_address=%p .pw_md.md_condition=%d .pw_md.md_length=%d}\n", status7, errno, pw.pw_index, pw.pw_lwpid, pw.pw_md.md_address, pw.pw_md.md_condition, pw.pw_md.md_length); for (i = 0; i < rv; i++) { memset(&pw, 0xab, sizeof(pw)); pw.pw_index = i; errno = 0; status7 = ptrace(PT_READ_WATCHPOINT, child, &pw, sizeof(pw)); printf("GET i=%d status7=%d errno=%d pw { .pw_index=%d .pw_lwpid=%d .pw_md.md_address=%p .pw_md.md_condition=%d .pw_md.md_length=%d}\n", i, status7, errno, pw.pw_index, pw.pw_lwpid, pw.pw_md.md_address, pw.pw_md.md_condition, pw.pw_md.md_length); } printf("Before resuming the child process where it left off and " "without signal to be sent\n"); ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); printf("Before calling %s() for the child\n", TWAIT_FNAME); TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); validate_status_stopped(status, SIGTRAP); pw.pw_index = 0; pw.pw_lwpid = 0; pw.pw_md.md_address = NULL; pw.pw_md.md_condition = 0x3; pw.pw_md.md_length = 0x3; errno = 0; status7 = ptrace(PT_WRITE_WATCHPOINT, child, &pw, sizeof(pw)); printf("SET i=0 status7=%d errno=%d pw { .pw_index=%d .pw_lwpid=%d .pw_md.md_address=%p .pw_md.md_condition=%d .pw_md.md_length=%d}\n", status7, errno, pw.pw_index, pw.pw_lwpid, pw.pw_md.md_address, pw.pw_md.md_condition, pw.pw_md.md_length); for (i = 0; i < rv; i++) { memset(&pw, 0xab, sizeof(pw)); pw.pw_index = i; errno = 0; status7 = ptrace(PT_READ_WATCHPOINT, child, &pw, sizeof(pw)); printf("GET i=%d status7=%d errno=%d pw { .pw_index=%d .pw_lwpid=%d .pw_md.md_address=%p .pw_md.md_condition=%d .pw_md.md_length=%d}\n", i, status7, errno, pw.pw_index, pw.pw_lwpid, pw.pw_md.md_address, pw.pw_md.md_condition, pw.pw_md.md_length); } printf("Before resuming the child process where it left off and " "without signal to be sent\n"); ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); printf("Before calling %s() for the child\n", TWAIT_FNAME); TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); validate_status_exited(status, exitval); printf("Before calling %s() for the child\n", TWAIT_FNAME); wpid = TWAIT_GENERIC(child, &status, 0); printf("wpid=%d, errno=%d\n", wpid, errno); assert(wpid == -1); assert(errno = ECHILD); printf("passed!\n"); return 0; }