/* * Dynamic function tracer architecture backend. * * Copyright IBM Corp. 2009 * * Author(s): Heiko Carstens , * */ #include #include #include #include #include #include #include #ifdef CONFIG_DYNAMIC_FTRACE void ftrace_disable_code(void); void ftrace_disable_return(void); void ftrace_call_code(void); void ftrace_nop_code(void); #define FTRACE_INSN_SIZE 4 #ifdef CONFIG_64BIT asm( " .align 4\n" "ftrace_disable_code:\n" " j 0f\n" " .word 0x0024\n" " lg %r1,"__stringify(__LC_FTRACE_FUNC)"\n" " basr %r14,%r1\n" "ftrace_disable_return:\n" " lg %r14,8(15)\n" " lgr %r0,%r0\n" "0:\n"); asm( " .align 4\n" "ftrace_nop_code:\n" " j .+"__stringify(MCOUNT_INSN_SIZE)"\n"); asm( " .align 4\n" "ftrace_call_code:\n" " stg %r14,8(%r15)\n"); #else /* CONFIG_64BIT */ asm( " .align 4\n" "ftrace_disable_code:\n" " j 0f\n" " l %r1,"__stringify(__LC_FTRACE_FUNC)"\n" " basr %r14,%r1\n" "ftrace_disable_return:\n" " l %r14,4(%r15)\n" " j 0f\n" " bcr 0,%r7\n" " bcr 0,%r7\n" " bcr 0,%r7\n" " bcr 0,%r7\n" " bcr 0,%r7\n" " bcr 0,%r7\n" "0:\n"); asm( " .align 4\n" "ftrace_nop_code:\n" " j .+"__stringify(MCOUNT_INSN_SIZE)"\n"); asm( " .align 4\n" "ftrace_call_code:\n" " st %r14,4(%r15)\n"); #endif /* CONFIG_64BIT */ static int ftrace_modify_code(unsigned long ip, void *old_code, int old_size, void *new_code, int new_size) { unsigned char replaced[MCOUNT_INSN_SIZE]; /* * Note: Due to modules code can disappear and change. * We need to protect against faulting as well as code * changing. We do this by using the probe_kernel_* * functions. * This however is just a simple sanity check. */ if (probe_kernel_read(replaced, (void *)ip, old_size)) return -EFAULT; if (memcmp(replaced, old_code, old_size) != 0) return -EINVAL; if (probe_kernel_write((void *)ip, new_code, new_size)) return -EPERM; return 0; } static int ftrace_make_initial_nop(struct module *mod, struct dyn_ftrace *rec, unsigned long addr) { return ftrace_modify_code(rec->ip, ftrace_call_code, FTRACE_INSN_SIZE, ftrace_disable_code, MCOUNT_INSN_SIZE); } int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec, unsigned long addr) { if (addr == MCOUNT_ADDR) return ftrace_make_initial_nop(mod, rec, addr); return ftrace_modify_code(rec->ip, ftrace_call_code, FTRACE_INSN_SIZE, ftrace_nop_code, FTRACE_INSN_SIZE); } int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) { return ftrace_modify_code(rec->ip, ftrace_nop_code, FTRACE_INSN_SIZE, ftrace_call_code, FTRACE_INSN_SIZE); } int ftrace_update_ftrace_func(ftrace_func_t func) { ftrace_dyn_func = (unsigned long)func; return 0; } int __init ftrace_dyn_arch_init(void *data) { *(unsigned long *)data = 0; return 0; } #endif /* CONFIG_DYNAMIC_FTRACE */ #ifdef CONFIG_FUNCTION_GRAPH_TRACER #ifdef CONFIG_DYNAMIC_FTRACE /* * Patch the kernel code at ftrace_graph_caller location: * The instruction there is branch relative on condition. The condition mask * is either all ones (always branch aka disable ftrace_graph_caller) or all * zeroes (nop aka enable ftrace_graph_caller). * Instruction format for brc is a7m4xxxx where m is the condition mask. */ int ftrace_enable_ftrace_graph_caller(void) { unsigned short opcode = 0xa704; return probe_kernel_write(ftrace_graph_caller, &opcode, sizeof(opcode)); } int ftrace_disable_ftrace_graph_caller(void) { unsigned short opcode = 0xa7f4; return probe_kernel_write(ftrace_graph_caller, &opcode, sizeof(opcode)); } static inline unsigned long ftrace_mcount_call_adjust(unsigned long addr) { return addr - (ftrace_disable_return - ftrace_disable_code); } #else /* CONFIG_DYNAMIC_FTRACE */ static inline unsigned long ftrace_mcount_call_adjust(unsigned long addr) { return addr - MCOUNT_OFFSET_RET; } #endif /* CONFIG_DYNAMIC_FTRACE */ /* * Hook the return address and push it in the stack of return addresses * in current thread info. */ unsigned long prepare_ftrace_return(unsigned long ip, unsigned long parent) { struct ftrace_graph_ent trace; if (unlikely(atomic_read(¤t->tracing_graph_pause))) goto out; if (ftrace_push_return_trace(parent, ip, &trace.depth, 0) == -EBUSY) goto out; trace.func = ftrace_mcount_call_adjust(ip) & PSW_ADDR_INSN; /* Only trace if the calling function expects to. */ if (!ftrace_graph_entry(&trace)) { current->curr_ret_stack--; goto out; } parent = (unsigned long)return_to_handler; out: return parent; } #endif /* CONFIG_FUNCTION_GRAPH_TRACER */