ptrace.c revision 8fd04b8338ed5093ec2d1e668fa620b7de44c177
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <errno.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/lx_misc.h>
#include <sys/lx_debug.h>
#include <sys/lx_syscall.h>
#include <sys/lx_signal.h>
#include <sys/lx_thread.h>
#include <sys/lwp.h>
#include <unistd.h>
#include <fcntl.h>
#include <procfs.h>
#include <sys/frame.h>
#include <strings.h>
#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/auxv.h>
#include <thread.h>
#include <pthread.h>
#include <synch.h>
#include <elf.h>
#include <ieeefp.h>
#include <assert.h>
#include <libintl.h>
/*
* Linux ptrace compatibility.
*
* The brand support for ptrace(2) is built on top of the Solaris /proc
* interfaces, mounted at /native/proc in the zone. This gets quite
* complicated due to the way ptrace works and the Solaris realization of the
* Linux threading model.
*
* ptrace can only interact with a process if we are tracing it, and it is
* currently stopped. There are two ways a process can begin tracing another
* process:
*
* PTRACE_TRACEME
*
* A child process can use PTRACE_TRACEME to indicate that it wants to be
* traced by the parent. This sets the ptrace compatibility flag in /proc
* which causes ths ptrace consumer to be notified through the wait(2)
* system call of events of interest. PTRACE_TRACEME is typically used by
* the debugger by forking a process, using PTRACE_TRACEME, and finally
* doing an exec of the specified program.
*
*
* PTRACE_ATTACH
*
* We can attach to a process using PTRACE_ATTACH. This is considerably
* more complicated than the previous case. On Linux, the traced process is
* effectively reparented to the ptrace consumer so that event notification
* can go through the normal wait(2) system call. Solaris has no such
* ability to reparent a process (nor should it) so some trickery was
* required.
*
* When the ptrace consumer uses PTRACE_ATTACH it forks a monitor child
* process. The monitor enables the /proc ptrace flag for itself and uses
* the native /proc mechanisms to observe the traced process and wait for
* events of interest. When the traced process stops, the monitor process
* sends itself a SIGTRAP thus rousting its parent process (the ptrace
* consumer) out of wait(2). We then translate the process id and status
* code from wait(2) to those of the traced process.
*
* To detach from the process we just have to clean up tracing flags and
* clean up the monitor.
*
* ptrace can only interact with a process if we have traced it, and it is
* currently stopped (see is_traced()). For threads, there's no way to
* distinguish whether ptrace() has been called for all threads or some
* subset. Since most clients will be tracing all threads, and erroneously
* allowing ptrace to access a non-traced thread is non-fatal (or at least
* would be fatal on linux), we ignore this aspect of the problem.
*/
#define LX_PTRACE_TRACEME 0
#define LX_PTRACE_PEEKTEXT 1
#define LX_PTRACE_PEEKDATA 2
#define LX_PTRACE_PEEKUSER 3
#define LX_PTRACE_POKETEXT 4
#define LX_PTRACE_POKEDATA 5
#define LX_PTRACE_POKEUSER 6
#define LX_PTRACE_CONT 7
#define LX_PTRACE_KILL 8
#define LX_PTRACE_SINGLESTEP 9
#define LX_PTRACE_GETREGS 12
#define LX_PTRACE_SETREGS 13
#define LX_PTRACE_GETFPREGS 14
#define LX_PTRACE_SETFPREGS 15
#define LX_PTRACE_ATTACH 16
#define LX_PTRACE_DETACH 17
#define LX_PTRACE_GETFPXREGS 18
#define LX_PTRACE_SETFPXREGS 19
#define LX_PTRACE_SYSCALL 24
/*
* This corresponds to the user_i387_struct Linux structure.
*/
typedef struct lx_user_fpregs {
long lxuf_cwd;
long lxuf_swd;
long lxuf_twd;
long lxuf_fip;
long lxuf_fcs;
long lxuf_foo;
long lxuf_fos;
long lxuf_st_space[20];
} lx_user_fpregs_t;
/*
* This corresponds to the user_fxsr_struct Linux structure.
*/
typedef struct lx_user_fpxregs {
uint16_t lxux_cwd;
uint16_t lxux_swd;
uint16_t lxux_twd;
uint16_t lxux_fop;
long lxux_fip;
long lxux_fcs;
long lxux_foo;
long lxux_fos;
long lxux_mxcsr;
long lxux_reserved;
long lxux_st_space[32];
long lxux_xmm_space[32];
long lxux_padding[56];
} lx_user_fpxregs_t;
/*
* This corresponds to the user_regs_struct Linux structure.
*/
typedef struct lx_user_regs {
long lxur_ebx;
long lxur_ecx;
long lxur_edx;
long lxur_esi;
long lxur_edi;
long lxur_ebp;
long lxur_eax;
long lxur_xds;
long lxur_xes;
long lxur_xfs;
long lxur_xgs;
long lxur_orig_eax;
long lxur_eip;
long lxur_xcs;
long lxur_eflags;
long lxur_esp;
long lxur_xss;
} lx_user_regs_t;
typedef struct lx_user {
lx_user_regs_t lxu_regs;
int lxu_fpvalid;
lx_user_fpregs_t lxu_i387;
ulong_t lxu_tsize;
ulong_t lxu_dsize;
ulong_t lxu_ssize;
ulong_t lxu_start_code;
ulong_t lxu_start_stack;
long lxu_signal;
int lxu_reserved;
lx_user_regs_t *lxu_ar0;
lx_user_fpregs_t *lxu_fpstate;
ulong_t lxu_magic;
char lxu_comm[32];
int lxu_debugreg[8];
} lx_user_t;
typedef struct ptrace_monitor_map {
struct ptrace_monitor_map *pmm_next; /* next pointer */
pid_t pmm_monitor; /* monitor child process */
pid_t pmm_target; /* traced Linux pid */
pid_t pmm_pid; /* Solaris pid */
lwpid_t pmm_lwpid; /* Solaris lwpid */
uint_t pmm_exiting; /* detached */
} ptrace_monitor_map_t;
typedef struct ptrace_state_map {
struct ptrace_state_map *psm_next; /* next pointer */
pid_t psm_pid; /* Solaris pid */
uintptr_t psm_debugreg[8]; /* debug registers */
} ptrace_state_map_t;
static ptrace_monitor_map_t *ptrace_monitor_map = NULL;
static ptrace_state_map_t *ptrace_state_map = NULL;
static mutex_t ptrace_map_mtx = DEFAULTMUTEX;
extern void *_START_;
static sigset_t blockable_sigs;
#pragma init(ptrace_init)
void
ptrace_init(void)
{
(void) sigfillset(&blockable_sigs);
(void) sigdelset(&blockable_sigs, SIGKILL);
(void) sigdelset(&blockable_sigs, SIGSTOP);
}
/*
* Given a pid, open the named file under /native/proc/<pid>/name using the
* given mode.
*/
static int
open_procfile(pid_t pid, int mode, const char *name)
{
char path[MAXPATHLEN];
(void) snprintf(path, sizeof (path), "/native/proc/%d/%s", pid, name);
return (open(path, mode));
}
/*
* Given a pid and lwpid, open the named file under
* /native/proc/<pid>/<lwpid>/name using the given mode.
*/
static int
open_lwpfile(pid_t pid, lwpid_t lwpid, int mode, const char *name)
{
char path[MAXPATHLEN];
(void) snprintf(path, sizeof (path), "/native/proc/%d/lwp/%d/%s",
pid, lwpid, name);
return (open(path, mode));
}
static int
get_status(pid_t pid, pstatus_t *psp)
{
int fd;
if ((fd = open_procfile(pid, O_RDONLY, "status")) < 0)
return (-ESRCH);
if (read(fd, psp, sizeof (pstatus_t)) != sizeof (pstatus_t)) {
(void) close(fd);
return (-EIO);
}
(void) close(fd);
return (0);
}
static int
get_lwpstatus(pid_t pid, lwpid_t lwpid, lwpstatus_t *lsp)
{
int fd;
if ((fd = open_lwpfile(pid, lwpid, O_RDONLY, "lwpstatus")) < 0)
return (-ESRCH);
if (read(fd, lsp, sizeof (lwpstatus_t)) != sizeof (lwpstatus_t)) {
(void) close(fd);
return (-EIO);
}
(void) close(fd);
return (0);
}
static uintptr_t
syscall_regs(int fd, uintptr_t fp, pid_t pid)
{
uintptr_t addr, done;
struct frame fr;
auxv_t auxv;
int afd;
Elf32_Phdr phdr;
/*
* Try to walk the stack looking for a return address that corresponds
* to the traced process's lx_emulate_done symbol. This relies on the
* fact that the brand library in the traced process is the same as the
* brand library in this process (indeed, this is true of all processes
* in a given branded zone).
*/
/*
* Find the base address for the brand library in the traced process
* by grabbing the AT_PHDR auxv entry, reading in the program header
* at that location and subtracting off the p_vaddr member. We use
* this to compute the location of lx_emulate done in the traced
* process.
*/
if ((afd = open_procfile(pid, O_RDONLY, "auxv")) < 0)
return (0);
do {
if (read(afd, &auxv, sizeof (auxv)) != sizeof (auxv)) {
(void) close(afd);
return (0);
}
} while (auxv.a_type != AT_PHDR);
(void) close(afd);
if (pread(fd, &phdr, sizeof (phdr), auxv.a_un.a_val) != sizeof (phdr)) {
lx_debug("failed to read brand library's phdr");
return (0);
}
addr = auxv.a_un.a_val - phdr.p_vaddr;
done = (uintptr_t)&lx_emulate_done - (uintptr_t)&_START_ + addr;
fr.fr_savfp = fp;
do {
addr = fr.fr_savfp;
if (pread(fd, &fr, sizeof (fr), addr) != sizeof (fr)) {
lx_debug("ptrace read failed for stack walk");
return (0);
}
if (addr >= fr.fr_savfp) {
lx_debug("ptrace stack not monotonically increasing "
"%p %p (%p)", addr, fr.fr_savfp, done);
return (0);
}
} while (fr.fr_savpc != done);
/*
* The first argument to lx_emulate is known to be an lx_regs_t
* structure and the ABI specifies that it will be placed on the stack
* immediately preceeding the return address.
*/
addr += sizeof (fr);
if (pread(fd, &addr, sizeof (addr), addr) != sizeof (addr)) {
lx_debug("ptrace stack failed to read register set address");
return (0);
}
return (addr);
}
static int
getregs(pid_t pid, lwpid_t lwpid, lx_user_regs_t *rp)
{
lwpstatus_t status;
uintptr_t addr;
int fd, ret;
if ((ret = get_lwpstatus(pid, lwpid, &status)) != 0)
return (ret);
if ((fd = open_procfile(pid, O_RDONLY, "as")) < 0)
return (-ESRCH);
/*
* If we find the syscall regs (and are therefore in an emulated
* syscall, use the register set at given address. Otherwise, use the
* registers as reported by /proc.
*/
if ((addr = syscall_regs(fd, status.pr_reg[EBP], pid)) != 0) {
lx_regs_t regs;
if (pread(fd, &regs, sizeof (regs), addr) != sizeof (regs)) {
(void) close(fd);
lx_debug("ptrace failed to read register set");
return (-EIO);
}
(void) close(fd);
rp->lxur_ebx = regs.lxr_ebx;
rp->lxur_ecx = regs.lxr_ecx;
rp->lxur_edx = regs.lxr_edx;
rp->lxur_esi = regs.lxr_esi;
rp->lxur_edi = regs.lxr_edi;
rp->lxur_ebp = regs.lxr_ebp;
rp->lxur_eax = regs.lxr_eax;
rp->lxur_xds = status.pr_reg[DS];
rp->lxur_xes = status.pr_reg[ES];
rp->lxur_xfs = status.pr_reg[FS];
rp->lxur_xgs = regs.lxr_gs;
rp->lxur_orig_eax = regs.lxr_orig_eax;
rp->lxur_eip = regs.lxr_eip;
rp->lxur_xcs = status.pr_reg[CS];
rp->lxur_eflags = status.pr_reg[EFL];
rp->lxur_esp = regs.lxr_esp;
rp->lxur_xss = status.pr_reg[SS];
} else {
(void) close(fd);
rp->lxur_ebx = status.pr_reg[EBX];
rp->lxur_ecx = status.pr_reg[ECX];
rp->lxur_edx = status.pr_reg[EDX];
rp->lxur_esi = status.pr_reg[ESI];
rp->lxur_edi = status.pr_reg[EDI];
rp->lxur_ebp = status.pr_reg[EBP];
rp->lxur_eax = status.pr_reg[EAX];
rp->lxur_xds = status.pr_reg[DS];
rp->lxur_xes = status.pr_reg[ES];
rp->lxur_xfs = status.pr_reg[FS];
rp->lxur_xgs = status.pr_reg[GS];
rp->lxur_orig_eax = 0;
rp->lxur_eip = status.pr_reg[EIP];
rp->lxur_xcs = status.pr_reg[CS];
rp->lxur_eflags = status.pr_reg[EFL];
rp->lxur_esp = status.pr_reg[UESP];
rp->lxur_xss = status.pr_reg[SS];
/*
* If the target process has just returned from exec, it's not
* going to be sitting in the emulation function. In that case
* we need to manually fake up the values for %eax and orig_eax
* to indicate a successful return and that the traced process
* had called execve (respectively).
*/
if (status.pr_why == PR_SYSEXIT &&
status.pr_what == SYS_execve) {
rp->lxur_eax = 0;
rp->lxur_orig_eax = LX_SYS_execve;
}
}
return (0);
}
static int
setregs(pid_t pid, lwpid_t lwpid, const lx_user_regs_t *rp)
{
long ctl[1 + sizeof (prgregset_t) / sizeof (long)];
lwpstatus_t status;
uintptr_t addr;
int fd, ret;
if ((ret = get_lwpstatus(pid, lwpid, &status)) != 0)
return (ret);
if ((fd = open_procfile(pid, O_RDWR, "as")) < 0)
return (-ESRCH);
/*
* If we find the syscall regs (and are therefore in an emulated
* syscall, modify the register set at given address and set the
* remaining registers through the /proc interface. Otherwise just use
* the /proc interface to set register values;
*/
if ((addr = syscall_regs(fd, status.pr_reg[EBP], pid)) != 0) {
lx_regs_t regs;
regs.lxr_ebx = rp->lxur_ebx;
regs.lxr_ecx = rp->lxur_ecx;
regs.lxr_edx = rp->lxur_edx;
regs.lxr_esi = rp->lxur_esi;
regs.lxr_edi = rp->lxur_edi;
regs.lxr_ebp = rp->lxur_ebp;
regs.lxr_eax = rp->lxur_eax;
regs.lxr_gs = rp->lxur_xgs;
regs.lxr_orig_eax = rp->lxur_orig_eax;
regs.lxr_eip = rp->lxur_eip;
regs.lxr_esp = rp->lxur_esp;
if (pwrite(fd, &regs, sizeof (regs), addr) != sizeof (regs)) {
(void) close(fd);
lx_debug("ptrace failed to write register set");
return (-EIO);
}
(void) close(fd);
status.pr_reg[DS] = rp->lxur_xds;
status.pr_reg[ES] = rp->lxur_xes;
status.pr_reg[FS] = rp->lxur_xfs;
status.pr_reg[CS] = rp->lxur_xcs;
status.pr_reg[EFL] = rp->lxur_eflags;
status.pr_reg[SS] = rp->lxur_xss;
} else {
(void) close(fd);
status.pr_reg[EBX] = rp->lxur_ebx;
status.pr_reg[ECX] = rp->lxur_ecx;
status.pr_reg[EDX] = rp->lxur_edx;
status.pr_reg[ESI] = rp->lxur_esi;
status.pr_reg[EDI] = rp->lxur_edi;
status.pr_reg[EBP] = rp->lxur_ebp;
status.pr_reg[EAX] = rp->lxur_eax;
status.pr_reg[DS] = rp->lxur_xds;
status.pr_reg[ES] = rp->lxur_xes;
status.pr_reg[FS] = rp->lxur_xfs;
status.pr_reg[GS] = rp->lxur_xgs;
status.pr_reg[EIP] = rp->lxur_eip;
status.pr_reg[CS] = rp->lxur_xcs;
status.pr_reg[EFL] = rp->lxur_eflags;
status.pr_reg[UESP] = rp->lxur_esp;
status.pr_reg[SS] = rp->lxur_xss;
status.pr_reg[SS] = rp->lxur_xss;
}
if ((fd = open_lwpfile(pid, lwpid, O_WRONLY, "lwpctl")) < 0)
return (-ESRCH);
ctl[0] = PCSREG;
bcopy(status.pr_reg, &ctl[1], sizeof (prgregset_t));
if (write(fd, &ctl, sizeof (ctl)) != sizeof (ctl)) {
(void) close(fd);
return (-EIO);
}
(void) close(fd);
return (0);
}
static int
getfpregs(pid_t pid, lwpid_t lwpid, lx_user_fpregs_t *rp)
{
lwpstatus_t status;
struct _fpstate *fp;
char *data;
int ret, i;
if ((ret = get_lwpstatus(pid, lwpid, &status)) != 0)
return (ret);
fp = (struct _fpstate *)&status.pr_fpreg.fp_reg_set.fpchip_state;
rp->lxuf_cwd = fp->cw;
rp->lxuf_swd = fp->sw;
rp->lxuf_twd = fp->tag;
rp->lxuf_fip = fp->ipoff;
rp->lxuf_fcs = fp->cssel;
rp->lxuf_foo = fp->dataoff;
rp->lxuf_fos = fp->datasel;
/*
* The Linux structure uses 10 bytes per floating-point register.
*/
data = (char *)&rp->lxuf_st_space[0];
for (i = 0; i < 8; i++) {
bcopy(&fp->_st[i], data, 10);
data += 10;
}
return (0);
}
static int
setfpregs(pid_t pid, lwpid_t lwpid, const lx_user_fpregs_t *rp)
{
lwpstatus_t status;
struct {
long cmd;
prfpregset_t regs;
} ctl;
struct _fpstate *fp = (struct _fpstate *)&ctl.regs;
char *data;
int ret, i, fd;
if ((ret = get_lwpstatus(pid, lwpid, &status)) != 0)
return (ret);
bcopy(&status.pr_fpreg, &ctl.regs, sizeof (ctl.regs));
fp->cw = rp->lxuf_cwd;
fp->sw = rp->lxuf_swd;
fp->tag = rp->lxuf_twd;
fp->ipoff = rp->lxuf_fip;
fp->cssel = rp->lxuf_fcs;
fp->dataoff = rp->lxuf_foo;
fp->datasel = rp->lxuf_fos;
/*
* The Linux structure uses 10 bytes per floating-point register.
*/
data = (char *)&rp->lxuf_st_space[0];
for (i = 0; i < 8; i++) {
bcopy(data, &fp->_st[i], 10);
data += 10;
}
if ((fd = open_lwpfile(pid, lwpid, O_WRONLY, "lwpctl")) < 0)
return (-ESRCH);
ctl.cmd = PCSFPREG;
if (write(fd, &ctl, sizeof (ctl)) != sizeof (ctl)) {
(void) close(fd);
return (-EIO);
}
(void) close(fd);
return (0);
}
static int
getfpxregs(pid_t pid, lwpid_t lwpid, lx_user_fpxregs_t *rp)
{
lwpstatus_t status;
struct _fpstate *fp;
int ret, i;
if ((ret = get_lwpstatus(pid, lwpid, &status)) != 0)
return (ret);
fp = (struct _fpstate *)&status.pr_fpreg.fp_reg_set.fpchip_state;
rp->lxux_cwd = (uint16_t)fp->cw;
rp->lxux_swd = (uint16_t)fp->sw;
rp->lxux_twd = (uint16_t)fp->tag;
rp->lxux_fop = (uint16_t)(fp->cssel >> 16);
rp->lxux_fip = fp->ipoff;
rp->lxux_fcs = (uint16_t)fp->cssel;
rp->lxux_foo = fp->dataoff;
rp->lxux_fos = fp->datasel;
rp->lxux_mxcsr = status.pr_fpreg.fp_reg_set.fpchip_state.mxcsr;
bcopy(fp->xmm, rp->lxux_xmm_space, sizeof (rp->lxux_xmm_space));
bzero(rp->lxux_st_space, sizeof (rp->lxux_st_space));
for (i = 0; i < 8; i++) {
bcopy(&fp->_st[i], &rp->lxux_st_space[i * 4],
sizeof (fp->_st[i]));
}
return (0);
}
static int
setfpxregs(pid_t pid, lwpid_t lwpid, const lx_user_fpxregs_t *rp)
{
lwpstatus_t status;
struct {
long cmd;
prfpregset_t regs;
} ctl;
struct _fpstate *fp = (struct _fpstate *)&ctl.regs;
int ret, i, fd;
if ((ret = get_lwpstatus(pid, lwpid, &status)) != 0)
return (ret);
bcopy(&status.pr_fpreg, &ctl.regs, sizeof (ctl.regs));
fp->cw = rp->lxux_cwd;
fp->sw = rp->lxux_swd;
fp->tag = rp->lxux_twd;
fp->ipoff = rp->lxux_fip;
fp->cssel = rp->lxux_fcs | (rp->lxux_fop << 16);
fp->dataoff = rp->lxux_foo;
fp->datasel = rp->lxux_fos;
bcopy(rp->lxux_xmm_space, fp->xmm, sizeof (rp->lxux_xmm_space));
for (i = 0; i < 8; i++) {
bcopy(&rp->lxux_st_space[i * 4], &fp->_st[i],
sizeof (fp->_st[i]));
}
if ((fd = open_lwpfile(pid, lwpid, O_WRONLY, "lwpctl")) < 0)
return (-ESRCH);
ctl.cmd = PCSFPREG;
if (write(fd, &ctl, sizeof (ctl)) != sizeof (ctl)) {
(void) close(fd);
return (-EIO);
}
(void) close(fd);
return (0);
}
/*
* Solaris does not allow a process to manipulate its own or some
* other process's debug registers. Linux ptrace(2) allows this
* and gdb manipulates them for its watchpoint implementation.
*
* We keep a pseudo set of debug registers for each traced process
* and map their contents into the appropriate PCWATCH /proc
* operations when they are activated by gdb.
*
* To understand how the debug registers work on x86 machines,
* see section 13.1 of the AMD x86-64 Architecture Programmer's
* Manual, Volume 2, System Programming.
*/
static uintptr_t *
debug_registers(pid_t pid)
{
ptrace_state_map_t *p;
(void) mutex_lock(&ptrace_map_mtx);
for (p = ptrace_state_map; p != NULL; p = p->psm_next) {
if (p->psm_pid == pid)
break;
}
if (p == NULL && (p = malloc(sizeof (*p))) != NULL) {
bzero(p, sizeof (*p));
p->psm_pid = pid;
p->psm_next = ptrace_state_map;
p->psm_debugreg[6] = 0xffff0ff0; /* read as ones */
ptrace_state_map = p;
}
(void) mutex_unlock(&ptrace_map_mtx);
return (p != NULL? p->psm_debugreg : NULL);
}
static void
free_debug_registers(pid_t pid)
{
ptrace_state_map_t **pp;
ptrace_state_map_t *p;
/* ASSERT(MUTEX_HELD(&ptrace_map_mtx) */
for (pp = &ptrace_state_map; (p = *pp) != NULL; pp = &p->psm_next) {
if (p->psm_pid == pid) {
*pp = p->psm_next;
free(p);
break;
}
}
}
static int
setup_watchpoints(pid_t pid, uintptr_t *debugreg)
{
int dr7 = debugreg[7];
int lrw;
int fd;
size_t size;
prwatch_t prwatch[4];
int nwatch;
int i;
int wflags;
int error;
struct {
long req;
prwatch_t prwatch;
} ctl;
/* find all watched areas */
if ((fd = open_procfile(pid, O_RDONLY, "watch")) < 0)
return (-ESRCH);
nwatch = read(fd, prwatch, sizeof (prwatch)) / sizeof (prwatch_t);
(void) close(fd);
if ((fd = open_procfile(pid, O_WRONLY, "ctl")) < 0)
return (-ESRCH);
/* clear all watched areas */
for (i = 0; i < nwatch; i++) {
ctl.req = PCWATCH;
ctl.prwatch = prwatch[i];
ctl.prwatch.pr_wflags = 0;
if (write(fd, &ctl, sizeof (ctl)) != sizeof (ctl)) {
error = -errno;
(void) close(fd);
return (error);
}
}
/* establish all new watched areas */
for (i = 0; i < 4; i++) {
if ((dr7 & (1 << (2 * i))) == 0) /* enabled? */
continue;
lrw = (dr7 >> (16 + (4 * i))) & 0xf;
switch (lrw >> 2) { /* length */
case 0: size = 1; break;
case 1: size = 2; break;
case 2: size = 8; break;
case 3: size = 4; break;
}
switch (lrw & 0x3) { /* mode */
case 0: wflags = WA_EXEC; break;
case 1: wflags = WA_WRITE; break;
case 2: continue;
case 3: wflags = WA_READ | WA_WRITE; break;
}
ctl.req = PCWATCH;
ctl.prwatch.pr_vaddr = debugreg[i];
ctl.prwatch.pr_size = size;
ctl.prwatch.pr_wflags = wflags | WA_TRAPAFTER;
if (write(fd, &ctl, sizeof (ctl)) != sizeof (ctl)) {
error = -errno;
(void) close(fd);
return (error);
}
}
(void) close(fd);
return (0);
}
/*
* Returns TRUE if the process is traced, FALSE otherwise. This is only true
* if the process is currently stopped, and has been traced using PTRACE_TRACEME
* or PTRACE_ATTACH.
*/
static int
is_traced(pid_t pid)
{
ptrace_monitor_map_t *p;
pstatus_t status;
if (get_status(pid, &status) != 0)
return (0);
if ((status.pr_flags & PR_PTRACE) &&
(status.pr_ppid == getpid()) &&
(status.pr_lwp.pr_flags & PR_ISTOP))
return (1);
(void) mutex_lock(&ptrace_map_mtx);
for (p = ptrace_monitor_map; p != NULL; p = p->pmm_next) {
if (p->pmm_target == pid) {
(void) mutex_unlock(&ptrace_map_mtx);
return (1);
}
}
(void) mutex_unlock(&ptrace_map_mtx);
return (0);
}
static int
ptrace_trace_common(int fd)
{
struct {
long cmd;
union {
long flags;
sigset_t signals;
fltset_t faults;
} arg;
} ctl;
size_t size;
ctl.cmd = PCSTRACE;
prfillset(&ctl.arg.signals);
size = sizeof (long) + sizeof (sigset_t);
if (write(fd, &ctl, size) != size)
return (-1);
ctl.cmd = PCSFAULT;
premptyset(&ctl.arg.faults);
size = sizeof (long) + sizeof (fltset_t);
if (write(fd, &ctl, size) != size)
return (-1);
ctl.cmd = PCUNSET;
ctl.arg.flags = PR_FORK;
size = sizeof (long) + sizeof (long);
if (write(fd, &ctl, size) != size)
return (-1);
return (0);
}
/*
* Notify that parent that we wish to be traced. This is the equivalent of:
*
* 1. Stop on all signals, and nothing else
* 2. Turn off inherit-on-fork flag
* 3. Set ptrace compatible flag
*
* If we are not the main thread, then the client is trying to request behavior
* by which one of its own thread is to be traced. We don't support this mode
* of operation.
*/
static int
ptrace_traceme(void)
{
int fd, ret;
int error;
long ctl[2];
pstatus_t status;
pid_t pid = getpid();
if (_lwp_self() != 1) {
lx_unsupported(gettext(
"thread %d calling PTRACE_TRACEME is unsupported"),
_lwp_self());
return (-ENOTSUP);
}
if ((ret = get_status(pid, &status)) != 0)
return (ret);
/*
* Why would a process try to do this twice? I'm not sure, but there's
* a conformance test which wants this to fail just so.
*/
if (status.pr_flags & PR_PTRACE)
return (-EPERM);
if ((fd = open_procfile(pid, O_WRONLY, "ctl")) < 0)
return (-errno);
ctl[0] = PCSET;
ctl[1] = PR_PTRACE;
error = 0;
if (write(fd, ctl, sizeof (ctl)) != sizeof (ctl) ||
ptrace_trace_common(fd) != 0)
error = -errno;
(void) close(fd);
return (error);
}
/*
* Read a word of data from the given address. Because this is a process-wide
* action, we don't need the lwpid.
*/
static int
ptrace_peek(pid_t pid, uintptr_t addr, int *ret)
{
int fd, data;
if (!is_traced(pid))
return (-ESRCH);
if ((fd = open_procfile(pid, O_RDONLY, "as")) < 0)
return (-ESRCH);
if (pread(fd, &data, sizeof (data), addr) != sizeof (data)) {
(void) close(fd);
return (-EIO);
}
(void) close(fd);
if (uucopy(&data, ret, sizeof (data)) != 0)
return (-errno);
return (0);
}
#define LX_USER_BOUND(m) \
(offsetof(lx_user_t, m) + sizeof (((lx_user_t *)NULL)->m))
static int
ptrace_peek_user(pid_t pid, lwpid_t lwpid, uintptr_t off, int *ret)
{
int err, data;
uintptr_t *debugreg;
int dreg;
if (!is_traced(pid))
return (-ESRCH);
/*
* The offset specified by the user is an offset into the Linux
* user structure (seriously). Rather than constructing a full
* user structure, we figure out which part of the user structure
* the offset is in, and fill in just that component.
*/
if (off < LX_USER_BOUND(lxu_regs)) {
lx_user_regs_t regs;
if ((err = getregs(pid, lwpid, &regs)) != 0)
return (err);
data = *(int *)((uintptr_t)&regs + off -
offsetof(lx_user_t, lxu_regs));
} else if (off < LX_USER_BOUND(lxu_fpvalid)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_i387)) {
lx_user_fpregs_t regs;
if ((err = getfpregs(pid, lwpid, &regs)) != 0)
return (err);
data = *(int *)((uintptr_t)&regs + off -
offsetof(lx_user_t, lxu_i387));
} else if (off < LX_USER_BOUND(lxu_tsize)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_dsize)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_ssize)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_start_code)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_start_stack)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_signal)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_reserved)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_ar0)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_fpstate)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_magic)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_comm)) {
lx_err(gettext("offset = %lu\n"), off);
assert(0);
} else if (off < LX_USER_BOUND(lxu_debugreg)) {
dreg = (off - offsetof(lx_user_t, lxu_debugreg)) / sizeof (int);
if (dreg == 4) /* aliased */
dreg = 6;
else if (dreg == 5) /* aliased */
dreg = 7;
if ((debugreg = debug_registers(pid)) != NULL)
data = debugreg[dreg];
else
data = 0;
} else {
lx_unsupported(gettext(
"unsupported ptrace %s user offset: 0x%x\n"), "peek", off);
assert(0);
return (-ENOTSUP);
}
if (uucopy(&data, ret, sizeof (data)) != 0)
return (-errno);
return (0);
}
/*
* Write a word of data to the given address. Because this is a process-wide
* action, we don't need the lwpid. Returns EINVAL if the address is not
* word-aligned.
*/
static int
ptrace_poke(pid_t pid, uintptr_t addr, int data)
{
int fd;
if (!is_traced(pid))
return (-ESRCH);
if (addr & 0x3)
return (-EINVAL);
if ((fd = open_procfile(pid, O_WRONLY, "as")) < 0)
return (-ESRCH);
if (pwrite(fd, &data, sizeof (data), addr) != sizeof (data)) {
(void) close(fd);
return (-EIO);
}
(void) close(fd);
return (0);
}
static int
ptrace_poke_user(pid_t pid, lwpid_t lwpid, uintptr_t off, int data)
{
lx_user_regs_t regs;
int err = 0;
uintptr_t *debugreg;
int dreg;
if (!is_traced(pid))
return (-ESRCH);
if (off & 0x3)
return (-EINVAL);
if (off < offsetof(lx_user_t, lxu_regs) + sizeof (lx_user_regs_t)) {
if ((err = getregs(pid, lwpid, &regs)) != 0)
return (err);
*(int *)((uintptr_t)&regs + off -
offsetof(lx_user_t, lxu_regs)) = data;
return (setregs(pid, lwpid, &regs));
}
if (off >= offsetof(lx_user_t, lxu_debugreg) &&
off < offsetof(lx_user_t, lxu_debugreg) + 8 * sizeof (int)) {
dreg = (off - offsetof(lx_user_t, lxu_debugreg)) / sizeof (int);
if (dreg == 4) /* aliased */
dreg = 6;
else if (dreg == 5) /* aliased */
dreg = 7;
if ((debugreg = debug_registers(pid)) != NULL) {
debugreg[dreg] = data;
if (dreg == 7)
err = setup_watchpoints(pid, debugreg);
}
return (err);
}
lx_unsupported(gettext("unsupported ptrace %s user offset: 0x%x\n"),
"poke", off);
assert(0);
return (-ENOTSUP);
}
static int
ptrace_cont_common(int fd, int sig, int run, int step)
{
long ctl[1 + 1 + sizeof (siginfo_t) / sizeof (long) + 2];
long *ctlp = ctl;
size_t size;
assert(0 <= sig && sig < LX_NSIG);
assert(!step || run);
/*
* Clear the current signal.
*/
*ctlp++ = PCCSIG;
/*
* Send a signal if one was specified.
*/
if (sig != 0 && sig != LX_SIGSTOP) {
siginfo_t *infop;
*ctlp++ = PCSSIG;
infop = (siginfo_t *)ctlp;
bzero(infop, sizeof (siginfo_t));
infop->si_signo = ltos_signo[sig];
ctlp += sizeof (siginfo_t) / sizeof (long);
}
/*
* If run is true, set the lwp running.
*/
if (run) {
*ctlp++ = PCRUN;
*ctlp++ = step ? PRSTEP : 0;
}
size = (char *)ctlp - (char *)&ctl[0];
assert(size <= sizeof (ctl));
if (write(fd, ctl, size) != size) {
lx_debug("failed to continue %s", strerror(errno));
return (-EIO);
}
return (0);
}
static int
ptrace_cont_monitor(ptrace_monitor_map_t *p)
{
long ctl[2];
int fd;
fd = open_procfile(p->pmm_monitor, O_WRONLY, "ctl");
if (fd < 0) {
lx_debug("failed to open monitor ctl %d",
errno);
return (-EIO);
}
ctl[0] = PCRUN;
ctl[1] = PRCSIG;
if (write(fd, ctl, sizeof (ctl)) != sizeof (ctl)) {
(void) close(fd);
return (-EIO);
}
(void) close(fd);
return (0);
}
static int
ptrace_cont(pid_t lxpid, pid_t pid, lwpid_t lwpid, int sig, int step)
{
ptrace_monitor_map_t *p;
uintptr_t *debugreg;
int fd, ret;
if (!is_traced(pid))
return (-ESRCH);
if (sig < 0 || sig >= LX_NSIG)
return (-EINVAL);
if ((fd = open_lwpfile(pid, lwpid, O_WRONLY, "lwpctl")) < 0)
return (-ESRCH);
if ((ret = ptrace_cont_common(fd, sig, 1, step)) != 0) {
(void) close(fd);
return (ret);
}
(void) close(fd);
/* kludge: use debugreg[4] to remember the single-step flag */
if ((debugreg = debug_registers(pid)) != NULL)
debugreg[4] = step;
/*
* Check for a monitor and get it moving if we find it. If any of the
* /proc operations fail, we're kind of sunk so just return an error.
*/
(void) mutex_lock(&ptrace_map_mtx);
for (p = ptrace_monitor_map; p != NULL; p = p->pmm_next) {
if (p->pmm_target == lxpid) {
if ((ret = ptrace_cont_monitor(p)) != 0)
return (ret);
break;
}
}
(void) mutex_unlock(&ptrace_map_mtx);
return (0);
}
/*
* If a monitor exists for this traced process, dispose of it.
* First turn off its ptrace flag so we won't be notified of its
* impending demise. We ignore errors for this step since they
* indicate only that the monitor has been damaged due to pilot
* error. Then kill the monitor, and wait for it. If the wait
* succeeds we can dispose of the corpse, otherwise another thread's
* wait call has collected it and we need to set a flag in the
* structure so that if can be picked up in wait.
*/
static void
monitor_kill(pid_t lxpid, pid_t pid)
{
ptrace_monitor_map_t *p, **pp;
pid_t mpid;
int fd;
long ctl[2];
(void) mutex_lock(&ptrace_map_mtx);
free_debug_registers(pid);
for (pp = &ptrace_monitor_map; (p = *pp) != NULL; pp = &p->pmm_next) {
if (p->pmm_target == lxpid) {
mpid = p->pmm_monitor;
if ((fd = open_procfile(mpid, O_WRONLY, "ctl")) >= 0) {
ctl[0] = PCUNSET;
ctl[1] = PR_PTRACE;
(void) write(fd, ctl, sizeof (ctl));
(void) close(fd);
}
(void) kill(mpid, SIGKILL);
if (waitpid(mpid, NULL, 0) == mpid) {
*pp = p->pmm_next;
free(p);
} else {
p->pmm_exiting = 1;
}
break;
}
}
(void) mutex_unlock(&ptrace_map_mtx);
}
static int
ptrace_kill(pid_t lxpid, pid_t pid)
{
int ret;
if (!is_traced(pid))
return (-ESRCH);
ret = kill(pid, SIGKILL);
/* kill off the monitor process, if any */
monitor_kill(lxpid, pid);
return (ret);
}
static int
ptrace_step(pid_t lxpid, pid_t pid, lwpid_t lwpid, int sig)
{
return (ptrace_cont(lxpid, pid, lwpid, sig, 1));
}
static int
ptrace_getregs(pid_t pid, lwpid_t lwpid, uintptr_t addr)
{
lx_user_regs_t regs;
int ret;
if (!is_traced(pid))
return (-ESRCH);
if ((ret = getregs(pid, lwpid, &regs)) != 0)
return (ret);
if (uucopy(&regs, (void *)addr, sizeof (regs)) != 0)
return (-errno);
return (0);
}
static int
ptrace_setregs(pid_t pid, lwpid_t lwpid, uintptr_t addr)
{
lx_user_regs_t regs;
if (!is_traced(pid))
return (-ESRCH);
if (uucopy((void *)addr, &regs, sizeof (regs)) != 0)
return (-errno);
return (setregs(pid, lwpid, &regs));
}
static int
ptrace_getfpregs(pid_t pid, lwpid_t lwpid, uintptr_t addr)
{
lx_user_fpregs_t regs;
int ret;
if (!is_traced(pid))
return (-ESRCH);
if ((ret = getfpregs(pid, lwpid, &regs)) != 0)
return (ret);
if (uucopy(&regs, (void *)addr, sizeof (regs)) != 0)
return (-errno);
return (0);
}
static int
ptrace_setfpregs(pid_t pid, lwpid_t lwpid, uintptr_t addr)
{
lx_user_fpregs_t regs;
if (!is_traced(pid))
return (-ESRCH);
if (uucopy((void *)addr, &regs, sizeof (regs)) != 0)
return (-errno);
return (setfpregs(pid, lwpid, &regs));
}
static int
ptrace_getfpxregs(pid_t pid, lwpid_t lwpid, uintptr_t addr)
{
lx_user_fpxregs_t regs;
int ret;
if (!is_traced(pid))
return (-ESRCH);
if ((ret = getfpxregs(pid, lwpid, &regs)) != 0)
return (ret);
if (uucopy(&regs, (void *)addr, sizeof (regs)) != 0)
return (-errno);
return (0);
}
static int
ptrace_setfpxregs(pid_t pid, lwpid_t lwpid, uintptr_t addr)
{
lx_user_fpxregs_t regs;
if (!is_traced(pid))
return (-ESRCH);
if (uucopy((void *)addr, &regs, sizeof (regs)) != 0)
return (-errno);
return (setfpxregs(pid, lwpid, &regs));
}
static void __NORETURN
ptrace_monitor(int fd)
{
struct {
long cmd;
union {
long flags;
sigset_t signals;
fltset_t faults;
} arg;
} ctl;
size_t size;
int monfd;
int rv;
monfd = open_procfile(getpid(), O_WRONLY, "ctl");
ctl.cmd = PCSTRACE; /* trace only SIGTRAP */
premptyset(&ctl.arg.signals);
praddset(&ctl.arg.signals, SIGTRAP);
size = sizeof (long) + sizeof (sigset_t);
(void) write(monfd, &ctl, size); /* can't fail */
ctl.cmd = PCSFAULT;
premptyset(&ctl.arg.faults);
size = sizeof (long) + sizeof (fltset_t);
(void) write(monfd, &ctl, size); /* can't fail */
ctl.cmd = PCUNSET;
ctl.arg.flags = PR_FORK;
size = sizeof (long) + sizeof (long);
(void) write(monfd, &ctl, size); /* can't fail */
ctl.cmd = PCSET; /* wait()able by the parent */
ctl.arg.flags = PR_PTRACE;
size = sizeof (long) + sizeof (long);
(void) write(monfd, &ctl, size); /* can't fail */
(void) close(monfd);
ctl.cmd = PCWSTOP;
size = sizeof (long);
for (;;) {
/*
* Wait for the traced process to stop.
*/
if (write(fd, &ctl, size) != size) {
rv = (errno == ENOENT)? 0 : 1;
lx_debug("monitor failed to wait for LWP to stop: %s",
strerror(errno));
_exit(rv);
}
lx_debug("monitor caught traced LWP");
/*
* Pull the ptrace trigger by sending ourself a SIGTRAP. This
* will cause this, the monitor process, to stop which will
* cause the parent's waitid(2) call to return this process
* id. In lx_wait(), we remap the monitor process's pid and
* status to those of the traced LWP. When the parent process
* uses ptrace to resume the traced LWP, it will additionally
* restart this process.
*/
(void) _lwp_kill(_lwp_self(), SIGTRAP);
lx_debug("monitor was resumed");
}
}
static int
ptrace_attach_common(int fd, pid_t lxpid, pid_t pid, lwpid_t lwpid, int run)
{
pid_t child;
ptrace_monitor_map_t *p;
sigset_t unblock;
pstatus_t status;
long ctl[1 + sizeof (sysset_t) / sizeof (long) + 2];
long *ctlp = ctl;
size_t size;
sysset_t *sysp;
int ret;
/*
* We're going to need this structure so better to fail now before its
* too late to turn back.
*/
if ((p = malloc(sizeof (ptrace_monitor_map_t))) == NULL)
return (-EIO);
if ((ret = get_status(pid, &status)) != 0) {
free(p);
return (ret);
}
/*
* If this process is already traced, bail.
*/
if (status.pr_flags & PR_PTRACE) {
free(p);
return (-EPERM);
}
/*
* Turn on the appropriate tracing flags. It's exceedingly unlikely
* that this operation will fail; any failure would probably be due
* to another /proc consumer mucking around.
*/
if (ptrace_trace_common(fd) != 0) {
free(p);
return (-EIO);
}
/*
* Native ptrace automatically catches processes when they exec so we
* have to do that explicitly here.
*/
*ctlp++ = PCSEXIT;
sysp = (sysset_t *)ctlp;
ctlp += sizeof (sysset_t) / sizeof (long);
premptyset(sysp);
praddset(sysp, SYS_execve);
if (run) {
*ctlp++ = PCRUN;
*ctlp++ = 0;
}
size = (char *)ctlp - (char *)&ctl[0];
if (write(fd, ctl, size) != size) {
free(p);
return (-EIO);
}
/*
* Spawn the monitor proceses to notify this process of events of
* interest in the traced process. We block signals here both so
* we're not interrupted during this operation and so that the
* monitor process doesn't accept signals.
*/
(void) sigprocmask(SIG_BLOCK, &blockable_sigs, &unblock);
if ((child = fork1()) == 0)
ptrace_monitor(fd);
(void) sigprocmask(SIG_SETMASK, &unblock, NULL);
if (child == -1) {
lx_debug("failed to fork monitor process\n");
free(p);
return (-EIO);
}
p->pmm_monitor = child;
p->pmm_target = lxpid;
p->pmm_pid = pid;
p->pmm_lwpid = lwpid;
p->pmm_exiting = 0;
(void) mutex_lock(&ptrace_map_mtx);
p->pmm_next = ptrace_monitor_map;
ptrace_monitor_map = p;
(void) mutex_unlock(&ptrace_map_mtx);
return (0);
}
static int
ptrace_attach(pid_t lxpid, pid_t pid, lwpid_t lwpid)
{
int fd, ret;
long ctl;
/*
* Linux doesn't let you trace process 1 -- go figure.
*/
if (lxpid == 1)
return (-EPERM);
if ((fd = open_lwpfile(pid, lwpid, O_WRONLY | O_EXCL, "lwpctl")) < 0)
return (errno == EBUSY ? -EPERM : -ESRCH);
ctl = PCSTOP;
if (write(fd, &ctl, sizeof (ctl)) != sizeof (ctl)) {
lx_err(gettext("failed to stop %d/%d\n"), (int)pid, (int)lwpid);
assert(0);
}
ret = ptrace_attach_common(fd, lxpid, pid, lwpid, 0);
(void) close(fd);
return (ret);
}
static int
ptrace_detach(pid_t lxpid, pid_t pid, lwpid_t lwpid, int sig)
{
long ctl[2];
int fd, ret;
if (!is_traced(pid))
return (-ESRCH);
if (sig < 0 || sig >= LX_NSIG)
return (-EINVAL);
if ((fd = open_lwpfile(pid, lwpid, O_WRONLY, "lwpctl")) < 0)
return (-ESRCH);
/*
* The /proc ptrace flag may not be set, but we clear it
* unconditionally since doing so doesn't hurt anything.
*/
ctl[0] = PCUNSET;
ctl[1] = PR_PTRACE;
if (write(fd, ctl, sizeof (ctl)) != sizeof (ctl)) {
(void) close(fd);
return (-EIO);
}
/*
* Clear the brand-specific system call tracing flag to ensure that
* the target doesn't stop unexpectedly some time in the future.
*/
if ((ret = syscall(SYS_brand, B_PTRACE_SYSCALL, pid, lwpid, 0)) != 0) {
(void) close(fd);
return (-ret);
}
/* kill off the monitor process, if any */
monitor_kill(lxpid, pid);
/*
* Turn on the run-on-last-close flag so that all tracing flags will be
* cleared when we close the control file descriptor.
*/
ctl[0] = PCSET;
ctl[1] = PR_RLC;
if (write(fd, ctl, sizeof (ctl)) != sizeof (ctl)) {
(void) close(fd);
return (-EIO);
}
/*
* Clear the current signal (if any) and possibly send the traced
* process a new signal.
*/
ret = ptrace_cont_common(fd, sig, 0, 0);
(void) close(fd);
return (ret);
}
static int
ptrace_syscall(pid_t lxpid, pid_t pid, lwpid_t lwpid, int sig)
{
int ret;
if (!is_traced(pid))
return (-ESRCH);
if ((ret = syscall(SYS_brand, B_PTRACE_SYSCALL, pid, lwpid, 1)) != 0)
return (-ret);
return (ptrace_cont(lxpid, pid, lwpid, sig, 0));
}
int
lx_ptrace(uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4)
{
pid_t pid, lxpid = (pid_t)p2;
lwpid_t lwpid;
if ((p1 != LX_PTRACE_TRACEME) &&
(lx_lpid_to_spair(lxpid, &pid, &lwpid) < 0))
return (-ESRCH);
switch (p1) {
case LX_PTRACE_TRACEME:
return (ptrace_traceme());
case LX_PTRACE_PEEKTEXT:
case LX_PTRACE_PEEKDATA:
return (ptrace_peek(pid, p3, (int *)p4));
case LX_PTRACE_PEEKUSER:
return (ptrace_peek_user(pid, lwpid, p3, (int *)p4));
case LX_PTRACE_POKETEXT:
case LX_PTRACE_POKEDATA:
return (ptrace_poke(pid, p3, (int)p4));
case LX_PTRACE_POKEUSER:
return (ptrace_poke_user(pid, lwpid, p3, (int)p4));
case LX_PTRACE_CONT:
return (ptrace_cont(lxpid, pid, lwpid, (int)p4, 0));
case LX_PTRACE_KILL:
return (ptrace_kill(lxpid, pid));
case LX_PTRACE_SINGLESTEP:
return (ptrace_step(lxpid, pid, lwpid, (int)p4));
case LX_PTRACE_GETREGS:
return (ptrace_getregs(pid, lwpid, p4));
case LX_PTRACE_SETREGS:
return (ptrace_setregs(pid, lwpid, p4));
case LX_PTRACE_GETFPREGS:
return (ptrace_getfpregs(pid, lwpid, p4));
case LX_PTRACE_SETFPREGS:
return (ptrace_setfpregs(pid, lwpid, p4));
case LX_PTRACE_ATTACH:
return (ptrace_attach(lxpid, pid, lwpid));
case LX_PTRACE_DETACH:
return (ptrace_detach(lxpid, pid, lwpid, (int)p4));
case LX_PTRACE_GETFPXREGS:
return (ptrace_getfpxregs(pid, lwpid, p4));
case LX_PTRACE_SETFPXREGS:
return (ptrace_setfpxregs(pid, lwpid, p4));
case LX_PTRACE_SYSCALL:
return (ptrace_syscall(lxpid, pid, lwpid, (int)p4));
default:
return (-EINVAL);
}
}
void
lx_ptrace_fork(void)
{
/*
* Send a special signal (that has no Linux equivalent) to indicate
* that we're in this particularly special case. The signal will be
* ignored by this process, but noticed by /proc consumers tracing
* this process.
*/
(void) _lwp_kill(_lwp_self(), SIGWAITING);
}
static void
ptrace_catch_fork(pid_t pid, int monitor)
{
long ctl[14 + 2 * sizeof (sysset_t) / sizeof (long)];
long *ctlp;
sysset_t *sysp;
size_t size;
pstatus_t ps;
pid_t child;
int fd, err;
/*
* If any of this fails, we're really sunk since the child
* will be stuck in the middle of lx_ptrace_fork().
* Fortunately it's practically assured to succeed unless
* something is seriously wrong on the system.
*/
if ((fd = open_procfile(pid, O_WRONLY, "ctl")) < 0) {
lx_debug("lx_catch_fork: failed to control %d",
(int)pid);
return;
}
/*
* Turn off the /proc PR_PTRACE flag so the parent doesn't get
* spurious wake ups while we're working our dark magic. Arrange to
* catch the process when it exits from fork, and turn on the /proc
* inherit-on-fork flag so we catcht the child as well. We then run
* the process, wait for it to stop on the fork1(2) call and reset
* the tracing flags to their original state.
*/
ctlp = ctl;
*ctlp++ = PCCSIG;
if (!monitor) {
*ctlp++ = PCUNSET;
*ctlp++ = PR_PTRACE;
}
*ctlp++ = PCSET;
*ctlp++ = PR_FORK;
*ctlp++ = PCSEXIT;
sysp = (sysset_t *)ctlp;
ctlp += sizeof (sysset_t) / sizeof (long);
premptyset(sysp);
praddset(sysp, SYS_forksys); /* fork1() is forksys(0, 0) */
*ctlp++ = PCRUN;
*ctlp++ = 0;
*ctlp++ = PCWSTOP;
if (!monitor) {
*ctlp++ = PCSET;
*ctlp++ = PR_PTRACE;
}
*ctlp++ = PCUNSET;
*ctlp++ = PR_FORK;
*ctlp++ = PCSEXIT;
sysp = (sysset_t *)ctlp;
ctlp += sizeof (sysset_t) / sizeof (long);
premptyset(sysp);
if (monitor)
praddset(sysp, SYS_execve);
size = (char *)ctlp - (char *)&ctl[0];
assert(size <= sizeof (ctl));
if (write(fd, ctl, size) != size) {
(void) close(fd);
lx_debug("lx_catch_fork: failed to set %d running",
(int)pid);
return;
}
/*
* Get the status so we can find the value returned from fork1() --
* the child process's pid.
*/
if (get_status(pid, &ps) != 0) {
(void) close(fd);
lx_debug("lx_catch_fork: failed to get status for %d",
(int)pid);
return;
}
child = (pid_t)ps.pr_lwp.pr_reg[R_R0];
/*
* We're done with the parent -- off you go.
*/
ctl[0] = PCRUN;
ctl[1] = 0;
size = 2 * sizeof (long);
if (write(fd, ctl, size) != size) {
(void) close(fd);
lx_debug("lx_catch_fork: failed to set %d running",
(int)pid);
return;
}
(void) close(fd);
/*
* If fork1(2) failed, we're done.
*/
if (child < 0) {
lx_debug("lx_catch_fork: fork1 failed");
return;
}
/*
* Now we need to screw with the child process.
*/
if ((fd = open_lwpfile(child, 1, O_WRONLY, "lwpctl")) < 0) {
lx_debug("lx_catch_fork: failed to control %d",
(int)child);
return;
}
ctlp = ctl;
*ctlp++ = PCUNSET;
*ctlp++ = PR_FORK;
*ctlp++ = PCSEXIT;
sysp = (sysset_t *)ctlp;
ctlp += sizeof (sysset_t) / sizeof (long);
premptyset(sysp);
size = (char *)ctlp - (char *)&ctl[0];
if (write(fd, ctl, size) != size) {
(void) close(fd);
lx_debug("lx_catch_fork: failed to clear trace flags for %d",
(int)child);
return;
}
/*
* Now treat the child as though we had attached to it explicitly.
*/
err = ptrace_attach_common(fd, child, child, 1, 1);
assert(err == 0);
(void) close(fd);
}
static void
set_dr6(pid_t pid, siginfo_t *infop)
{
uintptr_t *debugreg;
uintptr_t addr;
uintptr_t base;
size_t size;
int dr7;
int lrw;
int i;
if ((debugreg = debug_registers(pid)) == NULL)
return;
debugreg[6] = 0xffff0ff0; /* read as ones */
switch (infop->si_code) {
case TRAP_TRACE:
debugreg[6] |= 0x4000; /* single-step */
break;
case TRAP_RWATCH:
case TRAP_WWATCH:
case TRAP_XWATCH:
dr7 = debugreg[7];
addr = (uintptr_t)infop->si_addr;
for (i = 0; i < 4; i++) {
if ((dr7 & (1 << (2 * i))) == 0) /* enabled? */
continue;
lrw = (dr7 >> (16 + (4 * i))) & 0xf;
switch (lrw >> 2) { /* length */
case 0: size = 1; break;
case 1: size = 2; break;
case 2: size = 8; break;
case 3: size = 4; break;
}
base = debugreg[i];
if (addr >= base && addr < base + size)
debugreg[6] |= (1 << i);
}
/*
* Were we also attempting a single-step?
* (kludge: we use debugreg[4] for this flag.)
*/
if (debugreg[4])
debugreg[6] |= 0x4000;
break;
default:
break;
}
}
/*
* This is called from the emulation of the wait4 and waitpid system call to
* take into account the monitor processes which we spawn to observe other
* processes from ptrace_attach().
*/
int
lx_ptrace_wait(siginfo_t *infop)
{
ptrace_monitor_map_t *p, **pp;
pid_t lxpid, pid = infop->si_pid;
lwpid_t lwpid;
int fd;
pstatus_t status;
/*
* If the process observed by waitid(2) corresponds to the monitor
* process for a traced thread, we need to rewhack the siginfo_t to
* look like it came from the traced thread with the flags set
* according to the current state.
*/
(void) mutex_lock(&ptrace_map_mtx);
for (pp = &ptrace_monitor_map; (p = *pp) != NULL; pp = &p->pmm_next) {
if (p->pmm_monitor == pid) {
assert(infop->si_code == CLD_EXITED ||
infop->si_code == CLD_KILLED ||
infop->si_code == CLD_DUMPED ||
infop->si_code == CLD_TRAPPED);
goto found;
}
}
(void) mutex_unlock(&ptrace_map_mtx);
/*
* If the traced process got a SIGWAITING, we must be in the middle
* of a clone(2) with CLONE_PTRACE set.
*/
if (infop->si_code == CLD_TRAPPED && infop->si_status == SIGWAITING) {
ptrace_catch_fork(pid, 0);
return (-1);
}
if (get_status(pid, &status) == 0 &&
(status.pr_lwp.pr_flags & PR_STOPPED) &&
status.pr_lwp.pr_why == PR_SIGNALLED &&
status.pr_lwp.pr_info.si_signo == SIGTRAP)
set_dr6(pid, &status.pr_lwp.pr_info);
return (0);
found:
/*
* If the monitor is in the exiting state, ignore the event and free
* the monitor structure if the monitor has exited. By returning -1 we
* indicate to the caller that this was a spurious return from
* waitid(2) and that it should ignore the result and try again.
*/
if (p->pmm_exiting) {
if (infop->si_code == CLD_EXITED ||
infop->si_code == CLD_KILLED ||
infop->si_code == CLD_DUMPED) {
*pp = p->pmm_next;
(void) mutex_unlock(&ptrace_map_mtx);
free(p);
}
return (-1);
}
lxpid = p->pmm_target;
pid = p->pmm_pid;
lwpid = p->pmm_lwpid;
(void) mutex_unlock(&ptrace_map_mtx);
/*
* If we can't find the traced process, kill off its monitor.
*/
if ((fd = open_lwpfile(pid, lwpid, O_RDONLY, "lwpstatus")) < 0) {
assert(errno == ENOENT);
monitor_kill(lxpid, pid);
infop->si_code = CLD_EXITED;
infop->si_status = 0;
infop->si_pid = lxpid;
return (0);
}
if (read(fd, &status.pr_lwp, sizeof (status.pr_lwp)) !=
sizeof (status.pr_lwp)) {
lx_err(gettext("read lwpstatus failed %d %s"),
fd, strerror(errno));
assert(0);
}
(void) close(fd);
/*
* If the traced process isn't stopped, this is a truly spurious
* event probably caused by another /proc consumer tracing the
* monitor.
*/
if (!(status.pr_lwp.pr_flags & PR_STOPPED)) {
(void) ptrace_cont_monitor(p);
return (-1);
}
switch (status.pr_lwp.pr_why) {
case PR_SIGNALLED:
/*
* If the traced process got a SIGWAITING, we must be in the
* middle of a clone(2) with CLONE_PTRACE set.
*/
if (status.pr_lwp.pr_what == SIGWAITING) {
ptrace_catch_fork(lxpid, 1);
(void) ptrace_cont_monitor(p);
return (-1);
}
infop->si_code = CLD_TRAPPED;
infop->si_status = status.pr_lwp.pr_what;
if (status.pr_lwp.pr_info.si_signo == SIGTRAP)
set_dr6(pid, &status.pr_lwp.pr_info);
break;
case PR_REQUESTED:
/*
* Make it look like the traced process stopped on an
* event of interest.
*/
infop->si_code = CLD_TRAPPED;
infop->si_status = SIGTRAP;
break;
case PR_JOBCONTROL:
/*
* Ignore this as it was probably caused by another /proc
* consumer tracing the monitor.
*/
(void) ptrace_cont_monitor(p);
return (-1);
case PR_SYSEXIT:
/*
* Processes traced via a monitor (rather than using the
* native Solaris ptrace support) explicitly trace returns
* from exec system calls since it's an implicit ptrace
* trace point. Accordingly we need to present a process
* in that state as though it had reached the ptrace trace
* point.
*/
if (status.pr_lwp.pr_what == SYS_execve) {
infop->si_code = CLD_TRAPPED;
infop->si_status = SIGTRAP;
break;
}
/*FALLTHROUGH*/
case PR_SYSENTRY:
case PR_FAULTED:
case PR_SUSPENDED:
default:
lx_err(gettext("didn't expect %d (%d %d)"),
status.pr_lwp.pr_why,
status.pr_lwp.pr_what, status.pr_lwp.pr_flags);
assert(0);
}
infop->si_pid = lxpid;
return (0);
}