Add ptrace(PT_COREDUMP)
It writes the core of live stopped process to the file descriptor provided as an argument. Based on the initial version from https://reviews.freebsd.org/D29691, submitted by Michał Górny <mgorny@gentoo.org>. Reviewed by: markj Tested by: pho Sponsored by: The FreeBSD Foundation MFC after: 1 week Differential revision: https://reviews.freebsd.org/D29955
This commit is contained in:
parent
68d311b666
commit
87a64872cd
@ -2,7 +2,7 @@
|
|||||||
.\" $NetBSD: ptrace.2,v 1.2 1995/02/27 12:35:37 cgd Exp $
|
.\" $NetBSD: ptrace.2,v 1.2 1995/02/27 12:35:37 cgd Exp $
|
||||||
.\"
|
.\"
|
||||||
.\" This file is in the public domain.
|
.\" This file is in the public domain.
|
||||||
.Dd July 15, 2019
|
.Dd April 10, 2021
|
||||||
.Dt PTRACE 2
|
.Dt PTRACE 2
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -807,6 +807,70 @@ and extends up to
|
|||||||
The
|
The
|
||||||
.Fa data
|
.Fa data
|
||||||
argument is ignored.
|
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
|
.El
|
||||||
.Sh ARM MACHINE-SPECIFIC REQUESTS
|
.Sh ARM MACHINE-SPECIFIC REQUESTS
|
||||||
.Bl -tag -width "Dv PT_SETVFPREGS"
|
.Bl -tag -width "Dv PT_SETVFPREGS"
|
||||||
|
@ -429,4 +429,11 @@ struct timex32 {
|
|||||||
int32_t stbcnt;
|
int32_t stbcnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ptrace_coredump32 {
|
||||||
|
int pc_fd;
|
||||||
|
uint32_t pc_flags;
|
||||||
|
uint32_t pc_limit1, pc_limit2;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif /* !_COMPAT_FREEBSD32_FREEBSD32_H_ */
|
#endif /* !_COMPAT_FREEBSD32_FREEBSD32_H_ */
|
||||||
|
@ -932,6 +932,7 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap)
|
|||||||
struct ptrace_io_desc piod;
|
struct ptrace_io_desc piod;
|
||||||
struct ptrace_lwpinfo pl;
|
struct ptrace_lwpinfo pl;
|
||||||
struct ptrace_vm_entry pve;
|
struct ptrace_vm_entry pve;
|
||||||
|
struct ptrace_coredump pc;
|
||||||
struct dbreg32 dbreg;
|
struct dbreg32 dbreg;
|
||||||
struct fpreg32 fpreg;
|
struct fpreg32 fpreg;
|
||||||
struct reg32 reg;
|
struct reg32 reg;
|
||||||
@ -943,6 +944,7 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap)
|
|||||||
struct ptrace_io_desc32 piod;
|
struct ptrace_io_desc32 piod;
|
||||||
struct ptrace_lwpinfo32 pl;
|
struct ptrace_lwpinfo32 pl;
|
||||||
struct ptrace_vm_entry32 pve;
|
struct ptrace_vm_entry32 pve;
|
||||||
|
struct ptrace_coredump32 pc;
|
||||||
uint32_t args[nitems(td->td_sa.args)];
|
uint32_t args[nitems(td->td_sa.args)];
|
||||||
struct ptrace_sc_ret32 psr;
|
struct ptrace_sc_ret32 psr;
|
||||||
} r32;
|
} r32;
|
||||||
@ -1021,6 +1023,16 @@ freebsd32_ptrace(struct thread *td, struct freebsd32_ptrace_args *uap)
|
|||||||
CP(r32.pve, r.pve, pve_fsid);
|
CP(r32.pve, r.pve, pve_fsid);
|
||||||
PTRIN_CP(r32.pve, r.pve, pve_path);
|
PTRIN_CP(r32.pve, r.pve, pve_path);
|
||||||
break;
|
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:
|
default:
|
||||||
addr = uap->addr;
|
addr = uap->addr;
|
||||||
break;
|
break;
|
||||||
|
@ -2521,6 +2521,42 @@ tdsigwakeup(struct thread *td, int sig, sig_t action, int intrval)
|
|||||||
thread_unlock(td);
|
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
|
static int
|
||||||
sig_suspend_threads(struct thread *td, struct proc *p, int sending)
|
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;
|
td->td_dbgflags |= TDB_SSWITCH;
|
||||||
thread_suspend_switch(td, p);
|
thread_suspend_switch(td, p);
|
||||||
td->td_dbgflags &= ~TDB_SSWITCH;
|
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)
|
if (p->p_xthread == td)
|
||||||
p->p_xthread = NULL;
|
p->p_xthread = NULL;
|
||||||
if (!(p->p_flag & P_TRACED))
|
if (!(p->p_flag & P_TRACED))
|
||||||
|
@ -51,6 +51,8 @@ __FBSDID("$FreeBSD$");
|
|||||||
#include <sys/sx.h>
|
#include <sys/sx.h>
|
||||||
#include <sys/malloc.h>
|
#include <sys/malloc.h>
|
||||||
#include <sys/signalvar.h>
|
#include <sys/signalvar.h>
|
||||||
|
#include <sys/caprights.h>
|
||||||
|
#include <sys/filedesc.h>
|
||||||
|
|
||||||
#include <machine/reg.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_io_desc piod;
|
||||||
struct ptrace_lwpinfo pl;
|
struct ptrace_lwpinfo pl;
|
||||||
struct ptrace_vm_entry pve;
|
struct ptrace_vm_entry pve;
|
||||||
|
struct ptrace_coredump pc;
|
||||||
struct dbreg dbreg;
|
struct dbreg dbreg;
|
||||||
struct fpreg fpreg;
|
struct fpreg fpreg;
|
||||||
struct reg reg;
|
struct reg reg;
|
||||||
@ -519,6 +522,12 @@ sys_ptrace(struct thread *td, struct ptrace_args *uap)
|
|||||||
case PT_VM_ENTRY:
|
case PT_VM_ENTRY:
|
||||||
error = copyin(uap->addr, &r.pve, sizeof(r.pve));
|
error = copyin(uap->addr, &r.pve, sizeof(r.pve));
|
||||||
break;
|
break;
|
||||||
|
case PT_COREDUMP:
|
||||||
|
if (uap->data != sizeof(r.pc))
|
||||||
|
error = EINVAL;
|
||||||
|
else
|
||||||
|
error = copyin(uap->addr, &r.pc, uap->data);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
addr = uap->addr;
|
addr = uap->addr;
|
||||||
break;
|
break;
|
||||||
@ -632,6 +641,22 @@ proc_can_ptrace(struct thread *td, struct proc *p)
|
|||||||
|
|
||||||
return (0);
|
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
|
int
|
||||||
kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
|
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_io_desc *piod = NULL;
|
||||||
struct ptrace_lwpinfo *pl;
|
struct ptrace_lwpinfo *pl;
|
||||||
struct ptrace_sc_ret *psr;
|
struct ptrace_sc_ret *psr;
|
||||||
|
struct file *fp;
|
||||||
|
struct ptrace_coredump *pc;
|
||||||
|
struct thr_coredump_req *tcq;
|
||||||
int error, num, tmp;
|
int error, num, tmp;
|
||||||
lwpid_t tid = 0, *buf;
|
lwpid_t tid = 0, *buf;
|
||||||
#ifdef COMPAT_FREEBSD32
|
#ifdef COMPAT_FREEBSD32
|
||||||
@ -1348,6 +1376,62 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
|
|||||||
PROC_LOCK(p);
|
PROC_LOCK(p);
|
||||||
break;
|
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:
|
default:
|
||||||
#ifdef __HAVE_PTRACE_MACHDEP
|
#ifdef __HAVE_PTRACE_MACHDEP
|
||||||
if (req >= PT_FIRSTMACH) {
|
if (req >= PT_FIRSTMACH) {
|
||||||
@ -1360,7 +1444,6 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
|
|||||||
error = EINVAL;
|
error = EINVAL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
/* Drop our hold on this process now that the request has completed. */
|
/* Drop our hold on this process now that the request has completed. */
|
||||||
_PRELE(p);
|
_PRELE(p);
|
||||||
|
@ -377,6 +377,7 @@ struct thread {
|
|||||||
int td_oncpu; /* (t) Which cpu we are on. */
|
int td_oncpu; /* (t) Which cpu we are on. */
|
||||||
void *td_lkpi_task; /* LinuxKPI task struct pointer */
|
void *td_lkpi_task; /* LinuxKPI task struct pointer */
|
||||||
int td_pmcpend;
|
int td_pmcpend;
|
||||||
|
void *td_coredump; /* (c) coredump request. */
|
||||||
#ifdef EPOCH_TRACE
|
#ifdef EPOCH_TRACE
|
||||||
SLIST_HEAD(, epoch_tracker) td_epochs;
|
SLIST_HEAD(, epoch_tracker) td_epochs;
|
||||||
#endif
|
#endif
|
||||||
@ -486,6 +487,7 @@ do { \
|
|||||||
#define TDB_FSTP 0x00001000 /* The thread is PT_ATTACH leader */
|
#define TDB_FSTP 0x00001000 /* The thread is PT_ATTACH leader */
|
||||||
#define TDB_STEP 0x00002000 /* (x86) PSL_T set for PT_STEP */
|
#define TDB_STEP 0x00002000 /* (x86) PSL_T set for PT_STEP */
|
||||||
#define TDB_SSWITCH 0x00004000 /* Suspended in ptracestop */
|
#define TDB_SSWITCH 0x00004000 /* Suspended in ptracestop */
|
||||||
|
#define TDB_COREDUMPRQ 0x00008000 /* Coredump request */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "Private" flags kept in td_pflags:
|
* "Private" flags kept in td_pflags:
|
||||||
|
@ -74,6 +74,8 @@
|
|||||||
#define PT_GET_SC_ARGS 27 /* fetch syscall args */
|
#define PT_GET_SC_ARGS 27 /* fetch syscall args */
|
||||||
#define PT_GET_SC_RET 28 /* fetch syscall results */
|
#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_GETREGS 33 /* get general-purpose registers */
|
||||||
#define PT_SETREGS 34 /* set general-purpose registers */
|
#define PT_SETREGS 34 /* set general-purpose registers */
|
||||||
#define PT_GETFPREGS 35 /* get floating-point registers */
|
#define PT_GETFPREGS 35 /* get floating-point registers */
|
||||||
@ -176,8 +178,27 @@ struct ptrace_vm_entry {
|
|||||||
char *pve_path; /* Path name of object. */
|
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
|
#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_set_pc(struct thread *_td, unsigned long _addr);
|
||||||
int ptrace_single_step(struct thread *_td);
|
int ptrace_single_step(struct thread *_td);
|
||||||
int ptrace_clear_single_step(struct thread *_td);
|
int ptrace_clear_single_step(struct thread *_td);
|
||||||
|
Loading…
Reference in New Issue
Block a user