aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm64/kernel/stacktrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm64/kernel/stacktrace.c')
-rw-r--r--arch/arm64/kernel/stacktrace.c62
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 */
32int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) 41int 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}
115NOKPROBE_SYMBOL(unwind_frame);
76 116
77void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame, 117void 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}
130NOKPROBE_SYMBOL(walk_stackframe);
90 131
91#ifdef CONFIG_STACKTRACE 132#ifdef CONFIG_STACKTRACE
92struct stack_trace_data { 133struct 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}
133EXPORT_SYMBOL_GPL(save_stack_trace_regs); 169EXPORT_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