diff options
Diffstat (limited to 'arch/arm64/kernel/stacktrace.c')
-rw-r--r-- | arch/arm64/kernel/stacktrace.c | 62 |
1 files changed, 48 insertions, 14 deletions
diff --git a/arch/arm64/kernel/stacktrace.c b/arch/arm64/kernel/stacktrace.c index 62d395151abe..a336cb124320 100644 --- a/arch/arm64/kernel/stacktrace.c +++ b/arch/arm64/kernel/stacktrace.c | |||
@@ -7,6 +7,7 @@ | |||
7 | #include <linux/kernel.h> | 7 | #include <linux/kernel.h> |
8 | #include <linux/export.h> | 8 | #include <linux/export.h> |
9 | #include <linux/ftrace.h> | 9 | #include <linux/ftrace.h> |
10 | #include <linux/kprobes.h> | ||
10 | #include <linux/sched.h> | 11 | #include <linux/sched.h> |
11 | #include <linux/sched/debug.h> | 12 | #include <linux/sched/debug.h> |
12 | #include <linux/sched/task_stack.h> | 13 | #include <linux/sched/task_stack.h> |
@@ -29,9 +30,18 @@ | |||
29 | * ldp x29, x30, [sp] | 30 | * ldp x29, x30, [sp] |
30 | * add sp, sp, #0x10 | 31 | * add sp, sp, #0x10 |
31 | */ | 32 | */ |
33 | |||
34 | /* | ||
35 | * Unwind from one frame record (A) to the next frame record (B). | ||
36 | * | ||
37 | * We terminate early if the location of B indicates a malformed chain of frame | ||
38 | * records (e.g. a cycle), determined based on the location and fp value of A | ||
39 | * and the location (but not the fp value) of B. | ||
40 | */ | ||
32 | int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) | 41 | int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) |
33 | { | 42 | { |
34 | unsigned long fp = frame->fp; | 43 | unsigned long fp = frame->fp; |
44 | struct stack_info info; | ||
35 | 45 | ||
36 | if (fp & 0xf) | 46 | if (fp & 0xf) |
37 | return -EINVAL; | 47 | return -EINVAL; |
@@ -39,11 +49,40 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) | |||
39 | if (!tsk) | 49 | if (!tsk) |
40 | tsk = current; | 50 | tsk = current; |
41 | 51 | ||
42 | if (!on_accessible_stack(tsk, fp, NULL)) | 52 | if (!on_accessible_stack(tsk, fp, &info)) |
53 | return -EINVAL; | ||
54 | |||
55 | if (test_bit(info.type, frame->stacks_done)) | ||
43 | return -EINVAL; | 56 | return -EINVAL; |
44 | 57 | ||
58 | /* | ||
59 | * As stacks grow downward, any valid record on the same stack must be | ||
60 | * at a strictly higher address than the prior record. | ||
61 | * | ||
62 | * Stacks can nest in several valid orders, e.g. | ||
63 | * | ||
64 | * TASK -> IRQ -> OVERFLOW -> SDEI_NORMAL | ||
65 | * TASK -> SDEI_NORMAL -> SDEI_CRITICAL -> OVERFLOW | ||
66 | * | ||
67 | * ... but the nesting itself is strict. Once we transition from one | ||
68 | * stack to another, it's never valid to unwind back to that first | ||
69 | * stack. | ||
70 | */ | ||
71 | if (info.type == frame->prev_type) { | ||
72 | if (fp <= frame->prev_fp) | ||
73 | return -EINVAL; | ||
74 | } else { | ||
75 | set_bit(frame->prev_type, frame->stacks_done); | ||
76 | } | ||
77 | |||
78 | /* | ||
79 | * Record this frame record's values and location. The prev_fp and | ||
80 | * prev_type are only meaningful to the next unwind_frame() invocation. | ||
81 | */ | ||
45 | frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp)); | 82 | frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp)); |
46 | frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8)); | 83 | frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8)); |
84 | frame->prev_fp = fp; | ||
85 | frame->prev_type = info.type; | ||
47 | 86 | ||
48 | #ifdef CONFIG_FUNCTION_GRAPH_TRACER | 87 | #ifdef CONFIG_FUNCTION_GRAPH_TRACER |
49 | if (tsk->ret_stack && | 88 | if (tsk->ret_stack && |
@@ -73,6 +112,7 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) | |||
73 | 112 | ||
74 | return 0; | 113 | return 0; |
75 | } | 114 | } |
115 | NOKPROBE_SYMBOL(unwind_frame); | ||
76 | 116 | ||
77 | void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame, | 117 | void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame, |
78 | int (*fn)(struct stackframe *, void *), void *data) | 118 | int (*fn)(struct stackframe *, void *), void *data) |
@@ -87,6 +127,7 @@ void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame, | |||
87 | break; | 127 | break; |
88 | } | 128 | } |
89 | } | 129 | } |
130 | NOKPROBE_SYMBOL(walk_stackframe); | ||
90 | 131 | ||
91 | #ifdef CONFIG_STACKTRACE | 132 | #ifdef CONFIG_STACKTRACE |
92 | struct stack_trace_data { | 133 | struct stack_trace_data { |
@@ -122,12 +163,7 @@ void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace) | |||
122 | data.skip = trace->skip; | 163 | data.skip = trace->skip; |
123 | data.no_sched_functions = 0; | 164 | data.no_sched_functions = 0; |
124 | 165 | ||
125 | frame.fp = regs->regs[29]; | 166 | start_backtrace(&frame, regs->regs[29], regs->pc); |
126 | frame.pc = regs->pc; | ||
127 | #ifdef CONFIG_FUNCTION_GRAPH_TRACER | ||
128 | frame.graph = 0; | ||
129 | #endif | ||
130 | |||
131 | walk_stackframe(current, &frame, save_trace, &data); | 167 | walk_stackframe(current, &frame, save_trace, &data); |
132 | } | 168 | } |
133 | EXPORT_SYMBOL_GPL(save_stack_trace_regs); | 169 | EXPORT_SYMBOL_GPL(save_stack_trace_regs); |
@@ -146,17 +182,15 @@ static noinline void __save_stack_trace(struct task_struct *tsk, | |||
146 | data.no_sched_functions = nosched; | 182 | data.no_sched_functions = nosched; |
147 | 183 | ||
148 | if (tsk != current) { | 184 | if (tsk != current) { |
149 | frame.fp = thread_saved_fp(tsk); | 185 | start_backtrace(&frame, thread_saved_fp(tsk), |
150 | frame.pc = thread_saved_pc(tsk); | 186 | thread_saved_pc(tsk)); |
151 | } else { | 187 | } else { |
152 | /* We don't want this function nor the caller */ | 188 | /* We don't want this function nor the caller */ |
153 | data.skip += 2; | 189 | data.skip += 2; |
154 | frame.fp = (unsigned long)__builtin_frame_address(0); | 190 | start_backtrace(&frame, |
155 | frame.pc = (unsigned long)__save_stack_trace; | 191 | (unsigned long)__builtin_frame_address(0), |
192 | (unsigned long)__save_stack_trace); | ||
156 | } | 193 | } |
157 | #ifdef CONFIG_FUNCTION_GRAPH_TRACER | ||
158 | frame.graph = 0; | ||
159 | #endif | ||
160 | 194 | ||
161 | walk_stackframe(tsk, &frame, save_trace, &data); | 195 | walk_stackframe(tsk, &frame, save_trace, &data); |
162 | 196 | ||