Commit 5f307491f3a0c8551cedf5d90d660d656e0d56ec

Authored by Daniel Kalmar
Committed by Robert Richter
1 parent 94ea09c6a8

MIPS: oprofile: Add callgraph support

Stack unwinding is done by code examination. For kernelspace, the
already existing unwind function is utilized that uses kallsyms to
quickly find the beginning of functions. For userspace a new function
was added that examines code at and before the pc.

Signed-off-by: Daniel Kalmar <kalmard@homejinni.com>
Signed-off-by: Gergely Kis <gergely@homejinni.com>
Signed-off-by: Robert Richter <robert.richter@amd.com>

Showing 4 changed files with 179 additions and 1 deletions Side-by-side Diff

arch/mips/oprofile/Makefile
... ... @@ -8,7 +8,7 @@
8 8 oprofilefs.o oprofile_stats.o \
9 9 timer_int.o )
10 10  
11   -oprofile-y := $(DRIVER_OBJS) common.o
  11 +oprofile-y := $(DRIVER_OBJS) common.o backtrace.o
12 12  
13 13 oprofile-$(CONFIG_CPU_MIPS32) += op_model_mipsxx.o
14 14 oprofile-$(CONFIG_CPU_MIPS64) += op_model_mipsxx.o
arch/mips/oprofile/backtrace.c
  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 +
  12 +struct stackframe {
  13 + unsigned long sp;
  14 + unsigned long pc;
  15 + unsigned long ra;
  16 +};
  17 +
  18 +static 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 + */
  31 +static 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 +
  38 +static 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 + */
  52 +static 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 +
  71 +static 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 +
  130 +static 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
  145 +static inline void do_kernel_backtrace(unsigned long low_addr,
  146 + struct stackframe *frame,
  147 + unsigned int depth) { }
  148 +#else
  149 +static 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 +
  163 +void 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 +}
arch/mips/oprofile/common.c
... ... @@ -115,6 +115,7 @@
115 115 ops->start = op_mips_start;
116 116 ops->stop = op_mips_stop;
117 117 ops->cpu_type = lmodel->cpu_type;
  118 + ops->backtrace = op_mips_backtrace;
118 119  
119 120 printk(KERN_INFO "oprofile: using %s performance monitoring.\n",
120 121 lmodel->cpu_type);
arch/mips/oprofile/op_impl.h
... ... @@ -36,5 +36,7 @@
36 36 unsigned char num_counters;
37 37 };
38 38  
  39 +void op_mips_backtrace(struct pt_regs * const regs, unsigned int depth);
  40 +
39 41 #endif