patch-2.4.0-test3 linux/arch/arm/kernel/ptrace.c

Next file: linux/arch/arm/kernel/ptrace.h
Previous file: linux/arch/arm/kernel/process.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test2/linux/arch/arm/kernel/ptrace.c linux/arch/arm/kernel/ptrace.c
@@ -8,7 +8,6 @@
 #include <linux/mm.h>
 #include <linux/smp.h>
 #include <linux/smp_lock.h>
-#include <linux/errno.h>
 #include <linux/ptrace.h>
 #include <linux/user.h>
 
@@ -16,6 +15,10 @@
 #include <asm/pgtable.h>
 #include <asm/system.h>
 
+#include "ptrace.h"
+
+#define REG_PC	15
+#define REG_PSR	16
 /*
  * does not yet catch signals sent when the child dies.
  * in exit.c or in signal.c.
@@ -27,6 +30,18 @@
 #define BREAKINST	0xef9f0001
 
 /*
+ * Get the address of the live pt_regs for the specified task.
+ * These are saved onto the top kernel stack when the process
+ * is not running.
+ */
+static inline struct pt_regs *
+get_user_regs(struct task_struct *task)
+{
+	return (struct pt_regs *)
+		((unsigned long)task + 8192 - sizeof(struct pt_regs));
+}
+
+/*
  * this routine will get a word off of the processes privileged stack.
  * the offset is how far from the base addr as stored in the THREAD.
  * this routine assumes that all the privileged stacks are in our
@@ -34,11 +49,7 @@
  */
 static inline long get_stack_long(struct task_struct *task, int offset)
 {
-	struct pt_regs *regs;
-
-	regs = (struct pt_regs *)((unsigned long)task + 8192 - sizeof(struct pt_regs));
-
-	return regs->uregs[offset];
+	return get_user_regs(task)->uregs[offset];
 }
 
 /*
@@ -47,20 +58,16 @@
  * this routine assumes that all the privileged stacks are in our
  * data space.
  */
-static inline long put_stack_long(struct task_struct *task, int offset,
-	unsigned long data)
+static inline int
+put_stack_long(struct task_struct *task, int offset, long data)
 {
-	struct pt_regs *regs;
-
-	regs = (struct pt_regs *)((unsigned long)task + 8192 - sizeof(struct pt_regs));
-
-	regs->uregs[offset] = data;
+	get_user_regs(task)->uregs[offset] = data;
 
 	return 0;
 }
 
-static int
-read_long(struct task_struct *child, unsigned long addr, unsigned long *res)
+static inline int
+read_tsk_long(struct task_struct *child, unsigned long addr, unsigned long *res)
 {
 	int copied;
 
@@ -69,8 +76,8 @@
 	return copied != sizeof(*res) ? -EIO : 0;
 }
 
