]>
Commit | Line | Data |
---|---|---|
0ea2dc7c | 1 | // SPDX-License-Identifier: GPL-2.0 |
0ea2dc7c GR |
2 | |
3 | #include <linux/sched/debug.h> | |
4 | #include <linux/sched/task_stack.h> | |
5 | #include <linux/stacktrace.h> | |
6 | #include <linux/ftrace.h> | |
18c07d23 | 7 | #include <linux/ptrace.h> |
0ea2dc7c | 8 | |
18c07d23 GR |
9 | #ifdef CONFIG_FRAME_POINTER |
10 | ||
11 | struct stackframe { | |
12 | unsigned long fp; | |
13 | unsigned long ra; | |
14 | }; | |
15 | ||
16 | void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs, | |
17 | bool (*fn)(unsigned long, void *), void *arg) | |
0ea2dc7c | 18 | { |
18c07d23 GR |
19 | unsigned long fp, sp, pc; |
20 | ||
21 | if (regs) { | |
22 | fp = frame_pointer(regs); | |
23 | sp = user_stack_pointer(regs); | |
24 | pc = instruction_pointer(regs); | |
25 | } else if (task == NULL || task == current) { | |
26 | const register unsigned long current_sp __asm__ ("sp"); | |
27 | const register unsigned long current_fp __asm__ ("r8"); | |
28 | fp = current_fp; | |
29 | sp = current_sp; | |
30 | pc = (unsigned long)walk_stackframe; | |
31 | } else { | |
32 | /* task blocked in __switch_to */ | |
33 | fp = thread_saved_fp(task); | |
34 | sp = thread_saved_sp(task); | |
35 | pc = thread_saved_lr(task); | |
36 | } | |
37 | ||
38 | for (;;) { | |
39 | unsigned long low, high; | |
40 | struct stackframe *frame; | |
41 | ||
42 | if (unlikely(!__kernel_text_address(pc) || fn(pc, arg))) | |
43 | break; | |
44 | ||
45 | /* Validate frame pointer */ | |
46 | low = sp; | |
47 | high = ALIGN(sp, THREAD_SIZE); | |
48 | if (unlikely(fp < low || fp > high || fp & 0x3)) | |
49 | break; | |
50 | /* Unwind stack frame */ | |
51 | frame = (struct stackframe *)fp; | |
52 | sp = fp; | |
53 | fp = frame->fp; | |
54 | pc = ftrace_graph_ret_addr(current, NULL, frame->ra, | |
55 | (unsigned long *)(fp - 8)); | |
56 | } | |
0ea2dc7c | 57 | } |
0ea2dc7c | 58 | |
18c07d23 GR |
59 | #else /* !CONFIG_FRAME_POINTER */ |
60 | ||
61 | static void notrace walk_stackframe(struct task_struct *task, | |
62 | struct pt_regs *regs, bool (*fn)(unsigned long, void *), void *arg) | |
0ea2dc7c | 63 | { |
18c07d23 GR |
64 | unsigned long sp, pc; |
65 | unsigned long *ksp; | |
0ea2dc7c | 66 | |
18c07d23 GR |
67 | if (regs) { |
68 | sp = user_stack_pointer(regs); | |
69 | pc = instruction_pointer(regs); | |
70 | } else if (task == NULL || task == current) { | |
71 | const register unsigned long current_sp __asm__ ("sp"); | |
72 | sp = current_sp; | |
73 | pc = (unsigned long)walk_stackframe; | |
0ea2dc7c | 74 | } else { |
18c07d23 GR |
75 | /* task blocked in __switch_to */ |
76 | sp = thread_saved_sp(task); | |
77 | pc = thread_saved_lr(task); | |
0ea2dc7c GR |
78 | } |
79 | ||
18c07d23 GR |
80 | if (unlikely(sp & 0x3)) |
81 | return; | |
0ea2dc7c | 82 | |
18c07d23 GR |
83 | ksp = (unsigned long *)sp; |
84 | while (!kstack_end(ksp)) { | |
85 | if (__kernel_text_address(pc) && unlikely(fn(pc, arg))) | |
0ea2dc7c | 86 | break; |
18c07d23 | 87 | pc = (*ksp++) - 0x4; |
0ea2dc7c GR |
88 | } |
89 | } | |
18c07d23 GR |
90 | #endif /* CONFIG_FRAME_POINTER */ |
91 | ||
92 | static bool print_trace_address(unsigned long pc, void *arg) | |
93 | { | |
aeeb59d6 | 94 | print_ip_sym((const char *)arg, pc); |
18c07d23 GR |
95 | return false; |
96 | } | |
97 | ||
9cb8f069 | 98 | void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl) |
aeeb59d6 DS |
99 | { |
100 | pr_cont("Call Trace:\n"); | |
101 | walk_stackframe(task, NULL, print_trace_address, (void *)loglvl); | |
102 | } | |
103 | ||
18c07d23 GR |
104 | static bool save_wchan(unsigned long pc, void *arg) |
105 | { | |
106 | if (!in_sched_functions(pc)) { | |
107 | unsigned long *p = arg; | |
108 | *p = pc; | |
109 | return true; | |
110 | } | |
111 | return false; | |
112 | } | |
113 | ||
114 | unsigned long get_wchan(struct task_struct *task) | |
115 | { | |
116 | unsigned long pc = 0; | |
117 | ||
118 | if (likely(task && task != current && task->state != TASK_RUNNING)) | |
119 | walk_stackframe(task, NULL, save_wchan, &pc); | |
120 | return pc; | |
121 | } | |
122 | ||
123 | #ifdef CONFIG_STACKTRACE | |
124 | static bool __save_trace(unsigned long pc, void *arg, bool nosched) | |
125 | { | |
126 | struct stack_trace *trace = arg; | |
127 | ||
128 | if (unlikely(nosched && in_sched_functions(pc))) | |
129 | return false; | |
130 | if (unlikely(trace->skip > 0)) { | |
131 | trace->skip--; | |
132 | return false; | |
133 | } | |
134 | ||
135 | trace->entries[trace->nr_entries++] = pc; | |
136 | return (trace->nr_entries >= trace->max_entries); | |
137 | } | |
138 | ||
139 | static bool save_trace(unsigned long pc, void *arg) | |
140 | { | |
141 | return __save_trace(pc, arg, false); | |
142 | } | |
143 | ||
144 | /* | |
145 | * Save stack-backtrace addresses into a stack_trace buffer. | |
146 | */ | |
147 | void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) | |
148 | { | |
149 | walk_stackframe(tsk, NULL, save_trace, trace); | |
150 | } | |
0ea2dc7c | 151 | EXPORT_SYMBOL_GPL(save_stack_trace_tsk); |
18c07d23 GR |
152 | |
153 | void save_stack_trace(struct stack_trace *trace) | |
154 | { | |
155 | save_stack_trace_tsk(NULL, trace); | |
156 | } | |
157 | EXPORT_SYMBOL_GPL(save_stack_trace); | |
158 | ||
159 | #endif /* CONFIG_STACKTRACE */ |