Re: [PATCH] RFC. User space backtrace on segv

Alex Bennee <[email protected]> Wed, 06 Oct 2004 17:39:41 +0100
Newsgroups gmane.linux.ports.sh.devel,gmane.linux.ports.sh.general,gmane.linux.kernel
Organization Hackers Inc
Message-ID <1097080781.5420.36.camel@cambridge>
On Wed, 2004-10-06 at 17:37, Alex Bennee wrote:
> Hi,
> 
> I hacked up this little patch to dump the stack and attempt to generate
> a back-trace for errant user-space tasks.
>  <snip>

It would of been helpful if I'd actually attached the patch. Doh!

-- 
Alex, Kernel Hacker: http://www.bennee.com/~alex/

Test input for validity and plausibility.
- The Elements of Programming Style (Kernighan & Plaugher)
lk.dump_user_task.patch (text/x-patch, 3.9 KB)
Index: arch/sh/kernel/traps.c
===================================================================
RCS file: /cvsroot/linuxsh/linux/arch/sh/kernel/traps.c,v
retrieving revision 1.1.1.1.2.5
diff -u -b -r1.1.1.1.2.5 traps.c
--- arch/sh/kernel/traps.c	23 Oct 2003 22:08:56 -0000	1.1.1.1.2.5
+++ arch/sh/kernel/traps.c	6 Oct 2004 16:27:06 -0000
@@ -1,4 +1,4 @@
-/* $Id: traps.c,v 1.1.1.1.2.4 2002/05/10 17:58:54 jzs Exp $
+/* $Id: traps.c,v 1.1.1.1.2.5 2003/10/23 22:08:56 yoshii Exp $
  *
  *  linux/arch/sh/traps.c
  *
@@ -32,6 +32,8 @@
 #include <asm/atomic.h>
 #include <asm/processor.h>
 
+void dump_user_task (struct task_struct *tsk, struct pt_regs *regs);
+
 #ifdef CONFIG_SH_KGDB
 #include <asm/kgdb.h>
 #define CHK_REMOTE_DEBUG(regs)                                               \
@@ -114,6 +116,7 @@
 			regs->pc = fixup;
 			return 0;
 		}
+		printk(KERN_ALERT "no fixup: pid=%d pc=0x%lx\n",current->pid,current->thread.pc);
 		die(str, regs, err);
 	}
 	return -EFAULT;
@@ -515,6 +518,7 @@
 
 	uspace_segv:
 		printk(KERN_NOTICE "Killing process \"%s\" due to unaligned access\n", current->comm);
+		dump_user_task(current, regs);
 		force_sig(SIGSEGV, current);
 	} else {
 		if (regs->pc & 1)
@@ -605,6 +609,11 @@
 		     : "memory");
 }
 
+/*
+** This attempts to do a backtrace through the
+** kernel-space stack (therfor shows the kernel backtrace)
+*/
+
 void show_task(unsigned long *sp)
 {
 	unsigned long *stack, addr;
@@ -653,3 +662,104 @@
 {
 	show_task((unsigned long *)tsk->thread.sp);
 }
+
+
+/*
+** Generate Userspace Backtrace
+**
+** In theory this shouldn't be needed as when a userspace task
+** dies you can generate a core dump. However for embedded systems it
+** may just be easier to dump the registers and the user-stack
+*/
+
+void dump_user_frame(int frame, unsigned long *start_addr, unsigned long *end_addr)
+{
+    unsigned long addr;
+
+#if 0
+    printk(KERN_ALERT "Frame %d (%p->%p)\n",
+	    frame, start_addr, end_addr);
+#endif
+    
+    printk(KERN_ALERT "Frame %2d: ", frame);
+
+    do {
+	get_user(addr, start_addr);
+	printk("0x%08lx ", addr);
+	start_addr++;
+    } while (start_addr<end_addr);
+	
+    get_user(addr, end_addr);
+    printk(": <0x%08lx>\n",addr);
+}
+
+void dump_user_task (struct task_struct *tsk, struct pt_regs *regs)
+{
+    unsigned long start_code, end_code, *sp;
+    unsigned long *start_frame, *end_frame;
+    int frame=0;
+    
+    if (!user_mode(regs)) {
+	printk(KERN_ALERT "dump_user_task: not in userspace");
+	return;
+    }
+
+    printk(KERN_ALERT "dump_user_task (%p): %s, pid %d, pc 0x%lx\n",
+	    tsk, tsk->comm, tsk->pid, regs->pc);
+     
+    show_regs(regs);
+
+    /*
+     * Find the start and end code addresses from the
+     * process descriptor. We shall need this to work
+     * out if values on the stack are return addresses
+     */
+    start_code = tsk->mm->start_code;
+    end_code = tsk->mm->end_code;
+    sp = (unsigned long *)  regs->regs[14];
+    
+    printk(KERN_ALERT "start_code=0x%lx, end_code=0x%lx, sp=%p/0x%lx\n",
+	    start_code,
+	    end_code,
+	    sp, tsk->mm->start_stack);
+   
+    /*
+     * R14 points at the bottom of the stack frame (it grows down)
+     * which include the local variables and return addresses.
+     *
+     * We simply follow the stack back up, a long word at a time
+     * printing anything that falls in a vma with execute privalages
+     */
+
+    start_frame = sp;
+    
+    do {
+	unsigned long addr;
+	struct vm_area_struct * vma;
+
+	get_user(addr, sp);
+
+	if(addr==0)
+	    vma = NULL;
+	else
+	    vma = find_vma(tsk->mm, addr);
+	
+	if (vma) {
+	    if (vma->vm_flags&VM_EXECUTABLE)
+	    {
+#if 0
+		printk(KERN_ALERT "addr %p, vma 0x%lx (0x%lx->0x%lx), flags 0x%x\n",
+			addr, vma, vma->vm_start, vma->vm_end, vma->vm_flags);
+#endif
+		end_frame = sp;
+		dump_user_frame(frame, start_frame, end_frame);
+		frame++;
+		start_frame = end_frame;
+		start_frame++;
+	    }
+	}
+	sp++;
+    } while ((unsigned long)sp<tsk->mm->start_stack && frame<4 );
+    
+}
+