linux/arch/mips/oprofile/backtrace.c
<<
>>
Prefs
   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}
 176