diff options
Diffstat (limited to 'arch/arm/kernel')
| -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 |
4 files changed, 85 insertions, 54 deletions
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 |
