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)) 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 /* 63 * This is a case where function graph tracer has 64 * modified a return address (LR) in a stack frame 65 * to hook a function return. 66 * So replace it to an original value. 67 */ 68 frame->pc = tsk->ret_stack[frame->graph--].ret; 69 } 70 #endif /* CONFIG_FUNCTION_GRAPH_TRACER */ 71 72 /* 73 * Frames created upon entry from EL0 have NULL FP and PC values, so 74 * don't bother reporting these. Frames created by __noreturn functions 75 * might have a valid FP even if PC is bogus, so only terminate where 76 * both are NULL. 77 */ 78 if (!frame->fp && !frame->pc) 79 return -EINVAL; 80 81 return 0; 82 } 83 84 void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame, 85 int (*fn)(struct stackframe *, void *), void *data) 86 { 87 while (1) { 88 int ret; 89 90 if (fn(frame, data)) 91 break; 92 ret = unwind_frame(tsk, frame); 93 if (ret < 0) 94 break; 95 } 96 } 97 98 #ifdef CONFIG_STACKTRACE 99 struct stack_trace_data { 100 struct stack_trace *trace; 101 unsigned int no_sched_functions; 102 unsigned int skip; 103 }; 104 105 static int save_trace(struct stackframe *frame, void *d) 106 { 107 struct stack_trace_data *data = d; 108 struct stack_trace *trace = data->trace; 109 unsigned long addr = frame->pc; 110 111 if (data->no_sched_functions && in_sched_functions(addr)) 112 return 0; 113 if (data->skip) { 114 data->skip--; 115 return 0; 116 } 117 118 trace->entries[trace->nr_entries++] = addr; 119 120 return trace->nr_entries >= trace->max_entries; 121 } 122 123 void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace) 124 { 125 struct stack_trace_data data; 126 struct stackframe frame; 127 128 data.trace = trace; 129 data.skip = trace->skip; 130 data.no_sched_functions = 0; 131 132 frame.fp = regs->regs[29]; 133 frame.pc = regs->pc; 134 #ifdef CONFIG_FUNCTION_GRAPH_TRACER 135 frame.graph = current->curr_ret_stack; 136 #endif 137 138 walk_stackframe(current, &frame, save_trace, &data); 139 if (trace->nr_entries < trace->max_entries) 140 trace->entries[trace->nr_entries++] = ULONG_MAX; 141 } 142 143 static noinline void __save_stack_trace(struct task_struct *tsk, 144 struct stack_trace *trace, unsigned int nosched) 145 { 146 struct stack_trace_data data; 147 struct stackframe frame; 148 149 if (!try_get_task_stack(tsk)) 150 return; 151 152 data.trace = trace; 153 data.skip = trace->skip; 154 data.no_sched_functions = nosched; 155 156 if (tsk != current) { 157 frame.fp = thread_saved_fp(tsk); 158 frame.pc = thread_saved_pc(tsk); 159 } else { 160 /* We don't want this function nor the caller */ 161 data.skip += 2; 162 frame.fp = (unsigned long)__builtin_frame_address(0); 163 frame.pc = (unsigned long)__save_stack_trace; 164 } 165 #ifdef CONFIG_FUNCTION_GRAPH_TRACER 166 frame.graph = tsk->curr_ret_stack; 167 #endif 168 169 walk_stackframe(tsk, &frame, save_trace, &data); 170 if (trace->nr_entries < trace->max_entries) 171 trace->entries[trace->nr_entries++] = ULONG_MAX; 172 173 put_task_stack(tsk); 174 } 175 EXPORT_SYMBOL_GPL(save_stack_trace_tsk); 176 177 void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) 178 { 179 __save_stack_trace(tsk, trace, 1); 180 } 181 182 void save_stack_trace(struct stack_trace *trace) 183 { 184 __save_stack_trace(current, trace, 0); 185 } 186 187 EXPORT_SYMBOL_GPL(save_stack_trace); 188 #endif 189