patch-2.2.19 linux/arch/arm/kernel/ptrace.c

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

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.18/arch/arm/kernel/ptrace.c linux/arch/arm/kernel/ptrace.c
@@ -16,6 +16,9 @@
 #include <asm/pgtable.h>
 #include <asm/system.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.
@@ -25,11 +28,12 @@
  * Breakpoint SWI instruction: SWI &9F0001
  */
 #define BREAKINST	0xef9f0001
-#define PTRACE_GETREGS		12
-#define PTRACE_SETREGS		13
-#define PTRACE_GETFPREGS	14
-#define PTRACE_SETFPREGS	15
 
+/*
+ * 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)
 {
@@ -299,38 +303,42 @@
 	return 0;
 }
 
+#define write_tsk_long(chld, addr, val) write_long((chld), (addr), (val))
+#define read_tsk_long(chld, addr, val)  read_long((chld), (addr), (val))
+
 /*
  * 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 + 8);
+
 
-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);
 
@@ -340,9 +348,9 @@
 			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;
@@ -350,27 +358,28 @@
 		val = (((signed long)val) >> shift);
 		break;
 	case 3:
-		__asm__ __volatile__("mov %0, %0, ror %1" : "=r" (val) : "0" (val), "r" (shift));
+		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;
@@ -378,115 +387,88 @@
 		val = (((signed long)val) >> shift);
 		break;
 	case 3:
-		__asm__ __volatile__("mov %0, %0, ror %1" : "=r" (val) : "0" (val), "r" (shift));
+		val = (val >> shift) | (val << (32 - shift));
 		break;
 	}
-printk ("=%08lX ", val);
+
 	return val;
 }
-#undef pc_pointer
-#define pc_pointer(x) ((x) & 0x03fffffc)
-int ptrace_set_bpt (struct task_struct *child)
-{
-	unsigned long insn, pc, alt;
-	int i, nsaved = 0, res;
-
-	pc = pc_pointer (get_stack_long (child, 15/*REG_PC*/));
-
-	res = read_long (child, pc, &insn);
-	if (res < 0)
-		return res;
-
-	child->tss.debug[nsaved++] = alt = pc + 4;
-printk ("ptrace_set_bpt: insn=%08lX pc=%08lX ", insn, pc);
-	switch (insn & 0x0e100000) {
+
+static unsigned long
+get_branch_address(struct task_struct *child, unsigned long pc, unsigned long insn)
+{
+	unsigned long alt = 0;
+
+	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 = pc + 4; /* 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 = pc + 4; /* 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;
@@ -506,23 +488,23 @@
 					nr_regs = 0;
 			}
 
-			base = ptrace_getrn (child, insn);
+			base = ptrace_getrn(child, insn);
 
-			if (read_long (child, base + nr_regs, &alt) < 0)
-				alt = pc + 4; /* not valid */
-			else
-				alt = pc_pointer (alt);
+			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.
@@ -534,196 +516,230 @@
 	    }
 	    break;
 	}
-printk ("=%08lX\n", alt);
-	if (alt != pc + 4)
-		child->tss.debug[nsaved++] = alt;
 
