int procfs_dofpregs(struct proc *curp, struct lwp *lp, struct pfsnode *pfs, struct uio *uio) { struct proc *p = lp->lwp_proc; int error; struct fpreg r; /* Can't trace a process that's currently exec'ing. */ if ((p->p_flags & P_INEXEC) != 0) return EAGAIN; if (!CHECKIO(curp, p) || p_trespass(curp->p_ucred, p->p_ucred)) return EPERM; LWPHOLD(lp); error = procfs_read_fpregs(lp, &r); if (error == 0) error = uiomove_frombuf(&r, sizeof(r), uio); if (error == 0 && uio->uio_rw == UIO_WRITE) { if (lp->lwp_stat != LSSTOP) error = EBUSY; else error = procfs_write_fpregs(lp, &r); } LWPRELE(lp); uio->uio_offset = 0; return (error); }
/* * Kill all LWPs except the current one. Do not try to signal * LWPs which have exited on their own or have already been * signaled. */ static void killlwps(struct lwp *lp) { struct proc *p = lp->lwp_proc; struct lwp *tlp; /* * Kill the remaining LWPs. We must send the signal before setting * LWP_WEXIT. The setting of WEXIT is optional but helps reduce * races. tlp must be held across the call as it might block and * allow the target lwp to rip itself out from under our loop. */ FOREACH_LWP_IN_PROC(tlp, p) { LWPHOLD(tlp); if ((tlp->lwp_flag & LWP_WEXIT) == 0) { lwpsignal(p, tlp, SIGKILL); tlp->lwp_flag |= LWP_WEXIT; } LWPRELE(tlp); }
/* * MPALMOSTSAFE */ int sys_linux_ptrace(struct linux_ptrace_args *uap) { struct thread *td = curthread; struct proc *curp = td->td_proc; 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; struct proc *p = NULL; /* held process */ 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(curp, req, pid, addr, uap->data, &uap->sysmsg_iresult); break; case PTRACE_PEEKTEXT: case PTRACE_PEEKDATA: { /* need to preserve return value, use dummy */ l_int rval = 0; error = kern_ptrace(curp, req, pid, addr, 0, &rval); if (error == 0) { error = copyout(&rval, (caddr_t)uap->data, sizeof(l_int)); } break; } case PTRACE_DETACH: error = kern_ptrace(curp, PT_DETACH, pid, (void *)1, map_signum(uap->data), &uap->sysmsg_iresult); break; case PTRACE_SINGLESTEP: case PTRACE_CONT: error = kern_ptrace(curp, req, pid, (void *)1, map_signum(uap->data), &uap->sysmsg_iresult); break; case PTRACE_ATTACH: error = kern_ptrace(curp, PT_ATTACH, pid, addr, uap->data, &uap->sysmsg_iresult); break; case PTRACE_GETREGS: /* Linux is using data where FreeBSD is using addr */ error = kern_ptrace(curp, PT_GETREGS, pid, &u.bsd_reg, 0, &uap->sysmsg_iresult); 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((caddr_t)uap->data, &r.reg, sizeof(r.reg)); if (error == 0) { map_regs_from_linux(&u.bsd_reg, &r.reg); error = kern_ptrace(curp, PT_SETREGS, pid, &u.bsd_reg, 0, &uap->sysmsg_iresult); } break; case PTRACE_GETFPREGS: /* Linux is using data where FreeBSD is using addr */ error = kern_ptrace(curp, PT_GETFPREGS, pid, &u.bsd_fpreg, 0, &uap->sysmsg_iresult); if (error == 0) { map_fpregs_to_linux(&u.bsd_fpreg, &r.fpreg); error = copyout(&r.fpreg, (caddr_t)uap->data, sizeof(r.fpreg)); } break; case PTRACE_SETFPREGS: /* Linux is using data where FreeBSD is using addr */ error = copyin((caddr_t)uap->data, &r.fpreg, sizeof(r.fpreg)); if (error == 0) { map_fpregs_from_linux(&u.bsd_fpreg, &r.fpreg); error = kern_ptrace(curp, PT_SETFPREGS, pid, &u.bsd_fpreg, 0, &uap->sysmsg_iresult); } break; case PTRACE_SETFPXREGS: #ifndef CPU_DISABLE_SSE error = copyin((caddr_t)uap->data, &r.fpxreg, sizeof(r.fpxreg)); if (error) break; #endif /* FALL THROUGH */ case PTRACE_GETFPXREGS: { #ifndef CPU_DISABLE_SSE struct proc *p; struct lwp *lp; if (sizeof(struct linux_pt_fpxreg) != sizeof(struct savexmm)) { static int once = 0; if (!once) { kprintf("linux: savexmm != linux_pt_fpxreg\n"); once = 1; } error = EIO; break; } if ((p = pfind(uap->pid)) == NULL) { error = ESRCH; break; } if (!PRISON_CHECK(curp->p_ucred, p->p_ucred)) { error = ESRCH; 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 != curp) { error = EBUSY; goto fail; } /* not currently stopped */ if ((p->p_flag & (P_TRACED|P_WAITED)) == 0) { error = EBUSY; goto fail; } /* XXX lwp */ lp = FIRST_LWP_IN_PROC(p); if (req == PTRACE_GETFPXREGS) { LWPHOLD(lp); error = linux_proc_read_fpxregs(lp, &r.fpxreg); LWPRELE(lp); if (error == 0) error = copyout(&r.fpxreg, (caddr_t)uap->data, sizeof(r.fpxreg)); } else { /* clear dangerous bits exactly as Linux does*/ r.fpxreg.mxcsr &= 0xffbf; LWPHOLD(lp); error = linux_proc_write_fpxregs(lp, &r.fpxreg); LWPRELE(lp); } break; fail: #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(curp, PT_GETREGS, pid, &u.bsd_reg, 0, &uap->sysmsg_iresult); if (error != 0) break; map_regs_to_linux(&u.bsd_reg, &r.reg); if (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; map_regs_from_linux(&u.bsd_reg, &r.reg); error = kern_ptrace(curp, PT_SETREGS, pid, &u.bsd_reg, 0, &uap->sysmsg_iresult); } /* * Simulate debug registers access */ if (uap->addr >= LINUX_DBREG_OFFSET && uap->addr <= LINUX_DBREG_OFFSET + LINUX_DBREG_SIZE) { error = kern_ptrace(curp, PT_GETDBREGS, pid, &u.bsd_dbreg, 0, &uap->sysmsg_iresult); if (error != 0) break; uap->addr -= LINUX_DBREG_OFFSET; if (req == PTRACE_PEEKUSR) { error = copyout((char *)&u.bsd_dbreg + uap->addr, (caddr_t)uap->data, sizeof(l_int)); break; } *(l_int *)((char *)&u.bsd_dbreg + uap->addr) = uap->data; error = kern_ptrace(curp, PT_SETDBREGS, pid, &u.bsd_dbreg, 0, &uap->sysmsg_iresult); } break; } case PTRACE_SYSCALL: /* fall through */ default: kprintf("linux: ptrace(%u, ...) not implemented\n", (unsigned int)uap->req); error = EINVAL; break; } /* * Release held proces (if any) before returning. */ if (p) PRELE(p); return (error); }
static int procfs_control(struct proc *curp, struct lwp *lp, int op) { struct proc *p = lp->lwp_proc; int error; ASSERT_LWKT_TOKEN_HELD(&p->p_token); ASSERT_LWKT_TOKEN_HELD(&proc_token); /* Can't trace a process that's currently exec'ing. */ if ((p->p_flags & P_INEXEC) != 0) return EAGAIN; /* * Authorization check: rely on normal debugging protection, except * allow processes to disengage debugging on a process onto which * they have previously attached, but no longer have permission to * debug. */ if (op != PROCFS_CTL_DETACH) { if (securelevel > 0 && p->p_pid == 1) return (EPERM); if (!CHECKIO(curp, p) || p_trespass(curp->p_ucred, p->p_ucred)) return (EPERM); } /* * Attach - attaches the target process for debugging * by the calling process. */ if (op == PROCFS_CTL_ATTACH) { /* check whether already being traced */ if (p->p_flags & P_TRACED) return (EBUSY); /* can't trace yourself! */ if (p->p_pid == curp->p_pid) return (EINVAL); /* * Go ahead and set the trace flag. * Save the old parent (it's reset in * _DETACH, and also in kern_exit.c:wait4() * Reparent the process so that the tracing * proc gets to see all the action. * Stop the target. */ p->p_flags |= P_TRACED; faultin(p); p->p_xstat = 0; /* XXX ? */ if (p->p_pptr != curp) { p->p_oppid = p->p_pptr->p_pid; proc_reparent(p, curp); } proc_stop(p); return (0); } /* * Target process must be stopped, owned by (curp) and * be set up for tracing (P_TRACED flag set). * Allow DETACH to take place at any time for sanity. * Allow WAIT any time, of course. */ switch (op) { case PROCFS_CTL_DETACH: case PROCFS_CTL_WAIT: break; default: if (!TRACE_WAIT_P(curp, p)) return (EBUSY); } #ifdef FIX_SSTEP /* * do single-step fixup if needed */ FIX_SSTEP(lp); #endif /* * Don't deliver any signal by default. * To continue with a signal, just send * the signal name to the ctl file */ p->p_xstat = 0; switch (op) { /* * Detach. Cleans up the target process, reparent it if possible * and set it running once more. */ case PROCFS_CTL_DETACH: /* if not being traced, then this is a painless no-op */ if ((p->p_flags & P_TRACED) == 0) return (0); /* not being traced any more */ p->p_flags &= ~P_TRACED; /* remove pending SIGTRAP, else the process will die */ spin_lock(&lp->lwp_spin); lwp_delsig(lp, SIGTRAP); spin_unlock(&lp->lwp_spin); /* give process back to original parent */ if (p->p_oppid != p->p_pptr->p_pid) { struct proc *pp; pp = pfs_pfind(p->p_oppid); if (pp) { proc_reparent(p, pp); pfs_pdone(pp); } } p->p_oppid = 0; p->p_flags &= ~P_WAITED; /* XXX ? */ wakeup((caddr_t) curp); /* XXX for CTL_WAIT below ? */ break; /* * Step. Let the target process execute a single instruction. */ case PROCFS_CTL_STEP: LWPHOLD(lp); error = procfs_sstep(lp); LWPRELE(lp); if (error) return (error); break; /* * Run. Let the target process continue running until a breakpoint * or some other trap. */ case PROCFS_CTL_RUN: break; /* * Wait for the target process to stop. * If the target is not being traced then just wait * to enter */ case PROCFS_CTL_WAIT: error = 0; if (p->p_flags & P_TRACED) { while (error == 0 && p->p_stat != SSTOP && (p->p_flags & P_TRACED) && (p->p_pptr == curp)) { error = tsleep((caddr_t) p, PCATCH, "procfsx", 0); } if (error == 0 && !TRACE_WAIT_P(curp, p)) error = EBUSY; } else { while (error == 0 && p->p_stat != SSTOP) { error = tsleep((caddr_t) p, PCATCH, "procfs", 0); } } return (error); default: panic("procfs_control"); } /* * If the process is in a stopped state, make it runnable again. * Do not set LWP_MP_BREAKTSLEEP - that is, do not break a tsleep * that might be in progress. */ if (p->p_stat == SSTOP) proc_unstop(p); return (0); }