4ab7403bbd
1. Linux sigset always 64 bit on all platforms. In order to move Linux sigset code to the linux_common module define it as 64 bit int. Move Linux sigset manipulation routines to the MI path. 2. Move Linux signal number definitions to the MI path. In general, they are the same on all platforms except for a few signals. 3. Map Linux RT signals to the FreeBSD RT signals and hide signal conversion tables to avoid conversion errors. 4. Emulate Linux SIGPWR signal via FreeBSD SIGRTMIN signal which is outside of allowed on Linux signal numbers. PR: 197216
487 lines
12 KiB
C
487 lines
12 KiB
C
/*-
|
|
* Copyright (c) 2001 Alexander Kabaev
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer
|
|
* in this position and unchanged.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "opt_cpu.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/syscallsubr.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <machine/md_var.h>
|
|
#include <machine/pcb.h>
|
|
#include <machine/reg.h>
|
|
|
|
#include <i386/linux/linux.h>
|
|
#include <i386/linux/linux_proto.h>
|
|
#include <compat/linux/linux_signal.h>
|
|
|
|
#if !defined(CPU_DISABLE_SSE) && defined(I686_CPU)
|
|
#define CPU_ENABLE_SSE
|
|
#endif
|
|
|
|
/*
|
|
* Linux ptrace requests numbers. Mostly identical to FreeBSD,
|
|
* except for MD ones and PT_ATTACH/PT_DETACH.
|
|
*/
|
|
#define PTRACE_TRACEME 0
|
|
#define PTRACE_PEEKTEXT 1
|
|
#define PTRACE_PEEKDATA 2
|
|
#define PTRACE_PEEKUSR 3
|
|
#define PTRACE_POKETEXT 4
|
|
#define PTRACE_POKEDATA 5
|
|
#define PTRACE_POKEUSR 6
|
|
#define PTRACE_CONT 7
|
|
#define PTRACE_KILL 8
|
|
#define PTRACE_SINGLESTEP 9
|
|
|
|
#define PTRACE_ATTACH 16
|
|
#define PTRACE_DETACH 17
|
|
|
|
#define PTRACE_SYSCALL 24
|
|
|
|
#define PTRACE_GETREGS 12
|
|
#define PTRACE_SETREGS 13
|
|
#define PTRACE_GETFPREGS 14
|
|
#define PTRACE_SETFPREGS 15
|
|
#define PTRACE_GETFPXREGS 18
|
|
#define PTRACE_SETFPXREGS 19
|
|
|
|
#define PTRACE_SETOPTIONS 21
|
|
|
|
/*
|
|
* Linux keeps debug registers at the following
|
|
* offset in the user struct
|
|
*/
|
|
#define LINUX_DBREG_OFFSET 252
|
|
#define LINUX_DBREG_SIZE (8*sizeof(l_int))
|
|
|
|
static __inline int
|
|
map_signum(int signum)
|
|
{
|
|
|
|
signum = linux_to_bsd_signal(signum);
|
|
return ((signum == SIGSTOP)? 0 : signum);
|
|
}
|
|
|
|
struct linux_pt_reg {
|
|
l_long ebx;
|
|
l_long ecx;
|
|
l_long edx;
|
|
l_long esi;
|
|
l_long edi;
|
|
l_long ebp;
|
|
l_long eax;
|
|
l_int xds;
|
|
l_int xes;
|
|
l_int xfs;
|
|
l_int xgs;
|
|
l_long orig_eax;
|
|
l_long eip;
|
|
l_int xcs;
|
|
l_long eflags;
|
|
l_long esp;
|
|
l_int xss;
|
|
};
|
|
|
|
/*
|
|
* Translate i386 ptrace registers between Linux and FreeBSD formats.
|
|
* The translation is pretty straighforward, for all registers, but
|
|
* orig_eax on Linux side and r_trapno and r_err in FreeBSD
|
|
*/
|
|
static void
|
|
map_regs_to_linux(struct reg *bsd_r, struct linux_pt_reg *linux_r)
|
|
{
|
|
linux_r->ebx = bsd_r->r_ebx;
|
|
linux_r->ecx = bsd_r->r_ecx;
|
|
linux_r->edx = bsd_r->r_edx;
|
|
linux_r->esi = bsd_r->r_esi;
|
|
linux_r->edi = bsd_r->r_edi;
|
|
linux_r->ebp = bsd_r->r_ebp;
|
|
linux_r->eax = bsd_r->r_eax;
|
|
linux_r->xds = bsd_r->r_ds;
|
|
linux_r->xes = bsd_r->r_es;
|
|
linux_r->xfs = bsd_r->r_fs;
|
|
linux_r->xgs = bsd_r->r_gs;
|
|
linux_r->orig_eax = bsd_r->r_eax;
|
|
linux_r->eip = bsd_r->r_eip;
|
|
linux_r->xcs = bsd_r->r_cs;
|
|
linux_r->eflags = bsd_r->r_eflags;
|
|
linux_r->esp = bsd_r->r_esp;
|
|
linux_r->xss = bsd_r->r_ss;
|
|
}
|
|
|
|
static void
|
|
map_regs_from_linux(struct reg *bsd_r, struct linux_pt_reg *linux_r)
|
|
{
|
|
bsd_r->r_ebx = linux_r->ebx;
|
|
bsd_r->r_ecx = linux_r->ecx;
|
|
bsd_r->r_edx = linux_r->edx;
|
|
bsd_r->r_esi = linux_r->esi;
|
|
bsd_r->r_edi = linux_r->edi;
|
|
bsd_r->r_ebp = linux_r->ebp;
|
|
bsd_r->r_eax = linux_r->eax;
|
|
bsd_r->r_ds = linux_r->xds;
|
|
bsd_r->r_es = linux_r->xes;
|
|
bsd_r->r_fs = linux_r->xfs;
|
|
bsd_r->r_gs = linux_r->xgs;
|
|
bsd_r->r_eip = linux_r->eip;
|
|
bsd_r->r_cs = linux_r->xcs;
|
|
bsd_r->r_eflags = linux_r->eflags;
|
|
bsd_r->r_esp = linux_r->esp;
|
|
bsd_r->r_ss = linux_r->xss;
|
|
}
|
|
|
|
struct linux_pt_fpreg {
|
|
l_long cwd;
|
|
l_long swd;
|
|
l_long twd;
|
|
l_long fip;
|
|
l_long fcs;
|
|
l_long foo;
|
|
l_long fos;
|
|
l_long st_space[2*10];
|
|
};
|
|
|
|
static void
|
|
map_fpregs_to_linux(struct fpreg *bsd_r, struct linux_pt_fpreg *linux_r)
|
|
{
|
|
linux_r->cwd = bsd_r->fpr_env[0];
|
|
linux_r->swd = bsd_r->fpr_env[1];
|
|
linux_r->twd = bsd_r->fpr_env[2];
|
|
linux_r->fip = bsd_r->fpr_env[3];
|
|
linux_r->fcs = bsd_r->fpr_env[4];
|
|
linux_r->foo = bsd_r->fpr_env[5];
|
|
linux_r->fos = bsd_r->fpr_env[6];
|
|
bcopy(bsd_r->fpr_acc, linux_r->st_space, sizeof(linux_r->st_space));
|
|
}
|
|
|
|
static void
|
|
map_fpregs_from_linux(struct fpreg *bsd_r, struct linux_pt_fpreg *linux_r)
|
|
{
|
|
bsd_r->fpr_env[0] = linux_r->cwd;
|
|
bsd_r->fpr_env[1] = linux_r->swd;
|
|
bsd_r->fpr_env[2] = linux_r->twd;
|
|
bsd_r->fpr_env[3] = linux_r->fip;
|
|
bsd_r->fpr_env[4] = linux_r->fcs;
|
|
bsd_r->fpr_env[5] = linux_r->foo;
|
|
bsd_r->fpr_env[6] = linux_r->fos;
|
|
bcopy(bsd_r->fpr_acc, linux_r->st_space, sizeof(bsd_r->fpr_acc));
|
|
}
|
|
|
|
struct linux_pt_fpxreg {
|
|
l_ushort cwd;
|
|
l_ushort swd;
|
|
l_ushort twd;
|
|
l_ushort fop;
|
|
l_long fip;
|
|
l_long fcs;
|
|
l_long foo;
|
|
l_long fos;
|
|
l_long mxcsr;
|
|
l_long reserved;
|
|
l_long st_space[32];
|
|
l_long xmm_space[32];
|
|
l_long padding[56];
|
|
};
|
|
|
|
#ifdef CPU_ENABLE_SSE
|
|
static int
|
|
linux_proc_read_fpxregs(struct thread *td, struct linux_pt_fpxreg *fpxregs)
|
|
{
|
|
|
|
PROC_LOCK_ASSERT(td->td_proc, MA_OWNED);
|
|
if (cpu_fxsr == 0 || (td->td_proc->p_flag & P_INMEM) == 0)
|
|
return (EIO);
|
|
bcopy(&get_pcb_user_save_td(td)->sv_xmm, fpxregs, sizeof(*fpxregs));
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
linux_proc_write_fpxregs(struct thread *td, struct linux_pt_fpxreg *fpxregs)
|
|
{
|
|
|
|
PROC_LOCK_ASSERT(td->td_proc, MA_OWNED);
|
|
if (cpu_fxsr == 0 || (td->td_proc->p_flag & P_INMEM) == 0)
|
|
return (EIO);
|
|
bcopy(fpxregs, &get_pcb_user_save_td(td)->sv_xmm, sizeof(*fpxregs));
|
|
return (0);
|
|
}
|
|
#endif
|
|
|
|
int
|
|
linux_ptrace(struct thread *td, struct linux_ptrace_args *uap)
|
|
{
|
|
union {
|
|
struct linux_pt_reg reg;
|
|
struct linux_pt_fpreg fpreg;
|
|
struct linux_pt_fpxreg fpxreg;
|
|
} r;
|
|
union {
|
|
struct reg bsd_reg;
|
|
struct fpreg bsd_fpreg;
|
|
struct dbreg bsd_dbreg;
|
|
} u;
|
|
void *addr;
|
|
pid_t pid;
|
|
int error, req;
|
|
|
|
error = 0;
|
|
|
|
/* by default, just copy data intact */
|
|
req = uap->req;
|
|
pid = (pid_t)uap->pid;
|
|
addr = (void *)uap->addr;
|
|
|
|
switch (req) {
|
|
case PTRACE_TRACEME:
|
|
case PTRACE_POKETEXT:
|
|
case PTRACE_POKEDATA:
|
|
case PTRACE_KILL:
|
|
error = kern_ptrace(td, req, pid, addr, uap->data);
|
|
break;
|
|
case PTRACE_PEEKTEXT:
|
|
case PTRACE_PEEKDATA: {
|
|
/* need to preserve return value */
|
|
int rval = td->td_retval[0];
|
|
error = kern_ptrace(td, req, pid, addr, 0);
|
|
if (error == 0)
|
|
error = copyout(td->td_retval, (void *)uap->data,
|
|
sizeof(l_int));
|
|
td->td_retval[0] = rval;
|
|
break;
|
|
}
|
|
case PTRACE_DETACH:
|
|
error = kern_ptrace(td, PT_DETACH, pid, (void *)1,
|
|
map_signum(uap->data));
|
|
break;
|
|
case PTRACE_SINGLESTEP:
|
|
case PTRACE_CONT:
|
|
error = kern_ptrace(td, req, pid, (void *)1,
|
|
map_signum(uap->data));
|
|
break;
|
|
case PTRACE_ATTACH:
|
|
error = kern_ptrace(td, PT_ATTACH, pid, addr, uap->data);
|
|
break;
|
|
case PTRACE_GETREGS:
|
|
/* Linux is using data where FreeBSD is using addr */
|
|
error = kern_ptrace(td, PT_GETREGS, pid, &u.bsd_reg, 0);
|
|
if (error == 0) {
|
|
map_regs_to_linux(&u.bsd_reg, &r.reg);
|
|
error = copyout(&r.reg, (void *)uap->data,
|
|
sizeof(r.reg));
|
|
}
|
|
break;
|
|
case PTRACE_SETREGS:
|
|
/* Linux is using data where FreeBSD is using addr */
|
|
error = copyin((void *)uap->data, &r.reg, sizeof(r.reg));
|
|
if (error == 0) {
|
|
map_regs_from_linux(&u.bsd_reg, &r.reg);
|
|
error = kern_ptrace(td, PT_SETREGS, pid, &u.bsd_reg, 0);
|
|
}
|
|
break;
|
|
case PTRACE_GETFPREGS:
|
|
/* Linux is using data where FreeBSD is using addr */
|
|
error = kern_ptrace(td, PT_GETFPREGS, pid, &u.bsd_fpreg, 0);
|
|
if (error == 0) {
|
|
map_fpregs_to_linux(&u.bsd_fpreg, &r.fpreg);
|
|
error = copyout(&r.fpreg, (void *)uap->data,
|
|
sizeof(r.fpreg));
|
|
}
|
|
break;
|
|
case PTRACE_SETFPREGS:
|
|
/* Linux is using data where FreeBSD is using addr */
|
|
error = copyin((void *)uap->data, &r.fpreg, sizeof(r.fpreg));
|
|
if (error == 0) {
|
|
map_fpregs_from_linux(&u.bsd_fpreg, &r.fpreg);
|
|
error = kern_ptrace(td, PT_SETFPREGS, pid,
|
|
&u.bsd_fpreg, 0);
|
|
}
|
|
break;
|
|
case PTRACE_SETFPXREGS:
|
|
#ifdef CPU_ENABLE_SSE
|
|
error = copyin((void *)uap->data, &r.fpxreg, sizeof(r.fpxreg));
|
|
if (error)
|
|
break;
|
|
#endif
|
|
/* FALL THROUGH */
|
|
case PTRACE_GETFPXREGS: {
|
|
#ifdef CPU_ENABLE_SSE
|
|
struct proc *p;
|
|
struct thread *td2;
|
|
|
|
if (sizeof(struct linux_pt_fpxreg) != sizeof(struct savexmm)) {
|
|
static int once = 0;
|
|
if (!once) {
|
|
printf("linux: savexmm != linux_pt_fpxreg\n");
|
|
once = 1;
|
|
}
|
|
error = EIO;
|
|
break;
|
|
}
|
|
|
|
if ((p = pfind(uap->pid)) == NULL) {
|
|
error = ESRCH;
|
|
break;
|
|
}
|
|
|
|
/* Exiting processes can't be debugged. */
|
|
if ((p->p_flag & P_WEXIT) != 0) {
|
|
error = ESRCH;
|
|
goto fail;
|
|
}
|
|
|
|
if ((error = p_candebug(td, p)) != 0)
|
|
goto fail;
|
|
|
|
/* System processes can't be debugged. */
|
|
if ((p->p_flag & P_SYSTEM) != 0) {
|
|
error = EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/* not being traced... */
|
|
if ((p->p_flag & P_TRACED) == 0) {
|
|
error = EPERM;
|
|
goto fail;
|
|
}
|
|
|
|
/* not being traced by YOU */
|
|
if (p->p_pptr != td->td_proc) {
|
|
error = EBUSY;
|
|
goto fail;
|
|
}
|
|
|
|
/* not currently stopped */
|
|
if (!P_SHOULDSTOP(p) || (p->p_flag & P_WAITED) == 0) {
|
|
error = EBUSY;
|
|
goto fail;
|
|
}
|
|
|
|
if (req == PTRACE_GETFPXREGS) {
|
|
_PHOLD(p); /* may block */
|
|
td2 = FIRST_THREAD_IN_PROC(p);
|
|
error = linux_proc_read_fpxregs(td2, &r.fpxreg);
|
|
_PRELE(p);
|
|
PROC_UNLOCK(p);
|
|
if (error == 0)
|
|
error = copyout(&r.fpxreg, (void *)uap->data,
|
|
sizeof(r.fpxreg));
|
|
} else {
|
|
/* clear dangerous bits exactly as Linux does*/
|
|
r.fpxreg.mxcsr &= 0xffbf;
|
|
_PHOLD(p); /* may block */
|
|
td2 = FIRST_THREAD_IN_PROC(p);
|
|
error = linux_proc_write_fpxregs(td2, &r.fpxreg);
|
|
_PRELE(p);
|
|
PROC_UNLOCK(p);
|
|
}
|
|
break;
|
|
|
|
fail:
|
|
PROC_UNLOCK(p);
|
|
#else
|
|
error = EIO;
|
|
#endif
|
|
break;
|
|
}
|
|
case PTRACE_PEEKUSR:
|
|
case PTRACE_POKEUSR: {
|
|
error = EIO;
|
|
|
|
/* check addr for alignment */
|
|
if (uap->addr < 0 || uap->addr & (sizeof(l_int) - 1))
|
|
break;
|
|
/*
|
|
* Allow linux programs to access register values in
|
|
* user struct. We simulate this through PT_GET/SETREGS
|
|
* as necessary.
|
|
*/
|
|
if (uap->addr < sizeof(struct linux_pt_reg)) {
|
|
error = kern_ptrace(td, PT_GETREGS, pid, &u.bsd_reg, 0);
|
|
if (error != 0)
|
|
break;
|
|
|
|
map_regs_to_linux(&u.bsd_reg, &r.reg);
|
|
if (req == PTRACE_PEEKUSR) {
|
|
error = copyout((char *)&r.reg + uap->addr,
|
|
(void *)uap->data, sizeof(l_int));
|
|
break;
|
|
}
|
|
|
|
*(l_int *)((char *)&r.reg + uap->addr) =
|
|
(l_int)uap->data;
|
|
|
|
map_regs_from_linux(&u.bsd_reg, &r.reg);
|
|
error = kern_ptrace(td, PT_SETREGS, pid, &u.bsd_reg, 0);
|
|
}
|
|
|
|
/*
|
|
* Simulate debug registers access
|
|
*/
|
|
if (uap->addr >= LINUX_DBREG_OFFSET &&
|
|
uap->addr <= LINUX_DBREG_OFFSET + LINUX_DBREG_SIZE) {
|
|
error = kern_ptrace(td, PT_GETDBREGS, pid, &u.bsd_dbreg,
|
|
0);
|
|
if (error != 0)
|
|
break;
|
|
|
|
uap->addr -= LINUX_DBREG_OFFSET;
|
|
if (req == PTRACE_PEEKUSR) {
|
|
error = copyout((char *)&u.bsd_dbreg +
|
|
uap->addr, (void *)uap->data,
|
|
sizeof(l_int));
|
|
break;
|
|
}
|
|
|
|
*(l_int *)((char *)&u.bsd_dbreg + uap->addr) =
|
|
uap->data;
|
|
error = kern_ptrace(td, PT_SETDBREGS, pid,
|
|
&u.bsd_dbreg, 0);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case PTRACE_SYSCALL:
|
|
/* fall through */
|
|
default:
|
|
printf("linux: ptrace(%u, ...) not implemented\n",
|
|
(unsigned int)uap->req);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
return (error);
|
|
}
|