diff --git a/lib/libc/sys/ptrace.2 b/lib/libc/sys/ptrace.2 index 8e9c5d8ab87a..6148e6d333d5 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 July 15, 2019 +.Dd April 10, 2021 .Dt PTRACE 2 .Os .Sh NAME @@ -807,6 +807,70 @@ and extends up to The .Fa data argument is ignored. +.It Dv PT_COREDUMP +This request creates a coredump for the stopped program. +The +.Fa addr +argument specifies a pointer to a +.Vt "struct ptrace_coredump" , +which is defined as follows: +.Bd -literal +struct ptrace_coredump { + int pc_fd; + uint32_t pc_flags; + off_t pc_limit; +}; +.Ed +The fields of the structure are: +.Bl -tag -width pc_flags +.It Dv pc_fd +File descriptor to write the dump to. +It must refer to a regular file, opened for writing. +.It Dv pc_flags +Flags. +The following flags are defined: +.Bl -tag -width PC_COMPRESS +.It Dv PC_COMPRESS +Request compression of the dump. +.It Dv PC_ALL +Include non-dumpable entries into the dump. +The dumper ignores +.Dv MAP_NOCORE +flag of the process map entry, but device mappings are not dumped even with +.Dv PC_ALL +set. +.El +.It Dv pc_limit +Maximum size of the coredump. +Specify zero for no limit. +.El +.Pp +The size of +.Vt "struct ptrace_coredump" +must be passed in +.Fa data . +.Pp +The process must be stopped before dumping core. +A single thread in the target process is temporarily unsuspended +in kernel to write the dump. +If the +.Nm +call fails before a thread is unsuspended, there is no event to +.Xr waitpid 2 +for. +If a thread was unsuspended, it will stop again before the +.Nm +call returns, and the process must be waited upon using +.Xr waitpid 2 +to consume the new stop event. +Since it is hard to deduce whether a thread was unsuspended before +an error occurred, it is recommended to unconditionally perform +.Xr waitpid 2 +with +.Dv WNOHANG +flag after +.Dv PT_COREDUMP , +and silently accept zero result from it. .El .Sh ARM MACHINE-SPECIFIC REQUESTS .Bl -tag -width "Dv PT_SETVFPREGS" diff --git a/sys/compat/freebsd32/freebsd32.h b/sys/compat/freebsd32/freebsd32.h index 4227d9037afb..2e4f5155cbf4 100644 --- a/sys/compat/freebsd32/freebsd32.h +++ b/sys/compat/freebsd32/freebsd32.h @@ -429,4 +429,11 @@ struct timex32 { int32_t stbcnt; }; +struct ptrace_coredump32 { + int pc_fd; + uint32_t pc_flags; + uint32_t pc_limit1, pc_limit2; +}; + + #endif /* !_COMPAT_FREEBSD32_FREEBSD32_H_ */ diff --git a/sys/compat/freebsd32/freebsd32_misc.c b/sys/compat/freebsd32/freebsd32_misc.c index c2caa7c10544..f221397e91dc 100644 --- a/sys/compat/freebsd32/freebsd32_misc.c +++ b/sys/compat/freebsd32/freebsd32_misc.c @@ -932,6 +932,7 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap) struct ptrace_io_desc piod; struct ptrace_lwpinfo pl; struct ptrace_vm_entry pve; + struct ptrace_coredump pc; struct dbreg32 dbreg; struct fpreg32 fpreg; struct reg32 reg; @@ -943,6 +944,7 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap) struct ptrace_io_desc32 piod; struct ptrace_lwpinfo32 pl; struct ptrace_vm_entry32 pve; + struct ptrace_coredump32 pc; uint32_t args[nitems(td->td_sa.args)]; struct ptrace_sc_ret32 psr; } r32; @@ -1021,6 +1023,16 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap) CP(r32.pve, r.pve, pve_fsid); PTRIN_CP(r32.pve, r.pve, pve_path); break; + case PT_COREDUMP: + if (uap->data != sizeof(r32.pc)) + error = EINVAL; + else + error = copyin(uap->addr, &r32.pc, uap->data); + CP(r32.pc, r.pc, pc_fd); + CP(r32.pc, r.pc, pc_flags); + r.pc.pc_limit = PAIR32TO64(off_t, r32.pc.pc_limit); + data = sizeof(r.pc); + break; default: addr = uap->addr; break; diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c index 445582a176c8..0453d3b2702c 100644 --- a/sys/kern/kern_sig.c +++ b/sys/kern/kern_sig.c @@ -2521,6 +2521,42 @@ tdsigwakeup(struct thread *td, int sig, sig_t action, int intrval) thread_unlock(td); } +static void +ptrace_coredump(struct thread *td) +{ + struct proc *p; + struct thr_coredump_req *tcq; + void *rl_cookie; + + MPASS(td == curthread); + p = td->td_proc; + PROC_LOCK_ASSERT(p, MA_OWNED); + if ((td->td_dbgflags & TDB_COREDUMPRQ) == 0) + return; + KASSERT((p->p_flag & P_STOPPED_TRACE) != 0, ("not stopped")); + + tcq = td->td_coredump; + KASSERT(tcq != NULL, ("td_coredump is NULL")); + + if (p->p_sysent->sv_coredump == NULL) { + tcq->tc_error = ENOSYS; + goto wake; + } + + PROC_UNLOCK(p); + rl_cookie = vn_rangelock_wlock(tcq->tc_vp, 0, OFF_MAX); + + tcq->tc_error = p->p_sysent->sv_coredump(td, tcq->tc_vp, + tcq->tc_limit, tcq->tc_flags); + + vn_rangelock_unlock(tcq->tc_vp, rl_cookie); + PROC_LOCK(p); +wake: + td->td_dbgflags &= ~TDB_COREDUMPRQ; + td->td_coredump = NULL; + wakeup(p); +} + static int sig_suspend_threads(struct thread *td, struct proc *p, int sending) { @@ -2651,6 +2687,12 @@ ptracestop(struct thread *td, int sig, ksiginfo_t *si) td->td_dbgflags |= TDB_SSWITCH; thread_suspend_switch(td, p); td->td_dbgflags &= ~TDB_SSWITCH; + if ((td->td_dbgflags & TDB_COREDUMPRQ) != 0) { + PROC_SUNLOCK(p); + ptrace_coredump(td); + PROC_SLOCK(p); + goto stopme; + } if (p->p_xthread == td) p->p_xthread = NULL; if (!(p->p_flag & P_TRACED)) diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c index 27849de47fde..50157106a35e 100644 --- a/sys/kern/sys_process.c +++ b/sys/kern/sys_process.c @@ -51,6 +51,8 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include +#include #include @@ -469,6 +471,7 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap) struct ptrace_io_desc piod; struct ptrace_lwpinfo pl; struct ptrace_vm_entry pve; + struct ptrace_coredump pc; struct dbreg dbreg; struct fpreg fpreg; struct reg reg; @@ -519,6 +522,12 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap) case PT_VM_ENTRY: error = copyin(uap->addr, &r.pve, sizeof(r.pve)); break; + case PT_COREDUMP: + if (uap->data != sizeof(r.pc)) + error = EINVAL; + else + error = copyin(uap->addr, &r.pc, uap->data); + break; default: addr = uap->addr; break; @@ -632,6 +641,22 @@ proc_can_ptrace(struct thread *td, struct proc *p) return (0); } + +static struct thread * +ptrace_sel_coredump_thread(struct proc *p) +{ + struct thread *td2; + + PROC_LOCK_ASSERT(p, MA_OWNED); + MPASS((p->p_flag & P_STOPPED_TRACE) != 0); + + FOREACH_THREAD_IN_PROC(p, td2) { + if ((td2->td_dbgflags & TDB_SSWITCH) != 0) + return (td2); + } + return (NULL); +} + int kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) { @@ -642,6 +667,9 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) struct ptrace_io_desc *piod = NULL; struct ptrace_lwpinfo *pl; struct ptrace_sc_ret *psr; + struct file *fp; + struct ptrace_coredump *pc; + struct thr_coredump_req *tcq; int error, num, tmp; lwpid_t tid = 0, *buf; #ifdef COMPAT_FREEBSD32 @@ -1348,6 +1376,62 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) PROC_LOCK(p); break; + case PT_COREDUMP: + pc = addr; + CTR2(KTR_PTRACE, "PT_COREDUMP: pid %d, fd %d", + p->p_pid, pc->pc_fd); + + if ((pc->pc_flags & ~(PC_COMPRESS | PC_ALL)) != 0) { + error = EINVAL; + break; + } + PROC_UNLOCK(p); + + tcq = malloc(sizeof(*tcq), M_TEMP, M_WAITOK | M_ZERO); + fp = NULL; + error = fget_write(td, pc->pc_fd, &cap_write_rights, &fp); + if (error != 0) + goto coredump_cleanup_nofp; + if (fp->f_type != DTYPE_VNODE || fp->f_vnode->v_type != VREG) { + error = EPIPE; + goto coredump_cleanup; + } + + PROC_LOCK(p); + error = proc_can_ptrace(td, p); + if (error != 0) + goto coredump_cleanup_locked; + + td2 = ptrace_sel_coredump_thread(p); + if (td2 == NULL) { + error = EBUSY; + goto coredump_cleanup_locked; + } + KASSERT((td2->td_dbgflags & TDB_COREDUMPRQ) == 0, + ("proc %d tid %d req coredump", p->p_pid, td2->td_tid)); + + tcq->tc_vp = fp->f_vnode; + tcq->tc_limit = pc->pc_limit == 0 ? OFF_MAX : pc->pc_limit; + tcq->tc_flags = SVC_PT_COREDUMP; + if ((pc->pc_flags & PC_COMPRESS) == 0) + tcq->tc_flags |= SVC_NOCOMPRESS; + if ((pc->pc_flags & PC_ALL) != 0) + tcq->tc_flags |= SVC_ALL; + td2->td_coredump = tcq; + td2->td_dbgflags |= TDB_COREDUMPRQ; + thread_run_flash(td2); + while ((td2->td_dbgflags & TDB_COREDUMPRQ) != 0) + msleep(p, &p->p_mtx, PPAUSE, "crdmp", 0); + error = tcq->tc_error; +coredump_cleanup_locked: + PROC_UNLOCK(p); +coredump_cleanup: + fdrop(fp, td); +coredump_cleanup_nofp: + free(tcq, M_TEMP); + PROC_LOCK(p); + break; + default: #ifdef __HAVE_PTRACE_MACHDEP if (req >= PT_FIRSTMACH) { @@ -1360,7 +1444,6 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) error = EINVAL; break; } - out: /* Drop our hold on this process now that the request has completed. */ _PRELE(p); diff --git a/sys/sys/proc.h b/sys/sys/proc.h index b959681e992b..ebd396b4aebe 100644 --- a/sys/sys/proc.h +++ b/sys/sys/proc.h @@ -377,6 +377,7 @@ struct thread { int td_oncpu; /* (t) Which cpu we are on. */ void *td_lkpi_task; /* LinuxKPI task struct pointer */ int td_pmcpend; + void *td_coredump; /* (c) coredump request. */ #ifdef EPOCH_TRACE SLIST_HEAD(, epoch_tracker) td_epochs; #endif @@ -486,6 +487,7 @@ do { \ #define TDB_FSTP 0x00001000 /* The thread is PT_ATTACH leader */ #define TDB_STEP 0x00002000 /* (x86) PSL_T set for PT_STEP */ #define TDB_SSWITCH 0x00004000 /* Suspended in ptracestop */ +#define TDB_COREDUMPRQ 0x00008000 /* Coredump request */ /* * "Private" flags kept in td_pflags: diff --git a/sys/sys/ptrace.h b/sys/sys/ptrace.h index 1ee42318e57e..06f01a04fd9d 100644 --- a/sys/sys/ptrace.h +++ b/sys/sys/ptrace.h @@ -74,6 +74,8 @@ #define PT_GET_SC_ARGS 27 /* fetch syscall args */ #define PT_GET_SC_RET 28 /* fetch syscall results */ +#define PT_COREDUMP 29 /* create a coredump */ + #define PT_GETREGS 33 /* get general-purpose registers */ #define PT_SETREGS 34 /* set general-purpose registers */ #define PT_GETFPREGS 35 /* get floating-point registers */ @@ -176,8 +178,27 @@ struct ptrace_vm_entry { char *pve_path; /* Path name of object. */ }; +/* Argument structure for PT_COREDUMP */ +struct ptrace_coredump { + int pc_fd; /* File descriptor to write dump to. */ + uint32_t pc_flags; /* Flags PC_* */ + off_t pc_limit; /* Maximum size of the coredump, + 0 for no limit. */ +}; + +/* Flags for PT_COREDUMP pc_flags */ +#define PC_COMPRESS 0x00000001 /* Allow compression */ +#define PC_ALL 0x00000002 /* Include non-dumpable entries */ + #ifdef _KERNEL +struct thr_coredump_req { + struct vnode *tc_vp; /* vnode to write coredump to. */ + off_t tc_limit; /* max coredump file size. */ + int tc_flags; /* user flags */ + int tc_error; /* request result */ +}; + int ptrace_set_pc(struct thread *_td, unsigned long _addr); int ptrace_single_step(struct thread *_td); int ptrace_clear_single_step(struct thread *_td);