1 /* 2 * Stack tracing support 3 * 4 * Copyright (C) 2012 ARM Ltd. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 #include <linux/kernel.h> 19 #include <linux/export.h> 20 #include <linux/ftrace.h> 21 #include <linux/sched.h> 22 #include <linux/sched/debug.h> 23 #include <linux/sched/task_stack.h> 24 #include <linux/stacktrace.h> 25 26 #include <asm/irq.h> 27 #include <asm/stack_pointer.h> 28 #include <asm/stacktrace.h> 29 30 /* 31 * AArch64 PCS assigns the frame pointer to x29. 32 * 33 * A simple function prologue looks like this: 34 * sub sp, sp, #0x10 35 * stp x29, x30, [sp] 36 * mov x29, sp 37 * 38 * A simple function epilogue looks like this: 39 * mov sp, x29 40 * ldp x29, x30, [sp] 41 * add sp, sp, #0x10 42 */ 43 int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) 44 { 45 unsigned long fp = frame->fp; 46 47 if (fp & 0xf) 48 return -EINVAL; 49 50 if (!tsk) 51 tsk = current; 52 53 if (!on_accessible_stack(tsk, fp, NULL)) 54 return -EINVAL; 55 56 frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp)); 57 frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8)); 58 59 #ifdef CONFIG_FUNCTION_GRAPH_TRACER 60 if (tsk->ret_stack && 61 (frame->pc == (unsigned long)return_to_handler)) { 62 if (WARN_ON_ONCE(frame->graph == -1)) 63 return -EINVAL; 64 if (frame->graph < -1) 65 frame->graph += FTRACE_NOTRACE_DEPTH; 66 67 /* 68 * This is a case where function graph tracer has 69 * modified a return address (LR) in a stack frame 70 * to hook a function return. 71 * So replace it to an original value. 72 */ 73 frame->pc = tsk->ret_stack[frame->graph--].ret; 74 } 75 #endif /* CONFIG_FUNCTION_GRAPH_TRACER */ 76 77 /* 78 * Frames created upon entry from EL0 have NULL FP and PC values, so 79 * don't bother reporting these. Frames created by __noreturn functions 80 * might have a valid FP even if PC is bogus, so only terminate where 81 * both are NULL. 82 */ 83 if (!frame->fp && !frame->pc) 84 return -EINVAL; 85 86 return 0; 87 } 88 89 void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame, 90 int (*fn)(struct stackframe *, void *), void *data) 91 { 92 while (1) { 93 int ret; 94 95 if (fn(frame, data)) 96 break; 97 ret = unwind_frame(tsk, frame); 98 if (ret < 0) 99 break; 100 } 101 } 102 103 #ifdef CONFIG_STACKTRACE 104 struct stack_trace_data { 105 struct stack_trace *trace; 106 unsigned int no_sched_functions; 107 unsigned int skip; 108 }; 109 110 static int save_trace(struct stackframe *frame, void *d) 111 { 112 struct stack_trace_data *data = d; 113 struct stack_trace *trace = data->trace; 114 unsigned long addr = frame->pc; 115 116 if (data->no_sched_functions && in_sched_functions(addr)) 117 return 0; 118 if (data->skip) { 119 data->skip--; 120 return 0; 121 } 122 123 trace->entries[trace->nr_entries++] = addr; 124 125 return trace->nr_entries >= trace->max_entries; 126 } 127 128 void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace) 129 { 130 struct stack_trace_data data; 131 struct stackframe frame; 132 133 data.trace = trace; 134 data.skip = trace->skip; 135 data.no_sched_functions = 0; 136 137 frame.fp = regs->regs[29]; 138 frame.pc = regs->pc; 139 #ifdef CONFIG_FUNCTION_GRAPH_TRACER 140 frame.graph = current->curr_ret_stack; 141 #endif 142 143 walk_stackframe(current, &frame, save_trace, &data); 144 if (trace->nr_entries < trace->max_entries) 145 trace->entries[trace->nr_entries++] = ULONG_MAX; 146 } 147 148 static noinline void __save_stack_trace(struct task_struct *tsk, 149 struct stack_trace *trace, unsigned int nosched) 150 { 151 struct stack_trace_data data; 152 struct stackframe frame; 153 154 if (!try_get_task_stack(tsk)) 155 return; 156 157 data.trace = trace; 158 data.skip = trace->skip; 159 data.no_sched_functions = nosched; 160 161 if (tsk != current) { 162 frame.fp = thread_saved_fp(tsk); 163 frame.pc = thread_saved_pc(tsk); 164 } else { 165 /* We don't want this function nor the caller */ 166 data.skip += 2; 167 frame.fp = (unsigned long)__builtin_frame_address(0); 168 frame.pc = (unsigned long)__save_stack_trace; 169 } 170 #ifdef CONFIG_FUNCTION_GRAPH_TRACER 171 frame.graph = tsk->curr_ret_stack; 172 #endif 173 174 walk_stackframe(tsk, &frame, save_trace, &data); 175 if (trace->nr_entries < trace->max_entries) 176 trace->entries[trace->nr_entries++] = ULONG_MAX; 177 178 put_task_stack(tsk); 179 } 180 EXPORT_SYMBOL_GPL(save_stack_trace_tsk); 181 182 void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) 183 { 184 __save_stack_trace(tsk, trace, 1); 185 } 186 187 void save_stack_trace(struct stack_trace *trace) 188 { 189 __save_stack_trace(current, trace, 0); 190 } 191 192 EXPORT_SYMBOL_GPL(save_stack_trace); 193 #endif 194