Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v3.16 175 lines 4.5 kB view raw
1#include <linux/oprofile.h> 2#include <linux/sched.h> 3#include <linux/mm.h> 4#include <linux/uaccess.h> 5#include <asm/ptrace.h> 6#include <asm/stacktrace.h> 7#include <linux/stacktrace.h> 8#include <linux/kernel.h> 9#include <asm/sections.h> 10#include <asm/inst.h> 11 12struct stackframe { 13 unsigned long sp; 14 unsigned long pc; 15 unsigned long ra; 16}; 17 18static inline int get_mem(unsigned long addr, unsigned long *result) 19{ 20 unsigned long *address = (unsigned long *) addr; 21 if (!access_ok(VERIFY_READ, addr, sizeof(unsigned long))) 22 return -1; 23 if (__copy_from_user_inatomic(result, address, sizeof(unsigned long))) 24 return -3; 25 return 0; 26} 27 28/* 29 * These two instruction helpers were taken from process.c 30 */ 31static inline int is_ra_save_ins(union mips_instruction *ip) 32{ 33 /* sw / sd $ra, offset($sp) */ 34 return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) 35 && ip->i_format.rs == 29 && ip->i_format.rt == 31; 36} 37 38static inline int is_sp_move_ins(union mips_instruction *ip) 39{ 40 /* addiu/daddiu sp,sp,-imm */ 41 if (ip->i_format.rs != 29 || ip->i_format.rt != 29) 42 return 0; 43 if (ip->i_format.opcode == addiu_op || ip->i_format.opcode == daddiu_op) 44 return 1; 45 return 0; 46} 47 48/* 49 * Looks for specific instructions that mark the end of a function. 50 * This usually means we ran into the code area of the previous function. 51 */ 52static inline int is_end_of_function_marker(union mips_instruction *ip) 53{ 54 /* jr ra */ 55 if (ip->r_format.func == jr_op && ip->r_format.rs == 31) 56 return 1; 57 /* lui gp */ 58 if (ip->i_format.opcode == lui_op && ip->i_format.rt == 28) 59 return 1; 60 return 0; 61} 62 63/* 64 * TODO for userspace stack unwinding: 65 * - handle cases where the stack is adjusted inside a function 66 * (generally doesn't happen) 67 * - find optimal value for max_instr_check 68 * - try to find a way to handle leaf functions 69 */ 70 71static inline int unwind_user_frame(struct stackframe *old_frame, 72 const unsigned int max_instr_check) 73{ 74 struct stackframe new_frame = *old_frame; 75 off_t ra_offset = 0; 76 size_t stack_size = 0; 77 unsigned long addr; 78 79 if (old_frame->pc == 0 || old_frame->sp == 0 || old_frame->ra == 0) 80 return -9; 81 82 for (addr = new_frame.pc; (addr + max_instr_check > new_frame.pc) 83 && (!ra_offset || !stack_size); --addr) { 84 union mips_instruction ip; 85 86 if (get_mem(addr, (unsigned long *) &ip)) 87 return -11; 88 89 if (is_sp_move_ins(&ip)) { 90 int stack_adjustment = ip.i_format.simmediate; 91 if (stack_adjustment > 0) 92 /* This marks the end of the previous function, 93 which means we overran. */ 94 break; 95 stack_size = (unsigned) stack_adjustment; 96 } else if (is_ra_save_ins(&ip)) { 97 int ra_slot = ip.i_format.simmediate; 98 if (ra_slot < 0) 99 /* This shouldn't happen. */ 100 break; 101 ra_offset = ra_slot; 102 } else if (is_end_of_function_marker(&ip)) 103 break; 104 } 105 106 if (!ra_offset || !stack_size) 107 return -1; 108 109 if (ra_offset) { 110 new_frame.ra = old_frame->sp + ra_offset; 111 if (get_mem(new_frame.ra, &(new_frame.ra))) 112 return -13; 113 } 114 115 if (stack_size) { 116 new_frame.sp = old_frame->sp + stack_size; 117 if (get_mem(new_frame.sp, &(new_frame.sp))) 118 return -14; 119 } 120 121 if (new_frame.sp > old_frame->sp) 122 return -2; 123 124 new_frame.pc = old_frame->ra; 125 *old_frame = new_frame; 126 127 return 0; 128} 129 130static inline void do_user_backtrace(unsigned long low_addr, 131 struct stackframe *frame, 132 unsigned int depth) 133{ 134 const unsigned int max_instr_check = 512; 135 const unsigned long high_addr = low_addr + THREAD_SIZE; 136 137 while (depth-- && !unwind_user_frame(frame, max_instr_check)) { 138 oprofile_add_trace(frame->ra); 139 if (frame->sp < low_addr || frame->sp > high_addr) 140 break; 141 } 142} 143 144#ifndef CONFIG_KALLSYMS 145static inline void do_kernel_backtrace(unsigned long low_addr, 146 struct stackframe *frame, 147 unsigned int depth) { } 148#else 149static inline void do_kernel_backtrace(unsigned long low_addr, 150 struct stackframe *frame, 151 unsigned int depth) 152{ 153 while (depth-- && frame->pc) { 154 frame->pc = unwind_stack_by_address(low_addr, 155 &(frame->sp), 156 frame->pc, 157 &(frame->ra)); 158 oprofile_add_trace(frame->ra); 159 } 160} 161#endif 162 163void notrace op_mips_backtrace(struct pt_regs *const regs, unsigned int depth) 164{ 165 struct stackframe frame = { .sp = regs->regs[29], 166 .pc = regs->cp0_epc, 167 .ra = regs->regs[31] }; 168 const int userspace = user_mode(regs); 169 const unsigned long low_addr = ALIGN(frame.sp, THREAD_SIZE); 170 171 if (userspace) 172 do_user_backtrace(low_addr, &frame, depth); 173 else 174 do_kernel_backtrace(low_addr, &frame, depth); 175}