[parisc-linux-cvs] sys_ptrace for 32 bit processes on 64 bit kernels

Richard Hirst rhirst@linuxcare.com
Thu, 15 Feb 2001 15:46:46 +0000


Make sys_ptrace work for 32 bit processes under 64 bit kernels.
strace now works for me on the A500, but gdb does not.  Possible
cache flushing problem?  gdb seems to do the right thing, but the
process doesn't always stop on the breakpoints gdb plants.

Richard


Index: arch/parisc/kernel/ptrace.c
===================================================================
RCS file: /home/cvs/parisc/linux/arch/parisc/kernel/ptrace.c,v
retrieving revision 1.9
diff -u -r1.9 ptrace.c
--- ptrace.c	2001/01/11 13:07:23	1.9
+++ ptrace.c	2001/02/15 15:35:23
@@ -31,13 +31,52 @@
 #define PT_SINGLESTEP	0x10000
 #define PT_BLOCKSTEP	0x20000
 
-/* PSW bits we allow the degubber to modify */
+/* PSW bits we allow the debugger to modify */
 #define USER_PSW_BITS	(PSW_N | PSW_V | PSW_CB)
 
+#ifdef __LP64__
+
+/* CHILD_IS_32BIT is always true for now, but once we have a 64 bit userland
+ * if probably wants to be something like
+ * 	(child->thread.flags & PARISC_FLAG_32BIT)
+ * for __LP64__.
+ */
+#define CHILD_IS_32BIT	1
+
+/* This function is needed to translate 32 bit pt_regs offsets in to
+ * 64 bit pt_regs offsets.  For example, a 32 bit gdb under a 64 bit kernel
+ * will request offset 12 if it wants gr3, but the lower 32 bits of
+ * the 64 bit kernels view of gr3 will be at offset 28 (3*8 + 4).
+ * This code relies on a 32 bit pt_regs being comprised of 32 bit values
+ * except for the fp registers which (a) are 64 bits, and (b) follow
+ * the gr registers at the start of pt_regs.  The 32 bit pt_regs should
+ * be half the size of the 64 bit pt_regs, plus 32*4 to allow for fr[]
+ * being 64 bit in both cases.
+ */
+
+static long translate_usr_offset(long offset)
+{
+	if (offset < 0)
+		return -1;
+	else if (offset <= 32*4)	/* gr[0..31] */
+		return offset * 2 + 4;
+	else if (offset <= 32*4+32*8)	/* gr[0..31] + fr[0..31] */
+		return offset + 32*4;
+	else if (offset < sizeof(struct pt_regs)/2 + 32*4)
+		return offset * 2 + 4 - 32*8;
+	else
+		return -1;
+}
+#endif
+
+
 long sys_ptrace(long request, pid_t pid, long addr, long data)
 {
 	struct task_struct *child;
 	long ret;
+#ifdef DEBUG_PTRACE
+long oaddr=addr, odata=data;
+#endif
 
 	lock_kernel();
 	ret = -EPERM;
@@ -64,6 +103,9 @@
 		goto out_tsk;
 
 	if (request == PTRACE_ATTACH) {
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(ATTACH)\n");
+#endif
 		if (child == current)
 			goto out_tsk;
 		if ((!child->dumpable ||
@@ -105,14 +147,33 @@
 	switch (request) {
 	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))
-			goto out_tsk;
-		ret = put_user(tmp,(unsigned long *) data);
+#ifdef __LP64__
+		if (CHILD_IS_32BIT) {
+			unsigned int tmp;
+
+			addr &= 0xffffffffL;
+			copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
+			ret = -EIO;
+			if (copied != sizeof(tmp))
+				goto out_tsk;
+			ret = put_user(tmp,(unsigned int *) data);
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(PEEK%s, %d, %lx, %lx) returning %ld, data %x\n", request == PTRACE_PEEKTEXT ? "TEXT" : "DATA" , pid, oaddr, odata, ret, tmp);
+#endif
+		}
+		else
+#endif
+		{
+			unsigned long tmp;
+
+			copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
+			ret = -EIO;
+			if (copied != sizeof(tmp))
+				goto out_tsk;
+			ret = put_user(tmp,(unsigned long *) data);
+		}
 		goto out_tsk;
 	}
 
@@ -120,22 +181,55 @@
 	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))