-	for (i = 0; i < nsaved; i++) {
-		res = read_long (child, child->tss.debug[i], &insn);
-		if (res >= 0) {
-			child->tss.debug[i + 2] = insn;
-			res = write_long (child, child->tss.debug[i], BREAKINST);
-		}
-		if (res < 0) {
-			child->tss.debug[4] = 0;
-			return res;
+	return alt;
+}
+
+static int
+add_breakpoint(struct task_struct *child, struct debug_info *dbg, unsigned long addr)
+{
+	int nr = dbg->nsaved;
+	int res = -EINVAL;
+
+	if (nr < 2) {
+		res = read_tsk_long(child, addr, &dbg->bp[nr].insn);
+		if (res == 0)
+			res = write_tsk_long(child, addr, BREAKINST);
+
+		if (res == 0) {
+			dbg->bp[nr].address = addr;
+			dbg->nsaved += 1;
 		}
+	} else
+		printk(KERN_DEBUG "add_breakpoint: too many breakpoints\n");
+
+	return res;
+}
+
+int ptrace_set_bpt(struct task_struct *child)
+{
+	unsigned long insn, pc;
+	int res;
+
+	pc = pc_pointer(get_stack_long(child, REG_PC));
+
+	res = read_long(child, pc, &insn);
+	if (!res) {
+		struct debug_info *dbg = &child->tss.debug;
+		unsigned long alt;
+
+		dbg->nsaved = 0;
+
+		alt = get_branch_address(child, pc, insn);
+
+		if (alt)
+			res = add_breakpoint(child, dbg, alt);
+
+		/*
+		 * Note that we ignore the result of setting the above
+		 * breakpoint since it may fail.  When it does, this is
+		 * not so much an error, but a forewarning that we will
+		 * be receiving a prefetch abort shortly.
+		 *
+		 * If we don't set this breakpoint here, then we can
+		 * loose control of the thread during single stepping.
+		 */
+		if (!alt || predicate(insn) != PREDICATE_ALWAYS)
+			res = add_breakpoint(child, dbg, pc + 4);
 	}
-	child->tss.debug[4] = nsaved;
-	return 0;
+
+	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)
 {
-	int i, nsaved = child->tss.debug[4];
+	struct debug_info *dbg = &child->tss.debug;
+	int i, nsaved = dbg->nsaved;
 
-	child->tss.debug[4] = 0;
+	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++)
-		write_long (child, child->tss.debug[i], child->tss.debug[i + 2]);
+
+	for (i = 0; i < nsaved; i++) {
+		unsigned long tmp;
+
+		read_tsk_long(child, dbg->bp[i].address, &tmp);
+		if (tmp != BREAKINST)
+			printk(KERN_ERR "ptrace_cancel_bpt: weirdness\n");
+		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->flags & PF_PTRACED)
-			goto out;
-		/* set the ptrace bit in the process flags. */
-		current->flags |= PF_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) ||
-	 	    (current->gid != child->gid)) && !capable(CAP_SYS_PTRACE))
-			goto out;
-		/* the same process cannot be attached many times */
-		if (child->flags & PF_PTRACED)
-			goto out;
-		child->flags |= PF_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->flags & PF_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);
-			if (ret >= 0)
-				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;
+		/*
+		 * 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);
+			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))
+			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->flags |= PF_TRACESYS;
 			else
 				child->flags &= ~PF_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;
-			child->tss.debug[4] = -1;
+				break;
+			child->tss.debug.nsaved = -1;
 			child->flags &= ~PF_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->flags &= ~(PF_PTRACED|PF_TRACESYS);
+			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);
+			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;
+		}
 
-		/* Set all gp regs in the child. */
-		case PTRACE_SETREGS:
-		{
+		/*
+		 * Set all gp regs in the child.
+		 */
+		case PTRACE_SETREGS: {
 			struct pt_regs newregs;
 
 			ret = -EFAULT;
@@ -737,44 +753,103 @@
 					ret = 0;
 				}
 			}
-			goto out;
+			break;
 		}
 
+		/*
+		 * Get the child FPU state.
+		 */
 		case PTRACE_GETFPREGS: 
-			/* Get the child FPU state. */
-			ret = 0;
-			if (copy_to_user((void *)data, &child->tss.fpstate,
-					 sizeof(struct user_fp)))
-				ret = -EFAULT;
-			goto out;
-		
-		case PTRACE_SETFPREGS:
-			/* Set the child FPU state. */
-			ret = 0;
-			if (copy_from_user(&child->tss.fpstate, (void *)data,
-					   sizeof(struct user_fp)))
-				ret = -EFAULT;
-			goto out;
+			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->tss.fpstate,
+					     sizeof(struct user_fp)) ? -EFAULT : 0;
+			break;
 
-		case PTRACE_DETACH:				/* detach a process that was attached. */
+		/*
+		 * Set the child FPU state.
+		 */
+		case PTRACE_SETFPREGS:
 			ret = -EIO;
-			if ((unsigned long) data > _NSIG)
-				goto out;
-			child->flags &= ~(PF_PTRACED|PF_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->tss.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->flags & PF_PTRACED)
 			goto out;
+		/* set the ptrace bit in the process flags. */
+		current->flags |= PF_PTRACED;
+		ret = 0;
+		goto out;
 	}
+	ret = -ESRCH;
+	if (!(child = find_task_by_pid(pid)))
+		goto out;
+
+	ret = -EPERM;
+	if (pid == 1)		/* you may not mess with init */
+		goto out;
+
+	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) ||
+	 	    (current->gid != child->gid)) && !capable(CAP_SYS_PTRACE))
+			goto out;
+		/* the same process cannot be attached many times */
+		if (child->flags & PF_PTRACED)
+			goto out;
+		child->flags |= PF_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->flags & PF_PTRACED))
+		goto out;
+	if (child->state != TASK_STOPPED && request != PTRACE_KILL)
+		goto out;
+	if (child->p_pptr != current)
+		goto out;
+
+	ret = do_ptrace(request, child, addr, data);
+
 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)