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 | #include <asm/stacktrace.h> |
16 | |
17 | #ifdef CONFIG_FRAME_POINTER |
18 | |
19 | extern asmlinkage void ret_from_exception(void); |
20 | |
21 | void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs, |
22 | bool (*fn)(void *, unsigned long), void *arg) |
23 | { |
24 | unsigned long fp, sp, pc; |
25 | int level = 0; |
26 | |
27 | if (regs) { |
28 | fp = frame_pointer(regs); |
29 | sp = user_stack_pointer(regs); |
30 | pc = instruction_pointer(regs); |
31 | } else if (task == NULL || task == current) { |
32 | fp = (unsigned long)__builtin_frame_address(0); |
33 | sp = current_stack_pointer; |
34 | pc = (unsigned long)walk_stackframe; |
35 | level = -1; |
36 | } else { |
37 | /* task blocked in __switch_to */ |
38 | fp = task->thread.s[0]; |
39 | sp = task->thread.sp; |
40 | pc = task->thread.ra; |
41 | } |
42 | |
43 | for (;;) { |
44 | unsigned long low, high; |
45 | struct stackframe *frame; |
46 | |
47 | if (unlikely(!__kernel_text_address(pc) || (level++ >= 0 && !fn(arg, pc)))) |
48 | break; |
49 | |
50 | /* Validate frame pointer */ |
51 | low = sp + sizeof(struct stackframe); |
52 | high = ALIGN(sp, THREAD_SIZE); |
53 | if (unlikely(fp < low || fp > high || fp & 0x7)) |
54 | break; |
55 | /* Unwind stack frame */ |
56 | frame = (struct stackframe *)fp - 1; |
57 | sp = fp; |
58 | if (regs && (regs->epc == pc) && (frame->fp & 0x7)) { |
59 | fp = frame->ra; |
60 | pc = regs->ra; |
61 | } else { |
62 | fp = frame->fp; |
63 | pc = ftrace_graph_ret_addr(current, NULL, frame->ra, |
64 | &frame->ra); |
65 | if (pc == (unsigned long)ret_from_exception) { |
66 | if (unlikely(!__kernel_text_address(pc) || !fn(arg, pc))) |
67 | break; |
68 | |
69 | pc = ((struct pt_regs *)sp)->epc; |
70 | fp = ((struct pt_regs *)sp)->s0; |
71 | } |
72 | } |
73 | |
74 | } |
75 | } |
76 | |
77 | #else /* !CONFIG_FRAME_POINTER */ |
78 | |
79 | void notrace walk_stackframe(struct task_struct *task, |
80 | struct pt_regs *regs, bool (*fn)(void *, unsigned long), void *arg) |
81 | { |
82 | unsigned long sp, pc; |
83 | unsigned long *ksp; |
84 | |
85 | if (regs) { |
86 | sp = user_stack_pointer(regs); |
87 | pc = instruction_pointer(regs); |
88 | } else if (task == NULL || task == current) { |
89 | sp = current_stack_pointer; |
90 | pc = (unsigned long)walk_stackframe; |
91 | } else { |
92 | /* task blocked in __switch_to */ |
93 | sp = task->thread.sp; |
94 | pc = task->thread.ra; |
95 | } |
96 | |
97 | if (unlikely(sp & 0x7)) |
98 | return; |
99 | |
100 | ksp = (unsigned long *)sp; |
101 | while (!kstack_end(addr: ksp)) { |
102 | if (__kernel_text_address(addr: pc) && unlikely(!fn(arg, pc))) |
103 | break; |
104 | pc = READ_ONCE_NOCHECK(*ksp++) - 0x4; |
105 | } |
106 | } |
107 | |
108 | #endif /* CONFIG_FRAME_POINTER */ |
109 | |
110 | static bool print_trace_address(void *arg, unsigned long pc) |
111 | { |
112 | const char *loglvl = arg; |
113 | |
114 | print_ip_sym(loglvl, ip: pc); |
115 | return true; |
116 | } |
117 | |
118 | noinline void dump_backtrace(struct pt_regs *regs, struct task_struct *task, |
119 | const char *loglvl) |
120 | { |
121 | walk_stackframe(task, regs, fn: print_trace_address, arg: (void *)loglvl); |
122 | } |
123 | |
124 | void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl) |
125 | { |
126 | pr_cont("%sCall Trace:\n" , loglvl); |
127 | dump_backtrace(NULL, task, loglvl); |
128 | } |
129 | |
130 | static bool save_wchan(void *arg, unsigned long pc) |
131 | { |
132 | if (!in_sched_functions(addr: pc)) { |
133 | unsigned long *p = arg; |
134 | *p = pc; |
135 | return false; |
136 | } |
137 | return true; |
138 | } |
139 | |
140 | unsigned long __get_wchan(struct task_struct *task) |
141 | { |
142 | unsigned long pc = 0; |
143 | |
144 | if (!try_get_task_stack(tsk: task)) |
145 | return 0; |
146 | walk_stackframe(task, NULL, fn: save_wchan, arg: &pc); |
147 | put_task_stack(tsk: task); |
148 | return pc; |
149 | } |
150 | |
151 | noinline void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie, |
152 | struct task_struct *task, struct pt_regs *regs) |
153 | { |
154 | walk_stackframe(task, regs, fn: consume_entry, arg: cookie); |
155 | } |
156 | |