Index: lib/libc/sys/ptrace.2 =================================================================== --- lib/libc/sys/ptrace.2 +++ 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 July 15, 2019 +.Dd August 9, 2019 .Dt PTRACE 2 .Os .Sh NAME @@ -437,6 +437,22 @@ .In machine/reg.h ) pointed to by .Fa addr . +.It Dv PT_GETREGSET +This request reads the traced process's machine registers. +The +.Fa data +argument specifies the register set to read, with the +.Fa addr +argument pointing at a register set specific structure to hold the +registers. +.It Dv PT_SETREGSET +This request writes to the traced process's machine registers. +The +.Fa data +argument specifies the register set to write to, with the +.Fa addr +argument pointing at a request specific structure that holds the new +registers. .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. Index: sys/kern/sys_process.c =================================================================== --- sys/kern/sys_process.c +++ sys/kern/sys_process.c @@ -93,8 +93,18 @@ u_int pve_fsid; uint32_t pve_path; }; + +struct iovec32 { + uint32_t iov_base; /* Base address. */ + uint32_t iov_len; /* Length. */ +}; #endif +struct ptrace_regset { + void *prs_addr; + size_t prs_size; +}; + /* * Functions implemented using PROC_ACTION(): * @@ -178,6 +188,109 @@ PROC_ACTION(set_fpregs(td, fpregs)); } +SET_DECLARE(elf_regset, struct regset); +#ifdef COMPAT_FREEBSD32 +SET_DECLARE(elf_regset_compat32, struct regset); +#endif +static struct regset * +proc_regset_find(int note, int compat32) +{ + struct regset **regsetpp, *regsetp; + +#ifdef COMPAT_FREEBSD32 + if (compat32) { + SET_FOREACH(regsetpp, elf_regset_compat32) { + regsetp = *regsetpp; + if (regsetp->note != note) + continue; + + return (regsetp); + } + } else +#endif + { + SET_FOREACH(regsetpp, elf_regset) { + regsetp = *regsetpp; + if (regsetp->note != note) + continue; + + return (regsetp); + } + } + + return (NULL); +} + +static size_t +proc_regset_maxsize(int note, int compat32) +{ + struct regset *regset; + + regset = proc_regset_find(note, compat32); + if (regset != NULL) + return (regset->size); + + return (0); +} + +static int +proc_read_regset_common(struct thread *td, int note, struct iovec *vec, + int compat32) +{ + struct regset *regset; + size_t size; + int err; + + regset = proc_regset_find(note, compat32); + if (regset == NULL) + return (EINVAL); + + err = 0; + size = vec->iov_len; + if (!regset->get(regset, td, vec->iov_base, &size)) + err = EINVAL; + KASSERT(size == vec->iov_len, + ("proc_read_regset: Get function changed the size")); + + return (err); +} + +static int +proc_read_regset(struct thread *td, int note, struct iovec *vec) +{ + + return (proc_read_regset_common(td, note, vec, 0)); +} + +static int +proc_write_regset_common(struct thread *td, int note, struct iovec *vec, + int compat32) +{ + struct regset *regset; + size_t size; + int err; + + regset = proc_regset_find(note, compat32); + if (regset == NULL) + return (EINVAL); + + err = 0; + size = vec->iov_len; + if (!regset->set(regset, td, vec->iov_base, &size)) + err = EINVAL; + KASSERT(size == vec->iov_len, + ("proc_write_regset: Set function changed the size")); + + return (err); +} + +static int +proc_write_regset(struct thread *td, int note, struct iovec *vec) +{ + + return (proc_write_regset_common(td, note, vec, 0)); +} + #ifdef COMPAT_FREEBSD32 /* For 32 bit binaries, we need to expose the 32 bit regs layouts. */ int @@ -221,6 +334,20 @@ PROC_ACTION(set_fpregs32(td, fpregs32)); } + +static int +proc_read_regset32(struct thread *td, int note, struct iovec *vec) +{ + + return (proc_read_regset_common(td, note, vec, 1)); +} + +static int +proc_write_regset32(struct thread *td, int note, struct iovec *vec) +{ + + return (proc_write_regset_common(td, note, vec, 1)); +} #endif int @@ -568,10 +695,15 @@ #define COPYOUT(k, u, s) wrap32 ? \ copyout(k ## 32, u, s ## 32) : \ copyout(k, u, s) +#define IOV_BASE(v) \ + (wrap32 ? (void *)(uintptr_t)__CONCAT(v, 32).iov_base : (v).iov_base) +#define IOV_LEN(v) (wrap32 ? __CONCAT(v, 32).iov_len : (v).iov_len) #else #define BZERO(a, s) bzero(a, s) #define COPYIN(u, k, s) copyin(u, k, s) #define COPYOUT(k, u, s) copyout(k, u, s) +#define IOV_BASE(v) (v).iov_base +#define IOV_LEN(v) (v).iov_len #endif int sys_ptrace(struct thread *td, struct ptrace_args *uap) @@ -587,6 +719,7 @@ struct dbreg dbreg; struct fpreg fpreg; struct reg reg; + struct iovec vec; #ifdef COMPAT_FREEBSD32 struct dbreg32 dbreg32; struct fpreg32 fpreg32; @@ -594,16 +727,18 @@ struct ptrace_io_desc32 piod32; struct ptrace_lwpinfo32 pl32; struct ptrace_vm_entry32 pve32; + struct iovec32 vec32; #endif char args[sizeof(td->td_sa.args)]; struct ptrace_sc_ret psr; int ptevents; } r; + struct iovec kern_vec; void *addr; int error = 0; -#ifdef COMPAT_FREEBSD32 int wrap32 = 0; +#ifdef COMPAT_FREEBSD32 if (SV_CURPROC_FLAG(SV_ILP32)) wrap32 = 1; #endif @@ -611,6 +746,8 @@ AUDIT_ARG_CMD(uap->req); AUDIT_ARG_VALUE(uap->data); addr = &r; + kern_vec.iov_base = NULL; + kern_vec.iov_len = 0; switch (uap->req) { case PT_GET_EVENT_MASK: case PT_LWPINFO: @@ -626,6 +763,49 @@ case PT_GETDBREGS: BZERO(&r.dbreg, sizeof r.dbreg); break; + case PT_SETREGSET: + error = COPYIN(uap->addr, &r.vec, sizeof r.vec); + if (error != 0) + break; + kern_vec.iov_len = proc_regset_maxsize(uap->data, wrap32); + /* Check the regset is valid and the length is correct */ + if (kern_vec.iov_len == 0 || + kern_vec.iov_len != IOV_LEN(r.vec)) { + error = EINVAL; + break; + } + kern_vec.iov_base = malloc(kern_vec.iov_len, M_TEMP, + M_WAITOK); + error = copyin(IOV_BASE(r.vec), kern_vec.iov_base, + kern_vec.iov_len); + if (error != 0) + break; + addr = &kern_vec; + break; + case PT_GETREGSET: + error = COPYIN(uap->addr, &r.vec, sizeof r.vec); + if (error != 0) + break; + kern_vec.iov_len = proc_regset_maxsize(uap->data, wrap32); + if (kern_vec.iov_len == 0) { + error = EINVAL; + break; + } + /* Just return the size when the base is NULL */ + if (IOV_BASE(r.vec) == NULL) { +#ifdef COMPAT_FREEBSD32 + if (wrap32) + r.vec32.iov_len = kern_vec.iov_len; + else +#endif + r.vec.iov_len = kern_vec.iov_len; + error = COPYOUT(&r.vec, uap->addr, sizeof r.vec); + goto out; + } + kern_vec.iov_base = malloc(kern_vec.iov_len, M_TEMP, + M_WAITOK | M_ZERO); + addr = &kern_vec; + break; case PT_SETREGS: error = COPYIN(uap->addr, &r.reg, sizeof r.reg); break; @@ -652,11 +832,11 @@ break; } if (error) - return (error); + goto out; error = kern_ptrace(td, uap->req, uap->pid, addr, uap->data); if (error) - return (error); + goto out; switch (uap->req) { case PT_VM_ENTRY: @@ -674,6 +854,19 @@ case PT_GETDBREGS: error = COPYOUT(&r.dbreg, uap->addr, sizeof r.dbreg); break; + case PT_GETREGSET: +#ifdef COMPAT_FREEBSD32 + if (wrap32) + r.vec32.iov_len = MIN(r.vec32.iov_len,kern_vec.iov_len); + else +#endif + r.vec.iov_len = MIN(r.vec.iov_len, kern_vec.iov_len); + error = copyout(kern_vec.iov_base, IOV_BASE(r.vec), + IOV_LEN(r.vec)); + if (error != 0) + break; + 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); @@ -692,6 +885,8 @@ break; } +out: + free(kern_vec.iov_base, M_TEMP); return (error); } #undef COPYIN @@ -712,9 +907,15 @@ #define PROC_READ(w, t, a) wrap32 ? \ proc_read_ ## w ## 32(t, a) : \ proc_read_ ## w (t, a) +#define PROC_READ_REGSET(w, t, d, a) wrap32 ? \ + proc_read_ ## w ## 32(t, d, a) : \ + proc_read_ ## w (t, d, a) #define PROC_WRITE(w, t, a) wrap32 ? \ (safe ? proc_write_ ## w ## 32(t, a) : EINVAL ) : \ proc_write_ ## w (t, a) +#define PROC_WRITE_REGSET(w, t, d, a) wrap32 ? \ + (safe ? proc_write_ ## w ## 32(t, d, a) : EINVAL ) : \ + proc_write_ ## w (t, d, a) #else #define PROC_READ(w, t, a) proc_read_ ## w (t, a) #define PROC_WRITE(w, t, a) proc_write_ ## w (t, a) @@ -1379,6 +1580,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(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(regset, td2, data, addr); + break; + case PT_LWPINFO: if (data <= 0 || #ifdef COMPAT_FREEBSD32 Index: sys/sys/ptrace.h =================================================================== --- sys/sys/ptrace.h +++ sys/sys/ptrace.h @@ -84,6 +84,9 @@ #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 */ Index: sys/sys/reg.h =================================================================== --- sys/sys/reg.h +++ sys/sys/reg.h @@ -5,6 +5,23 @@ #include #ifdef _KERNEL +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; + size_t align; + regset_get *get; + regset_set *set; +}; + +#define ELF_REGSET(regset) DATA_SET(elf_regset, regset) + int fill_regs(struct thread *, struct reg *); int set_regs(struct thread *, struct reg *); int fill_fpregs(struct thread *, struct fpreg *);