-			goto out_tsk;
+#ifdef __LP64__
+		if (CHILD_IS_32BIT) {
+			unsigned int tmp = (unsigned int)data;
+
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(POKE%s, %d, %lx, %lx)\n", request == PTRACE_POKETEXT ? "TEXT" : "DATA" , pid, oaddr, odata);
+#endif
+			addr &= 0xffffffffL;
+			if (access_process_vm(child, addr, &tmp, sizeof(tmp), 1) == sizeof(tmp))
+				goto out_tsk;
+		}
+		else
+#endif
+		{
+			if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data))
+				goto out_tsk;
+		}
 		ret = -EIO;
 		goto out_tsk;
 
-	/* Read the word at location addr in the USER area.  This will need
-	   to change when the kernel no longer saves all regs on a syscall. */
+	/* Read the word at location addr in the USER area.  For ptraced
+	   processes, the kernel saves all regs on a syscall. */
 	case PTRACE_PEEKUSR: {
-		unsigned long tmp;
-
 		ret = -EIO;
-		if ((addr & 3) || (unsigned long) addr >= sizeof(struct pt_regs))
-			goto out_tsk;
-
-		tmp = *(unsigned long *) ((char *) task_regs(child) + addr);
-		ret = put_user(tmp, (unsigned long *) data);
+#ifdef __LP64__
+		if (CHILD_IS_32BIT) {
+			unsigned int tmp;
+
+			if (addr & (sizeof(int)-1))
+				goto out_tsk;
+			if ((addr = translate_usr_offset(addr)) < 0)
+				goto out_tsk;
+
+			tmp = *(unsigned int *) ((char *) task_regs(child) + addr);
+			ret = put_user(tmp, (unsigned int *) data);
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(PEEKUSR, %d, %lx, %lx) returning %ld, addr %lx, data %x\n", pid, oaddr, odata, ret, addr, tmp);
+#endif
+		}
+		else
+#endif
+		{
+			unsigned long tmp;
+
+			if ((addr & (sizeof(long)-1)) || (unsigned long) addr >= sizeof(struct pt_regs))
+				goto out_tsk;
+			tmp = *(unsigned long *) ((char *) task_regs(child) + addr);
+			ret = put_user(tmp, (unsigned long *) data);
+		}
 		goto out_tsk;
 	}
 
@@ -147,38 +241,76 @@
 	   exit. */
 	case PTRACE_POKEUSR:
 		ret = -EIO;
-		if ((addr & 3) || (unsigned long) addr >= sizeof(struct pt_regs))
-			goto out_tsk;
 		/* Some register values written here may be ignored in
 		 * entry.S:syscall_restore_rfi; e.g. iaoq is written with
 		 * r31/r31+4, and not with the values in pt_regs.
 		 */
+		 /* PT_PSW=0, so this is valid for 32 bit processes under 64
+		 * bit kernels.
+		 */
 		if (addr == PT_PSW) {
-			/* Allow writing to Nullify, Divide-step-correction,
+			/* PT_PSW=0, so this is valid for 32 bit processes
+			 * under 64 bit kernels.
+			 *
+			 * Allow writing to Nullify, Divide-step-correction,
 			 * and carry/borrow bits.
 			 * BEWARE, if you set N, and then single step, it wont
 			 * stop on the nullified instruction.
 			 */
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(POKEUSR, %d, %lx, %lx)\n", pid, oaddr, odata);
+#endif
 			data &= USER_PSW_BITS;
 			task_regs(child)->gr[0] &= ~USER_PSW_BITS;
 			task_regs(child)->gr[0] |= data;
 			ret = 0;
 			goto out_tsk;
 		}
