diff options
author | Catalin Marinas <catalin.marinas@arm.com> | 2009-02-11 07:07:53 -0500 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-02-12 08:21:17 -0500 |
commit | 2d7c11bfc91637e5f9bc5f8c9a82aaffcc0e97aa (patch) | |
tree | 0bb67dae38b1185089b6c9813769689cb79c5ee3 /arch | |
parent | 67a94c23bb7338c321ae71aa33f8d398c94e1d0c (diff) |
[ARM] 5382/1: unwind: Reorganise the stacktrace support
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>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/include/asm/stacktrace.h | 15 | ||||
-rw-r--r-- | arch/arm/include/asm/thread_info.h | 2 | ||||
-rw-r--r-- | arch/arm/kernel/process.c | 21 | ||||
-rw-r--r-- | arch/arm/kernel/stacktrace.c | 88 | ||||
-rw-r--r-- | arch/arm/kernel/stacktrace.h | 9 | ||||
-rw-r--r-- | arch/arm/kernel/time.c | 21 | ||||
-rw-r--r-- | arch/arm/oprofile/backtrace.c | 14 |
7 files changed, 110 insertions, 60 deletions
diff --git a/arch/arm/include/asm/stacktrace.h b/arch/arm/include/asm/stacktrace.h new file mode 100644 index 000000000000..4d0a16441b29 --- /dev/null +++ b/arch/arm/include/asm/stacktrace.h | |||
@@ -0,0 +1,15 @@ | |||
1 | #ifndef __ASM_STACKTRACE_H | ||
2 | #define __ASM_STACKTRACE_H | ||
3 | |||
4 | struct stackframe { | ||
5 | unsigned long fp; | ||
6 | unsigned long sp; | ||
7 | unsigned long lr; | ||
8 | unsigned long pc; | ||
9 | }; | ||
10 | |||
11 | extern int unwind_frame(struct stackframe *frame); | ||
12 | extern void walk_stackframe(struct stackframe *frame, | ||
13 | int (*fn)(struct stackframe *, void *), void *data); | ||
14 | |||
15 | #endif /* __ASM_STACKTRACE_H */ | ||
diff --git a/arch/arm/include/asm/thread_info.h b/arch/arm/include/asm/thread_info.h index b9dc8a842573..4f8848260ee2 100644 --- a/arch/arm/include/asm/thread_info.h +++ b/arch/arm/include/asm/thread_info.h | |||
@@ -99,6 +99,8 @@ static inline struct thread_info *current_thread_info(void) | |||
99 | 99 | ||
100 | #define thread_saved_pc(tsk) \ | 100 | #define thread_saved_pc(tsk) \ |
101 | ((unsigned long)(task_thread_info(tsk)->cpu_context.pc)) | 101 | ((unsigned long)(task_thread_info(tsk)->cpu_context.pc)) |
102 | #define thread_saved_sp(tsk) \ | ||
103 | ((unsigned long)(task_thread_info(tsk)->cpu_context.sp)) | ||
102 | #define thread_saved_fp(tsk) \ | 104 | #define thread_saved_fp(tsk) \ |
103 | ((unsigned long)(task_thread_info(tsk)->cpu_context.fp)) | 105 | ((unsigned long)(task_thread_info(tsk)->cpu_context.fp)) |
104 | 106 | ||
diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index d3ea6fa89521..af377c73d90b 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c | |||
@@ -34,6 +34,7 @@ | |||
34 | #include <asm/processor.h> | 34 | #include <asm/processor.h> |
35 | #include <asm/system.h> | 35 | #include <asm/system.h> |
36 | #include <asm/thread_notify.h> | 36 | #include <asm/thread_notify.h> |
37 | #include <asm/stacktrace.h> | ||
37 | #include <asm/mach/time.h> | 38 | #include <asm/mach/time.h> |
38 | 39 | ||
39 | static const char *processor_modes[] = { | 40 | static const char *processor_modes[] = { |
@@ -372,23 +373,21 @@ EXPORT_SYMBOL(kernel_thread); | |||
372 | 373 | ||
373 | unsigned long get_wchan(struct task_struct *p) | 374 | unsigned long get_wchan(struct task_struct *p) |
374 | { | 375 | { |
375 | unsigned long fp, lr; | 376 | struct stackframe frame; |
376 | unsigned long stack_start, stack_end; | ||
377 | int count = 0; | 377 | int count = 0; |
378 | if (!p || p == current || p->state == TASK_RUNNING) | 378 | if (!p || p == current || p->state == TASK_RUNNING) |
379 | return 0; | 379 | return 0; |
380 | 380 | ||
381 | stack_start = (unsigned long)end_of_stack(p); | 381 | frame.fp = thread_saved_fp(p); |
382 | stack_end = (unsigned long)task_stack_page(p) + THREAD_SIZE; | 382 | frame.sp = thread_saved_sp(p); |
383 | 383 | frame.lr = 0; /* recovered from the stack */ | |
384 | fp = thread_saved_fp(p); | 384 | frame.pc = thread_saved_pc(p); |
385 | do { | 385 | do { |
386 | if (fp < stack_start || fp > stack_end) | 386 | int ret = unwind_frame(&frame); |
387 | if (ret < 0) | ||
387 | return 0; | 388 | return 0; |
388 | lr = ((unsigned long *)fp)[-1]; | 389 | if (!in_sched_functions(frame.pc)) |
389 | if (!in_sched_functions(lr)) | 390 | return frame.pc; |
390 | return lr; | ||
391 | fp = *(unsigned long *) (fp - 12); | ||
392 | } while (count ++ < 16); | 391 | } while (count ++ < 16); |
393 | return 0; | 392 | return 0; |
394 | } | 393 | } |
diff --git a/arch/arm/kernel/stacktrace.c b/arch/arm/kernel/stacktrace.c index fc650f64df43..9f444e5cc165 100644 --- a/arch/arm/kernel/stacktrace.c +++ b/arch/arm/kernel/stacktrace.c | |||
@@ -2,35 +2,60 @@ | |||
2 | #include <linux/sched.h> | 2 | #include <linux/sched.h> |
3 | #include <linux/stacktrace.h> | 3 | #include <linux/stacktrace.h> |
4 | 4 | ||
5 | #include "stacktrace.h" | 5 | #include <asm/stacktrace.h> |
6 | 6 | ||
7 | int walk_stackframe(unsigned long fp, unsigned long low, unsigned long high, | 7 | #if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND) |
8 | int (*fn)(struct stackframe *, void *), void *data) | 8 | /* |
9 | * Unwind the current stack frame and store the new register values in the | ||
10 | * structure passed as argument. Unwinding is equivalent to a function return, | ||
11 | * hence the new PC value rather than LR should be used for backtrace. | ||
12 | * | ||
13 | * With framepointer enabled, a simple function prologue looks like this: | ||
14 | * mov ip, sp | ||
15 | * stmdb sp!, {fp, ip, lr, pc} | ||
16 | * sub fp, ip, #4 | ||
17 | * | ||
18 | * A simple function epilogue looks like this: | ||
19 | * ldm sp, {fp, sp, pc} | ||
20 | * | ||
21 | * Note that with framepointer enabled, even the leaf functions have the same | ||
22 | * prologue and epilogue, therefore we can ignore the LR value in this case. | ||
23 | */ | ||
24 | int unwind_frame(struct stackframe *frame) | ||
9 | { | 25 | { |
10 | struct stackframe *frame; | 26 | unsigned long high, low; |
11 | 27 | unsigned long fp = frame->fp; | |
12 | do { | ||
13 | /* | ||
14 | * Check current frame pointer is within bounds | ||
15 | */ | ||
16 | if (fp < (low + 12) || fp + 4 >= high) | ||
17 | break; | ||
18 | 28 | ||
19 | frame = (struct stackframe *)(fp - 12); | 29 | /* only go to a higher address on the stack */ |
30 | low = frame->sp; | ||
31 | high = ALIGN(low, THREAD_SIZE) + THREAD_SIZE; | ||
20 | 32 | ||
21 | if (fn(frame, data)) | 33 | /* check current frame pointer is within bounds */ |
22 | break; | 34 | if (fp < (low + 12) || fp + 4 >= high) |
35 | return -EINVAL; | ||
23 | 36 | ||
24 | /* | 37 | /* restore the registers from the stack frame */ |
25 | * Update the low bound - the next frame must always | 38 | frame->fp = *(unsigned long *)(fp - 12); |
26 | * be at a higher address than the current frame. | 39 | frame->sp = *(unsigned long *)(fp - 8); |
27 | */ | 40 | frame->pc = *(unsigned long *)(fp - 4); |
28 | low = fp + 4; | ||
29 | fp = frame->fp; | ||
30 | } while (fp); | ||
31 | 41 | ||
32 | return 0; | 42 | return 0; |
33 | } | 43 | } |
44 | #endif | ||
45 | |||
46 | void walk_stackframe(struct stackframe *frame, | ||
47 | int (*fn)(struct stackframe *, void *), void *data) | ||
48 | { | ||
49 | while (1) { | ||
50 | int ret; | ||
51 | |||
52 | if (fn(frame, data)) | ||
53 | break; | ||
54 | ret = unwind_frame(frame); | ||
55 | if (ret < 0) | ||
56 | break; | ||
57 | } | ||
58 | } | ||
34 | EXPORT_SYMBOL(walk_stackframe); | 59 | EXPORT_SYMBOL(walk_stackframe); |
35 | 60 | ||
36 | #ifdef CONFIG_STACKTRACE | 61 | #ifdef CONFIG_STACKTRACE |
@@ -44,7 +69,7 @@ static int save_trace(struct stackframe *frame, void *d) | |||
44 | { | 69 | { |
45 | struct stack_trace_data *data = d; | 70 | struct stack_trace_data *data = d; |
46 | struct stack_trace *trace = data->trace; | 71 | struct stack_trace *trace = data->trace; |
47 | unsigned long addr = frame->lr; | 72 | unsigned long addr = frame->pc; |
48 | 73 | ||
49 | if (data->no_sched_functions && in_sched_functions(addr)) | 74 | if (data->no_sched_functions && in_sched_functions(addr)) |
50 | return 0; | 75 | return 0; |
@@ -61,11 +86,10 @@ static int save_trace(struct stackframe *frame, void *d) | |||
61 | void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) | 86 | void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) |
62 | { | 87 | { |
63 | struct stack_trace_data data; | 88 | struct stack_trace_data data; |
64 | unsigned long fp, base; | 89 | struct stackframe frame; |
65 | 90 | ||
66 | data.trace = trace; | 91 | data.trace = trace; |
67 | data.skip = trace->skip; | 92 | data.skip = trace->skip; |
68 | base = (unsigned long)task_stack_page(tsk); | ||
69 | 93 | ||
70 | if (tsk != current) { | 94 | if (tsk != current) { |
71 | #ifdef CONFIG_SMP | 95 | #ifdef CONFIG_SMP |
@@ -76,14 +100,22 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) | |||
76 | BUG(); | 100 | BUG(); |
77 | #else | 101 | #else |
78 | data.no_sched_functions = 1; | 102 | data.no_sched_functions = 1; |
79 | fp = thread_saved_fp(tsk); | 103 | frame.fp = thread_saved_fp(tsk); |
104 | frame.sp = thread_saved_sp(tsk); | ||
105 | frame.lr = 0; /* recovered from the stack */ | ||
106 | frame.pc = thread_saved_pc(tsk); | ||
80 | #endif | 107 | #endif |
81 | } else { | 108 | } else { |
109 | register unsigned long current_sp asm ("sp"); | ||
110 | |||
82 | data.no_sched_functions = 0; | 111 | data.no_sched_functions = 0; |
83 | asm("mov %0, fp" : "=r" (fp)); | 112 | frame.fp = (unsigned long)__builtin_frame_address(0); |
113 | frame.sp = current_sp; | ||
114 | frame.lr = (unsigned long)__builtin_return_address(0); | ||
115 | frame.pc = (unsigned long)save_stack_trace_tsk; | ||
84 | } | 116 | } |
85 | 117 | ||
86 | walk_stackframe(fp, base, base + THREAD_SIZE, save_trace, &data); | 118 | walk_stackframe(&frame, save_trace, &data); |
87 | if (trace->nr_entries < trace->max_entries) | 119 | if (trace->nr_entries < trace->max_entries) |
88 | trace->entries[trace->nr_entries++] = ULONG_MAX; | 120 | trace->entries[trace->nr_entries++] = ULONG_MAX; |
89 | } | 121 | } |
diff --git a/arch/arm/kernel/stacktrace.h b/arch/arm/kernel/stacktrace.h deleted file mode 100644 index e9fd20cb5662..000000000000 --- a/arch/arm/kernel/stacktrace.h +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | struct stackframe { | ||
2 | unsigned long fp; | ||
3 | unsigned long sp; | ||
4 | unsigned long lr; | ||
5 | unsigned long pc; | ||
6 | }; | ||
7 | |||
8 | int walk_stackframe(unsigned long fp, unsigned long low, unsigned long high, | ||
9 | int (*fn)(struct stackframe *, void *), void *data); | ||
diff --git a/arch/arm/kernel/time.c b/arch/arm/kernel/time.c index c68b44aa88d2..4cdc4a0bd02d 100644 --- a/arch/arm/kernel/time.c +++ b/arch/arm/kernel/time.c | |||
@@ -33,6 +33,7 @@ | |||
33 | 33 | ||
34 | #include <asm/leds.h> | 34 | #include <asm/leds.h> |
35 | #include <asm/thread_info.h> | 35 | #include <asm/thread_info.h> |
36 | #include <asm/stacktrace.h> | ||
36 | #include <asm/mach/time.h> | 37 | #include <asm/mach/time.h> |
37 | 38 | ||
38 | /* | 39 | /* |
@@ -55,14 +56,22 @@ EXPORT_SYMBOL(rtc_lock); | |||
55 | #ifdef CONFIG_SMP | 56 | #ifdef CONFIG_SMP |
56 | unsigned long profile_pc(struct pt_regs *regs) | 57 | unsigned long profile_pc(struct pt_regs *regs) |
57 | { | 58 | { |
58 | unsigned long fp, pc = instruction_pointer(regs); | 59 | struct stackframe frame; |
59 | 60 | ||
60 | if (in_lock_functions(pc)) { | 61 | if (!in_lock_functions(regs->ARM_pc)) |
61 | fp = regs->ARM_fp; | 62 | return regs->ARM_pc; |
62 | pc = ((unsigned long *)fp)[-1]; | 63 | |
63 | } | 64 | frame.fp = regs->ARM_fp; |
65 | frame.sp = regs->ARM_sp; | ||
66 | frame.lr = regs->ARM_lr; | ||
67 | frame.pc = regs->ARM_pc; | ||
68 | do { | ||
69 | int ret = unwind_frame(&frame); | ||
70 | if (ret < 0) | ||
71 | return 0; | ||
72 | } while (in_lock_functions(frame.pc)); | ||
64 | 73 | ||
65 | return pc; | 74 | return frame.pc; |
66 | } | 75 | } |
67 | EXPORT_SYMBOL(profile_pc); | 76 | EXPORT_SYMBOL(profile_pc); |
68 | #endif | 77 | #endif |
diff --git a/arch/arm/oprofile/backtrace.c b/arch/arm/oprofile/backtrace.c index cefc21c2eee4..d805a52b5032 100644 --- a/arch/arm/oprofile/backtrace.c +++ b/arch/arm/oprofile/backtrace.c | |||
@@ -18,15 +18,14 @@ | |||
18 | #include <linux/mm.h> | 18 | #include <linux/mm.h> |
19 | #include <linux/uaccess.h> | 19 | #include <linux/uaccess.h> |
20 | #include <asm/ptrace.h> | 20 | #include <asm/ptrace.h> |
21 | 21 | #include <asm/stacktrace.h> | |
22 | #include "../kernel/stacktrace.h" | ||
23 | 22 | ||
24 | static int report_trace(struct stackframe *frame, void *d) | 23 | static int report_trace(struct stackframe *frame, void *d) |
25 | { | 24 | { |
26 | unsigned int *depth = d; | 25 | unsigned int *depth = d; |
27 | 26 | ||
28 | if (*depth) { | 27 | if (*depth) { |
29 | oprofile_add_trace(frame->lr); | 28 | oprofile_add_trace(frame->pc); |
30 | (*depth)--; | 29 | (*depth)--; |
31 | } | 30 | } |
32 | 31 | ||
@@ -70,9 +69,12 @@ void arm_backtrace(struct pt_regs * const regs, unsigned int depth) | |||
70 | struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1; | 69 | struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1; |
71 | 70 | ||
72 | if (!user_mode(regs)) { | 71 | if (!user_mode(regs)) { |
73 | unsigned long base = ((unsigned long)regs) & ~(THREAD_SIZE - 1); | 72 | struct stackframe frame; |
74 | walk_stackframe(regs->ARM_fp, base, base + THREAD_SIZE, | 73 | frame.fp = regs->ARM_fp; |
75 | report_trace, &depth); | 74 | frame.sp = regs->ARM_sp; |
75 | frame.lr = regs->ARM_lr; | ||
76 | frame.pc = regs->ARM_pc; | ||
77 | walk_stackframe(&frame, report_trace, &depth); | ||
76 | return; | 78 | return; |
77 | } | 79 | } |
78 | 80 | ||