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