-		else if ((addr >= PT_GR1 && addr <= PT_GR31) ||
-				addr == PT_IAOQ0 || addr == PT_IAOQ1 ||
-				(addr >= PT_FR0 && addr <= PT_FR31) ||
-				addr == PT_SAR) {
-			*(unsigned long *) ((char *) task_regs(child) + addr) = data;
-			ret = 0;
+#ifdef __LP64__
+		if (CHILD_IS_32BIT) {
+			if (addr & (sizeof(int)-1))
+				goto out_tsk;
+			if ((addr = translate_usr_offset(addr)) < 0)
+				goto out_tsk;
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(POKEUSR, %d, %lx, %lx) addr %lx\n", pid, oaddr, odata, addr);
+#endif
+			if (addr >= PT_FR0 && addr <= PT_FR31) {
+				/* Special case, fp regs are 64 bits anyway */
+				*(unsigned int *) ((char *) task_regs(child) + addr) = data;
+				ret = 0;
+			}
+			else if ((addr >= PT_GR1+4 && addr <= PT_GR31+4) ||
+					addr == PT_IAOQ0+4 || addr == PT_IAOQ1+4 ||
+					addr == PT_SAR+4) {
+				/* Zero the top 32 bits */
+				*(unsigned int *) ((char *) task_regs(child) + addr - 4) = 0;
+				*(unsigned int *) ((char *) task_regs(child) + addr) = data;
+				ret = 0;
+			}
 			goto out_tsk;
 		}
 		else
+#endif
+		{
+			if ((addr & (sizeof(long)-1)) || (unsigned long) addr >= sizeof(struct pt_regs))
+				goto out_tsk;
+			if ((addr >= PT_GR1 && addr <= PT_GR31) ||
+					addr == PT_IAOQ0 || addr == PT_IAOQ1 ||
+					(addr >= PT_FR0 && addr <= PT_FR31) ||
+					addr == PT_SAR) {
+				*(unsigned long *) ((char *) task_regs(child) + addr) = data;
+				ret = 0;
+			}
 			goto out_tsk;
+		}
 
 	case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */
 	case PTRACE_CONT:
 		ret = -EIO;
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(%s)\n", request == PTRACE_SYSCALL ? "SYSCALL" : "CONT");
+#endif
 		if ((unsigned long) data > _NSIG)
 			goto out_tsk;
 		child->ptrace &= ~(PT_SINGLESTEP|PT_BLOCKSTEP);
@@ -195,12 +327,18 @@
 		 * sigkill.  perhaps it should be put in the status
 		 * that it wants to exit.
 		 */
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(KILL)\n");
+#endif
 		if (child->state == TASK_ZOMBIE)	/* already dead */
 			goto out_tsk;
 		child->exit_code = SIGKILL;
 		goto out_wake_notrap;
 
 	case PTRACE_SINGLEBLOCK:
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(SINGLEBLOCK)\n");
+#endif
 		ret = -EIO;
 		if ((unsigned long) data > _NSIG)
 			goto out_tsk;
@@ -216,6 +354,9 @@
 		goto out_wake;
 
 	case PTRACE_SINGLESTEP:
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(SINGLESTEP)\n");
+#endif
 		ret = -EIO;
 		if ((unsigned long) data > _NSIG)
 			goto out_tsk;
@@ -267,6 +408,9 @@
 		goto out_wake;
 
 	case PTRACE_DETACH:
+#ifdef DEBUG_PTRACE
+printk("sys_ptrace(DETACH)\n");
+#endif
 		ret = -EIO;
 		if ((unsigned long) data > _NSIG)
 			goto out_tsk;
@@ -297,6 +441,9 @@
 	free_task_struct(child);
 out:
 	unlock_kernel();
+#ifdef DEBUG_PTRACE
+if (ret) printk("sys_ptrace(%ld, %d, %lx, %lx) returning %ld\n", request, pid, oaddr, odata, ret);
+#endif
 	return ret;
 }