-static int
-write_long(struct task_struct *child, unsigned long addr, unsigned long val)
+static inline int
+write_tsk_long(struct task_struct *child, unsigned long addr, unsigned long val)
 {
 	int copied;
 
@@ -82,35 +89,33 @@
 /*
  * Get value of register `rn' (in the instruction)
  */
-static unsigned long ptrace_getrn (struct task_struct *child, unsigned long insn)
+static unsigned long
+ptrace_getrn(struct task_struct *child, unsigned long insn)
 {
 	unsigned int reg = (insn >> 16) & 15;
 	unsigned long val;
 
+	val = get_stack_long(child, reg);
 	if (reg == 15)
-		val = pc_pointer (get_stack_long (child, reg));
-	else
-		val = get_stack_long (child, reg);
+		val = pc_pointer(val);
 
-printk ("r%02d=%08lX ", reg, val);
 	return val;
 }
 
 /*
  * Get value of operand 2 (in an ALU instruction)
  */
-static unsigned long ptrace_getaluop2 (struct task_struct *child, unsigned long insn)
+static unsigned long
+ptrace_getaluop2(struct task_struct *child, unsigned long insn)
 {
 	unsigned long val;
 	int shift;
 	int type;
 
-printk ("op2=");
 	if (insn & 1 << 25) {
 		val = insn & 255;
 		shift = (insn >> 8) & 15;
 		type = 3;
-printk ("(imm)");
 	} else {
 		val = get_stack_long (child, insn & 15);
 
@@ -120,9 +125,8 @@
 			shift = (insn >> 7) & 31;
 
 		type = (insn >> 5) & 3;
-printk ("(r%02ld)", insn & 15);
 	}
-printk ("sh%dx%d", type, shift);
+
 	switch (type) {
 	case 0:	val <<= shift;	break;
 	case 1:	val >>= shift;	break;
@@ -133,24 +137,23 @@
  		val = (val >> shift) | (val << (32 - shift));
 		break;
 	}
-printk ("=%08lX ", val);
 	return val;
 }
 
 /*
  * Get value of operand 2 (in a LDR instruction)
  */
-static unsigned long ptrace_getldrop2 (struct task_struct *child, unsigned long insn)
+static unsigned long
+ptrace_getldrop2(struct task_struct *child, unsigned long insn)
 {
 	unsigned long val;
 	int shift;
 	int type;
 
-	val = get_stack_long (child, insn & 15);
+	val = get_stack_long(child, insn & 15);
 	shift = (insn >> 7) & 31;
 	type = (insn >> 5) & 3;
 
-printk ("op2=r%02ldsh%dx%d", insn & 15, shift, type);
 	switch (type) {
 	case 0:	val <<= shift;	break;
 	case 1:	val >>= shift;	break;
@@ -161,7 +164,6 @@
  		val = (val >> shift) | (val << (32 - shift));
 		break;
 	}
-printk ("=%08lX ", val);
 	return val;
 }
 
@@ -170,95 +172,72 @@
 {
 	unsigned long alt = 0;
 
-printk(KERN_DEBUG "ptrace_set_bpt: insn=%08lX pc=%08lX ", insn, pc);
-	switch (insn & 0x0e100000) {
+	switch (insn & 0x0e000000) {
 	case 0x00000000:
-	case 0x00100000:
-	case 0x02000000:
-	case 0x02100000: /* data processing */
-		printk ("data ");
-		switch (insn & 0x01e0f000) {
-		case 0x0000f000:
-			alt = ptrace_getrn(child, insn) & ptrace_getaluop2(child, insn);
-			break;
-		case 0x0020f000:
-			alt = ptrace_getrn(child, insn) ^ ptrace_getaluop2(child, insn);
-			break;
-		case 0x0040f000:
-			alt = ptrace_getrn(child, insn) - ptrace_getaluop2(child, insn);
-			break;
-		case 0x0060f000:
-			alt = ptrace_getaluop2(child, insn) - ptrace_getrn(child, insn);
-			break;
-		case 0x0080f000:
-			alt = ptrace_getrn(child, insn) + ptrace_getaluop2(child, insn);
-			break;
-		case 0x00a0f000:
-			alt = ptrace_getrn(child, insn) + ptrace_getaluop2(child, insn) +
-				(get_stack_long (child, 16/*REG_PSR*/) & CC_C_BIT ? 1 : 0);
-			break;
-		case 0x00c0f000:
-			alt = ptrace_getrn(child, insn) - ptrace_getaluop2(child, insn) +
-				(get_stack_long (child, 16/*REG_PSR*/) & CC_C_BIT ? 1 : 0);
-			break;
-		case 0x00e0f000:
-			alt = ptrace_getaluop2(child, insn) - ptrace_getrn(child, insn) +
-				(get_stack_long (child, 16/*REG_PSR*/) & CC_C_BIT ? 1 : 0);
-			break;
-		case 0x0180f000:
-			alt = ptrace_getrn(child, insn) | ptrace_getaluop2(child, insn);
-			break;
-		case 0x01a0f000:
-			alt = ptrace_getaluop2(child, insn);
-			break;
-		case 0x01c0f000:
-			alt = ptrace_getrn(child, insn) & ~ptrace_getaluop2(child, insn);
-			break;
-		case 0x01e0f000:
-			alt = ~ptrace_getaluop2(child, insn);
+	case 0x02000000: {
+		/*
+		 * data processing
+		 */
+		long aluop1, aluop2, ccbit;
+
+		if ((insn & 0xf000) != 0xf000)
 			break;
+
+		aluop1 = ptrace_getrn(child, insn);
+		aluop2 = ptrace_getaluop2(child, insn);
+		ccbit  = get_stack_long(child, REG_PSR) & CC_C_BIT ? 1 : 0;
+
+		switch (insn & 0x01e00000) {
+		case 0x00000000: alt = aluop1 & aluop2;		break;
+		case 0x00200000: alt = aluop1 ^ aluop2;		break;
+		case 0x00400000: alt = aluop1 - aluop2;		break;
+		case 0x00600000: alt = aluop2 - aluop1;		break;
+		case 0x00800000: alt = aluop1 + aluop2;		break;
+		case 0x00a00000: alt = aluop1 + aluop2 + ccbit;	break;
+		case 0x00c00000: alt = aluop1 - aluop2 + ccbit;	break;
+		case 0x00e00000: alt = aluop2 - aluop1 + ccbit;	break;
+		case 0x01800000: alt = aluop1 | aluop2;		break;
+		case 0x01a00000: alt = aluop2;			break;
+		case 0x01c00000: alt = aluop1 & ~aluop2;	break;
+		case 0x01e00000: alt = ~aluop2;			break;
 		}
 		break;
+	}
+
+	case 0x04000000:
+	case 0x06000000:
+		/*
+		 * ldr
+		 */
+		if ((insn & 0x0010f000) == 0x0010f000) {
+			unsigned long base;
 
-	case 0x04100000: /* ldr */
-		if ((insn & 0xf000) == 0xf000) {
-printk ("ldr ");
-			alt = ptrace_getrn(child, insn);
+			base = ptrace_getrn(child, insn);
 			if (insn & 1 << 24) {
-				if (insn & 1 << 23)
-					alt += ptrace_getldrop2 (child, insn);
+				long aluop2;
+
+				if (insn & 0x02000000)
+					aluop2 = ptrace_getldrop2(child, insn);
 				else
-					alt -= ptrace_getldrop2 (child, insn);
-			}
-			if (read_long (child, alt, &alt) < 0)
-				alt = 0; /* not valid */
-			else
-				alt = pc_pointer (alt);
-		}
-		break;
+					aluop2 = insn & 0xfff;
 
-	case 0x06100000: /* ldr imm */
-		if ((insn & 0xf000) == 0xf000) {
-printk ("ldrimm ");
-			alt = ptrace_getrn(child, insn);
-			if (insn & 1 << 24) {
 				if (insn & 1 << 23)
-					alt += insn & 0xfff;
+					base += aluop2;
 				else
-					alt -= insn & 0xfff;
+					base -= aluop2;
 			}
-			if (read_long (child, alt, &alt) < 0)
-				alt = 0; /* not valid */
-			else
-				alt = pc_pointer (alt);
+			if (read_tsk_long(child, base, &alt) == 0)
+				alt = pc_pointer(alt);
 		}
 		break;
 
-	case 0x08100000: /* ldm */
-		if (insn & (1 << 15)) {
+	case 0x08000000:
+		/*
+		 * ldm
+		 */
+		if ((insn & 0x00108000) == 0x00108000) {
 			unsigned long base;
-			int nr_regs;
-printk ("ldm ");
+			unsigned int nr_regs;
 
 			if (insn & (1 << 23)) {
 				nr_regs = insn & 65535;
@@ -278,23 +257,22 @@
 					nr_regs = 0;
 			}
 
-			base = ptrace_getrn (child, insn);
+			base = ptrace_getrn(child, insn);
 
-			if (read_long (child, base + nr_regs, &alt) < 0)
-				alt = 0; /* not valid */
-			else
+			if (read_tsk_long(child, base + nr_regs, &alt) == 0)
 				alt = pc_pointer (alt);
 			break;
 		}
 		break;
 
-	case 0x0a000000:
-	case 0x0a100000: { /* bl or b */
+	case 0x0a000000: {
+		/*
+		 * bl or b
+		 */
 		signed long displ;
-printk ("b/bl ");
 		/* It's a branch/branch link: instead of trying to
 		 * figure out whether the branch will be taken or not,
-		 * we'll put a breakpoint at either location.  This is
+		 * we'll put a breakpoint at both locations.  This is
 		 * simpler, more reliable, and probably not a whole lot
 		 * slower than the alternative approach of emulating the
 		 * branch.
@@ -306,7 +284,6 @@
 	    }
 	    break;
 	}
-printk ("=%08lX\n", alt);
 
 	return alt;
 }
@@ -318,9 +295,9 @@
 	int res = -EINVAL;
 
 	if (nr < 2) {
-		res = read_long(child, addr, &dbg->bp[nr].insn);
+		res = read_tsk_long(child, addr, &dbg->bp[nr].insn);
 		if (res == 0)
-			res = write_long(child, addr, BREAKINST);
+			res = write_tsk_long(child, addr, BREAKINST);
 
 		if (res == 0) {
 			dbg->bp[nr].address = addr;
@@ -332,257 +309,309 @@
 	return res;
 }
 
-int ptrace_set_bpt (struct task_struct *child)
+int ptrace_set_bpt(struct task_struct *child)
 {
-	struct debug_info *dbg = &child->thread.debug;
-	unsigned long insn, pc, alt;
+	unsigned long insn, pc;
 	int res;
 
-	pc = pc_pointer (get_stack_long (child, 15/*REG_PC*/));
+	pc = pc_pointer(get_stack_long(child, REG_PC));
 
-	res = read_long(child, pc, &insn);
-	if (res >= 0) {
-		res = 0;
+	res = read_tsk_long(child, pc, &insn);
+	if (!res) {
+		struct debug_info *dbg = &child->thread.debug;
+		unsigned long alt;
 
 		dbg->nsaved = 0;
 
-		res = add_breakpoint(child, dbg, pc + 4);
+		alt = get_branch_address(child, pc, insn);
+		if (alt)
+			res = add_breakpoint(child, dbg, alt);
 
-		if (res == 0) {
-			alt = get_branch_address(child, pc, insn);
-			if (alt)
-				res = add_breakpoint(child, dbg, alt);
-		}
+		if (!res && (!alt || predicate(insn) != PREDICATE_ALWAYS))
+			res = add_breakpoint(child, dbg, pc + 4);
 	}
 
 	return res;
 }
 
-/* Ensure no single-step breakpoint is pending.  Returns non-zero
+/*
+ * Ensure no single-step breakpoint is pending.  Returns non-zero
  * value if child was being single-stepped.
  */
-int ptrace_cancel_bpt (struct task_struct *child)
+void __ptrace_cancel_bpt(struct task_struct *child)
 {
 	struct debug_info *dbg = &child->thread.debug;
-	unsigned long tmp;
 	int i, nsaved = dbg->nsaved;
 
 	dbg->nsaved = 0;
 
 	if (nsaved > 2) {
-		printk ("ptrace_cancel_bpt: bogus nsaved: %d!\n", nsaved);
+		printk("ptrace_cancel_bpt: bogus nsaved: %d!\n", nsaved);
 		nsaved = 2;
 	}
 
 	for (i = 0; i < nsaved; i++) {
-		read_long(child, dbg->bp[i].address, &tmp);
+		unsigned long tmp;
+
+		read_tsk_long(child, dbg->bp[i].address, &tmp);
 		if (tmp != BREAKINST)
 			printk(KERN_ERR "ptrace_cancel_bpt: weirdness\n");
-		write_long(child, dbg->bp[i].address, dbg->bp[i].insn);
+		write_tsk_long(child, dbg->bp[i].address, dbg->bp[i].insn);
 	}
-
-	return nsaved != 0;
 }
 
-asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
+static int do_ptrace(int request, struct task_struct *child, long addr, long data)
 {
-	struct task_struct *child;
+	unsigned long tmp;
 	int ret;
 
-	lock_kernel();
-	ret = -EPERM;
-	if (request == PTRACE_TRACEME) {
-		/* are we already being traced? */
-		if (current->ptrace & PT_PTRACED)
-			goto out;
-		/* set the ptrace bit in the process flags. */
-		current->ptrace |= PT_PTRACED;
-		ret = 0;
-		goto out;
-	}
-	if (pid == 1)		/* you may not mess with init */
-		goto out;
-	ret = -ESRCH;
-	if (!(child = find_task_by_pid(pid)))
-		goto out;
-	ret = -EPERM;
-	if (request == PTRACE_ATTACH) {
-		if (child == current)
-			goto out;
-		if ((!child->dumpable ||
-		    (current->uid != child->euid) ||
-		    (current->uid != child->suid) ||
-		    (current->uid != child->uid) ||
-	 	    (current->gid != child->egid) ||
-	 	    (current->gid != child->sgid) ||
-		    (!cap_issubset(child->cap_permitted, current->cap_permitted)) ||
-	 	    (current->gid != child->gid)) && !capable(CAP_SYS_PTRACE))
-			goto out;
-		/* the same process cannot be attached many times */
-		if (child->ptrace & PT_PTRACED)
-			goto out;
-		child->ptrace |= PT_PTRACED;
-		if (child->p_pptr != current) {
-			REMOVE_LINKS(child);
-			child->p_pptr = current;
-			SET_LINKS(child);
-		}
-		send_sig(SIGSTOP, child, 1);
-		ret = 0;
-		goto out;
-	}
-	ret = -ESRCH;
-	if (!(child->ptrace & PT_PTRACED))
-		goto out;
-	if (child->state != TASK_STOPPED) {
-		if (request != PTRACE_KILL)
-			goto out;
-	}
-	if (child->p_pptr != current)
-		goto out;
-
 	switch (request) {
-		case PTRACE_PEEKTEXT:				/* read word at location addr. */
-		case PTRACE_PEEKDATA: {
-			unsigned long tmp;
-
-			ret = read_long(child, addr, &tmp);
+		/*
+		 * read word at location "addr" in the child process.
+		 */
+		case PTRACE_PEEKTEXT:
+		case PTRACE_PEEKDATA:
+			ret = read_tsk_long(child, addr, &tmp);
 			if (!ret)
 				ret = put_user(tmp, (unsigned long *) data);
-			goto out;
-		}
-
-		case PTRACE_PEEKUSR: {				/* read the word at location addr in the USER area. */
-			unsigned long tmp;
+			break;
 
+		/*
+		 * read the word at location "addr" in the user registers.
+		 */
+		case PTRACE_PEEKUSR:
 			ret = -EIO;
 			if ((addr & 3) || addr < 0 || addr >= sizeof(struct user))
-				goto out;
+				break;
 
 			tmp = 0;  /* Default return condition */
-			if (addr < sizeof (struct pt_regs))
+			if (addr < sizeof(struct pt_regs))
 				tmp = get_stack_long(child, (int)addr >> 2);
 			ret = put_user(tmp, (unsigned long *)data);
-			goto out;
-		}
+			break;
 
-		case PTRACE_POKETEXT:				/* write the word at location addr. */
+		/*
+		 * write the word at location addr.
+		 */
+		case PTRACE_POKETEXT:
 		case PTRACE_POKEDATA:
-			ret = write_long(child, addr, data);
-			goto out;
+			ret = write_tsk_long(child, addr, data);
+			break;
 
-		case PTRACE_POKEUSR:				/* write the word at location addr in the USER area */
+		/*
+		 * write the word at location addr in the user registers.
+		 */
+		case PTRACE_POKEUSR:
 			ret = -EIO;
 			if ((addr & 3) || addr < 0 || addr >= sizeof(struct user))
-				goto out;
+				break;
 
 			if (addr < sizeof (struct pt_regs))
 				ret = put_stack_long(child, (int)addr >> 2, data);
-			goto out;
+			break;
 
-		case PTRACE_SYSCALL:				/* continue and stop at next (return from) syscall */
-		case PTRACE_CONT:				/* restart after signal. */
+		/*
+		 * continue/restart and stop at next (return from) syscall
+		 */
+		case PTRACE_SYSCALL:
+		case PTRACE_CONT:
 			ret = -EIO;
 			if ((unsigned long) data > _NSIG)
-				goto out;
+				break;
 			if (request == PTRACE_SYSCALL)
 				child->ptrace |= PT_TRACESYS;
 			else
 				child->ptrace &= ~PT_TRACESYS;
 			child->exit_code = data;
-			wake_up_process (child);
 			/* make sure single-step breakpoint is gone. */
-			ptrace_cancel_bpt (child);
+			__ptrace_cancel_bpt(child);
+			wake_up_process(child);
 			ret = 0;
-			goto out;
+			break;
 
-		/* make the child exit.  Best I can do is send it a sigkill.
+		/*
+		 * make the child exit.  Best I can do is send it a sigkill.
 		 * perhaps it should be put in the status that it wants to
 		 * exit.
 		 */
 		case PTRACE_KILL:
-			if (child->state == TASK_ZOMBIE)	/* already dead */
-				return 0;
-			wake_up_process (child);
+			/* already dead */
+			ret = 0;
+			if (child->state == TASK_ZOMBIE)
+				break;
 			child->exit_code = SIGKILL;
 			/* make sure single-step breakpoint is gone. */
-			ptrace_cancel_bpt (child);
+			__ptrace_cancel_bpt(child);
+			wake_up_process(child);
 			ret = 0;
-			goto out;
+			break;
 
-		case PTRACE_SINGLESTEP:				/* execute single instruction. */
+		/*
+		 * execute single instruction.
+		 */
+		case PTRACE_SINGLESTEP:
 			ret = -EIO;
 			if ((unsigned long) data > _NSIG)
-				goto out;
+				break;
 			child->thread.debug.nsaved = -1;
 			child->ptrace &= ~PT_TRACESYS;
-			wake_up_process(child);
 			child->exit_code = data;
 			/* give it a chance to run. */
+			wake_up_process(child);
 			ret = 0;
-			goto out;
-			
-		case PTRACE_GETREGS:
-		{	/* Get all gp regs from the child. */
-			unsigned char *stack;
+			break;
 
+		/*
+		 * detach a process that was attached.
+		 */
+		case PTRACE_DETACH:
+			ret = -EIO;
+			if ((unsigned long) data > _NSIG)
+				break;
+			child->ptrace &= ~(PT_PTRACED|PT_TRACESYS);
+			child->exit_code = data;
+			write_lock_irq(&tasklist_lock);
+			REMOVE_LINKS(child);
+			child->p_pptr = child->p_opptr;
+			SET_LINKS(child);
+			write_unlock_irq(&tasklist_lock);
+			/* make sure single-step breakpoint is gone. */
+			__ptrace_cancel_bpt(child);
+			wake_up_process (child);
 			ret = 0;
-			stack = (unsigned char *)((unsigned long)child + 8192 - sizeof(struct pt_regs));
-			if (copy_to_user((void *)data, stack,
+			break;
+
+		/*
+		 * Get all gp regs from the child.
+		 */
+		case PTRACE_GETREGS: {
+			struct pt_regs *regs = get_user_regs(child);
+
+			ret = 0;
+			if (copy_to_user((void *)data, regs,
 					 sizeof(struct pt_regs)))
 				ret = -EFAULT;
 
-			goto out;
-		};
+			break;
+		}
 
-		case PTRACE_SETREGS:
-		{
-			/* Set all gp regs in the child. */
-			unsigned char *stack;
+		/*
+		 * Set all gp regs in the child.
+		 */
+		case PTRACE_SETREGS: {
+			struct pt_regs *regs = get_user_regs(child);
 
 			ret = 0;
-			stack = (unsigned char *)((unsigned long)child + 8192 - sizeof(struct pt_regs));
-			if (copy_from_user(stack, (void *)data,
+			if (copy_from_user(regs, (void *)data,
 					   sizeof(struct pt_regs)))
 				ret = -EFAULT;
-			goto out;
-		  };
+			break;
+		}
 
-		case PTRACE_GETFPREGS: 
-			/* Get the child FPU state. */
-			ret = 0;
-			if (copy_to_user((void *)data, &child->thread.fpstate,
-					 sizeof(struct user_fp)))
-				ret = -EFAULT;
-			goto out;
+		/*
+		 * Get the child FPU state.
+		 */
+		case PTRACE_GETFPREGS:
+			ret = -EIO;
+			if (!access_ok(VERIFY_WRITE, (void *)data, sizeof(struct user_fp)))
+				break;
+
+			/* we should check child->used_math here */
+			ret = __copy_to_user((void *)data, &child->thread.fpstate,
+					     sizeof(struct user_fp)) ? -EFAULT : 0;
+			break;
 		
+		/*
+		 * Set the child FPU state.
+		 */
 		case PTRACE_SETFPREGS:
-			/* Set the child FPU state. */
-			ret = 0;
-			if (copy_from_user(&child->thread.fpstate, (void *)data,
-					   sizeof(struct user_fp)))
-				ret = -EFAULT;
-			goto out;
-
-		case PTRACE_DETACH:				/* detach a process that was attached. */
 			ret = -EIO;
-			if ((unsigned long) data > _NSIG)
-				goto out;
-			child->ptrace &= ~(PT_PTRACED|PT_TRACESYS);
-			wake_up_process (child);
-			child->exit_code = data;
-			REMOVE_LINKS(child);
-			child->p_pptr = child->p_opptr;
-			SET_LINKS(child);
-			/* make sure single-step breakpoint is gone. */
-			ptrace_cancel_bpt (child);
-			ret = 0;
-			goto out;
+			if (!access_ok(VERIFY_READ, (void *)data, sizeof(struct user_fp)))
+				break;
+
+			child->used_math = 1;
+			ret = __copy_from_user(&child->thread.fpstate, (void *)data,
+					   sizeof(struct user_fp)) ? -EFAULT : 0;
+			break;
 
 		default:
 			ret = -EIO;
+			break;
+	}
+
+	return ret;
+}
+
+asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
+{
+	struct task_struct *child;
+	int ret;
+
+	lock_kernel();
+	ret = -EPERM;
+	if (request == PTRACE_TRACEME) {
+		/* are we already being traced? */
+		if (current->ptrace & PT_PTRACED)
 			goto out;
+		/* set the ptrace bit in the process flags. */
+		current->ptrace |= PT_PTRACED;
+		ret = 0;
+		goto out;
 	}
+	ret = -ESRCH;
+	read_lock(&tasklist_lock);
+	child = find_task_by_pid(pid);
+	if (child)
+		get_task_struct(child);
+	read_unlock(&tasklist_lock);
+	if (!child)
+		goto out;
+
+	ret = -EPERM;
+	if (pid == 1)		/* you may not mess with init */
+		goto out_tsk;
+
+	if (request == PTRACE_ATTACH) {
+		if (child == current)
+			goto out_tsk;
+		if ((!child->dumpable ||
+		    (current->uid != child->euid) ||
+		    (current->uid != child->suid) ||
+		    (current->uid != child->uid) ||
+	 	    (current->gid != child->egid) ||
+	 	    (current->gid != child->sgid) ||
+		    (!cap_issubset(child->cap_permitted, current->cap_permitted)) ||
+	 	    (current->gid != child->gid)) && !capable(CAP_SYS_PTRACE))
+			goto out_tsk;
+		/* the same process cannot be attached many times */
+		if (child->ptrace & PT_PTRACED)
+			goto out_tsk;
+		child->ptrace |= PT_PTRACED;
+
+		write_lock_irq(&tasklist_lock);
+		if (child->p_pptr != current) {
+			REMOVE_LINKS(child);
+			child->p_pptr = current;
+			SET_LINKS(child);
+		}
+		write_unlock_irq(&tasklist_lock);
+
+		send_sig(SIGSTOP, child, 1);
+		ret = 0;
+		goto out_tsk;
+	}
+	ret = -ESRCH;
+	if (!(child->ptrace & PT_PTRACED))
+		goto out_tsk;
+	if (child->state != TASK_STOPPED && request != PTRACE_KILL)
+		goto out_tsk;
+	if (child->p_pptr != current)
+		goto out_tsk;
+
+	ret = do_ptrace(request, child, addr, data);
+
+out_tsk:
+	free_task_struct(child);
 out:
 	unlock_kernel();
 	return ret;

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)