Commit | Line | Data |
---|---|---|
ecea4ab6 | 1 | #include <linux/export.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 | */ | |
4bf1fa5a | 24 | int notrace 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; | |
d33aadbf | 31 | high = ALIGN(low, 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 | ||
4bf1fa5a | 46 | void notrace walk_stackframe(struct stackframe *frame, |
2d7c11bf CM |
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 | /* | |
d5996b2f RK |
97 | * What guarantees do we have here that 'tsk' is not |
98 | * running on another CPU? For now, ignore it as we | |
99 | * can't guarantee we won't explode. | |
f76e9154 | 100 | */ |
d5996b2f RK |
101 | if (trace->nr_entries < trace->max_entries) |
102 | trace->entries[trace->nr_entries++] = ULONG_MAX; | |
103 | return; | |
f76e9154 NP |
104 | #else |
105 | data.no_sched_functions = 1; | |
2d7c11bf CM |
106 | frame.fp = thread_saved_fp(tsk); |
107 | frame.sp = thread_saved_sp(tsk); | |
108 | frame.lr = 0; /* recovered from the stack */ | |
109 | frame.pc = thread_saved_pc(tsk); | |
f76e9154 NP |
110 | #endif |
111 | } else { | |
2d7c11bf CM |
112 | register unsigned long current_sp asm ("sp"); |
113 | ||
f76e9154 | 114 | data.no_sched_functions = 0; |
2d7c11bf CM |
115 | frame.fp = (unsigned long)__builtin_frame_address(0); |
116 | frame.sp = current_sp; | |
117 | frame.lr = (unsigned long)__builtin_return_address(0); | |
118 | frame.pc = (unsigned long)save_stack_trace_tsk; | |
f76e9154 | 119 | } |
f16fb1ec | 120 | |
2d7c11bf | 121 | walk_stackframe(&frame, save_trace, &data); |
f76e9154 NP |
122 | if (trace->nr_entries < trace->max_entries) |
123 | trace->entries[trace->nr_entries++] = ULONG_MAX; | |
124 | } | |
125 | ||
126 | void save_stack_trace(struct stack_trace *trace) | |
127 | { | |
128 | save_stack_trace_tsk(current, trace); | |
f16fb1ec | 129 | } |
7b4c9505 | 130 | EXPORT_SYMBOL_GPL(save_stack_trace); |
f16fb1ec | 131 | #endif |