1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (C) 2008 ARM Limited
4 * Copyright (C) 2014 Regents of the University of California
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>
13 #include <linux/ftrace.h>
14
15 register unsigned long sp_in_global __asm__("sp");
16
17 #ifdef CONFIG_FRAME_POINTER
18
19 struct stackframe {
20 unsigned long fp;
21 unsigned long ra;
22 };
23
walk_stackframe(struct task_struct * task,struct pt_regs * regs,bool (* fn)(unsigned long,void *),void * arg)24 void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
25 bool (*fn)(unsigned long, void *), void *arg)
26 {
27 unsigned long fp, sp, pc;
28
29 if (regs) {
30 fp = frame_pointer(regs);
31 sp = user_stack_pointer(regs);
32 pc = instruction_pointer(regs);
33 } else if (task == NULL || task == current) {
34 const register unsigned long current_sp = sp_in_global;
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 if (regs && (regs->epc == pc) && (frame->fp & 0x7)) {
61 fp = frame->ra;
62 pc = regs->ra;
63 } else {
64 fp = frame->fp;
65 pc = ftrace_graph_ret_addr(current, NULL, frame->ra,
66 &frame->ra);
67 }
68
69 }
70 }
71
72 #else /* !CONFIG_FRAME_POINTER */
73
walk_stackframe(struct task_struct * task,struct pt_regs * regs,bool (* fn)(unsigned long,void *),void * arg)74 void notrace walk_stackframe(struct task_struct *task,
75 struct pt_regs *regs, bool (*fn)(unsigned long, void *), void *arg)
76 {
77 unsigned long sp, pc;
78 unsigned long *ksp;
79
80 if (regs) {
81 sp = user_stack_pointer(regs);
82 pc = instruction_pointer(regs);
83 } else if (task == NULL || task == current) {
84 sp = sp_in_global;
85 pc = (unsigned long)walk_stackframe;
86 } else {
87 /* task blocked in __switch_to */
88 sp = task->thread.sp;
89 pc = task->thread.ra;
90 }
91
92 if (unlikely(sp & 0x7))
93 return;
94
95 ksp = (unsigned long *)sp;
96 while (!kstack_end(ksp)) {
97 if (__kernel_text_address(pc) && unlikely(fn(pc, arg)))
98 break;
99 pc = READ_ONCE_NOCHECK(*ksp++) - 0x4;
100 }
101 }
102
103 #endif /* CONFIG_FRAME_POINTER */
104
105
print_trace_address(unsigned long pc,void * arg)106 static bool print_trace_address(unsigned long pc, void *arg)
107 {
108 const char *loglvl = arg;
109
110 print_ip_sym(loglvl, pc);
111 return false;
112 }
113
show_stack(struct task_struct * task,unsigned long * sp,const char * loglvl)114 void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl)
115 {
116 pr_cont("Call Trace:\n");
117 walk_stackframe(task, NULL, print_trace_address, (void *)loglvl);
118 }
119
save_wchan(unsigned long pc,void * arg)120 static bool save_wchan(unsigned long pc, void *arg)
121 {
122 if (!in_sched_functions(pc)) {
123 unsigned long *p = arg;
124 *p = pc;
125 return true;
126 }
127 return false;
128 }
129
get_wchan(struct task_struct * task)130 unsigned long get_wchan(struct task_struct *task)
131 {
132 unsigned long pc = 0;
133
134 if (likely(task && task != current && task->state != TASK_RUNNING))
135 walk_stackframe(task, NULL, save_wchan, &pc);
136 return pc;
137 }
138
139
140 #ifdef CONFIG_STACKTRACE
141
__save_trace(unsigned long pc,void * arg,bool nosched)142 static bool __save_trace(unsigned long pc, void *arg, bool nosched)
143 {
144 struct stack_trace *trace = arg;
145
146 if (unlikely(nosched && in_sched_functions(pc)))
147 return false;
148 if (unlikely(trace->skip > 0)) {
149 trace->skip--;
150 return false;
151 }
152
153 trace->entries[trace->nr_entries++] = pc;
154 return (trace->nr_entries >= trace->max_entries);
155 }
156
save_trace(unsigned long pc,void * arg)157 static bool save_trace(unsigned long pc, void *arg)
158 {
159 return __save_trace(pc, arg, false);
160 }
161
162 /*
163 * Save stack-backtrace addresses into a stack_trace buffer.
164 */
save_stack_trace_tsk(struct task_struct * tsk,struct stack_trace * trace)165 void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
166 {
167 walk_stackframe(tsk, NULL, save_trace, trace);
168 }
169 EXPORT_SYMBOL_GPL(save_stack_trace_tsk);
170
save_stack_trace(struct stack_trace * trace)171 void save_stack_trace(struct stack_trace *trace)
172 {
173 save_stack_trace_tsk(NULL, trace);
174 }
175 EXPORT_SYMBOL_GPL(save_stack_trace);
176
177 #endif /* CONFIG_STACKTRACE */
178