Commit | Line | Data |
---|---|---|
7b104bcb | 1 | #include <linux/module.h> |
f16fb1ec RK |
2 | #include <linux/sched.h> |
3 | #include <linux/stacktrace.h> | |
4 | ||
2d7c11bf CM |
5 | #include <asm/stacktrace.h> |
6 | ||
7 | #if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND) | |
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) | |
f16fb1ec | 25 | { |
2d7c11bf CM |
26 | unsigned long high, low; |
27 | unsigned long fp = frame->fp; | |
f16fb1ec | 28 | |
2d7c11bf CM |
29 | /* only go to a higher address on the stack */ |
30 | low = frame->sp; | |
31 | high = ALIGN(low, THREAD_SIZE) + THREAD_SIZE; | |
f16fb1ec | 32 | |
2d7c11bf CM |
33 | /* check current frame pointer is within bounds */ |
34 | if (fp < (low + 12) || fp + 4 >= high) | |
35 | return -EINVAL; | |
f16fb1ec | 36 | |
2d7c11bf CM |
37 | /* restore the registers from the stack frame */ |
38 | frame->fp = *(unsigned long *)(fp - 12); | |
39 | frame->sp = *(unsigned long *)(fp - 8); | |
40 | frame->pc = *(unsigned long *)(fp - 4); | |
f16fb1ec RK |
41 | |
42 | return 0; | |
43 | } | |
2d7c11bf CM |
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 | } | |
7b104bcb | 59 | EXPORT_SYMBOL(walk_stackframe); |
f16fb1ec RK |
60 | |
61 | #ifdef CONFIG_STACKTRACE | |
62 | struct stack_trace_data { | |
63 | struct stack_trace *trace; | |
f76e9154 | 64 | unsigned int no_sched_functions; |
f16fb1ec RK |
65 | unsigned int skip; |
66 | }; | |
67 | ||
68 | static int save_trace(struct stackframe *frame, void *d) | |
69 | { | |
70 | struct stack_trace_data *data = d; | |
71 | struct stack_trace *trace = data->trace; | |
2d7c11bf | 72 | unsigned long addr = frame->pc; |
f16fb1ec | 73 | |
f76e9154 NP |
74 | if (data->no_sched_functions && in_sched_functions(addr)) |
75 | return 0; | |
f16fb1ec RK |
76 | if (data->skip) { |
77 | data->skip--; | |
78 | return 0; | |
79 | } | |
80 | ||
f76e9154 | 81 | trace->entries[trace->nr_entries++] = addr; |
f16fb1ec RK |
82 | |
83 | return trace->nr_entries >= trace->max_entries; | |
84 | } | |
85 | ||
f76e9154 | 86 | void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) |
f16fb1ec RK |
87 | { |
88 | struct stack_trace_data data; | |
2d7c11bf | 89 | struct stackframe frame; |
f16fb1ec RK |
90 | |
91 | data.trace = trace; | |
92 | data.skip = trace->skip; | |
f76e9154 NP |
93 | |
94 | if (tsk != current) { | |
95 | #ifdef CONFIG_SMP | |
96 | /* | |
97 | * What guarantees do we have here that 'tsk' | |
98 | * is not running on another CPU? | |
99 | */ | |
100 | BUG(); | |
101 | #else | |
102 | data.no_sched_functions = 1; | |
2d7c11bf CM |
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); | |
f76e9154 NP |
107 | #endif |
108 | } else { | |
2d7c11bf CM |
109 | register unsigned long current_sp asm ("sp"); |
110 | ||
f76e9154 | 111 | data.no_sched_functions = 0; |
2d7c11bf CM |
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; | |
f76e9154 | 116 | } |
f16fb1ec | 117 | |
2d7c11bf | 118 | walk_stackframe(&frame, save_trace, &data); |
f76e9154 NP |
119 | if (trace->nr_entries < trace->max_entries) |
120 | trace->entries[trace->nr_entries++] = ULONG_MAX; | |
121 | } | |
122 | ||
123 | void save_stack_trace(struct stack_trace *trace) | |
124 | { | |
125 | save_stack_trace_tsk(current, trace); | |
f16fb1ec | 126 | } |
7b4c9505 | 127 | EXPORT_SYMBOL_GPL(save_stack_trace); |
f16fb1ec | 128 | #endif |