[ARM] 5382/1: unwind: Reorganise the stacktrace support
Catalin Marinas [Wed, 11 Feb 2009 12:07:53 +0000 (13:07 +0100)]
This patch changes the walk_stacktrace and its callers for easier
integration of stack unwinding. The arch/arm/kernel/stacktrace.h file is
also moved to arch/arm/include/asm/stacktrace.h.

Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

arch/arm/include/asm/stacktrace.h [new file with mode: 0644]
arch/arm/include/asm/thread_info.h
arch/arm/kernel/process.c
arch/arm/kernel/stacktrace.c
arch/arm/kernel/stacktrace.h [deleted file]
arch/arm/kernel/time.c
arch/arm/oprofile/backtrace.c

diff --git a/arch/arm/include/asm/stacktrace.h b/arch/arm/include/asm/stacktrace.h
new file mode 100644 (file)
index 0000000..4d0a164
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef __ASM_STACKTRACE_H
+#define __ASM_STACKTRACE_H
+
+struct stackframe {
+       unsigned long fp;
+       unsigned long sp;
+       unsigned long lr;
+       unsigned long pc;
+};
+
+extern int unwind_frame(struct stackframe *frame);
+extern void walk_stackframe(struct stackframe *frame,
+                           int (*fn)(struct stackframe *, void *), void *data);
+
+#endif /* __ASM_STACKTRACE_H */
index b9dc8a8..4f88482 100644 (file)
@@ -99,6 +99,8 @@ static inline struct thread_info *current_thread_info(void)
 
 #define thread_saved_pc(tsk)   \
        ((unsigned long)(task_thread_info(tsk)->cpu_context.pc))
+#define thread_saved_sp(tsk)   \
+       ((unsigned long)(task_thread_info(tsk)->cpu_context.sp))
 #define thread_saved_fp(tsk)   \
        ((unsigned long)(task_thread_info(tsk)->cpu_context.fp))
 
index d3ea6fa..af377c7 100644 (file)
@@ -34,6 +34,7 @@
 #include <asm/processor.h>
 #include <asm/system.h>
 #include <asm/thread_notify.h>
