diff --git a/lib/libc/sys/ptrace.2 b/lib/libc/sys/ptrace.2 --- a/lib/libc/sys/ptrace.2 +++ b/lib/libc/sys/ptrace.2 @@ -2,7 +2,7 @@ .\" $NetBSD: ptrace.2,v 1.2 1995/02/27 12:35:37 cgd Exp $ .\" .\" This file is in the public domain. -.Dd January 22, 2022 +.Dd January 27, 2022 .Dt PTRACE 2 .Os .Sh NAME @@ -511,6 +511,40 @@ .In machine/reg.h ) pointed to by .Fa addr . +.It Dv PT_GETREGSET +This request reads the registers from the traced process. +The +.Fa data +argument specifies the register set to read, with the +.Fa addr +argument pointing at a +.Vt "struct iovec" +where the +.Va iov_base +field points to a register set specific structure to hold the registers, +and the +.Va iov_len +field holds the length of the structure. +.It Dv PT_SETREGSET +This request writes to the registers of the traced process. +The +.Fa data +argument specifies the register set to write to, with the +.Fa addr +argument pointing at a +.Vt "struct iovec" +where the +.Va iov_base +field points to a register set specific structure to hold the registers, +and the +.Va iov_len +field holds the length of the structure. +If +.Va iov_base +is NULL the kernel will return the expected length of the register set +specific structure in the +.Va iov_len +field and not change the target register set. .It Dv PT_LWPINFO This request can be used to obtain information about the kernel thread, also known as light-weight process, that caused the traced process to stop. diff --git a/sys/amd64/amd64/elf_machdep.c b/sys/amd64/amd64/elf_machdep.c --- a/sys/amd64/amd64/elf_machdep.c +++ b/sys/amd64/amd64/elf_machdep.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -95,6 +96,8 @@ .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, .sv_set_fork_retval = x86_set_fork_retval, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; struct sysentvec elf64_freebsd_sysvec_la57 = { @@ -137,6 +140,8 @@ .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, .sv_set_fork_retval= x86_set_fork_retval, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; static void diff --git a/sys/arm/arm/elf_machdep.c b/sys/arm/arm/elf_machdep.c --- a/sys/arm/arm/elf_machdep.c +++ b/sys/arm/arm/elf_machdep.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +104,8 @@ .sv_hwcap2 = &elf_hwcap2, .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; INIT_SYSENTVEC(elf32_sysvec, &elf32_freebsd_sysvec); diff --git a/sys/arm64/arm64/elf32_machdep.c b/sys/arm64/arm64/elf32_machdep.c --- a/sys/arm64/arm64/elf32_machdep.c +++ b/sys/arm64/arm64/elf32_machdep.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,8 @@ .sv_hwcap2 = &elf32_hwcap2, .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; INIT_SYSENTVEC(elf32_sysvec, &elf32_freebsd_sysvec); diff --git a/sys/arm64/arm64/elf_machdep.c b/sys/arm64/arm64/elf_machdep.c --- a/sys/arm64/arm64/elf_machdep.c +++ b/sys/arm64/arm64/elf_machdep.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,8 @@ .sv_hwcap2 = &elf_hwcap2, .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; INIT_SYSENTVEC(elf64_sysvec, &elf64_freebsd_sysvec); diff --git a/sys/compat/freebsd32/freebsd32_misc.c b/sys/compat/freebsd32/freebsd32_misc.c --- a/sys/compat/freebsd32/freebsd32_misc.c +++ b/sys/compat/freebsd32/freebsd32_misc.c @@ -964,6 +964,7 @@ struct dbreg32 dbreg; struct fpreg32 fpreg; struct reg32 reg; + struct iovec vec; register_t args[nitems(td->td_sa.args)]; struct ptrace_sc_ret psr; int ptevents; @@ -975,6 +976,7 @@ struct ptrace_coredump32 pc; uint32_t args[nitems(td->td_sa.args)]; struct ptrace_sc_ret32 psr; + struct iovec32 vec; } r32; void *addr; int data, error, i; @@ -1024,6 +1026,22 @@ case PT_SETDBREGS: error = copyin(uap->addr, &r.dbreg, sizeof(r.dbreg)); break; + case PT_SETREGSET: + error = copyin(uap->addr, &r32.vec, sizeof(r32.vec)); + if (error != 0) + break; + + r.vec.iov_len = r32.vec.iov_len; + r.vec.iov_base = PTRIN(r32.vec.iov_base); + break; + case PT_GETREGSET: + error = copyin(uap->addr, &r32.vec, sizeof(r32.vec)); + if (error != 0) + break; + + r.vec.iov_len = r32.vec.iov_len; + r.vec.iov_base = PTRIN(r32.vec.iov_base); + break; case PT_SET_EVENT_MASK: if (uap->data != sizeof(r.ptevents)) error = EINVAL; @@ -1102,6 +1120,10 @@ case PT_GETDBREGS: error = copyout(&r.dbreg, uap->addr, sizeof(r.dbreg)); break; + case PT_GETREGSET: + r32.vec.iov_len = r.vec.iov_len; + error = copyout(&r32.vec, uap->addr, sizeof(r32.vec)); + break; case PT_GET_EVENT_MASK: /* NB: The size in uap->data is validated in kern_ptrace(). */ error = copyout(&r.ptevents, uap->addr, uap->data); diff --git a/sys/compat/ia32/ia32_sysvec.c b/sys/compat/ia32/ia32_sysvec.c --- a/sys/compat/ia32/ia32_sysvec.c +++ b/sys/compat/ia32/ia32_sysvec.c @@ -33,6 +33,7 @@ #define __ELF_WORD_SIZE 32 #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -141,6 +143,8 @@ .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, .sv_set_fork_retval = x86_set_fork_retval, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; INIT_SYSENTVEC(elf_ia32_sysvec, &ia32_freebsd_sysvec); diff --git a/sys/i386/i386/elf_machdep.c b/sys/i386/i386/elf_machdep.c --- a/sys/i386/i386/elf_machdep.c +++ b/sys/i386/i386/elf_machdep.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,8 @@ .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, .sv_set_fork_retval = x86_set_fork_retval, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; INIT_SYSENTVEC(elf32_sysvec, &elf32_freebsd_sysvec); diff --git a/sys/kern/imgact_elf.c b/sys/kern/imgact_elf.c --- a/sys/kern/imgact_elf.c +++ b/sys/kern/imgact_elf.c @@ -2186,16 +2186,16 @@ *sizep = sizeof(*psinfo); } -static void -__elfN(note_prstatus)(void *arg, struct sbuf *sb, size_t *sizep) +static bool +__elfN(get_prstatus)(struct regset *rs, struct thread *td, void *buf, + size_t *sizep) { - struct thread *td; elf_prstatus_t *status; - td = arg; - if (sb != NULL) { - KASSERT(*sizep == sizeof(*status), ("invalid size")); - status = malloc(sizeof(*status), M_TEMP, M_ZERO | M_WAITOK); + if (buf != NULL) { + KASSERT(*sizep == sizeof(*status), ("%s: invalid size", + __func__)); + status = buf; status->pr_version = PRSTATUS_VERSION; status->pr_statussz = sizeof(elf_prstatus_t); status->pr_gregsetsz = sizeof(elf_gregset_t); @@ -2208,12 +2208,97 @@ #else fill_regs(td, &status->pr_reg); #endif + } + *sizep = sizeof(*status); + return (true); +} + +static bool +__elfN(set_prstatus)(struct regset *rs, struct thread *td, void *buf, + size_t size) +{ + elf_prstatus_t *status; + + KASSERT(size == sizeof(*status), ("%s: invalid size", __func__)); + status = buf; +#if defined(COMPAT_FREEBSD32) && __ELF_WORD_SIZE == 32 + set_regs32(td, &status->pr_reg); +#else + set_regs(td, &status->pr_reg); +#endif + return (true); +} + +static struct regset __elfN(regset_prstatus) = { + .note = NT_PRSTATUS, + .size = sizeof(elf_prstatus_t), + .get = __elfN(get_prstatus), + .set = __elfN(set_prstatus), +}; +ELF_REGSET(__elfN(regset_prstatus)); + +static void +__elfN(note_prstatus)(void *arg, struct sbuf *sb, size_t *sizep) +{ + struct thread *td; + elf_prstatus_t *status; + + td = arg; + if (sb != NULL) { + KASSERT(*sizep == sizeof(*status), ("%s: invalid size", + __func__)); + status = malloc(sizeof(*status), M_TEMP, M_ZERO | M_WAITOK); + __elfN(get_prstatus)(NULL, td, status, sizep); sbuf_bcat(sb, status, sizeof(*status)); free(status, M_TEMP); } *sizep = sizeof(*status); } +static bool +__elfN(get_fpregset)(struct regset *rs, struct thread *td, void *buf, + size_t *sizep) +{ + elf_prfpregset_t *fpregset; + + if (buf != NULL) { + KASSERT(*sizep == sizeof(*fpregset), ("%s: invalid size", + __func__)); + fpregset = buf; +#if defined(COMPAT_FREEBSD32) && __ELF_WORD_SIZE == 32 + fill_fpregs32(td, fpregset); +#else + fill_fpregs(td, fpregset); +#endif + } + *sizep = sizeof(fpregset); + return (true); +} + +static bool +__elfN(set_fpregset)(struct regset *rs, struct thread *td, void *buf, + size_t size) +{ + elf_prfpregset_t *fpregset; + + fpregset = buf; + KASSERT(size == sizeof(*fpregset), ("%s: invalid size", __func__)); +#if defined(COMPAT_FREEBSD32) && __ELF_WORD_SIZE == 32 + set_fpregs32(td, fpregset); +#else + set_fpregs(td, fpregset); +#endif + return (true); +} + +static struct regset __elfN(regset_fpregset) = { + .note = NT_FPREGSET, + .size = sizeof(elf_prfpregset_t), + .get = __elfN(get_fpregset), + .set = __elfN(set_fpregset), +}; +ELF_REGSET(__elfN(regset_fpregset)); + static void __elfN(note_fpregset)(void *arg, struct sbuf *sb, size_t *sizep) { @@ -2224,11 +2309,7 @@ if (sb != NULL) { KASSERT(*sizep == sizeof(*fpregset), ("invalid size")); fpregset = malloc(sizeof(*fpregset), M_TEMP, M_ZERO | M_WAITOK); -#if defined(COMPAT_FREEBSD32) && __ELF_WORD_SIZE == 32 - fill_fpregs32(td, fpregset); -#else - fill_fpregs(td, fpregset); -#endif + __elfN(get_fpregset)(NULL, td, fpregset, sizep); sbuf_bcat(sb, fpregset, sizeof(*fpregset)); free(fpregset, M_TEMP); } diff --git a/sys/kern/init_main.c b/sys/kern/init_main.c --- a/sys/kern/init_main.c +++ b/sys/kern/init_main.c @@ -438,6 +438,8 @@ .sv_thread_detach = NULL, .sv_trap = NULL, .sv_set_fork_retval = null_set_fork_retval, + .sv_regset_begin = NULL, + .sv_regset_end = NULL, }; /* diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c --- a/sys/kern/sys_process.c +++ b/sys/kern/sys_process.c @@ -70,6 +70,9 @@ #include #endif +/* Assert it's safe to unlock a process, e.g. to allocate working memory */ +#define PROC_ASSERT_TRACEREQ(p) MPASS(((p)->p_flag2 & P2_PTRACEREQ) != 0) + /* * Functions implemented using PROC_ACTION(): * @@ -153,6 +156,125 @@ PROC_ACTION(set_fpregs(td, fpregs)); } +static struct regset * +proc_find_regset(struct thread *td, int note) +{ + struct regset **regsetp, **regset_end, *regset; + struct sysentvec *sv; + + sv = td->td_proc->p_sysent; + regsetp = sv->sv_regset_begin; + if (regsetp == NULL) + return (NULL); + regset_end = sv->sv_regset_end; + MPASS(regset_end != NULL); + for (; regsetp < regset_end; regsetp++) { + regset = *regsetp; + if (regset->note != note) + continue; + + return (regset); + } + + return (NULL); +} + +static int +proc_read_regset(struct thread *td, int note, struct iovec *iov) +{ + struct regset *regset; + struct proc *p; + void *buf; + size_t size; + int error; + + regset = proc_find_regset(td, note); + if (regset == NULL) + return (EINVAL); + + if (iov->iov_base == NULL) { + iov->iov_len = regset->size; + if (iov->iov_len == 0) + return (EINVAL); + + return (0); + } + + /* The length is wrong, return an error */ + if (iov->iov_len != regset->size) + return (EINVAL); + + if (regset->get == NULL) + return (EINVAL); + + error = 0; + size = regset->size; + p = td->td_proc; + + /* Drop the proc lock while allocating the temp buffer */ + PROC_ASSERT_TRACEREQ(p); + PROC_UNLOCK(p); + buf = malloc(size, M_TEMP, M_WAITOK); + PROC_LOCK(p); + + if (!regset->get(regset, td, buf, &size)) { + error = EINVAL; + } else { + KASSERT(size == regset->size, + ("%s: Getter function changed the size", __func__)); + + iov->iov_len = size; + PROC_UNLOCK(p); + error = copyout(buf, iov->iov_base, size); + PROC_LOCK(p); + } + + free(buf, M_TEMP); + + return (error); +} + +static int +proc_write_regset(struct thread *td, int note, struct iovec *iov) +{ + struct regset *regset; + struct proc *p; + void *buf; + size_t size; + int error; + + regset = proc_find_regset(td, note); + if (regset == NULL) + return (EINVAL); + + /* The length is wrong, return an error */ + if (iov->iov_len != regset->size) + return (EINVAL); + + if (regset->set == NULL) + return (EINVAL); + + size = regset->size; + p = td->td_proc; + + /* Drop the proc lock while allocating the temp buffer */ + PROC_ASSERT_TRACEREQ(p); + PROC_UNLOCK(p); + buf = malloc(size, M_TEMP, M_WAITOK); + error = copyin(iov->iov_base, buf, size); + PROC_LOCK(p); + + if (error == 0) { + if (!regset->set(regset, td, buf, size)) { + error = EINVAL; + } + } + + free(buf, M_TEMP); + + return (error); +} + #ifdef COMPAT_FREEBSD32 /* For 32 bit binaries, we need to expose the 32 bit regs layouts. */ int @@ -474,6 +596,7 @@ struct dbreg dbreg; struct fpreg fpreg; struct reg reg; + struct iovec vec; char args[sizeof(td->td_sa.args)]; struct ptrace_sc_ret psr; int ptevents; @@ -504,6 +627,12 @@ case PT_GETDBREGS: bzero(&r.dbreg, sizeof(r.dbreg)); break; + case PT_SETREGSET: + error = copyin(uap->addr, &r.vec, sizeof(r.vec)); + break; + case PT_GETREGSET: + error = copyin(uap->addr, &r.vec, sizeof(r.vec)); + break; case PT_SETREGS: error = copyin(uap->addr, &r.reg, sizeof(r.reg)); break; @@ -558,6 +687,9 @@ case PT_GETDBREGS: error = copyout(&r.dbreg, uap->addr, sizeof(r.dbreg)); break; + case PT_GETREGSET: + error = copyout(&r.vec, uap->addr, sizeof(r.vec)); + break; case PT_GET_EVENT_MASK: /* NB: The size in uap->data is validated in kern_ptrace(). */ error = copyout(&r.ptevents, uap->addr, uap->data); @@ -1291,6 +1423,18 @@ error = PROC_READ(dbregs, td2, addr); break; + case PT_SETREGSET: + CTR2(KTR_PTRACE, "PT_SETREGSET: tid %d (pid %d)", td2->td_tid, + p->p_pid); + error = proc_write_regset(td2, data, addr); + break; + + case PT_GETREGSET: + CTR2(KTR_PTRACE, "PT_GETREGSET: tid %d (pid %d)", td2->td_tid, + p->p_pid); + error = proc_read_regset(td2, data, addr); + break; + case PT_LWPINFO: if (data <= 0 || data > sizeof(*pl)) { error = EINVAL; diff --git a/sys/powerpc/powerpc/elf32_machdep.c b/sys/powerpc/powerpc/elf32_machdep.c --- a/sys/powerpc/powerpc/elf32_machdep.c +++ b/sys/powerpc/powerpc/elf32_machdep.c @@ -138,6 +138,8 @@ .sv_hwcap2 = &cpu_features2, .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; INIT_SYSENTVEC(elf32_sysvec, &elf32_freebsd_sysvec); diff --git a/sys/powerpc/powerpc/elf64_machdep.c b/sys/powerpc/powerpc/elf64_machdep.c --- a/sys/powerpc/powerpc/elf64_machdep.c +++ b/sys/powerpc/powerpc/elf64_machdep.c @@ -30,12 +30,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -99,6 +101,8 @@ .sv_hwcap2 = &cpu_features2, .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; struct sysentvec elf64_freebsd_sysvec_v2 = { @@ -141,6 +145,8 @@ .sv_hwcap2 = &cpu_features2, .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; static boolean_t ppc64_elfv1_header_match(struct image_params *params, diff --git a/sys/riscv/riscv/elf_machdep.c b/sys/riscv/riscv/elf_machdep.c --- a/sys/riscv/riscv/elf_machdep.c +++ b/sys/riscv/riscv/elf_machdep.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -102,6 +103,8 @@ .sv_machine_arch = riscv_machine_arch, .sv_onexec_old = exec_onexec_old, .sv_onexit = exit_onexit, + .sv_regset_begin = SET_BEGIN(__elfN(regset)), + .sv_regset_end = SET_LIMIT(__elfN(regset)), }; INIT_SYSENTVEC(elf64_sysvec, &elf64_freebsd_sysvec); diff --git a/sys/sys/ptrace.h b/sys/sys/ptrace.h --- a/sys/sys/ptrace.h +++ b/sys/sys/ptrace.h @@ -85,6 +85,8 @@ #define PT_VM_TIMESTAMP 40 /* Get VM version (timestamp) */ #define PT_VM_ENTRY 41 /* Get VM map (entry) */ +#define PT_GETREGSET 42 /* Get a target register set */ +#define PT_SETREGSET 43 /* Set a target register set */ #define PT_FIRSTMACH 64 /* for machine-specific requests */ #include /* machine-specific requests, if any */ diff --git a/sys/sys/reg.h b/sys/sys/reg.h --- a/sys/sys/reg.h +++ b/sys/sys/reg.h @@ -41,6 +41,25 @@ #include #ifdef _KERNEL +struct sbuf; +struct regset; + +typedef bool (regset_get)(struct regset *, struct thread *, void *, + size_t *); +typedef bool (regset_set)(struct regset *, struct thread *, void *, size_t); + +struct regset { + int note; + size_t size; + regset_get *get; + regset_set *set; +}; + +#if defined(__ELF_WORD_SIZE) +SET_DECLARE(__elfN(regset), struct regset); +#define ELF_REGSET(_regset) DATA_SET(__elfN(regset), _regset) +#endif + int fill_regs(struct thread *, struct reg *); int set_regs(struct thread *, struct reg *); int fill_fpregs(struct thread *, struct fpreg *); diff --git a/sys/sys/sysent.h b/sys/sys/sysent.h --- a/sys/sys/sysent.h +++ b/sys/sys/sysent.h @@ -160,6 +160,8 @@ struct image_params *imgp); void (*sv_set_fork_retval)(struct thread *); /* Only used on x86 */ + struct regset **sv_regset_begin; + struct regset **sv_regset_end; }; #define SV_ILP32 0x000100 /* 32-bit executable. */ diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c --- a/tests/sys/kern/ptrace_test.c +++ b/tests/sys/kern/ptrace_test.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #define _WANT_MIPS_REGNUM #include +#include #include #include #include @@ -3196,6 +3198,64 @@ REQUIRE_EQ(errno, ECHILD); } +/* + * Verify that PT_GETREGSET returns registers and PT_SETREGSET updates them. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_REGSET); +ATF_TC_BODY(ptrace__PT_REGSET, tc) +{ + struct prstatus prstatus; + struct iovec vec; + pid_t child, wpid; + int status; + + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + trace_me(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Check the size is returned when vec.iov_base is NULL */ + vec.iov_base = NULL; + vec.iov_len = 0; + ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&vec, NT_PRSTATUS) != + -1); + ATF_REQUIRE(vec.iov_len == sizeof(prstatus)); + ATF_REQUIRE(vec.iov_base == NULL); + + /* Read the registers. */ + memset(&prstatus, 0, sizeof(prstatus)); + vec.iov_base = &prstatus; + ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&vec, NT_PRSTATUS) != + -1); + ATF_REQUIRE(vec.iov_len == sizeof(prstatus)); + ATF_REQUIRE(vec.iov_base == &prstatus); + ATF_REQUIRE(prstatus.pr_statussz == sizeof(prstatus)); + + /* Write the registers back. */ + ATF_REQUIRE(ptrace(PT_SETREGSET, wpid, (caddr_t)&vec, NT_PRSTATUS) != + -1); + + REQUIRE_EQ(ptrace(PT_CONTINUE, child, (caddr_t)1, 0), 0); + + /* The second wait() should report the exit status. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + /* The child should no longer exist. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + static void * raise_sigstop_thread(void *arg __unused) { @@ -4302,6 +4362,7 @@ ATF_TP_ADD_TC(tp, ptrace__killed_with_sigmask); ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_sigmask); ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_thread_sigmask); + ATF_TP_ADD_TC(tp, ptrace__PT_REGSET); ATF_TP_ADD_TC(tp, ptrace__parent_terminate_with_pending_sigstop1); ATF_TP_ADD_TC(tp, ptrace__parent_terminate_with_pending_sigstop2); ATF_TP_ADD_TC(tp, ptrace__event_mask_sigkill_discard);