[ARM] 5382/1: unwind: Reorganise the stacktrace support
[linux-2.6.git] / arch / arm / oprofile / backtrace.c
index df35c45..d805a52 100644 (file)
 #include <linux/oprofile.h>
 #include <linux/sched.h>
 #include <linux/mm.h>
+#include <linux/uaccess.h>
 #include <asm/ptrace.h>
-#include <asm/uaccess.h>
+#include <asm/stacktrace.h>
 
+static int report_trace(struct stackframe *frame, void *d)
+{
+       unsigned int *depth = d;
+
+       if (*depth) {
+               oprofile_add_trace(frame->pc);
+               (*depth)--;
+       }
+
+       return *depth == 0;
+}
 
 /*
  * The registers we're interested in are at the end of the variable
@@ -32,113 +44,40 @@ struct frame_tail {
        unsigned long lr;
 } __attribute__((packed));
 
-
-#ifdef CONFIG_FRAME_POINTER
-static struct frame_tail* kernel_backtrace(struct frame_tail *tail)
-{
-       oprofile_add_trace(tail->lr);
-
-       /* frame pointers should strictly progress back up the stack
-        * (towards higher addresses) */
-       if (tail >= tail->fp)
-               return NULL;
-
-       return tail->fp-1;
-}
-#endif
-
 static struct frame_tail* user_backtrace(struct frame_tail *tail)
 {
-       struct frame_tail buftail;
+       struct frame_tail buftail[2];
 
-       /* hardware pte might not be valid due to dirty/accessed bit emulation
-        * so we use copy_from_user and benefit from exception fixups */
-       if (copy_from_user(&buftail, tail, sizeof(struct frame_tail)))
+       /* Also check accessibility of one struct frame_tail beyond */
+       if (!access_ok(VERIFY_READ, tail, sizeof(buftail)))
+               return NULL;
+       if (__copy_from_user_inatomic(buftail, tail, sizeof(buftail)))
                return NULL;
 
-       oprofile_add_trace(buftail.lr);
+       oprofile_add_trace(buftail[0].lr);
 
        /* frame pointers should strictly progress back up the stack
         * (towards higher addresses) */
-       if (tail >= buftail.fp)
+       if (tail >= buftail[0].fp)
                return NULL;
 
-       return buftail.fp-1;
-}
-
-/* Compare two addresses and see if they're on the same page */
-#define CMP_ADDR_EQUAL(x,y,offset) ((((unsigned long) x) >> PAGE_SHIFT) \
-       == ((((unsigned long) y) + offset) >> PAGE_SHIFT))
-
-/* check that the page(s) containing the frame tail are present */
-static int pages_present(struct frame_tail *tail)
-{
-       struct mm_struct * mm = current->mm;
-
-       if (!check_user_page_readable(mm, (unsigned long)tail))
-               return 0;
-
-       if (CMP_ADDR_EQUAL(tail, tail, 8))
-               return 1;
-
-       if (!check_user_page_readable(mm, ((unsigned long)tail) + 8))
-               return 0;
-
-       return 1;
-}
-
-/*
- * |             | /\ Higher addresses
- * |             |
- * --------------- stack base (address of current_thread_info)
- * | thread info |
- * .             .
- * |    stack    |
- * --------------- saved regs->ARM_fp value if valid (frame_tail address)
- * .             .
- * --------------- struct pt_regs stored on stack (struct pt_regs *)
- * |             |
- * .             .
- * |             |
- * --------------- %esp
- * |             |
- * |             | \/ Lower addresses
- *
- * Thus, &pt_regs <-> stack base restricts the valid(ish) fp values
- */
-static int valid_kernel_stack(struct frame_tail *tail, struct pt_regs *regs)
-{
-       unsigned long tailaddr = (unsigned long)tail;
-       unsigned long stack = (unsigned long)regs;
-       unsigned long stack_base = (stack & ~(THREAD_SIZE - 1)) + THREAD_SIZE;
-
-       return (tailaddr > stack) && (tailaddr < stack_base);
+       return buftail[0].fp-1;
 }
 
 void arm_backtrace(struct pt_regs * const regs, unsigned int depth)
 {
-       struct frame_tail *tail;
-       unsigned long last_address = 0;
-
-       tail = ((struct frame_tail *) regs->ARM_fp) - 1;
+       struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1;
 
        if (!user_mode(regs)) {
-
-#ifdef CONFIG_FRAME_POINTER
-               while (depth-- && tail && valid_kernel_stack(tail, regs)) {
-                       tail = kernel_backtrace(tail);
-               }
-#endif
+               struct stackframe frame;
+               frame.fp = regs->ARM_fp;
+               frame.sp = regs->ARM_sp;
+               frame.lr = regs->ARM_lr;
+               frame.pc = regs->ARM_pc;
+               walk_stackframe(&frame, report_trace, &depth);
                return;
        }
 
-       while (depth-- && tail && !((unsigned long) tail & 3)) {
-               if ((!CMP_ADDR_EQUAL(last_address, tail, 0)
-                       || !CMP_ADDR_EQUAL(last_address, tail, 8))
-                               && !pages_present(tail))
-                       return;
-               last_address = (unsigned long) tail;
+       while (depth-- && tail && !((unsigned long) tail & 3))
                tail = user_backtrace(tail);
-       }
 }
-