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/sched.h> 21#include <linux/stacktrace.h> 22 23#include <asm/stacktrace.h> 24 25/* 26 * AArch64 PCS assigns the frame pointer to x29. 27 * 28 * A simple function prologue looks like this: 29 * sub sp, sp, #0x10 30 * stp x29, x30, [sp] 31 * mov x29, sp 32 * 33 * A simple function epilogue looks like this: 34 * mov sp, x29 35 * ldp x29, x30, [sp] 36 * add sp, sp, #0x10 37 */ 38int notrace unwind_frame(struct stackframe *frame) 39{ 40 unsigned long high, low; 41 unsigned long fp = frame->fp; 42 43 low = frame->sp; 44 high = ALIGN(low, THREAD_SIZE); 45 46 if (fp < low || fp > high - 0x18 || fp & 0xf) 47 return -EINVAL; 48 49 frame->sp = fp + 0x10; 50 frame->fp = *(unsigned long *)(fp); 51 frame->pc = *(unsigned long *)(fp + 8); 52 53 return 0; 54} 55 56void notrace walk_stackframe(struct stackframe *frame, 57 int (*fn)(struct stackframe *, void *), void *data) 58{ 59 while (1) { 60 int ret; 61 62 if (fn(frame, data)) 63 break; 64 ret = unwind_frame(frame); 65 if (ret < 0) 66 break; 67 } 68} 69EXPORT_SYMBOL(walk_stackframe); 70 71#ifdef CONFIG_STACKTRACE 72struct stack_trace_data { 73 struct stack_trace *trace; 74 unsigned int no_sched_functions; 75 unsigned int skip; 76}; 77 78static int save_trace(struct stackframe *frame, void *d) 79{ 80 struct stack_trace_data *data = d; 81 struct stack_trace *trace = data->trace; 82 unsigned long addr = frame->pc; 83 84 if (data->no_sched_functions && in_sched_functions(addr)) 85 return 0; 86 if (data->skip) { 87 data->skip--; 88 return 0; 89 } 90 91 trace->entries[trace->nr_entries++] = addr; 92 93 return trace->nr_entries >= trace->max_entries; 94} 95 96void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) 97{ 98 struct stack_trace_data data; 99 struct stackframe frame; 100 101 data.trace = trace; 102 data.skip = trace->skip; 103 104 if (tsk != current) { 105 data.no_sched_functions = 1; 106 frame.fp = thread_saved_fp(tsk); 107 frame.sp = thread_saved_sp(tsk); 108 frame.pc = thread_saved_pc(tsk); 109 } else { 110 data.no_sched_functions = 0; 111 frame.fp = (unsigned long)__builtin_frame_address(0); 112 frame.sp = current_stack_pointer; 113 frame.pc = (unsigned long)save_stack_trace_tsk; 114 } 115 116 walk_stackframe(&frame, save_trace, &data); 117 if (trace->nr_entries < trace->max_entries) 118 trace->entries[trace->nr_entries++] = ULONG_MAX; 119} 120 121void save_stack_trace(struct stack_trace *trace) 122{ 123 save_stack_trace_tsk(current, trace); 124} 125EXPORT_SYMBOL_GPL(save_stack_trace); 126#endif 127