Messages in this thread Patch in this message | | | From | Roland McGrath <> | Subject | [RFC PATCH 4/4] utrace: ptrace compatibility | Date | Tue, 13 Jun 2006 16:02:56 -0700 (PDT) |
| |
ptrace compatibility on top of <linux/utrace.h> interfaces. This attempts to be precisely compatible with existing ptrace behavior. It does not extend, improve, or change it.
The ptrace support is made an option, CONFIG_PTRACE. For now, noone will want to turn this off except maybe a bizarre embedded configuration. But it looks forward to a day when we can punt the ptrace system call completely.
---
arch/powerpc/kernel/ptrace.c | 11 arch/powerpc/kernel/signal_32.c | 52 ++ arch/powerpc/lib/sstep.c | 3 arch/x86_64/ia32/ia32entry.S | 2 include/asm-i386/tracehook.h | 25 + include/asm-powerpc/tracehook.h | 226 +++++++ include/asm-x86_64/tracehook.h | 53 ++ include/linux/ptrace.h | 65 +- include/linux/sched.h | 5 init/Kconfig | 12 kernel/exit.c | 13 kernel/fork.c | 3 kernel/ptrace.c | 1208 +++++++++++++++++++++++++++++++++++---- 13 files changed, 1520 insertions(+), 158 deletions(-)
diff --git a/arch/powerpc/kernel/ptrace.c b/arch/powerpc/kernel/ptrace.c index 6036f93..4f50020 100644 --- a/arch/powerpc/kernel/ptrace.c +++ b/arch/powerpc/kernel/ptrace.c @@ -410,8 +410,7 @@ static const struct utrace_regset native static const struct ptrace_uarea_segment native_uarea[] = { {0, PT_FPR0 * sizeof(long), 0, 0}, - {PT_FPR0 * sizeof(long), - (PT_FPSCR + 1 - PT_FPR0) * sizeof(long), 1, 0}, + {PT_FPR0 * sizeof(long), (PT_FPSCR + 1) * sizeof(long), 1, 0}, {0, 0, -1, 0} }; @@ -532,7 +531,7 @@ static const struct utrace_regset ppc32_ static const struct ptrace_uarea_segment ppc32_uarea[] = { {0, PT_FPR0 * sizeof(u32), 0, 0}, - {PT_FPR0 * sizeof(u32), (PT_FPSCR + 1 - PT_FPR0) * sizeof(u32), 1, 0}, + {PT_FPR0 * sizeof(u32), (PT_FPSCR + 1) * sizeof(u32), 1, 0}, {0, 0, -1, 0} }; @@ -543,12 +542,6 @@ const struct utrace_regset_view utrace_p .uarea_segments = ppc32_uarea }; EXPORT_SYMBOL_GPL(utrace_ppc32_view); - -long compat_sys_ptrace(int request, int pid, unsigned long addr, - unsigned long data) -{ - return -ENOSYS; -} #endif void do_syscall_trace_enter(struct pt_regs *regs) diff --git a/arch/powerpc/kernel/signal_32.c b/arch/powerpc/kernel/signal_32.c index 1b8cd82..3d0447b 100644 --- a/arch/powerpc/kernel/signal_32.c +++ b/arch/powerpc/kernel/signal_32.c @@ -629,6 +629,58 @@ int copy_siginfo_to_user32(struct compat #define copy_siginfo_to_user copy_siginfo_to_user32 +/* mostly stolen from arch/s390/kernel/compat_signal.c */ +int copy_siginfo_from_user32(siginfo_t *to, compat_siginfo_t __user *from) +{ + int err; + u32 tmp; + + if (!access_ok (VERIFY_READ, from, sizeof(compat_siginfo_t))) + return -EFAULT; + + err = __get_user(to->si_signo, &from->si_signo); + err |= __get_user(to->si_errno, &from->si_errno); + err |= __get_user(to->si_code, &from->si_code); + + if (to->si_code < 0) + err |= __copy_from_user(&to->_sifields._pad, &from->_sifields._pad, SI_PAD_SIZE); + else { + switch (to->si_code >> 16) { + case __SI_RT >> 16: /* This is not generated by the kernel as of now. */ + case __SI_MESGQ >> 16: + err |= __get_user(to->si_int, &from->si_int); + /* fallthrough */ + case __SI_KILL >> 16: + err |= __get_user(to->si_pid, &from->si_pid); + err |= __get_user(to->si_uid, &from->si_uid); + break; + case __SI_CHLD >> 16: + err |= __get_user(to->si_pid, &from->si_pid); + err |= __get_user(to->si_uid, &from->si_uid); + err |= __get_user(to->si_utime, &from->si_utime); + err |= __get_user(to->si_stime, &from->si_stime); + err |= __get_user(to->si_status, &from->si_status); + break; + case __SI_FAULT >> 16: + err |= __get_user(tmp, &from->si_addr); + to->si_addr = (void __user *)(u64) tmp; + break; + case __SI_POLL >> 16: + err |= __get_user(to->si_band, &from->si_band); + err |= __get_user(to->si_fd, &from->si_fd); + break; + case __SI_TIMER >> 16: + err |= __get_user(to->si_tid, &from->si_tid); + err |= __get_user(to->si_overrun, &from->si_overrun); + err |= __get_user(to->si_int, &from->si_int); + break; + default: + break; + } + } + return err; +} + /* * Note: it is necessary to treat pid and sig as unsigned ints, with the * corresponding cast to a signed int to insure that the proper conversion diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c index 666c2aa..400f81a 100644 --- a/arch/powerpc/lib/sstep.c +++ b/arch/powerpc/lib/sstep.c @@ -13,6 +13,9 @@ #include <linux/config.h> #include <asm/sstep.h> #include <asm/processor.h> +#ifdef CONFIG_PPC64 +#include <asm/paca.h> +#endif extern char system_call_common[]; diff --git a/arch/x86_64/ia32/ia32entry.S b/arch/x86_64/ia32/ia32entry.S index 00dee17..4ad17e3 100644 --- a/arch/x86_64/ia32/ia32entry.S +++ b/arch/x86_64/ia32/ia32entry.S @@ -399,7 +399,7 @@ ia32_sys_call_table: .quad sys_setuid16 .quad sys_getuid16 .quad compat_sys_stime /* stime */ /* 25 */ - .quad sys32_ptrace /* ptrace */ + .quad compat_sys_ptrace /* ptrace */ .quad sys_alarm .quad sys_fstat /* (old)fstat */ .quad sys_pause diff --git a/include/asm-i386/tracehook.h b/include/asm-i386/tracehook.h index 423ab18..09233b0 100644 --- a/include/asm-i386/tracehook.h +++ b/include/asm-i386/tracehook.h @@ -45,4 +45,29 @@ utrace_native_view(struct task_struct *t } +#ifdef CONFIG_PTRACE +static inline int arch_ptrace(long request, struct task_struct *child, + unsigned long addr, long data, + const struct utrace_regset_view **view, + int *regset, int *rw, + void __user **uaddr, int *nregs) +{ + switch (request) { + case PTRACE_GETFPXREGS: + case PTRACE_SETFPXREGS: + *regset = 2; + *rw = request == PTRACE_SETFPXREGS; + break; + case PTRACE_GET_THREAD_AREA: + case PTRACE_SET_THREAD_AREA: + *regset = 3; + *nregs = -1; + *rw = request == PTRACE_SET_THREAD_AREA; + break; + } + return -ENOSYS; +} +#endif /* CONFIG_PTRACE */ + + #endif diff --git a/include/asm-powerpc/tracehook.h b/include/asm-powerpc/tracehook.h index 9c86d24..3811515 100644 --- a/include/asm-powerpc/tracehook.h +++ b/include/asm-powerpc/tracehook.h @@ -62,10 +62,10 @@ static inline void tracehook_abort_sysca } +extern const struct utrace_regset_view utrace_ppc_native_view; static inline const struct utrace_regset_view * utrace_native_view(struct task_struct *tsk) { - extern const struct utrace_regset_view utrace_ppc_native_view; #ifdef CONFIG_PPC64 extern const struct utrace_regset_view utrace_ppc32_view; @@ -75,4 +75,228 @@ utrace_native_view(struct task_struct *t return &utrace_ppc_native_view; } + +#ifdef CONFIG_PTRACE +#include <linux/mm.h> +#include <asm/uaccess.h> + +static inline int arch_ptrace(long request, struct task_struct *child, + unsigned long addr, long data, + const struct utrace_regset_view **view, + int *regset, int *rw, + void __user **uaddr, int *nregs) +{ + switch (request) { +#ifdef CONFIG_PPC64 + case PPC_PTRACE_GETREGS: /* Get GPRs 0 - 31. */ + case PPC_PTRACE_SETREGS: /* Set GPRs 0 - 31. */ + *regset = 0; + *rw = request == PPC_PTRACE_SETREGS; + *nregs = 32; + *uaddr = (void __user *)addr; + break; + case PPC_PTRACE_GETFPREGS: /* Get FPRs 0 - 31. */ + case PPC_PTRACE_SETFPREGS: /* Get FPRs 0 - 31. */ + *regset = 1; + *rw = request == PPC_PTRACE_SETFPREGS; + *uaddr = (void __user *)addr; + *nregs = 32; + break; + case PTRACE_GET_DEBUGREG: + case PTRACE_SET_DEBUGREG: + *regset = 3; + *nregs = -1; + *rw = request == PTRACE_SET_DEBUGREG; + break; +#endif /* CONFIG_PPC64 */ +#ifdef CONFIG_ALTIVEC + case PTRACE_GETVRREGS: + case PTRACE_SETVRREGS: + *regset = 2; + *rw = request == PTRACE_SETVRREGS; + break; +#endif +#ifdef CONFIG_SPE + case PTRACE_GETEVRREGS: + case PTRACE_SETEVRREGS: +#ifdef CONFIG_ALTIVEC + *regset = 3; +#else + *regset = 2; +#endif + *rw = request == PTRACE_SETEVRREGS; + break; +#endif + + } + return -ENOSYS; +} + +static inline int arch_compat_ptrace(long request, struct task_struct *child, + unsigned long addr, long data, + const struct utrace_regset_view **view, + int *regset, int *rw, + void __user **uaddr, int *nregs) +{ + int ret = -ENOSYS; + + switch (request) { +#ifdef CONFIG_PPC64 + case PPC_PTRACE_GETREGS: /* Get GPRs 0 - 31. */ + case PPC_PTRACE_SETREGS: /* Set GPRs 0 - 31. */ + *regset = 0; + *rw = request == PPC_PTRACE_SETREGS; + *nregs = 32; + *uaddr = (void __user *)addr; + break; + case PPC_PTRACE_GETFPREGS: /* Get FPRs 0 - 31. */ + case PPC_PTRACE_SETFPREGS: /* Get FPRs 0 - 31. */ + *regset = 1; + *rw = request == PPC_PTRACE_SETFPREGS; + *uaddr = (void __user *)addr; + *nregs = 32; + break; + case PTRACE_GET_DEBUGREG: + case PTRACE_SET_DEBUGREG: + *regset = 3; + *nregs = -1; + *rw = request == PTRACE_SET_DEBUGREG; + break; + + /* + * Read 4 bytes of the other process' storage + * data is a pointer specifying where the user wants the + * 4 bytes copied into + * addr is a pointer in the user's storage that contains an 8 byte + * address in the other process of the 4 bytes that is to be read + * (this is run in a 32-bit process looking at a 64-bit process) + * when I and D space are separate, these will need to be fixed. + */ + case PPC_PTRACE_PEEKTEXT_3264: + case PPC_PTRACE_PEEKDATA_3264: { + u32 tmp; + int copied; + u32 __user * addrOthers; + + ret = -EIO; + + /* Get the addr in the other process that we want to read */ + if (get_user(addrOthers, (u32 __user * __user *)addr) != 0) + break; + + copied = access_process_vm(child, (u64)addrOthers, &tmp, + sizeof(tmp), 0); + if (copied != sizeof(tmp)) + break; + ret = put_user(tmp, (u32 __user *)data); + break; + } + + /* + * Write 4 bytes into the other process' storage + * data is the 4 bytes that the user wants written + * addr is a pointer in the user's storage that contains an + * 8 byte address in the other process where the 4 bytes + * that is to be written + * (this is run in a 32-bit process looking at a 64-bit process) + * when I and D space are separate, these will need to be fixed. + */ + case PPC_PTRACE_POKETEXT_3264: + case PPC_PTRACE_POKEDATA_3264: { + u32 tmp = data; + u32 __user * addrOthers; + + /* Get the addr in the other process that we want to write into */ + ret = -EIO; + if (get_user(addrOthers, (u32 __user * __user *)addr) != 0) + break; + ret = 0; + if (access_process_vm(child, (u64)addrOthers, &tmp, + sizeof(tmp), 1) == sizeof(tmp)) + break; + ret = -EIO; + break; + } + + /* + * This is like PTRACE_PEEKUSR on a 64-bit process, + * but here we access only 4 bytes at a time. + */ + case PPC_PTRACE_PEEKUSR_3264: { + union + { + u64 whole; + u32 half[2]; + } reg; + int setno; + const struct utrace_regset *regset; + + ret = -EIO; + if ((addr & 3) || addr > PT_FPSCR*8) + break; + + setno = 0; + if (addr >= PT_FPR0*8) { + setno = 1; + addr -= PT_FPR0*8; + } + regset = utrace_regset(child, NULL, + &utrace_ppc_native_view, setno); + ret = (*regset->get)(child, regset, addr &~ 7, + sizeof(reg.whole), ®.whole, NULL); + if (ret == 0) + ret = put_user(reg.half[(addr >> 2) & 1], + (u32 __user *)data); + break; + } + + /* + * This is like PTRACE_POKEUSR on a 64-bit process, + * but here we access only 4 bytes at a time. + */ + case PPC_PTRACE_POKEUSR_3264: { + union + { + u64 whole; + u32 half[2]; + } reg; + int setno; + const struct utrace_regset *regset; + + ret = -EIO; + if ((addr & 3) || addr > PT_FPSCR*8) + break; + + setno = 0; + if (addr >= PT_FPR0*8) { + setno = 1; + addr -= PT_FPR0*8; + } + regset = utrace_regset(child, NULL, + &utrace_ppc_native_view, setno); + ret = (*regset->get)(child, regset, addr &~ 7, + sizeof(reg.whole), ®.whole, NULL); + BUG_ON(ret); + reg.half[(addr >> 2) & 1] = data; + ret = (*regset->set)(child, regset, addr &~ 7, + sizeof(reg.whole), ®.whole, NULL); + break; + } + +#endif /* CONFIG_PPC64 */ +#ifdef CONFIG_ALTIVEC + case PTRACE_GETVRREGS: + case PTRACE_SETVRREGS: + *regset = 2; + *rw = request == PTRACE_SETVRREGS; + break; +#endif + + } + return ret; +} + +#endif /* CONFIG_PTRACE */ + + #endif diff --git a/include/asm-x86_64/tracehook.h b/include/asm-x86_64/tracehook.h index d710df0..e4f7afa 100644 --- a/include/asm-x86_64/tracehook.h +++ b/include/asm-x86_64/tracehook.h @@ -7,6 +7,7 @@ #include <linux/sched.h> #include <asm/ptrace.h> +#include <asm/proto.h> /* * See linux/tracehook.h for the descriptions of what these need to do. @@ -49,4 +50,56 @@ utrace_native_view(struct task_struct *t } +#ifdef CONFIG_PTRACE +static inline int arch_ptrace(long request, struct task_struct *child, + unsigned long addr, long data, + const struct utrace_regset_view **view, + int *regset, int *rw, + void __user **uaddr, int *nregs) +{ + switch (request) { +#ifdef CONFIG_IA32_EMULATION + case PTRACE_GET_THREAD_AREA: + case PTRACE_SET_THREAD_AREA: + *view = &utrace_ia32_view; + *regset = 3; + *nregs = -1; + *rw = request == PTRACE_SET_THREAD_AREA; + break; +#endif + /* normal 64bit interface to access TLS data. + Works just like arch_prctl, except that the arguments + are reversed. */ + case PTRACE_ARCH_PRCTL: + return do_arch_prctl(child, data, addr); + } + return -ENOSYS; +} + +#ifdef CONFIG_IA32_EMULATION +static inline int arch_compat_ptrace(s32 request, struct task_struct *child, + u32 addr, s32 data, + const struct utrace_regset_view **view, + int *regset, int *rw, + void __user **uaddr, int *nregs) +{ + switch (request) { + case PTRACE_GETFPXREGS: + case PTRACE_SETFPXREGS: + *regset = 2; + *rw = request == PTRACE_SETFPXREGS; + break; + case PTRACE_GET_THREAD_AREA: + case PTRACE_SET_THREAD_AREA: + *regset = 3; + *nregs = -1; + *rw = request == PTRACE_SET_THREAD_AREA; + break; + } + return -ENOSYS; +} +#endif /* CONFIG_IA32_EMULATION */ +#endif /* CONFIG_PTRACE */ + + #endif diff --git a/include/linux/ptrace.h b/include/linux/ptrace.h index 270db62..a2c6b34 100644 --- a/include/linux/ptrace.h +++ b/include/linux/ptrace.h @@ -49,45 +49,38 @@ #include <asm/ptrace.h> #ifdef __KERNEL__ +#include <linux/compiler.h> +#include <linux/types.h> +struct task_struct; +struct siginfo; +struct rusage; + +#ifdef CONFIG_PTRACE /* - * Ptrace flags + * Called in do_exit, after setting PF_EXITING, no locks are held. */ +void ptrace_exit(struct task_struct *tsk); -#define PT_PTRACED 0x00000001 -#define PT_DTRACE 0x00000002 /* delayed trace (used on m68k, i386) */ -#define PT_TRACESYSGOOD 0x00000004 -#define PT_PTRACE_CAP 0x00000008 /* ptracer can follow suid-exec */ -#define PT_TRACE_FORK 0x00000010 -#define PT_TRACE_VFORK 0x00000020 -#define PT_TRACE_CLONE 0x00000040 -#define PT_TRACE_EXEC 0x00000080 -#define PT_TRACE_VFORK_DONE 0x00000100 -#define PT_TRACE_EXIT 0x00000200 - -#define PT_TRACE_MASK 0x000003f4 - -/* single stepping state bits (used on ARM and PA-RISC) */ -#define PT_SINGLESTEP_BIT 31 -#define PT_SINGLESTEP (1<<PT_SINGLESTEP_BIT) -#define PT_BLOCKSTEP_BIT 30 -#define PT_BLOCKSTEP (1<<PT_BLOCKSTEP_BIT) - -#include <linux/compiler.h> /* For unlikely. */ -#include <linux/sched.h> /* For struct task_struct. */ - - -extern long arch_ptrace(struct task_struct *child, long request, long addr, long data); -extern struct task_struct *ptrace_get_task_struct(pid_t pid); -extern int ptrace_traceme(void); -extern int ptrace_readdata(struct task_struct *tsk, unsigned long src, char __user *dst, int len); -extern int ptrace_writedata(struct task_struct *tsk, char __user *src, unsigned long dst, int len); -extern int ptrace_attach(struct task_struct *tsk); -extern int ptrace_detach(struct task_struct *, unsigned int); -extern void __ptrace_detach(struct task_struct *, unsigned int); -extern void ptrace_disable(struct task_struct *); -extern int ptrace_check_attach(struct task_struct *task, int kill); -extern int ptrace_request(struct task_struct *child, long request, long addr, long data); -extern int ptrace_may_attach(struct task_struct *task); +/* + * Called in do_wait, with tasklist_lock held for reading. + * This reports any ptrace-child that is ready as do_wait would a normal child. + * If there are no ptrace children, returns -ECHILD. + * If there are some ptrace children but none reporting now, returns 0. + */ +int ptrace_do_wait(struct task_struct *tsk, + pid_t pid, int options, struct siginfo __user *infop, + int __user *stat_addr, struct rusage __user *rusagep); +#else +static inline void ptrace_exit(struct task_struct *tsk) { } +static inline int ptrace_do_wait(struct task_struct *tsk, + pid_t pid, int options, + struct siginfo __user *infop, + int __user *stat_addr, + struct rusage __user *rusagep) +{ + return -ECHILD; +} +#endif #ifndef force_successful_syscall_return diff --git a/include/linux/sched.h b/include/linux/sched.h index b5ae996..4d0debb 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -864,7 +864,12 @@ struct task_struct { int cpuset_mems_generation; #endif atomic_t fs_excl; /* holding fs exclusive resources */ + struct rcu_head rcu; + +#ifdef CONFIG_PTRACE + struct list_head ptracees; +#endif }; static inline pid_t process_group(struct task_struct *tsk) diff --git a/init/Kconfig b/init/Kconfig index 21d6806..73d5021 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -526,6 +526,18 @@ config UTRACE applications. Unless you are making a specially stripped-down kernel and are very sure you don't need these facilitiies, say Y. + +config PTRACE + bool "Legacy ptrace system call interface" + default y + depends on UTRACE + help + Enable the ptrace system call. + This is traditionally used by debuggers like GDB, + and is used by UML and some other applications. + Unless you are very sure you won't run anything that needs it, + say Y. + endmenu menu "Block layer" diff --git a/kernel/exit.c b/kernel/exit.c index b342369..3598b59 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -21,6 +21,7 @@ #include <linux/acct.h> #include <linux/file.h> #include <linux/binfmts.h> +#include <linux/ptrace.h> #include <linux/tracehook.h> #include <linux/profile.h> #include <linux/mount.h> @@ -736,6 +737,8 @@ fastcall NORET_TYPE void do_exit(long co tsk->flags |= PF_EXITING; + ptrace_exit(tsk); + /* * Make sure we don't try to process any timer firings * while we are already exiting. @@ -1326,8 +1329,14 @@ check_continued: break; } } - if (!flag) { - // XXX set flag if we have ptracees + retval = ptrace_do_wait(tsk, pid, options, + infop, stat_addr, ru); + if (retval != -ECHILD) { + flag = 1; + if (retval) { + read_unlock(&tasklist_lock); + goto end; + } } if (options & __WNOTHREAD) break; diff --git a/kernel/fork.c b/kernel/fork.c index 970e076..5296cc0 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -985,6 +985,9 @@ static task_t *copy_process(unsigned lon p->vfork_done = NULL; spin_lock_init(&p->alloc_lock); spin_lock_init(&p->proc_lock); +#ifdef CONFIG_PTRACE + INIT_LIST_HEAD(&p->ptracees); +#endif clear_tsk_thread_flag(p, TIF_SIGPENDING); init_sigpending(&p->pending); diff --git a/kernel/ptrace.c b/kernel/ptrace.c index 2b8eae1..1f73865 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -22,14 +22,15 @@ #include <asm/pgtable.h> #include <asm/uaccess.h> +#ifdef CONFIG_PTRACE +#include <linux/utrace.h> +#include <linux/tracehook.h> +#include <asm/tracehook.h> +#endif -/* - * Check that we have indeed attached to the thing.. - */ -int ptrace_check_attach(struct task_struct *child, int kill) -{ - return -ENOSYS; -} +int getrusage(struct task_struct *, int, struct rusage __user *); + +//#define PTRACE_DEBUG static int may_attach(struct task_struct *task) { @@ -58,36 +59,6 @@ int ptrace_may_attach(struct task_struct return !err; } -int ptrace_attach(struct task_struct *task) -{ - int retval; - - retval = -EPERM; - if (task->pid <= 1) - goto out; - if (task->tgid == current->tgid) - goto bad; - retval = may_attach(task); - if (retval) - goto bad; - - retval = -ENOSYS; - -bad: - write_unlock_irq(&tasklist_lock); - task_unlock(task); -out: - return retval; -} - -int ptrace_detach(struct task_struct *child, unsigned int data) -{ - if (!valid_signal(data)) - return -EIO; - - return -ENOSYS; -} - /* * Access another process' address space. * Source/target buffer must be kernel space, @@ -142,110 +113,1129 @@ int access_process_vm(struct task_struct return buf - old_buf; } -int ptrace_readdata(struct task_struct *tsk, unsigned long src, char __user *dst, int len) + +#ifndef CONFIG_PTRACE + +asmlinkage long sys_ptrace(long request, long pid, long addr, long data) { - int copied = 0; + return -ENOSYS; +} - while (len > 0) { - char buf[128]; - int this_len, retval; - - this_len = (len > sizeof(buf)) ? sizeof(buf) : len; - retval = access_process_vm(tsk, src, buf, this_len, 0); - if (!retval) { - if (copied) - break; - return -EIO; - } - if (copy_to_user(dst, buf, retval)) - return -EFAULT; - copied += retval; - src += retval; - dst += retval; - len -= retval; +#else + +struct ptrace_state +{ + union { + struct rcu_head dead; + struct { + struct utrace_attached_engine *engine; + struct task_struct *task; /* Target task. */ + struct task_struct *parent; /* Whom we report to. */ + + u8 options; /* PTRACE_SETOPTIONS bits. */ + unsigned int stopped:1; /* Stopped for report. */ + unsigned int reported:1; /* wait already reported. */ + unsigned int syscall:1; /* Reporting for syscall. */ +#ifdef PTRACE_SYSEMU + unsigned int sysemu:1; /* PTRACE_SYSEMU in progress. */ +#endif + unsigned int have_eventmsg:1; /* u.eventmsg valid. */ + + union + { + unsigned long eventmsg; + siginfo_t *siginfo; + } u; + } live; + } u; + struct list_head entry; /* Entry on parent->ptracees list. */ +}; + +static const struct utrace_engine_ops ptrace_utrace_ops; /* Initialized below. */ + + +static void +ptrace_state_link(struct ptrace_state *state) +{ + task_lock(state->u.live.parent); + list_add_rcu(&state->entry, &state->u.live.parent->ptracees); + task_unlock(state->u.live.parent); +} + +static void +ptrace_state_unlink(struct ptrace_state *state) +{ + task_lock(state->u.live.parent); + list_del_rcu(&state->entry); + task_unlock(state->u.live.parent); +} + +static int +ptrace_setup(struct task_struct *target, struct utrace_attached_engine *engine, + struct task_struct *parent, u8 options) +{ + struct ptrace_state *state = kzalloc(sizeof *state, GFP_USER); + if (unlikely(state == NULL)) + return -ENOMEM; + + state->u.live.engine = engine; + state->u.live.task = target; + state->u.live.parent = parent; + state->u.live.options = options; + ptrace_state_link(state); + + BUG_ON(engine->data != 0); + rcu_assign_pointer(engine->data, (unsigned long) state); + + return 0; +} + +static void +ptrace_state_free(struct rcu_head *rhead) +{ + struct ptrace_state *state = container_of(rhead, + struct ptrace_state, u.dead); + kfree(state); +} + +static void +ptrace_done(struct ptrace_state *state) +{ + INIT_RCU_HEAD(&state->u.dead); + call_rcu(&state->u.dead, ptrace_state_free); +} + +/* + * Update the tracing engine state to match the new ptrace state. + */ +static void +ptrace_update(struct task_struct *target, struct utrace_attached_engine *engine, + unsigned long flags) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + + /* + * These events are always reported. + */ + flags |= (UTRACE_EVENT(DEATH) | UTRACE_EVENT(EXEC) + | UTRACE_EVENT_SIGNAL_ALL); + + /* + * We always have to examine clone events to check for CLONE_PTRACE. + */ + flags |= UTRACE_EVENT(CLONE); + + /* + * PTRACE_SETOPTIONS can request more events. + */ + if (state->u.live.options & PTRACE_O_TRACEEXIT) + flags |= UTRACE_EVENT(EXIT); + if (state->u.live.options & PTRACE_O_TRACEVFORKDONE) + flags |= UTRACE_EVENT(VFORK_DONE); + + /* + * ptrace always inhibits normal parent reaping. + */ + flags |= UTRACE_ACTION_NOREAP; + + state->u.live.stopped = (flags & UTRACE_ACTION_QUIESCE) != 0; + if (!state->u.live.stopped) { + if (!state->u.live.have_eventmsg) + state->u.live.u.siginfo = NULL; + if (!(target->flags & PF_EXITING)) + target->exit_code = 0; } - return copied; + utrace_set_flags(target, engine, flags); } -int ptrace_writedata(struct task_struct *tsk, char __user *src, unsigned long dst, int len) +static int ptrace_traceme(void) { - int copied = 0; + struct utrace_attached_engine *engine; + int retval; - while (len > 0) { - char buf[128]; - int this_len, retval; + engine = utrace_attach(current, (UTRACE_ATTACH_CREATE + | UTRACE_ATTACH_EXCLUSIVE + | UTRACE_ATTACH_MATCH_OPS), + &ptrace_utrace_ops, 0UL); + + if (IS_ERR(engine)) { + retval = PTR_ERR(engine); + if (retval == -EEXIST) + retval = -EPERM; + } + else { + retval = security_ptrace(current->parent, current); + if (!retval) + retval = ptrace_setup(current, engine, + current->parent, 0); + if (retval) + utrace_detach(current, engine); + else + ptrace_update(current, engine, 0); + } - this_len = (len > sizeof(buf)) ? sizeof(buf) : len; - if (copy_from_user(buf, src, this_len)) - return -EFAULT; - retval = access_process_vm(tsk, dst, buf, this_len, 1); - if (!retval) { - if (copied) - break; - return -EIO; - } - copied += retval; - src += retval; - dst += retval; - len -= retval; + return retval; +} + +static int ptrace_attach(struct task_struct *task) +{ + struct utrace_attached_engine *engine; + int retval; + + retval = -EPERM; + if (task->pid <= 1) + goto bad; + if (task->tgid == current->tgid) + goto bad; + + engine = utrace_attach(task, (UTRACE_ATTACH_CREATE + | UTRACE_ATTACH_EXCLUSIVE + | UTRACE_ATTACH_MATCH_OPS), + &ptrace_utrace_ops, 0); + if (IS_ERR(engine)) { + retval = PTR_ERR(engine); + if (retval == -EEXIST) + retval = -EPERM; + goto bad; + } + + if (ptrace_may_attach(task)) + retval = ptrace_setup(task, engine, current, 0); + if (retval) + utrace_detach(task, engine); + else { + /* Go */ + ptrace_update(task, engine, 0); + force_sig_specific(SIGSTOP, task); } - return copied; + +bad: + return retval; } -int ptrace_request(struct task_struct *child, long request, - long addr, long data) +static int ptrace_detach(struct task_struct *task, + struct utrace_attached_engine *engine) { - return -ENOSYS; + struct ptrace_state *state = (struct ptrace_state *) engine->data; + utrace_detach(task, engine); + ptrace_state_unlink(state); + ptrace_done(state); + return 0; } -/** - * ptrace_traceme -- helper for PTRACE_TRACEME - * - * Performs checks and sets PT_PTRACED. - * Should be used by all ptrace implementations for PTRACE_TRACEME. + +/* + * This is called when we are exiting. We must stop all our ptracing. */ -int ptrace_traceme(void) +void +ptrace_exit(struct task_struct *tsk) { - int ret = -EPERM; + struct ptrace_state *state, *next; + task_lock(tsk); + list_for_each_entry_safe(state, next, &tsk->ptracees, entry) { + list_del_rcu(&state->entry); + utrace_detach(state->u.live.task, state->u.live.engine); + ptrace_done(state); + } + task_unlock(tsk); + BUG_ON(!list_empty(&tsk->ptracees)); +} - ret = security_ptrace(current->parent, current); - if (ret) - return -EPERM; +static int +ptrace_induce_signal(struct task_struct *target, + struct utrace_attached_engine *engine, + long signr) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; - return -ENOSYS; + if (signr == 0) + return 0; + + if (!valid_signal(signr)) + return -EIO; + + if (state->u.live.syscall) { + /* + * This is the traditional ptrace behavior when given + * a signal to resume from a syscall tracing stop. + */ + send_sig(signr, target, 1); + } + else if (!state->u.live.have_eventmsg && state->u.live.u.siginfo) { + siginfo_t *info = state->u.live.u.siginfo; + + /* Update the siginfo structure if the signal has + changed. If the debugger wanted something + specific in the siginfo structure then it should + have updated *info via PTRACE_SETSIGINFO. */ + if (signr != info->si_signo) { + info->si_signo = signr; + info->si_errno = 0; + info->si_code = SI_USER; + info->si_pid = current->pid; + info->si_uid = current->uid; + } + + return utrace_inject_signal(target, engine, + UTRACE_ACTION_RESUME, info, NULL); + } + + return 0; } -/** - * ptrace_get_task_struct -- grab a task struct reference for ptrace - * @pid: process id to grab a task_struct reference of - * - * This function is a helper for ptrace implementations. It checks - * permissions and then grabs a task struct for use of the actual - * ptrace implementation. - * - * Returns the task_struct for @pid or an ERR_PTR() on failure. - */ -struct task_struct *ptrace_get_task_struct(pid_t pid) +static int +ptrace_regset(struct task_struct *target, + struct utrace_attached_engine *engine, + const struct utrace_regset_view *view, int which, + int write, int nregs, unsigned long addr, void __user *datap) +{ + const struct utrace_regset *regset = utrace_regset(target, engine, + view, which); + int ret; + unsigned int pos = 0, n; + +#ifdef PTRACE_DEBUG + printk("%d ptrace_regset on %d view %p regset %p (%d/%d) +%lx\n", + current->pid, target->pid, view, regset, write, nregs, addr); +#endif + + if (unlikely(regset == NULL)) + return -EIO; + + if (nregs < 0) { + if (addr < regset->bias || addr >= regset->bias + regset->n) + return -EINVAL; + pos = (addr - regset->bias) * regset->size; + n = 1; + } + else + n = nregs == 0 ? regset->n : nregs; + + n *= regset->size; + if (write) { + if (!access_ok(VERIFY_READ, datap, n)) + ret = -EIO; + else + ret = (*regset->set)(target, regset, pos, n, + NULL, datap); + } + else { + if (!access_ok(VERIFY_WRITE, datap, n)) + ret = -EIO; + else + ret = (*regset->get)(target, regset, pos, n, + NULL, datap); + } + + return ret; +} + +static int +ptrace_uarea(struct task_struct *target, + struct utrace_attached_engine *engine, + int write, unsigned long addr, long data) +{ + const struct utrace_regset_view *view = utrace_native_view(current); + const struct ptrace_uarea_segment *seg = view->uarea_segments; + const struct utrace_regset *regset; + int ret = -EIO; + + if (unlikely(seg == NULL)) + goto out; + + while (addr >= seg->end && seg->end != 0) + ++seg; + + if (addr < seg->start || addr >= seg->end) + goto out; + addr -= seg->start; + addr += seg->offset; + + regset = utrace_regset(target, engine, view, seg->regset); + if (unlikely(regset == NULL)) + goto out; + + if ((addr & (regset->align - 1)) + || addr > (regset->n - 1) * regset->size) + goto out; + + if (write) { + if (likely(regset->size == sizeof(data))) + ret = (*regset->set)(target, regset, + addr, sizeof(data), &data, NULL); + else + switch (regset->size) { + s32 data4; + s64 data8; + case 4: + data4 = data; + ret = (*regset->set)(target, regset, + addr, 4, &data4, NULL); + break; + case 8: + data8 = data; + ret = (*regset->set)(target, regset, + addr, 8, &data8, NULL); + break; + default: + BUG(); + } + } + else { + void *datap = (void *) data; + if (likely(access_ok(VERIFY_WRITE, datap, regset->size))) + ret = (*regset->get)(target, regset, + addr, regset->size, NULL, datap); + } + +out: + return ret; +} + +static int +ptrace_start(long pid, long request, + struct task_struct **childp, + struct utrace_attached_engine **enginep, + struct ptrace_state **statep) + { struct task_struct *child; + struct utrace_attached_engine *engine; + struct ptrace_state *state; + int ret; - /* - * Tracing init is not allowed. - */ - if (pid == 1) - return ERR_PTR(-EPERM); + if (request == PTRACE_TRACEME) + return ptrace_traceme(); + ret = -ESRCH; read_lock(&tasklist_lock); child = find_task_by_pid(pid); if (child) get_task_struct(child); read_unlock(&tasklist_lock); +#ifdef PTRACE_DEBUG + printk("ptrace pid %ld => %p\n", pid, child); +#endif if (!child) - return ERR_PTR(-ESRCH); - return child; + goto out; + + ret = -EPERM; + if (pid == 1) /* you may not mess with init */ + goto out_tsk; + + if (request == PTRACE_ATTACH) { + ret = ptrace_attach(child); + goto out_tsk; + } + + engine = utrace_attach(child, UTRACE_ATTACH_MATCH_OPS, + &ptrace_utrace_ops, 0); + ret = -ESRCH; + if (IS_ERR(engine) || engine == NULL) + goto out_tsk; + rcu_read_lock(); + state = rcu_dereference((struct ptrace_state *) engine->data); + if (state == NULL || state->u.live.parent != current) { + rcu_read_unlock(); + goto out_tsk; + } + rcu_read_unlock(); + + /* + * Traditional ptrace behavior demands that the target already be + * quiescent, but not dead. + */ + if (request != PTRACE_KILL && !state->u.live.stopped) { +#ifdef PTRACE_DEBUG + printk("%d not stopped (%lx)\n", child->pid, child->state); +#endif + if (child->state != TASK_STOPPED) + goto out_tsk; + utrace_set_flags(child, engine, + engine->flags | UTRACE_ACTION_QUIESCE); + } + if (child->exit_state) + goto out_tsk; + + *childp = child; + *enginep = engine; + *statep = state; + return -EIO; + +out_tsk: + put_task_struct(child); +out: + return ret; +} + +static int +ptrace_common(long request, struct task_struct *child, + struct utrace_attached_engine *engine, + struct ptrace_state *state, + unsigned long addr, long data) +{ + unsigned long flags; + int ret = -EIO; + + switch (request) { + case PTRACE_DETACH: + /* + * Detach a process that was attached. + */ + ret = ptrace_induce_signal(child, engine, data); + if (!ret) + ret = ptrace_detach(child, engine); + break; + + /* + * These are the operations that resume the child running. + */ + case PTRACE_KILL: + data = SIGKILL; + case PTRACE_CONT: + case PTRACE_SYSCALL: + case PTRACE_SINGLESTEP: +#ifdef PTRACE_SINGLEBLOCK + case PTRACE_SINGLEBLOCK: +#endif +#ifdef PTRACE_SYSEMU + case PTRACE_SYSEMU: + case PTRACE_SYSEMU_SINGLESTEP: +#endif + ret = ptrace_induce_signal(child, engine, data); + if (ret) + break; + + /* + * Reset the action flags without QUIESCE, so it resumes. + */ + flags = 0; +#ifdef PTRACE_SYSEMU + state->u.live.sysemu = (request == PTRACE_SYSEMU_SINGLESTEP + || request == PTRACE_SYSEMU); +#endif + if (request == PTRACE_SINGLESTEP +#ifdef PTRACE_SYSEMU + || request == PTRACE_SYSEMU_SINGLESTEP +#endif + ) + flags |= UTRACE_ACTION_SINGLESTEP; +#ifdef PTRACE_SINGLEBLOCK + else if (request == PTRACE_SINGLEBLOCK) + flags |= UTRACE_ACTION_BLOCKSTEP; +#endif + if (request == PTRACE_SYSCALL) + flags |= UTRACE_EVENT_SYSCALL; +#ifdef PTRACE_SYSEMU + else if (request == PTRACE_SYSEMU + || request == PTRACE_SYSEMU_SINGLESTEP) + flags |= UTRACE_EVENT(SYSCALL_ENTRY); +#endif + ptrace_update(child, engine, flags); + ret = 0; + break; + +#ifdef PTRACE_OLDSETOPTIONS + case PTRACE_OLDSETOPTIONS: +#endif + case PTRACE_SETOPTIONS: + ret = -EINVAL; + if (data & ~PTRACE_O_MASK) + break; + state->u.live.options = data; + ptrace_update(child, engine, UTRACE_ACTION_QUIESCE); + ret = 0; + break; + +#ifdef PTRACE_GETREGS + case PTRACE_GETREGS: + ret = ptrace_regset(child, engine, utrace_native_view(current), + 0, 0, 0, addr, (void __user *)data); + break; + case PTRACE_SETREGS: + ret = ptrace_regset(child, engine, utrace_native_view(current), + 0, 1, 0, addr, (void __user *)data); + break; +#endif +#ifdef PTRACE_GETFPREGS + case PTRACE_GETFPREGS: + ret = ptrace_regset(child, engine, utrace_native_view(current), + 1, 0, 0, addr, (void __user *)data); + break; + case PTRACE_SETFPREGS: + ret = ptrace_regset(child, engine, utrace_native_view(current), + 1, 1, 0, addr, (void __user *)data); + break; +#endif + + case PTRACE_PEEKUSR: + ret = ptrace_uarea(child, engine, 0, addr, data); + break; + case PTRACE_POKEUSR: + ret = ptrace_uarea(child, engine, 1, addr, data); + break; + } + + return ret; } + asmlinkage long sys_ptrace(long request, long pid, long addr, long data) { - return -ENOSYS; + struct task_struct *child; + struct utrace_attached_engine *engine; + struct ptrace_state *state; + int ret; + const struct utrace_regset_view *view; + int regset, nregs, rw; + void __user *uaddr; + +#ifdef PTRACE_DEBUG + printk("%d sys_ptrace(%ld, %ld, %lx, %lx)\n", + current->pid, request, pid, addr, data); +#endif + + ret = ptrace_start(pid, request, &child, &engine, &state); + if (ret != -EIO) + goto out; + + view = utrace_native_view(current); + regset = rw = -1; + nregs = 0; + uaddr = (void __user *) data; + ret = arch_ptrace(request, child, addr, data, + &view, ®set, &rw, &uaddr, &nregs); + if (ret != -ENOSYS) + goto out_tsk; + if (regset >= 0) { + ret = ptrace_regset(child, engine, view, regset, + rw, nregs, addr, uaddr); + goto out_tsk; + } + + switch (request) { + default: + ret = ptrace_common(request, child, engine, state, addr, data); + break; + + case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKDATA: { + unsigned long tmp; + int copied; + + copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); + ret = -EIO; + if (copied != sizeof(tmp)) + break; + ret = put_user(tmp, (unsigned long __user *) data); + break; + } + + case PTRACE_POKETEXT: /* write the word at location addr. */ + case PTRACE_POKEDATA: + ret = 0; + if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data)) + break; + ret = -EIO; + break; + + case PTRACE_GETEVENTMSG: + ret = put_user(state->u.live.have_eventmsg + ? state->u.live.u.eventmsg : 0L, + (unsigned long __user *) data); + break; + case PTRACE_GETSIGINFO: + ret = -EINVAL; + if (!state->u.live.have_eventmsg && state->u.live.u.siginfo) + ret = copy_siginfo_to_user((siginfo_t __user *) data, + state->u.live.u.siginfo); + break; + case PTRACE_SETSIGINFO: + ret = -EINVAL; + if (!state->u.live.have_eventmsg && state->u.live.u.siginfo + && copy_from_user(state->u.live.u.siginfo, + (siginfo_t __user *) data, + sizeof(siginfo_t))) + ret = -EFAULT; + break; + } + +out_tsk: + put_task_struct(child); +out: +#ifdef PTRACE_DEBUG + printk("%d ptrace -> %x\n", current->pid, ret); +#endif + return ret; +} + + +#ifdef CONFIG_COMPAT +#include <linux/compat.h> + +asmlinkage long compat_sys_ptrace(compat_long_t request, compat_long_t pid, + compat_ulong_t addr, compat_long_t cdata) +{ + const unsigned long data = (unsigned long) (compat_ulong_t) cdata; + struct task_struct *child; + struct utrace_attached_engine *engine; + struct ptrace_state *state; + int ret; + const struct utrace_regset_view *view; + int regset, nregs, rw; + void __user *uaddr; + +#ifdef PTRACE_DEBUG + printk("%d compat_sys_ptrace(%d, %d, %x, %x)\n", + current->pid, request, pid, addr, cdata); +#endif + ret = ptrace_start(pid, request, &child, &engine, &state); + if (ret != -EIO) + goto out; + + view = utrace_native_view(current); + regset = rw = -1; + nregs = 0; + uaddr = (void __user *) (unsigned long) cdata; + ret = arch_compat_ptrace(request, child, addr, cdata, + &view, ®set, &rw, &uaddr, &nregs); + if (ret != -ENOSYS) + goto out_tsk; + if (regset >= 0) { + ret = ptrace_regset(child, engine, view, regset, + rw, nregs, addr, uaddr); + goto out_tsk; + } + + switch (request) { + default: + ret = ptrace_common(request, child, engine, state, addr, data); + break; + + case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKDATA: { + compat_ulong_t tmp; + int copied; + + copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); + ret = -EIO; + if (copied != sizeof(tmp)) + break; + ret = put_user(tmp, (compat_ulong_t __user *) data); + break; + } + + case PTRACE_POKETEXT: /* write the word at location addr. */ + case PTRACE_POKEDATA: + ret = 0; + if (access_process_vm(child, addr, &cdata, sizeof(cdata), 1) == sizeof(cdata)) + break; + ret = -EIO; + break; + + case PTRACE_GETEVENTMSG: + ret = put_user(state->u.live.have_eventmsg + ? state->u.live.u.eventmsg : 0L, + (compat_long_t __user *) data); + break; + case PTRACE_GETSIGINFO: + ret = -EINVAL; + if (!state->u.live.have_eventmsg && state->u.live.u.siginfo) + ret = copy_siginfo_to_user32( + (struct compat_siginfo __user *) data, + state->u.live.u.siginfo); + break; + case PTRACE_SETSIGINFO: + ret = -EINVAL; + if (!state->u.live.have_eventmsg && state->u.live.u.siginfo + && copy_siginfo_from_user32( + state->u.live.u.siginfo, + (struct compat_siginfo __user *) data)) + ret = -EFAULT; + break; + } + +out_tsk: + put_task_struct(child); +out: +#ifdef PTRACE_DEBUG + printk("%d ptrace -> %x\n", current->pid, ret); +#endif + return ret; +} +#endif + + +int +ptrace_do_wait(task_t *tsk, + pid_t pid, int options, struct siginfo __user *infop, + int __user *stat_addr, struct rusage __user *rusagep) +{ + struct ptrace_state *state; + task_t *p; + int err = -ECHILD; + int why, status; + + rcu_read_lock(); + list_for_each_entry_rcu(state, &tsk->ptracees, entry) { + p = state->u.live.task; + + if (pid > 0) { + if (p->pid != pid) + continue; + } else if (!pid) { + if (process_group(p) != process_group(current)) + continue; + } else if (pid != -1) { + if (process_group(p) != -pid) + continue; + } + if (((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0)) + && !(options & __WALL)) + continue; + if (security_task_wait(p)) + continue; + + err = 0; + if (state->u.live.reported) + continue; + + if (state->u.live.stopped) + goto found; + if ((p->state & (TASK_TRACED | TASK_STOPPED)) + && (p->signal->flags & SIGNAL_STOP_STOPPED)) + goto found; + if (p->exit_state == EXIT_ZOMBIE) { + if (!likely(options & WEXITED)) + continue; + goto found; + } + } + rcu_read_unlock(); + return err; + +found: + rcu_read_unlock(); + + if (p->exit_state) { + if ((p->exit_code & 0x7f) == 0) { + why = CLD_EXITED; + status = p->exit_code >> 8; + } else { + why = (p->exit_code & 0x80) ? CLD_DUMPED : CLD_KILLED; + status = p->exit_code & 0xff; + } + } + else { + why = CLD_TRAPPED; + status = (p->exit_code << 8) | 0x7f; + } + + if (rusagep) + err = getrusage(p, RUSAGE_BOTH, rusagep); + if (infop) { + if (!err) + err = put_user(SIGCHLD, &infop->si_signo); + if (!err) + err = put_user(0, &infop->si_errno); + if (!err) + err = put_user((short)why, &infop->si_code); + if (!err) + err = put_user(p->pid, &infop->si_pid); + if (!err) + err = put_user(p->uid, &infop->si_uid); + if (!err) + err = put_user(status, &infop->si_status); + } + if (!err && stat_addr) + err = put_user(status, stat_addr); + + if (!err) { + err = p->pid; + + utrace_lock(p->utrace); + if (unlikely(state->u.live.reported)) + err = -ERESTARTNOINTR; + state->u.live.reported = 1; + utrace_unlock(p->utrace); + } + + if (why != CLD_TRAPPED) + ptrace_detach(p, state->u.live.engine); + + return err; } + +static void +do_notify(struct task_struct *tsk, struct task_struct *parent, int exit) +{ + struct siginfo info; + unsigned long flags; + struct sighand_struct *sighand; + + info.si_signo = SIGCHLD; + info.si_errno = 0; + info.si_pid = tsk->pid; + info.si_uid = tsk->uid; + + /* FIXME: find out whether or not this is supposed to be c*time. */ + info.si_utime = cputime_to_jiffies(tsk->utime); + info.si_stime = cputime_to_jiffies(tsk->stime); + + info.si_code = CLD_TRAPPED; + info.si_status = tsk->exit_code & 0x7f; + if (exit) { + if (tsk->exit_code & 0x80) + info.si_code = CLD_DUMPED; + else if (tsk->exit_code & 0x7f) + info.si_code = CLD_KILLED; + else { + info.si_code = CLD_EXITED; + info.si_status = tsk->exit_code >> 8; + } + } + + sighand = parent->sighand; + spin_lock_irqsave(&sighand->siglock, flags); + if (sighand->action[SIGCHLD-1].sa.sa_handler != SIG_IGN && + !(sighand->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP)) + __group_send_sig_info(SIGCHLD, &info, parent); + /* + * Even if SIGCHLD is not generated, we must wake up wait4 calls. + */ + wake_up_interruptible_sync(&parent->signal->wait_chldexit); + spin_unlock_irqrestore(&sighand->siglock, flags); +} + +static u32 +ptrace_report(struct utrace_attached_engine *engine, struct task_struct *tsk, + int code) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + +#ifdef PTRACE_DEBUG + printk("%d ptrace_report %d engine %p state %p code %x parent %d (%p)\n", + current->pid, tsk->pid, engine, state, code, + state->u.live.parent->pid, state->u.live.parent); + if (!state->u.live.have_eventmsg && state->u.live.u.siginfo) { + const siginfo_t *si = state->u.live.u.siginfo; + printk(" si %d code %x errno %d addr %p\n", + si->si_signo, si->si_code, si->si_errno, + si->si_addr); + } +#endif + + state->u.live.stopped = 1; + state->u.live.reported = 0; + tsk->exit_code = code; + do_notify(tsk, state->u.live.parent, 0); + +#ifdef PTRACE_DEBUG + printk("%d ptrace_report quiescing exit_code %x\n", + current->pid, current->exit_code); +#endif + + return (UTRACE_ACTION_NEWSTATE + | UTRACE_ACTION_QUIESCE | UTRACE_ACTION_NOREAP); +} + +static inline u32 +ptrace_event(struct utrace_attached_engine *engine, struct task_struct *tsk, + int event) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + state->u.live.syscall = 0; + return ptrace_report(engine, tsk, (event << 8) | SIGTRAP); +} + + +static u32 +ptrace_report_death(struct utrace_attached_engine *engine, + struct task_struct *tsk) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + + if (tsk->parent == state->u.live.parent) { + ptrace_state_unlink(state); + ptrace_done(state); + return UTRACE_ACTION_DETACH; + } + + state->u.live.reported = 0; + do_notify(tsk, state->u.live.parent, 1); + return UTRACE_ACTION_RESUME; +} + + +static u32 +ptrace_report_clone(struct utrace_attached_engine *engine, + struct task_struct *parent, + unsigned long clone_flags, struct task_struct *child) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + struct utrace_attached_engine *child_engine; + int event = PTRACE_EVENT_FORK; + int option = PTRACE_O_TRACEFORK; + +#ifdef PTRACE_DEBUG + printk("%d (%p) engine %p ptrace_report_clone child %d (%p) fl %lx\n", + parent->pid, parent, engine, child->pid, child, clone_flags); +#endif + + if (clone_flags & CLONE_UNTRACED) + goto out; + + if (clone_flags & CLONE_VFORK) { + event = PTRACE_EVENT_VFORK; + option = PTRACE_O_TRACEVFORK; + } + else if ((clone_flags & CSIGNAL) != SIGCHLD) { + event = PTRACE_EVENT_CLONE; + option = PTRACE_O_TRACECLONE; + } + + if (!(clone_flags & CLONE_PTRACE) && !(state->u.live.options & option)) + goto out; + + child_engine = utrace_attach(child, (UTRACE_ATTACH_CREATE + | UTRACE_ATTACH_EXCLUSIVE + | UTRACE_ATTACH_MATCH_OPS), + &ptrace_utrace_ops, 0UL); + if (unlikely(IS_ERR(child_engine))) { + printk("XXX ptrace lost child %d (attach): %ld\n", + child->pid, PTR_ERR(child_engine)); + } + else { + int ret = ptrace_setup(child, child_engine, + state->u.live.parent, + state->u.live.options); + if (ret != 0) { + printk("XXX ptrace lost child %d (setup): %d\n", + child->pid, ret); + utrace_detach(child, child_engine); + } + else { + sigaddset(&child->pending.signal, SIGSTOP); + set_tsk_thread_flag(child, TIF_SIGPENDING); + ptrace_update(child, child_engine, 0); + } + } + + if (state->u.live.options & option) { + state->u.live.have_eventmsg = 1; + state->u.live.u.eventmsg = child->pid; + return ptrace_event(engine, parent, event); + } + +out: + return UTRACE_ACTION_RESUME; +} + + +static u32 +ptrace_report_vfork_done(struct utrace_attached_engine *engine, + struct task_struct *parent, struct task_struct *child) +{ + return ptrace_event(engine, parent, PTRACE_EVENT_VFORK_DONE); +} + + +static u32 +ptrace_report_signal(struct utrace_attached_engine *engine, + struct task_struct *tsk, struct pt_regs *regs, + u32 action, siginfo_t *info, + const struct k_sigaction *orig_ka, + struct k_sigaction *return_ka) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + int signo = info == NULL ? SIGTRAP : info->si_signo; + state->u.live.syscall = 0; + state->u.live.have_eventmsg = 0; + state->u.live.u.siginfo = info; + return ptrace_report(engine, tsk, signo) | UTRACE_SIGNAL_IGN; +} + +static u32 +ptrace_report_jctl(struct utrace_attached_engine *engine, + struct task_struct *tsk, int type) +{ + return UTRACE_ACTION_RESUME; /* XXX */ +} + +static u32 +ptrace_report_exec(struct utrace_attached_engine *engine, + struct task_struct *tsk, + const struct linux_binprm *bprm, + struct pt_regs *regs) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + if (state->u.live.options & PTRACE_O_TRACEEXEC) + return ptrace_event(engine, tsk, PTRACE_EVENT_EXEC); + state->u.live.syscall = 0; + return ptrace_report(engine, tsk, SIGTRAP); +} + +static u32 +ptrace_report_syscall(struct utrace_attached_engine *engine, + struct task_struct *tsk, struct pt_regs *regs) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; +#ifdef PTRACE_SYSEMU + if (state->u.live.sysemu) + tracehook_abort_syscall(regs); +#endif + state->u.live.syscall = 1; + return ptrace_report(engine, tsk, + ((state->u.live.options & PTRACE_O_TRACESYSGOOD) + ? 0x80 : 0) | SIGTRAP); +} + +static u32 +ptrace_report_exit(struct utrace_attached_engine *engine, + struct task_struct *tsk, long orig_code, long *code) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + state->u.live.have_eventmsg = 1; + state->u.live.u.eventmsg = *code; + return ptrace_event(engine, tsk, PTRACE_EVENT_EXIT); +} + +static pid_t +ptrace_tracer_pid(struct utrace_attached_engine *engine, + struct task_struct *target) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + /* XXX detach race */ + return state->u.live.parent->pid; +} + +static int +ptrace_allow_access_process_vm(struct utrace_attached_engine *engine, + struct task_struct *target, + struct task_struct *caller) +{ + struct ptrace_state *state = (struct ptrace_state *) engine->data; + /* XXX detach race */ + return (((engine->flags & UTRACE_ACTION_QUIESCE) + || (target->state == TASK_STOPPED)) + && state->u.live.parent == caller + && security_ptrace(caller, target) == 0); +} + + +static const struct utrace_engine_ops ptrace_utrace_ops = +{ + .report_syscall_entry = ptrace_report_syscall, + .report_syscall_exit = ptrace_report_syscall, + .report_exec = ptrace_report_exec, + .report_jctl = ptrace_report_jctl, + .report_signal = ptrace_report_signal, + .report_vfork_done = ptrace_report_vfork_done, + .report_clone = ptrace_report_clone, + .report_exit = ptrace_report_exit, + .report_death = ptrace_report_death, + .tracer_pid = ptrace_tracer_pid, + .allow_access_process_vm = ptrace_allow_access_process_vm, +}; + +#endif - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
| |