1 /* 2 * Copyright (C) 2008 Matt Fleming <mjf@gentoo.org> 3 * 4 * Code for replacing ftrace calls with jumps. 5 * 6 * Copyright (C) 2007-2008 Steven Rostedt <srostedt@redhat.com> 7 * 8 * Thanks goes to Ingo Molnar, for suggesting the idea. 9 * Mathieu Desnoyers, for suggesting postponing the modifications. 10 * Arjan van de Ven, for keeping me straight, and explaining to me 11 * the dangers of modifying code on the run. 12 */ 13 #include <linux/uaccess.h> 14 #include <linux/ftrace.h> 15 #include <linux/string.h> 16 #include <linux/init.h> 17 #include <linux/io.h> 18 #include <asm/ftrace.h> 19 #include <asm/cacheflush.h> 20 21 static unsigned char ftrace_nop[] = { 22 0x09, 0x00, /* nop */ 23 0x09, 0x00, /* nop */ 24 }; 25 26 static unsigned char ftrace_replaced_code[MCOUNT_INSN_SIZE]; 27 28 unsigned char *ftrace_nop_replace(void) 29 { 30 return ftrace_nop; 31 } 32 33 static int is_sh_nop(unsigned char *ip) 34 { 35 return strncmp(ip, ftrace_nop, sizeof(ftrace_nop)); 36 } 37 38 unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr) 39 { 40 /* Place the address in the memory table. */ 41 if (addr == CALLER_ADDR) 42 __raw_writel(addr + MCOUNT_INSN_OFFSET, ftrace_replaced_code); 43 else 44 __raw_writel(addr, ftrace_replaced_code); 45 46 /* 47 * No locking needed, this must be called via kstop_machine 48 * which in essence is like running on a uniprocessor machine. 49 */ 50 return ftrace_replaced_code; 51 } 52 53 int ftrace_modify_code(unsigned long ip, unsigned char *old_code, 54 unsigned char *new_code) 55 { 56 unsigned char replaced[MCOUNT_INSN_SIZE]; 57 58 /* 59 * Note: Due to modules and __init, code can 60 * disappear and change, we need to protect against faulting 61 * as well as code changing. We do this by using the 62 * probe_kernel_* functions. 63 * 64 * No real locking needed, this code is run through 65 * kstop_machine, or before SMP starts. 66 */ 67 68 /* 69 * If we're trying to nop out a call to a function, we instead 70 * place a call to the address after the memory table. 71 */ 72 if (is_sh_nop(new_code) == 0) 73 __raw_writel(ip + MCOUNT_INSN_SIZE, (unsigned long)new_code); 74 75 /* read the text we want to modify */ 76 if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE)) 77 return -EFAULT; 78 79 /* Make sure it is what we expect it to be */ 80 if (memcmp(replaced, old_code, MCOUNT_INSN_SIZE) != 0) 81 return -EINVAL; 82 83 /* replace the text with the new text */ 84 if (probe_kernel_write((void *)ip, new_code, MCOUNT_INSN_SIZE)) 85 return -EPERM; 86 87 flush_icache_range(ip, ip + MCOUNT_INSN_SIZE); 88 89 return 0; 90 } 91 92 int ftrace_update_ftrace_func(ftrace_func_t func) 93 { 94 unsigned long ip = (unsigned long)(&ftrace_call); 95 unsigned char old[MCOUNT_INSN_SIZE], *new; 96 97 memcpy(old, (unsigned char *)(ip + MCOUNT_INSN_OFFSET), MCOUNT_INSN_SIZE); 98 new = ftrace_call_replace(ip, (unsigned long)func); 99 100 return ftrace_modify_code(ip + MCOUNT_INSN_OFFSET, old, new); 101 } 102 103 int __init ftrace_dyn_arch_init(void *data) 104 { 105 /* The return code is retured via data */ 106 __raw_writel(0, (unsigned long)data); 107 108 return 0; 109 } 110