Date: Sat, 29 Dec 2001 11:21:30 -0500 (EST) From: "Alexander N. Kabaev" <ak03@gte.com> To: FreeBSD-gnats-submit@freebsd.org Subject: i386/33299: -CURRENT ptrace(2) implementation for linux emulator on i386 Message-ID: <200112291621.fBTGLUW50719@h132-197-179-27.gte.com>
next in thread | raw e-mail | index | archive | help
>Number: 33299 >Category: i386 >Synopsis: -CURRENT ptrace(2) implementation for linux emulator on i386 >Confidential: no >Severity: non-critical >Priority: low >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: change-request >Submitter-Id: current-users >Arrival-Date: Sat Dec 29 08:30:00 PST 2001 >Closed-Date: >Last-Modified: >Originator: Alexander N. Kabaev >Release: FreeBSD 5.0-CURRENT i386 >Organization: Verizon Data Services >Environment: System: FreeBSD kanpc.gte.com 5.0-CURRENT FreeBSD 5.0-CURRENT #13: Mon Dec 24 11:36:50 EST 2001 root@kanpc.gte.com:/usr/src/sys/i386/compile/KANPC i386 >Description: The patch below implements Linux ptrace syscall for linuxulator. With this patch applied, it is possible to use /compat/linux/usr/bin/gdb to debug Linux binaries. At the very least it provides much more meaningful backtraces. All gdb features I tried seem to be working correctly so far. The patch also fixes long-standing bug with FreeBSD native ptrace in kern/sys_process.c, where prace would not allow process using signal number > NSIG to be continued from gdb. The check for signal validity should use _SIG_MAXSIG instead of obsoleted NSIG. >How-To-Repeat: Apply the patch in /usr/src/sys. Do not forget to regenerate files from i386/linux/syscalls.master to get an undated linux_ptrace syscall signature. >Fix: Index: conf/files.i386 =================================================================== RCS file: /home/ncvs/src/sys/conf/files.i386,v retrieving revision 1.386 diff -u -r1.386 files.i386 --- conf/files.i386 22 Dec 2001 09:25:17 -0000 1.386 +++ conf/files.i386 29 Dec 2001 15:23:55 -0000 @@ -288,6 +288,7 @@ i386/linux/linux_locore.s optional compat_linux \ dependency "linux_assym.h" i386/linux/linux_machdep.c optional compat_linux +i386/linux/linux_ptrace.c optional compat_linux i386/linux/linux_sysent.c optional compat_linux i386/linux/linux_sysvec.c optional compat_linux i386/pci/pci_cfgreg.c optional pci Index: kern/sys_process.c =================================================================== RCS file: /home/ncvs/src/sys/kern/sys_process.c,v retrieving revision 1.76 diff -u -r1.76 sys_process.c --- kern/sys_process.c 21 Oct 2001 23:57:15 -0000 1.76 +++ kern/sys_process.c 26 Dec 2001 20:02:05 -0000 @@ -419,7 +419,7 @@ case PT_STEP: case PT_CONTINUE: case PT_DETACH: - if ((uap->req != PT_STEP) && ((unsigned)uap->data >= NSIG)) + if ((uap->req != PT_STEP) && ((unsigned)uap->data > _SIG_MAXSIG)) return (EINVAL); PHOLD(p); Index: i386/linux/linux_ptrace.c =================================================================== RCS file: i386/linux/linux_ptrace.c diff -N i386/linux/linux_ptrace.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ i386/linux/linux_ptrace.c 26 Dec 2001 20:08:45 -0000 @@ -0,0 +1,499 @@ +/* + * 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. + * + * $FreeBSD:$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/proc.h> +#include <sys/ptrace.h> +#include <sys/sysproto.h> +#include <sys/user.h> + +#include <machine/md_var.h> +#include <machine/npx.h> +#include <machine/reg.h> + +#include <i386/linux/linux.h> +#include <i386/linux/linux_proto.h> +#include <compat/linux/linux_util.h> + +/* + * 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) +{ +#ifndef __alpha__ + if (signum > 0 && signum <= LINUX_SIGTBLSZ) + signum = linux_to_bsd_signal[_SIG_IDX(signum)]; +#endif + 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 in Linux side and r_trapno and r_err in FreeBSD + */ +static int +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; + return (0); +} + +static int +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; + return (0); +} + +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 int +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)); + return (0); +} + +static int +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)); + return (0); +} + +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]; +}; + +static int +linux_proc_read_fpxregs(struct thread *td, struct linux_pt_fpxreg *fpxregs) +{ +#ifdef CPU_ENABLE_SSE + if (sizeof(*fpxregs) != sizeof(td->td_pcb->pcb_save.sv_xmm)) { + printf("linux: savexmm != linux_pt_fpxreg\n"); + return (EIO); + } + if (cpu_fxsr == 0) +#endif + return (EIO); + bcopy(&td->td_pcb->pcb_save.sv_xmm, fpxregs, sizeof *fpxregs); + return (0); +} + +static int +linux_proc_write_fpxregs(struct thread *td, struct linux_pt_fpxreg *fpxregs) +{ +#ifdef CPU_ENABLE_SSE + if (sizeof(*fpxregs) != sizeof(td->td_pcb->pcb_save.sv_xmm)) { + printf("linux: savexmm != linux_pt_fpxreg\n"); + return (EIO); + } + if (cpu_fxsr == 0) +#endif + return (EIO); + bcopy(fpxregs, &td->td_pcb->pcb_save.sv_xmm, sizeof *fpxregs); + return (0); +} + + +int +linux_ptrace(struct thread *td, struct linux_ptrace_args *uap) +{ + struct ptrace_args bsd_args; + int error; + caddr_t sg; + union { + struct linux_pt_reg reg; + struct linux_pt_fpreg fpreg; + struct linux_pt_fpxreg fpxreg; + } r; + + sg = stackgap_init(); + + error = 0; + + /* by default, just copy data intact */ + bsd_args.req = uap->req; + bsd_args.pid = (pid_t)uap->pid; + bsd_args.addr = (caddr_t)uap->addr; + bsd_args.data = uap->data; + + switch (uap->req) { + case PTRACE_TRACEME: + case PTRACE_POKETEXT: + case PTRACE_POKEDATA: + case PTRACE_KILL: + error = ptrace(td, &bsd_args); + break; + case PTRACE_PEEKTEXT: + case PTRACE_PEEKDATA: { + /* need to preserve return value */ + int rval = td->td_retval[0]; + bsd_args.data = 0; + error = ptrace(td, &bsd_args); + if (error == 0) + error = copyout(td->td_retval, + (caddr_t)uap->data, sizeof(l_int)); + td->td_retval[0] = rval; + break; + } + case PTRACE_DETACH: + bsd_args.req = PT_DETACH; + /* fall through */ + case PTRACE_SINGLESTEP: + case PTRACE_CONT: + bsd_args.data = map_signum(uap->data); + bsd_args.addr = (caddr_t)1; + error = ptrace(td, &bsd_args); + break; + case PTRACE_ATTACH: + bsd_args.req = PT_ATTACH; + error = ptrace(td, &bsd_args); + break; + case PTRACE_GETREGS: { + struct reg *bsd_r = (struct reg*)stackgap_alloc(&sg, + sizeof(*bsd_r)); + /* Linux is using data where FreeBSD is using addr */ + bsd_args.req = PT_GETREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + error = ptrace(td, &bsd_args); + if (error == 0) + error = map_regs_to_linux(bsd_r, &r.reg); + if (error == 0) + error = copyout(&r.reg, (caddr_t)uap->data, sizeof r.reg); + break; + } + case PTRACE_SETREGS: { + struct reg *bsd_r = (struct reg*)stackgap_alloc(&sg, + sizeof(*bsd_r)); + /* Linux is using data where FreeBSD is using addr */ + bsd_args.req = PT_SETREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + error = copyin((caddr_t)uap->data, &r.reg, sizeof r.reg); + if (error == 0) + error = map_regs_from_linux(bsd_r, &r.reg); + if (error == 0) + error = ptrace(td, &bsd_args); + break; + } + case PTRACE_GETFPREGS: { + struct fpreg *bsd_r = (struct fpreg*)stackgap_alloc(&sg, + sizeof(*bsd_r)); + /* Linux is using data where FreeBSD is using addr */ + bsd_args.req = PT_GETFPREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + error = ptrace(td, &bsd_args); + if (error == 0) + error = map_fpregs_to_linux(bsd_r, &r.fpreg); + if (error == 0) + error = copyout(&r.fpreg, (caddr_t)uap->data, sizeof r.fpreg); + break; + } + case PTRACE_SETFPREGS: { + struct fpreg *bsd_r = (struct fpreg*)stackgap_alloc(&sg, + sizeof(*bsd_r)); + /* Linux is using data where FreeBSD is using addr */ + bsd_args.req = PT_SETFPREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + error = copyin((caddr_t)uap->data, &r.fpreg, sizeof r.fpreg); + if (error == 0) + error = map_fpregs_from_linux(bsd_r, &r.fpreg); + if (error == 0) + error = ptrace(td, &bsd_args); + break; + } + case PTRACE_SETFPXREGS: + case PTRACE_GETFPXREGS: { + struct proc *p; + struct fpreg *bsd_r; + + /* + * Use FreeBSD PT_GETFPREGS for permisson testing. + */ + bsd_r = (struct fpreg*)stackgap_alloc(&sg, + sizeof(*bsd_r)); + bsd_args.req = PT_GETFPREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + error = ptrace(td, &bsd_args); + /* + * If this fails, PTRACE_GETFPXREGS should fail + * for exactly the same reason. + */ + if (error != 0) + break; + + if ((p = pfind(uap->pid)) == NULL) { + error = ESRCH; + break; + } + if (uap->req == PTRACE_GETFPXREGS) { + PHOLD(p); + error = linux_proc_read_fpxregs( + &p->p_thread, &r.fpxreg); + PRELE(p); + if (error == 0) + error = copyout(&r.fpxreg, + (caddr_t)uap->data, sizeof r.fpxreg); + } else { + error = copyin((caddr_t)uap->data, + &r.fpxreg, sizeof r.fpxreg); + if (error == 0) { + /* clear dangerous bits exactly as Linux does*/ + r.fpxreg.mxcsr &= 0xffbf; + PHOLD(p); + error = linux_proc_write_fpxregs( + &p->p_thread, &r.fpxreg); + PRELE(p); + } + } + 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)) { + struct reg *bsd_r; + + bsd_r = (struct reg*)stackgap_alloc(&sg, + sizeof(*bsd_r)); + bsd_args.req = PT_GETREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + + error = ptrace(td, &bsd_args); + if (error != 0) + break; + + error = map_regs_to_linux(bsd_r, &r.reg); + if (error != 0) + break; + + if (uap->req == PTRACE_PEEKUSR) { + error = copyout((char *)&r.reg + uap->addr, + (caddr_t)uap->data, sizeof(l_int)); + break; + } + + *(l_int *)((char *)&r.reg + uap->addr) = (l_int)uap->data; + + error = map_regs_from_linux(bsd_r, &r.reg); + if (error != 0) + break; + + bsd_args.req = PT_SETREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + error = ptrace(td, &bsd_args); + } + + /* + * Simulate debug registers access + */ + if (uap->addr >= LINUX_DBREG_OFFSET && + uap->addr <= LINUX_DBREG_OFFSET + LINUX_DBREG_SIZE) { + struct dbreg *bsd_r; + + bsd_r = (struct dbreg*)stackgap_alloc(&sg, + sizeof(*bsd_r)); + bsd_args.req = PT_GETDBREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + error = ptrace(td, &bsd_args); + if (error != 0) + break; + + uap->addr -= LINUX_DBREG_OFFSET; + if (uap->req == PTRACE_PEEKUSR) { + error = copyout((char *)bsd_r + uap->addr, + (caddr_t)uap->data, sizeof(l_int)); + break; + } + + *(l_int *)((char *)bsd_r + uap->addr) = uap->data; + bsd_args.req = PT_SETDBREGS; + bsd_args.addr = (caddr_t)bsd_r; + bsd_args.data = 0; + error = ptrace(td, &bsd_args); + } + + break; + } + case PTRACE_SYSCALL: + /* fall through */ + default: + error = EINVAL; + goto noimpl; + } + return (error); + + noimpl: + printf("linux: ptrace(%u, ...) not implemented\n", + (u_int32_t)uap->req); + return (error); +} Index: i386/linux/linux_dummy.c =================================================================== RCS file: /home/ncvs/src/sys/i386/linux/linux_dummy.c,v retrieving revision 1.32 diff -u -r1.32 linux_dummy.c --- i386/linux/linux_dummy.c 16 Oct 2001 06:15:36 -0000 1.32 +++ i386/linux/linux_dummy.c 24 Dec 2001 22:02:11 -0000 @@ -38,7 +38,6 @@ DUMMY(stat); DUMMY(stime); -DUMMY(ptrace); DUMMY(fstat); DUMMY(olduname); DUMMY(syslog); Index: i386/linux/syscalls.master =================================================================== RCS file: /home/ncvs/src/sys/i386/linux/syscalls.master,v retrieving revision 1.45 diff -u -r1.45 syscalls.master --- i386/linux/syscalls.master 16 Oct 2001 06:11:11 -0000 1.45 +++ i386/linux/syscalls.master 24 Dec 2001 22:02:11 -0000 @@ -65,7 +65,8 @@ 23 STD LINUX { int linux_setuid16(l_uid16_t uid); } 24 STD LINUX { int linux_getuid16(void); } 25 STD LINUX { int linux_stime(void); } -26 STD LINUX { int linux_ptrace(void); } +26 STD LINUX { int linux_ptrace(l_long req, l_long pid, l_long addr, \ + l_long data); } 27 STD LINUX { int linux_alarm(l_uint secs); } 28 STD LINUX { int linux_fstat(l_uint fd, struct ostat *up); } 29 STD LINUX { int linux_pause(void); } Index: modules/linux/Makefile =================================================================== RCS file: /home/ncvs/src/sys/modules/linux/Makefile,v retrieving revision 1.54 diff -u -r1.54 Makefile --- modules/linux/Makefile 18 Nov 2001 05:45:27 -0000 1.54 +++ modules/linux/Makefile 24 Dec 2001 22:03:37 -0000 @@ -8,7 +8,9 @@ SRCS= linux_dummy.c linux_file.c linux_getcwd.c linux_ioctl.c linux_ipc.c \ linux_machdep.c linux_mib.c linux_misc.c linux_signal.c linux_socket.c \ linux_stats.c linux_sysctl.c linux_sysent.c linux_sysvec.c \ - linux_util.c opt_compat.h opt_linux.h opt_vmpage.h vnode_if.h + linux_util.c linux_ptrace.c opt_compat.h opt_linux.h opt_vmpage.h \ + vnode_if.h + OBJS= linux_locore.o .if ${MACHINE_ARCH} == "i386" >Release-Note: >Audit-Trail: >Unformatted: To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-bugs" in the body of the message
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200112291621.fBTGLUW50719>