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