git: 51af25060861 - stable/13 - Add ptrace(PT_COREDUMP)

Konstantin Belousov kib at FreeBSD.org
Mon May 10 01:14:56 UTC 2021


The branch stable/13 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=51af25060861869b9ba59601eb67eae2445e61e0

commit 51af25060861869b9ba59601eb67eae2445e61e0
Author:     Konstantin Belousov <kib at FreeBSD.org>
AuthorDate: 2021-04-23 13:26:01 +0000
Commit:     Konstantin Belousov <kib at FreeBSD.org>
CommitDate: 2021-05-10 01:03:06 +0000

    Add ptrace(PT_COREDUMP)
    
    (cherry picked from commit 87a64872cd3166a09b58aac28cdb95380d6a38eb)
---
 lib/libc/sys/ptrace.2                 | 66 ++++++++++++++++++++++++++-
 sys/compat/freebsd32/freebsd32.h      |  7 +++
 sys/compat/freebsd32/freebsd32_misc.c | 12 +++++
 sys/kern/kern_sig.c                   | 42 +++++++++++++++++
 sys/kern/sys_process.c                | 85 ++++++++++++++++++++++++++++++++++-
 sys/sys/proc.h                        |  2 +
 sys/sys/ptrace.h                      | 21 +++++++++
 7 files changed, 233 insertions(+), 2 deletions(-)

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 b7db1c4468d7..d258afa2352d 100644
--- a/sys/compat/freebsd32/freebsd32_misc.c
+++ b/sys/compat/freebsd32/freebsd32_misc.c
@@ -920,6 +920,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;
@@ -931,6 +932,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;
@@ -1009,6 +1011,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 @@ out:
 	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 @@ stopme:
 			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 e89fc6dff7e0..bc38a8ee585d 100644
--- a/sys/kern/sys_process.c
+++ b/sys/kern/sys_process.c
@@ -51,6 +51,8 @@ __FBSDID("$FreeBSD$");
 #include <sys/sx.h>
 #include <sys/malloc.h>
 #include <sys/signalvar.h>
+#include <sys/caprights.h>
+#include <sys/filedesc.h>
 
 #include <machine/reg.h>
 
@@ -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 e4e01f9bec16..8e2a081eb027 100644
--- a/sys/sys/proc.h
+++ b/sys/sys/proc.h
@@ -376,6 +376,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
@@ -485,6 +486,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);


More information about the dev-commits-src-all mailing list