18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci#include <linux/oprofile.h> 38c2ecf20Sopenharmony_ci#include <linux/sched.h> 48c2ecf20Sopenharmony_ci#include <linux/mm.h> 58c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 68c2ecf20Sopenharmony_ci#include <asm/ptrace.h> 78c2ecf20Sopenharmony_ci#include <asm/stacktrace.h> 88c2ecf20Sopenharmony_ci#include <linux/stacktrace.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <asm/sections.h> 118c2ecf20Sopenharmony_ci#include <asm/inst.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_cistruct stackframe { 148c2ecf20Sopenharmony_ci unsigned long sp; 158c2ecf20Sopenharmony_ci unsigned long pc; 168c2ecf20Sopenharmony_ci unsigned long ra; 178c2ecf20Sopenharmony_ci}; 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistatic inline int get_mem(unsigned long addr, unsigned long *result) 208c2ecf20Sopenharmony_ci{ 218c2ecf20Sopenharmony_ci unsigned long *address = (unsigned long *) addr; 228c2ecf20Sopenharmony_ci if (!access_ok(address, sizeof(unsigned long))) 238c2ecf20Sopenharmony_ci return -1; 248c2ecf20Sopenharmony_ci if (__copy_from_user_inatomic(result, address, sizeof(unsigned long))) 258c2ecf20Sopenharmony_ci return -3; 268c2ecf20Sopenharmony_ci return 0; 278c2ecf20Sopenharmony_ci} 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* 308c2ecf20Sopenharmony_ci * These two instruction helpers were taken from process.c 318c2ecf20Sopenharmony_ci */ 328c2ecf20Sopenharmony_cistatic inline int is_ra_save_ins(union mips_instruction *ip) 338c2ecf20Sopenharmony_ci{ 348c2ecf20Sopenharmony_ci /* sw / sd $ra, offset($sp) */ 358c2ecf20Sopenharmony_ci return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) 368c2ecf20Sopenharmony_ci && ip->i_format.rs == 29 && ip->i_format.rt == 31; 378c2ecf20Sopenharmony_ci} 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic inline int is_sp_move_ins(union mips_instruction *ip) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci /* addiu/daddiu sp,sp,-imm */ 428c2ecf20Sopenharmony_ci if (ip->i_format.rs != 29 || ip->i_format.rt != 29) 438c2ecf20Sopenharmony_ci return 0; 448c2ecf20Sopenharmony_ci if (ip->i_format.opcode == addiu_op || ip->i_format.opcode == daddiu_op) 458c2ecf20Sopenharmony_ci return 1; 468c2ecf20Sopenharmony_ci return 0; 478c2ecf20Sopenharmony_ci} 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci/* 508c2ecf20Sopenharmony_ci * Looks for specific instructions that mark the end of a function. 518c2ecf20Sopenharmony_ci * This usually means we ran into the code area of the previous function. 528c2ecf20Sopenharmony_ci */ 538c2ecf20Sopenharmony_cistatic inline int is_end_of_function_marker(union mips_instruction *ip) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci /* jr ra */ 568c2ecf20Sopenharmony_ci if (ip->r_format.func == jr_op && ip->r_format.rs == 31) 578c2ecf20Sopenharmony_ci return 1; 588c2ecf20Sopenharmony_ci /* lui gp */ 598c2ecf20Sopenharmony_ci if (ip->i_format.opcode == lui_op && ip->i_format.rt == 28) 608c2ecf20Sopenharmony_ci return 1; 618c2ecf20Sopenharmony_ci return 0; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci/* 658c2ecf20Sopenharmony_ci * TODO for userspace stack unwinding: 668c2ecf20Sopenharmony_ci * - handle cases where the stack is adjusted inside a function 678c2ecf20Sopenharmony_ci * (generally doesn't happen) 688c2ecf20Sopenharmony_ci * - find optimal value for max_instr_check 698c2ecf20Sopenharmony_ci * - try to find a better way to handle leaf functions 708c2ecf20Sopenharmony_ci */ 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic inline int unwind_user_frame(struct stackframe *old_frame, 738c2ecf20Sopenharmony_ci const unsigned int max_instr_check) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct stackframe new_frame = *old_frame; 768c2ecf20Sopenharmony_ci off_t ra_offset = 0; 778c2ecf20Sopenharmony_ci size_t stack_size = 0; 788c2ecf20Sopenharmony_ci unsigned long addr; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci if (old_frame->pc == 0 || old_frame->sp == 0 || old_frame->ra == 0) 818c2ecf20Sopenharmony_ci return -9; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci for (addr = new_frame.pc; (addr + max_instr_check > new_frame.pc) 848c2ecf20Sopenharmony_ci && (!ra_offset || !stack_size); --addr) { 858c2ecf20Sopenharmony_ci union mips_instruction ip; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (get_mem(addr, (unsigned long *) &ip)) 888c2ecf20Sopenharmony_ci return -11; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (is_sp_move_ins(&ip)) { 918c2ecf20Sopenharmony_ci int stack_adjustment = ip.i_format.simmediate; 928c2ecf20Sopenharmony_ci if (stack_adjustment > 0) 938c2ecf20Sopenharmony_ci /* This marks the end of the previous function, 948c2ecf20Sopenharmony_ci which means we overran. */ 958c2ecf20Sopenharmony_ci break; 968c2ecf20Sopenharmony_ci stack_size = (unsigned long) stack_adjustment; 978c2ecf20Sopenharmony_ci } else if (is_ra_save_ins(&ip)) { 988c2ecf20Sopenharmony_ci int ra_slot = ip.i_format.simmediate; 998c2ecf20Sopenharmony_ci if (ra_slot < 0) 1008c2ecf20Sopenharmony_ci /* This shouldn't happen. */ 1018c2ecf20Sopenharmony_ci break; 1028c2ecf20Sopenharmony_ci ra_offset = ra_slot; 1038c2ecf20Sopenharmony_ci } else if (is_end_of_function_marker(&ip)) 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if (!ra_offset || !stack_size) 1088c2ecf20Sopenharmony_ci goto done; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (ra_offset) { 1118c2ecf20Sopenharmony_ci new_frame.ra = old_frame->sp + ra_offset; 1128c2ecf20Sopenharmony_ci if (get_mem(new_frame.ra, &(new_frame.ra))) 1138c2ecf20Sopenharmony_ci return -13; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci if (stack_size) { 1178c2ecf20Sopenharmony_ci new_frame.sp = old_frame->sp + stack_size; 1188c2ecf20Sopenharmony_ci if (get_mem(new_frame.sp, &(new_frame.sp))) 1198c2ecf20Sopenharmony_ci return -14; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci if (new_frame.sp > old_frame->sp) 1238c2ecf20Sopenharmony_ci return -2; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cidone: 1268c2ecf20Sopenharmony_ci new_frame.pc = old_frame->ra; 1278c2ecf20Sopenharmony_ci *old_frame = new_frame; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic inline void do_user_backtrace(unsigned long low_addr, 1338c2ecf20Sopenharmony_ci struct stackframe *frame, 1348c2ecf20Sopenharmony_ci unsigned int depth) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci const unsigned int max_instr_check = 512; 1378c2ecf20Sopenharmony_ci const unsigned long high_addr = low_addr + THREAD_SIZE; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci while (depth-- && !unwind_user_frame(frame, max_instr_check)) { 1408c2ecf20Sopenharmony_ci oprofile_add_trace(frame->ra); 1418c2ecf20Sopenharmony_ci if (frame->sp < low_addr || frame->sp > high_addr) 1428c2ecf20Sopenharmony_ci break; 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci#ifndef CONFIG_KALLSYMS 1478c2ecf20Sopenharmony_cistatic inline void do_kernel_backtrace(unsigned long low_addr, 1488c2ecf20Sopenharmony_ci struct stackframe *frame, 1498c2ecf20Sopenharmony_ci unsigned int depth) { } 1508c2ecf20Sopenharmony_ci#else 1518c2ecf20Sopenharmony_cistatic inline void do_kernel_backtrace(unsigned long low_addr, 1528c2ecf20Sopenharmony_ci struct stackframe *frame, 1538c2ecf20Sopenharmony_ci unsigned int depth) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci while (depth-- && frame->pc) { 1568c2ecf20Sopenharmony_ci frame->pc = unwind_stack_by_address(low_addr, 1578c2ecf20Sopenharmony_ci &(frame->sp), 1588c2ecf20Sopenharmony_ci frame->pc, 1598c2ecf20Sopenharmony_ci &(frame->ra)); 1608c2ecf20Sopenharmony_ci oprofile_add_trace(frame->ra); 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci#endif 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_civoid notrace op_mips_backtrace(struct pt_regs *const regs, unsigned int depth) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci struct stackframe frame = { .sp = regs->regs[29], 1688c2ecf20Sopenharmony_ci .pc = regs->cp0_epc, 1698c2ecf20Sopenharmony_ci .ra = regs->regs[31] }; 1708c2ecf20Sopenharmony_ci const int userspace = user_mode(regs); 1718c2ecf20Sopenharmony_ci const unsigned long low_addr = ALIGN(frame.sp, THREAD_SIZE); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (userspace) 1748c2ecf20Sopenharmony_ci do_user_backtrace(low_addr, &frame, depth); 1758c2ecf20Sopenharmony_ci else 1768c2ecf20Sopenharmony_ci do_kernel_backtrace(low_addr, &frame, depth); 1778c2ecf20Sopenharmony_ci} 178