+#include <asm/stacktrace.h>
 #include <asm/mach/time.h>
 
 static const char *processor_modes[] = {
@@ -372,23 +373,21 @@ EXPORT_SYMBOL(kernel_thread);
 
 unsigned long get_wchan(struct task_struct *p)
 {
-       unsigned long fp, lr;
-       unsigned long stack_start, stack_end;
+       struct stackframe frame;
        int count = 0;
        if (!p || p == current || p->state == TASK_RUNNING)
                return 0;
 
-       stack_start = (unsigned long)end_of_stack(p);
-       stack_end = (unsigned long)task_stack_page(p) + THREAD_SIZE;
-
-       fp = thread_saved_fp(p);
+       frame.fp = thread_saved_fp(p);
+       frame.sp = thread_saved_sp(p);
+       frame.lr = 0;                   /* recovered from the stack */
+       frame.pc = thread_saved_pc(p);
        do {
-               if (fp < stack_start || fp > stack_end)
+               int ret = unwind_frame(&frame);
+               if (ret < 0)
                        return 0;
-               lr = ((unsigned long *)fp)[-1];
-               if (!in_sched_functions(lr))
-                       return lr;
-               fp = *(unsigned long *) (fp - 12);
+               if (!in_sched_functions(frame.pc))
+                       return frame.pc;
        } while (count ++ < 16);
        return 0;
 }
index fc650f6..9f444e5 100644 (file)
@@ -2,35 +2,60 @@
 #include <linux/sched.h>
 #include <linux/stacktrace.h>
 
-#include "stacktrace.h"
-
-int walk_stackframe(unsigned long fp, unsigned long low, unsigned long high,
-                   int (*fn)(struct stackframe *, void *), void *data)
+#include <asm/stacktrace.h>
+
+#if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND)
+/*
+ * Unwind the current stack frame and store the new register values in the
+ * structure passed as argument. Unwinding is equivalent to a function return,
+ * hence the new PC value rather than LR should be used for backtrace.
+ *
+ * With framepointer enabled, a simple function prologue looks like this:
+ *     mov     ip, sp
+ *     stmdb   sp!, {fp, ip, lr, pc}
+ *     sub     fp, ip, #4
+ *
+ * A simple function epilogue looks like this:
+ *     ldm     sp, {fp, sp, pc}
+ *
+ * Note that with framepointer enabled, even the leaf functions have the same
+ * prologue and epilogue, therefore we can ignore the LR value in this case.
+ */
+int unwind_frame(struct stackframe *frame)
 {
-       struct stackframe *frame;
-
-       do {
-               /*
-                * Check current frame pointer is within bounds
-                */
-               if (fp < (low + 12) || fp + 4 >= high)
-                       break;
+       unsigned long high, low;
+       unsigned long fp = frame->fp;
 
-               frame = (struct stackframe *)(fp - 12);
+       /* only go to a higher address on the stack */
+       low = frame->sp;
+       high = ALIGN(low, THREAD_SIZE) + THREAD_SIZE;
 
-               if (fn(frame, data))
-                       break;
+       /* check current frame pointer is within bounds */
+       if (fp < (low + 12) || fp + 4 >= high)
+               return -EINVAL;
 
-               /*
-                * Update the low bound - the next frame must always
-                * be at a higher address than the current frame.
-                */
-               low = fp + 4;
-               fp = frame->fp;
-       } while (fp);
+       /* restore the registers from the stack frame */
+       frame->fp = *(unsigned long *)(fp - 12);
+       frame->sp = *(unsigned long *)(fp - 8);
+       frame->pc = *(unsigned long *)(fp - 4);
 
        return 0;
 }
+#endif
+
+void walk_stackframe(struct stackframe *frame,
+                    int (*fn)(struct stackframe *, void *), void *data)
+{
+       while (1) {
+               int ret;
+
+               if (fn(frame, data))
+                       break;
+               ret = unwind_frame(frame);
+               if (ret < 0)
+                       break;
+       }
+}
 EXPORT_SYMBOL(walk_stackframe);
 
 #ifdef CONFIG_STACKTRACE
@@ -44,7 +69,7 @@ static int save_trace(struct stackframe *frame, void *d)
 {
        struct stack_trace_data *data = d;
        struct stack_trace *trace = data->trace;
-       unsigned long addr = frame->lr;
+       unsigned long addr = frame->pc;
 
        if (data->no_sched_functions && in_sched_functions(addr))
                return 0;
@@ -61,11 +86,10 @@ static int save_trace(struct stackframe *frame, void *d)
 void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
 {
        struct stack_trace_data data;
-       unsigned long fp, base;
+       struct stackframe frame;
 
        data.trace = trace;
        data.skip = trace->skip;
-       base = (unsigned long)task_stack_page(tsk);
 
        if (tsk != current) {
 #ifdef CONFIG_SMP
@@ -76,14 +100,22 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
                BUG();
 #else
                data.no_sched_functions = 1;
-               fp = thread_saved_fp(tsk);
+               frame.fp = thread_saved_fp(tsk);
+               frame.sp = thread_saved_sp(tsk);
+               frame.lr = 0;           /* recovered from the stack */
+               frame.pc = thread_saved_pc(tsk);
 #endif
        } else {
+               register unsigned long current_sp asm ("sp");
+
                data.no_sched_functions = 0;
-               asm("mov %0, fp" : "=r" (fp));
+               frame.fp = (unsigned long)__builtin_frame_address(0);
+               frame.sp = current_sp;
+               frame.lr = (unsigned long)__builtin_return_address(0);
+               frame.pc = (unsigned long)save_stack_trace_tsk;
        }
 
-       walk_stackframe(fp, base, base + THREAD_SIZE, save_trace, &data);
+       walk_stackframe(&frame, save_trace, &data);
        if (trace->nr_entries < trace->max_entries)
                trace->entries[trace->nr_entries++] = ULONG_MAX;
 }
diff --git a/arch/arm/kernel/stacktrace.h b/arch/arm/kernel/stacktrace.h
deleted file mode 100644 (file)
index e9fd20c..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-struct stackframe {
-       unsigned long fp;
-       unsigned long sp;
-       unsigned long lr;
-       unsigned long pc;
-};
-
-int walk_stackframe(unsigned long fp, unsigned long low, unsigned long high,
-                   int (*fn)(struct stackframe *, void *), void *data);
index c68b44a..4cdc4a0 100644 (file)
@@ -33,6 +33,7 @@
 
 #include <asm/leds.h>
 #include <asm/thread_info.h>
+#include <asm/stacktrace.h>
 #include <asm/mach/time.h>
 
 /*
@@ -55,14 +56,22 @@ EXPORT_SYMBOL(rtc_lock);
 #ifdef CONFIG_SMP
 unsigned long profile_pc(struct pt_regs *regs)
 {
-       unsigned long fp, pc = instruction_pointer(regs);
+       struct stackframe frame;
 
-       if (in_lock_functions(pc)) {
-               fp = regs->ARM_fp;
-               pc = ((unsigned long *)fp)[-1];
-       }
+       if (!in_lock_functions(regs->ARM_pc))
+               return regs->ARM_pc;
+
+       frame.fp = regs->ARM_fp;
+       frame.sp = regs->ARM_sp;
+       frame.lr = regs->ARM_lr;
+       frame.pc = regs->ARM_pc;
+       do {
+               int ret = unwind_frame(&frame);
+               if (ret < 0)
+                       return 0;
+       } while (in_lock_functions(frame.pc));
 
-       return pc;
+       return frame.pc;
 }
 EXPORT_SYMBOL(profile_pc);
 #endif
index cefc21c..d805a52 100644 (file)
 #include <linux/mm.h>
 #include <linux/uaccess.h>
 #include <asm/ptrace.h>
-
-#include "../kernel/stacktrace.h"
+#include <asm/stacktrace.h>
 
 static int report_trace(struct stackframe *frame, void *d)
 {
        unsigned int *depth = d;
 
        if (*depth) {
-               oprofile_add_trace(frame->lr);
+               oprofile_add_trace(frame->pc);
                (*depth)--;
        }
 
@@ -70,9 +69,12 @@ void arm_backtrace(struct pt_regs * const regs, unsigned int depth)
        struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1;
 
        if (!user_mode(regs)) {
-               unsigned long base = ((unsigned long)regs) & ~(THREAD_SIZE - 1);
-               walk_stackframe(regs->ARM_fp, base, base + THREAD_SIZE,
-                               report_trace, &depth);
+               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;
        }