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