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 [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[Index of Archives]
[Kernel Newbies]
[Netfilter]
[Bugtraq]
[Photo]
[Stuff]
[Gimp]
[Yosemite News]
[MIPS Linux]
[ARM Linux]
[Linux Security]
[Linux RAID]
[Video 4 Linux]
[Linux for the blind]
[Linux Resources]