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

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

diff -u --recursive --new-file v2.1.79/linux/arch/arm/kernel/ptrace.c linux/arch/arm/kernel/ptrace.c
@@ -0,0 +1,745 @@
+/* ptrace.c */
+/* By Ross Biro 1/23/92 */
+/* edited by Linus Torvalds */
+/* edited for ARM by Russell King */
+
+#include <linux/head.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#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>
+
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/system.h>
+
+/*
+ * does not yet catch signals sent when the child dies.
+ * in exit.c or in signal.c.
+ */
+
+/*
+ * Breakpoint SWI instruction: SWI &9F0001
+ */
+#define BREAKINST	0xef9f0001
+
+/* change a pid into a task struct. */
+static inline struct task_struct * get_task(int pid)
+{
+	int i;
+
+	for (i = 1; i < NR_TASKS; i++) {
+		if (task[i] != NULL && (task[i]->pid == pid))
+			return task[i];
+	}
+	return NULL;
+}
+
+/*
+ * 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 TSS.
+ * this routine assumes that all the privileged stacks are in our
+ * data space.
+ */
+static inline long get_stack_long(struct task_struct *task, int offset)
+{
+	unsigned char *stack;
+
+	stack = (unsigned char *)((unsigned long)task + 8192 - sizeof(struct pt_regs));
+	stack += offset << 2;
+	return *(unsigned long *)stack;
+}
+
+/*
+ * this routine will put a word on the processes privileged stack.
+ * the offset is how far from the base addr as stored in the TSS.
+ * 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)
+{
+	unsigned char *stack;
+
+	stack = (unsigned char *)((unsigned long)task + 8192 - sizeof(struct pt_regs));
+	stack += offset << 2;
+	*(unsigned long *) stack = data;
+	return 0;
+}
+
+/*
+ * This routine gets a long from any process space by following the page
+ * tables. NOTE! You should check that the long isn't on a page boundary,
+ * and that it is in the task area before calling this: this routine does
+ * no checking.
+ */
+static unsigned long get_long(struct task_struct * tsk,
+	struct vm_area_struct * vma, unsigned long addr)
+{
+	pgd_t *pgdir;
+	pmd_t *pgmiddle;
+	pte_t *pgtable;
+	unsigned long page;
+
+repeat:
+	pgdir = pgd_offset(vma->vm_mm, addr);
+	if (pgd_none(*pgdir)) {
+		handle_mm_fault(tsk, vma, addr, 0);
+		goto repeat;
+	}
+	if (pgd_bad(*pgdir)) {
+		printk("ptrace: bad page directory %08lx\n", pgd_val(*pgdir));
+		pgd_clear(pgdir);
+		return 0;
+	}
+	pgmiddle = pmd_offset(pgdir, addr);
+	if (pmd_none(*pgmiddle)) {
+		handle_mm_fault(tsk, vma, addr, 0);
+		goto repeat;
+	}
+	if (pmd_bad(*pgmiddle)) {
+		printk("ptrace: bad page middle %08lx\n", pmd_val(*pgmiddle));
+		pmd_clear(pgmiddle);
+		return 0;
+	}
+	pgtable = pte_offset(pgmiddle, addr);
+	if (!pte_present(*pgtable)) {
+		handle_mm_fault(tsk, vma, addr, 0);
+		goto repeat;
+	}
+	page = pte_page(*pgtable);
+ 
+	if(MAP_NR(page) >= max_mapnr)
+		return 0;
+	page += addr & ~PAGE_MASK;
+	return *(unsigned long *)page;
+}
+
+/*
+ * This routine puts a long into any process space by following the page
+ * tables. NOTE! You should check that the long isn't on a page boundary,
+ * and that it is in the task area before calling this: this routine does
+ * no checking.
+ *
+ * Now keeps R/W state of the page so that a text page stays readonly
+ * even if a debugger scribbles breakpoints into it.  -M.U-
+ */
+static void put_long(struct task_struct * tsk, struct vm_area_struct * vma, unsigned long addr,
+	unsigned long data)
+{
+	pgd_t *pgdir;
+	pmd_t *pgmiddle;
+	pte_t *pgtable;
+	unsigned long page;
+
+repeat:
+	pgdir = pgd_offset(vma->vm_mm, addr);
+	if (!pgd_present(*pgdir)) {
+		handle_mm_fault(tsk, vma, addr, 1);
+		goto repeat;
+	}
+	if (pgd_bad(*pgdir)) {
+		printk("ptrace: bad page directory %08lx\n", pgd_val(*pgdir));
+		pgd_clear(pgdir);
+		return;
+	}
+	pgmiddle = pmd_offset(pgdir, addr);
+	if (pmd_none(*pgmiddle)) {
+		handle_mm_fault(tsk, vma, addr, 1);
+		goto repeat;
+	}
+	if (pmd_bad(*pgmiddle)) {
+		printk("ptrace: bad page middle %08lx\n", pmd_val(*pgmiddle));
+		pmd_clear(pgmiddle);
+		return;
+	}
+	pgtable = pte_offset(pgmiddle, addr);
+	if (!pte_present(*pgtable)) {
+		handle_mm_fault(tsk, vma, addr, 1);
+		goto repeat;
+	}
+	page = pte_page(*pgtable);
+	if (!pte_write(*pgtable)) {
+		handle_mm_fault(tsk, vma, addr, 1);
+		goto repeat;
+	}
+	
+	if (MAP_NR(page) < max_mapnr) {
+		page += addr & ~PAGE_MASK;
+		*(unsigned long *)page = data;
+		__flush_entry_to_ram(page);
+	}
+	set_pte(pgtable, pte_mkdirty(mk_pte(page, vma->vm_page_prot)));
+	flush_tlb();
+}
+
+static struct vm_area_struct * find_extend_vma(struct task_struct * tsk, unsigned long addr)
+{
+	struct vm_area_struct * vma;
+
+	addr &= PAGE_MASK;
+	vma = find_vma(tsk->mm,addr);
+	if (!vma)
+		return NULL;
+	if (vma->vm_start <= addr)
+		return vma;
+	if (!(vma->vm_flags & VM_GROWSDOWN))
+		return NULL;
+	if (vma->vm_end - addr > tsk->rlim[RLIMIT_STACK].rlim_cur)
+		return NULL;
+	vma->vm_offset -= vma->vm_start - addr;
+	vma->vm_start = addr;
+	return vma;
+}
+
+/*
+ * This routine checks the page boundaries, and that the offset is
+ * within the task area. It then calls get_long() to read a long.
+ */
+static int read_long(struct task_struct * tsk, unsigned long addr,
+	unsigned long * result)
+{
+	struct vm_area_struct * vma = find_extend_vma(tsk, addr);
+
+	if (!vma)
+		return -EIO;
+	if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long)) {
+		unsigned long low,high;
+		struct vm_area_struct * vma_high = vma;
+
+		if (addr + sizeof(long) >= vma->vm_end) {
+			vma_high = vma->vm_next;
+			if (!vma_high || vma_high->vm_start != vma->vm_end)
+				return -EIO;
+		}
+		low = get_long(tsk, vma, addr & ~(sizeof(long)-1));
+		high = get_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1));
+		switch (addr & (sizeof(long)-1)) {
+			case 1:
+				low >>= 8;
+				low |= high << 24;
+				break;
+			case 2:
+				low >>= 16;
+				low |= high << 16;
+				break;
+			case 3:
+				low >>= 24;
+				low |= high << 8;
+				break;
+		}
+		*result = low;
+	} else
+		*result = get_long(tsk, vma, addr);
+	return 0;
+}
+
+/*
+ * This routine checks the page boundaries, and that the offset is
+ * within the task area. It then calls put_long() to write a long.
+ */
+static int write_long(struct task_struct * tsk, unsigned long addr,
+	unsigned long data)
+{
+	struct vm_area_struct * vma = find_extend_vma(tsk, addr);
+
+	if (!vma)
+		return -EIO;
+	if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long)) {
+		unsigned long low,high;
+		struct vm_area_struct * vma_high = vma;
+
+		if (addr + sizeof(long) >= vma->vm_end) {
+			vma_high = vma->vm_next;
+			if (!vma_high || vma_high->vm_start != vma->vm_end)
+				return -EIO;
+		}
+		low = get_long(tsk, vma, addr & ~(sizeof(long)-1));
+		high = get_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1));
+		switch (addr & (sizeof(long)-1)) {
+			case 0: /* shouldn't happen, but safety first */
+				low = data;
+				break;
+			case 1:
+				low &= 0x000000ff;
+				low |= data << 8;
+				high &= ~0xff;
+				high |= data >> 24;
+				break;
+			case 2:
+				low &= 0x0000ffff;
+				low |= data << 16;
+				high &= ~0xffff;
+				high |= data >> 16;
+				break;
+			case 3:
+				low &= 0x00ffffff;
+				low |= data << 24;
+				high &= ~0xffffff;
+				high |= data >> 8;
+				break;
+		}
+		put_long(tsk, vma, addr & ~(sizeof(long)-1),low);
+		put_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1),high);
+	} else
+		put_long(tsk, vma, addr, data);
+	return 0;
+}
+
+/*
+ * Get value of register `rn' (in the instruction)
+ */
+static unsigned long ptrace_getrn (struct task_struct *child, unsigned long insn)
+{
+	unsigned int reg = (insn >> 16) & 15;
+	unsigned long val;
+
+	if (reg == 15)
+		val = pc_pointer (get_stack_long (child, reg));
+	else
+		val = get_stack_long (child, reg);
+
+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)
+{
+	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);
+
+		if (insn & (1 << 4))
+			shift = (int)get_stack_long (child, (insn >> 8) & 15);
+		else
+			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;
+	case 2:
+		val = (((signed long)val) >> shift);
+		break;
+	case 3:
+		__asm__ __volatile__("mov %0, %0, ror %1" : "=r" (val) : "0" (val), "r" (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)
+{
+	unsigned long val;
+	int shift;
+	int type;
+
+	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;
+	case 2:
+		val = (((signed long)val) >> shift);
+		break;
+	case 3:
+		__asm__ __volatile__("mov %0, %0, ror %1" : "=r" (val) : "0" (val), "r" (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->debugreg[nsaved++] = alt = pc + 4;
+printk ("ptrace_set_bpt: insn=%08lX pc=%08lX ", insn, pc);
+	switch (insn & 0x0e100000) {
+	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);
+			break;
+		}
+		break;
+
+	case 0x04100000: /* ldr */
+		if ((insn & 0xf000) == 0xf000) {
+printk ("ldr ");
+			alt = ptrace_getrn(child, insn);
+			if (insn & 1 << 24) {
+				if (insn & 1 << 23)
+					alt += 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;
+
+	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;
+				else
+					alt -= insn & 0xfff;
+			}
+			if (read_long (child, alt, &alt) < 0)
+				alt = pc + 4; /* not valid */
+			else
+				alt = pc_pointer (alt);
+		}
+		break;
+
+	case 0x08100000: /* ldm */
+		if (insn & (1 << 15)) {
+			unsigned long base;
+			int nr_regs;
+printk ("ldm ");
+
+			if (insn & (1 << 23)) {
+				nr_regs = insn & 65535;
+
+				nr_regs = (nr_regs & 0x5555) + ((nr_regs & 0xaaaa) >> 1);
+				nr_regs = (nr_regs & 0x3333) + ((nr_regs & 0xcccc) >> 2);
+				nr_regs = (nr_regs & 0x0707) + ((nr_regs & 0x7070) >> 4);
+				nr_regs = (nr_regs & 0x000f) + ((nr_regs & 0x0f00) >> 8);
+				nr_regs <<= 2;
+
+				if (!(insn & (1 << 24)))
+					nr_regs -= 4;
+			} else {
+				if (insn & (1 << 24))
+					nr_regs = -4;
+				else
+					nr_regs = 0;
+			}
+
+			base = ptrace_getrn (child, insn);
+
+			if (read_long (child, base + nr_regs, &alt) < 0)
+				alt = pc + 4; /* not valid */
+			else
+				alt = pc_pointer (alt);
+			break;
+		}
+		break;
+
+	case 0x0a000000:
+	case 0x0a100000: { /* 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
+		 * simpler, more reliable, and probably not a whole lot
+		 * slower than the alternative approach of emulating the
+		 * branch.
+		 */
+		displ = (insn & 0x00ffffff) << 8;
+		displ = (displ >> 6) + 8;
+		if (displ != 0 && displ != 4)
+			alt = pc + displ;
+	    }
+	    break;
+	}
+printk ("=%08lX\n", alt);
+	if (alt != pc + 4)
+		child->debugreg[nsaved++] = alt;
+
+	for (i = 0; i < nsaved; i++) {
+		res = read_long (child, child->debugreg[i], &insn);
+		if (res >= 0) {
+			child->debugreg[i + 2] = insn;
+			res = write_long (child, child->debugreg[i], BREAKINST);
+		}
+		if (res < 0) {
+			child->debugreg[4] = 0;
+			return res;
+		}
+	}
+	child->debugreg[4] = nsaved;
+	return 0;
+}
+
+/* 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->debugreg[4];
+
+	child->debugreg[4] = 0;
+
+	if (nsaved > 2) {
+		printk ("ptrace_cancel_bpt: bogus nsaved: %d!\n", nsaved);
+		nsaved = 2;
+	}
+	for (i = 0; i < nsaved; i++)
+		write_long (child, child->debugreg[i], child->debugreg[i + 2]);
+	return nsaved != 0;
+}
+
+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;
+	}
+	if (pid == 1)		/* you may not mess with init */
+		goto out;
+	ret = -ESRCH;
+	if (!(child = get_task(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)) && !suser())
+			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;
+
+			ret = -EIO;
+			if ((addr & 3) || addr < 0 || addr >= sizeof(struct user))
+				goto out;
+
+			tmp = 0;  /* Default return condition */
+			if (addr < sizeof (struct pt_regs))
+				tmp = get_stack_long(child, (int)addr >> 2);
+			ret = put_user(tmp, (unsigned long *)data);
+			goto out;
+		}
+
+		case PTRACE_POKETEXT:				/* write the word at location addr. */
+		case PTRACE_POKEDATA:
+			ret = write_long(child,addr,data);
+			goto out;
+
+		case PTRACE_POKEUSR:				/* write the word at location addr in the USER area */
+			ret = -EIO;
+			if ((addr & 3) || addr < 0 || addr >= sizeof(struct user))
+				goto out;
+
+			if (addr < sizeof (struct pt_regs))
+				ret = put_stack_long(child, (int)addr >> 2, data);
+			goto out;
+
+		case PTRACE_SYSCALL:				/* continue and stop at next (return from) syscall */
+		case PTRACE_CONT:				/* restart after signal. */
+			ret = -EIO;
+			if ((unsigned long) data > _NSIG)
+				goto out;
+			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);
+			ret = 0;
+			goto out;
+
+		/* 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);
+			child->exit_code = SIGKILL;
+			ptrace_cancel_bpt (child);
+			/* make sure single-step breakpoint is gone. */
+			ptrace_cancel_bpt (child);
+			ret = 0;
+			goto out;
+
+		case PTRACE_SINGLESTEP:				/* execute single instruction. */
+			ret = -EIO;
+			if ((unsigned long) data > _NSIG)
+				goto out;
+			child->debugreg[4] = -1;
+			child->flags &= ~PF_TRACESYS;
+			wake_up_process(child);
+			child->exit_code = data;
+			/* give it a chance to run. */
+			ret = 0;
+			goto out;
+
+		case PTRACE_DETACH:				/* detach a process that was attached. */
+			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;
+
+		default:
+			ret = -EIO;
+			goto out;
+	}
+out:
+	unlock_kernel();
+	return ret;
+}
+
+asmlinkage void syscall_trace(void)
+{
+	if ((current->flags & (PF_PTRACED|PF_TRACESYS))
+			!= (PF_PTRACED|PF_TRACESYS))
+		return;
+	current->exit_code = SIGTRAP;
+	current->state = TASK_STOPPED;
+	notify_parent(current, SIGCHLD);
+	schedule();
+	/*
+	 * this isn't the same as continuing with a signal, but it will do
+	 * for normal use.  strace only continues with a signal if the
+	 * stopping signal is not SIGTRAP.  -brl
+	 */
+	if (current->exit_code) {
+		send_sig(current->exit_code, current, 1);
+		current->exit_code = 0;
+	}
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov