From nobody Thu May 12 22:56:32 2022 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 81EBE1AE4973; Thu, 12 May 2022 22:56:33 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4KznFs1jfKz3GVc; Thu, 12 May 2022 22:56:32 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1652396193; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=MpgLmzMI+8O35tAG8KQTiTlsQ7/CReiDlQeBOOPYkxI=; b=SxRgU0IpLEK8v5JVboRPWcj5CVr7mA1CtuZ/8FRE+gQGG3GjRYHzBWYbaR+ZzSUIHHPyUv s/DUUf+Zd+d12VUMPmZhfL5mG/6JFpZIAXZuRSl81qhqN0QfgmHYlZiCh59OA3bmt6OhH3 dDeXZzoPNh6sd8nIR+MM6kWiGD9oRwMvF2gsCYt2qT59mYUSJY5rpEDxd5770qwk3kLjqu UoN42L49spwxZhYQrQbYvwe9Xmf8FSAaXj4EWLliLEtfuA0DIMGouRdzCL+ulFAl1G+Utz 5NGzgD4l8D5dxRWHNoF9lI4CeGuf8fQfFLQwr3Ud8GIrIwpZtbgPVpxgedpnEg== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 5BD5D172F4; Thu, 12 May 2022 22:56:32 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 24CMuW0W020618; Thu, 12 May 2022 22:56:32 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 24CMuWR4020617; Thu, 12 May 2022 22:56:32 GMT (envelope-from git) Date: Thu, 12 May 2022 22:56:32 GMT Message-Id: <202205122256.24CMuWR4020617@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: John Baldwin Subject: git: 658631a37d78 - stable/13 - Add PT_GETREGSET List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: jhb X-Git-Repository: src X-Git-Refname: refs/heads/stable/13 X-Git-Reftype: branch X-Git-Commit: 658631a37d78c9ea5debe4642630c7154b86bcd6 Auto-Submitted: auto-generated ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1652396193; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=MpgLmzMI+8O35tAG8KQTiTlsQ7/CReiDlQeBOOPYkxI=; b=d0UCAauwMBrNAabTYNq+jcvzoqNH2VKZPl/DKA+A5Ik6X8ykIZqUms2HESSQ302BeM7/GJ U8AeLO5l/xiQ65paDRymYLKO1P+EolxRVOww5wIfjqNH5lbXCkagTVjbkPw1PP7re8igta 3SQ89cx0mHiDqea1V6lkE3xwk0IgBftBfbJI8QG2I1Q9hWhy+WjO+RmiUEG/1H+FwwbIrr IbZeRuw3wUXIwLXVK7VdTIOoOJ/yPQx9zQZ3SyjoTDjLB6ibCNx/AebFKgxqFmd8iWdjZg b9kUvhs4O/pHvqPhmiNt+ZKZnM/O0FXOePy9MpJB7cZ+WcquHP38U2LonMEvFw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1652396193; a=rsa-sha256; cv=none; b=R+lMwf5UfviZootoPIpjkEDvfWwhA+3IMW/caJC/6Fa4FST6VVko7NaQGbJyC7MnUSqsuC ITEFc8UXDc5Srh2erocdkOFugKPj/pVXtS+DyGp6uTFZswoqE4ZqLz90cFZBUrncPL4DwS lh3w1QfF6X6pgCER6t3i0VtNpj5wHqo0URqB6NDEvYnRxadPNddPb1t4IvdCIO13UzvmZ/ SlkkW39bJEfMjkUIUmMV4gmcCKeAIuq1OaUC8y73kVzjZB2doN3IWo84HUlYE2yHk3KaFD I/CJTs7aaqeV4e9jEvF5UaFrm34rPkqCMw9syrEOi2QLY7N3ZQ7YSVkH+SR3YA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none X-ThisMailContainsUnwantedMimeParts: N The branch stable/13 has been updated by jhb: URL: https://cgit.FreeBSD.org/src/commit/?id=658631a37d78c9ea5debe4642630c7154b86bcd6 commit 658631a37d78c9ea5debe4642630c7154b86bcd6 Author: Andrew Turner AuthorDate: 2022-01-24 11:24:17 +0000 Commit: John Baldwin CommitDate: 2022-05-12 22:12:59 +0000 Add PT_GETREGSET This adds the PT_GETREGSET and PT_SETREGSET ptrace types. These can be used to access all the registers from a specified core dump note type. The NT_PRSTATUS and NT_FPREGSET notes are initially supported. Other machine-dependant types are expected to be added in the future. The ptrace addr points to a struct iovec pointing at memory to hold the registers along with its length. On success the length in the iovec is updated to tell userspace the actual length the kernel wrote or, if the base address is NULL, the length the kernel would have written. Because the data field is an int the arguments are backwards when compared to the Linux PTRACE_GETREGSET call. Reviewed by: kib Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D19831 (cherry picked from commit 548a2ec49bd4ebf9ab00d362257c6bb4d2d7edbc) --- lib/libc/sys/ptrace.2 | 36 ++++++++- sys/amd64/amd64/elf_machdep.c | 5 ++ sys/arm/arm/elf_machdep.c | 3 + sys/arm64/arm64/elf32_machdep.c | 3 + sys/arm64/arm64/elf_machdep.c | 3 + sys/compat/freebsd32/freebsd32_misc.c | 22 ++++++ sys/compat/ia32/ia32_sysvec.c | 4 + sys/i386/i386/elf_machdep.c | 3 + sys/kern/imgact_elf.c | 105 ++++++++++++++++++++++--- sys/kern/init_main.c | 2 + sys/kern/sys_process.c | 144 ++++++++++++++++++++++++++++++++++ sys/mips/mips/elf_machdep.c | 3 + sys/mips/mips/freebsd32_machdep.c | 2 + sys/powerpc/powerpc/elf32_machdep.c | 2 + sys/powerpc/powerpc/elf64_machdep.c | 6 ++ sys/riscv/riscv/elf_machdep.c | 3 + sys/sys/ptrace.h | 2 + sys/sys/reg.h | 19 +++++ sys/sys/sysent.h | 2 + tests/sys/kern/ptrace_test.c | 61 ++++++++++++++ 20 files changed, 417 insertions(+), 13 deletions(-) diff --git a/lib/libc/sys/ptrace.2 b/lib/libc/sys/ptrace.2 index ef791d22e22c..74bf646efd3c 100644 --- 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 @@ it loads the traced process's debug registers from the .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 index 891aa1f54bd9..6b39fd03f471 100644 --- a/sys/amd64/amd64/elf_machdep.c +++ b/sys/amd64/amd64/elf_machdep.c @@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -94,6 +95,8 @@ struct sysentvec elf64_freebsd_sysvec_la48 = { .sv_trap = NULL, .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_la57 = { @@ -135,6 +138,8 @@ struct sysentvec elf64_freebsd_sysvec_la57 = { .sv_trap = NULL, .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 void diff --git a/sys/arm/arm/elf_machdep.c b/sys/arm/arm/elf_machdep.c index a20268f42f67..d993ba57c7d0 100644 --- a/sys/arm/arm/elf_machdep.c +++ b/sys/arm/arm/elf_machdep.c @@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -103,6 +104,8 @@ struct sysentvec elf32_freebsd_sysvec = { .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 index 774fa5b9f701..4322c07d6e82 100644 --- a/sys/arm64/arm64/elf32_machdep.c +++ b/sys/arm64/arm64/elf32_machdep.c @@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -120,6 +121,8 @@ static struct sysentvec elf32_freebsd_sysvec = { .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 index 3de0f82839e6..d98512b71e6c 100644 --- a/sys/arm64/arm64/elf_machdep.c +++ b/sys/arm64/arm64/elf_machdep.c @@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -98,6 +99,8 @@ static struct sysentvec elf64_freebsd_sysvec = { .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 index 4ef33774d6bb..7c82aebb55c3 100644 --- a/sys/compat/freebsd32/freebsd32_misc.c +++ b/sys/compat/freebsd32/freebsd32_misc.c @@ -924,6 +924,7 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap) 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; @@ -935,6 +936,7 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap) 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; @@ -984,6 +986,22 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap) 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; @@ -1062,6 +1080,10 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap) 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 index 6acde07d4683..7f3ba2da352b 100644 --- a/sys/compat/ia32/ia32_sysvec.c +++ b/sys/compat/ia32/ia32_sysvec.c @@ -33,6 +33,7 @@ __FBSDID("$FreeBSD$"); #define __ELF_WORD_SIZE 32 #include +#include #include #include #include @@ -44,6 +45,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -140,6 +142,8 @@ struct sysentvec ia32_freebsd_sysvec = { .sv_trap = NULL, .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(elf_ia32_sysvec, &ia32_freebsd_sysvec); diff --git a/sys/i386/i386/elf_machdep.c b/sys/i386/i386/elf_machdep.c index dcacab307522..4e9fe52d4e7e 100644 --- a/sys/i386/i386/elf_machdep.c +++ b/sys/i386/i386/elf_machdep.c @@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -89,6 +90,8 @@ struct sysentvec elf32_freebsd_sysvec = { .sv_trap = NULL, .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/kern/imgact_elf.c b/sys/kern/imgact_elf.c index 939e0fe941b5..c72afb3c6a0b 100644 --- a/sys/kern/imgact_elf.c +++ b/sys/kern/imgact_elf.c @@ -2170,16 +2170,16 @@ __elfN(note_prpsinfo)(void *arg, struct sbuf *sb, size_t *sizep) *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); @@ -2192,12 +2192,97 @@ __elfN(note_prstatus)(void *arg, struct sbuf *sb, size_t *sizep) #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) { @@ -2208,11 +2293,7 @@ __elfN(note_fpregset)(void *arg, struct sbuf *sb, size_t *sizep) 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 index d82e4106d507..8226d8ffd434 100644 --- a/sys/kern/init_main.c +++ b/sys/kern/init_main.c @@ -431,6 +431,8 @@ struct sysentvec null_sysvec = { .sv_schedtail = NULL, .sv_thread_detach = NULL, .sv_trap = NULL, + .sv_regset_begin = NULL, + .sv_regset_end = NULL, }; /* diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c index 8c5f795df691..fa2617356e30 100644 --- a/sys/kern/sys_process.c +++ b/sys/kern/sys_process.c @@ -70,6 +70,9 @@ __FBSDID("$FreeBSD$"); #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_write_fpregs(struct thread *td, struct fpreg *fpregs) 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 @@ -473,6 +595,7 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap) 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; @@ -503,6 +626,12 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap) 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; @@ -557,6 +686,9 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap) 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); @@ -1290,6 +1422,18 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) 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/mips/mips/elf_machdep.c b/sys/mips/mips/elf_machdep.c index c6a45f71107b..dc8d8390fbf8 100644 --- a/sys/mips/mips/elf_machdep.c +++ b/sys/mips/mips/elf_machdep.c @@ -39,6 +39,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -93,6 +94,8 @@ static struct sysentvec elf_freebsd_sysvec = { .sv_trap = NULL, .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 __ElfN(Brandinfo) freebsd_brand_info = { diff --git a/sys/mips/mips/freebsd32_machdep.c b/sys/mips/mips/freebsd32_machdep.c index 710c7315de3c..87ce9aa48650 100644 --- a/sys/mips/mips/freebsd32_machdep.c +++ b/sys/mips/mips/freebsd32_machdep.c @@ -109,6 +109,8 @@ struct sysentvec elf32_freebsd_sysvec = { .sv_trap = NULL, .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/elf32_machdep.c b/sys/powerpc/powerpc/elf32_machdep.c index 993c55a3ce48..fd88900292ac 100644 --- a/sys/powerpc/powerpc/elf32_machdep.c +++ b/sys/powerpc/powerpc/elf32_machdep.c @@ -138,6 +138,8 @@ struct sysentvec elf32_freebsd_sysvec = { .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 index 83a488a25692..5c2e26d064b5 100644 --- 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 @@ struct sysentvec elf64_freebsd_sysvec_v1 = { .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 @@ struct sysentvec elf64_freebsd_sysvec_v2 = { .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 index 54dc8ab66a4e..0ed8b762ef7e 100644 --- a/sys/riscv/riscv/elf_machdep.c +++ b/sys/riscv/riscv/elf_machdep.c @@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -102,6 +103,8 @@ static struct sysentvec elf64_freebsd_sysvec = { .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 index 4cd7a3fceaec..197ac1692dfb 100644 --- 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 index 910ea802029e..e06a5f9a19ed 100644 --- 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 index 84bcebbcf4cc..f02383bacdbc 100644 --- a/sys/sys/sysent.h +++ b/sys/sys/sysent.h @@ -158,6 +158,8 @@ struct sysentvec { void (*sv_ontdexit)(struct thread *td); int (*sv_setid_allowed)(struct thread *td, struct image_params *imgp); + 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 index 16c047db574a..3e3a445c6152 100644 --- a/tests/sys/kern/ptrace_test.c +++ b/tests/sys/kern/ptrace_test.c @@ -28,6 +28,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include @@ -35,6 +36,7 @@ __FBSDID("$FreeBSD$"); #include #define _WANT_MIPS_REGNUM #include +#include #include #include #include @@ -3196,6 +3198,64 @@ ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_thread_sigmask, tc) 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_TCS(tp) 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);