1 /* 2 * Code for replacing ftrace calls with jumps. 3 * 4 * Copyright (C) 2007-2008 Steven Rostedt <srostedt@redhat.com> 5 * 6 * Thanks goes to Ingo Molnar, for suggesting the idea. 7 * Mathieu Desnoyers, for suggesting postponing the modifications. 8 * Arjan van de Ven, for keeping me straight, and explaining to me 9 * the dangers of modifying code on the run. 10 */ 11 12 #include <linux/spinlock.h> 13 #include <linux/hardirq.h> 14 #include <linux/uaccess.h> 15 #include <linux/ftrace.h> 16 #include <linux/percpu.h> 17 #include <linux/init.h> 18 #include <linux/list.h> 19 20 #include <asm/ftrace.h> 21 #include <asm/nops.h> 22 23 24 static unsigned char ftrace_nop[MCOUNT_INSN_SIZE]; 25 26 union ftrace_code_union { 27 char code[MCOUNT_INSN_SIZE]; 28 struct { 29 char e8; 30 int offset; 31 } __attribute__((packed)); 32 }; 33 34 35 static int ftrace_calc_offset(long ip, long addr) 36 { 37 return (int)(addr - ip); 38 } 39 40 unsigned char *ftrace_nop_replace(void) 41 { 42 return ftrace_nop; 43 } 44 45 unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr) 46 { 47 static union ftrace_code_union calc; 48 49 calc.e8 = 0xe8; 50 calc.offset = ftrace_calc_offset(ip + MCOUNT_INSN_SIZE, addr); 51 52 /* 53 * No locking needed, this must be called via kstop_machine 54 * which in essence is like running on a uniprocessor machine. 55 */ 56 return calc.code; 57 } 58 59 int 60 ftrace_modify_code(unsigned long ip, unsigned char *old_code, 61 unsigned char *new_code) 62 { 63 unsigned char replaced[MCOUNT_INSN_SIZE]; 64 65 /* 66 * Note: Due to modules and __init, code can 67 * disappear and change, we need to protect against faulting 68 * as well as code changing. We do this by using the 69 * probe_kernel_* functions. 70 * 71 * No real locking needed, this code is run through 72 * kstop_machine, or before SMP starts. 73 */ 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 sync_core(); 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 int ret; 97 98 memcpy(old, &ftrace_call, MCOUNT_INSN_SIZE); 99 new = ftrace_call_replace(ip, (unsigned long)func); 100 ret = ftrace_modify_code(ip, old, new); 101 102 return ret; 103 } 104 105 int __init ftrace_dyn_arch_init(void *data) 106 { 107 extern const unsigned char ftrace_test_p6nop[]; 108 extern const unsigned char ftrace_test_nop5[]; 109 extern const unsigned char ftrace_test_jmp[]; 110 int faulted = 0; 111 112 /* 113 * There is no good nop for all x86 archs. 114 * We will default to using the P6_NOP5, but first we 115 * will test to make sure that the nop will actually 116 * work on this CPU. If it faults, we will then 117 * go to a lesser efficient 5 byte nop. If that fails 118 * we then just use a jmp as our nop. This isn't the most 119 * efficient nop, but we can not use a multi part nop 120 * since we would then risk being preempted in the middle 121 * of that nop, and if we enabled tracing then, it might 122 * cause a system crash. 123 * 124 * TODO: check the cpuid to determine the best nop. 125 */ 126 asm volatile ( 127 "ftrace_test_jmp:" 128 "jmp ftrace_test_p6nop\n" 129 "nop\n" 130 "nop\n" 131 "nop\n" /* 2 byte jmp + 3 bytes */ 132 "ftrace_test_p6nop:" 133 P6_NOP5 134 "jmp 1f\n" 135 "ftrace_test_nop5:" 136 ".byte 0x66,0x66,0x66,0x66,0x90\n" 137 "1:" 138 ".section .fixup, \"ax\"\n" 139 "2: movl $1, %0\n" 140 " jmp ftrace_test_nop5\n" 141 "3: movl $2, %0\n" 142 " jmp 1b\n" 143 ".previous\n" 144 _ASM_EXTABLE(ftrace_test_p6nop, 2b) 145 _ASM_EXTABLE(ftrace_test_nop5, 3b) 146 : "=r"(faulted) : "0" (faulted)); 147 148 switch (faulted) { 149 case 0: 150 pr_info("ftrace: converting mcount calls to 0f 1f 44 00 00\n"); 151 memcpy(ftrace_nop, ftrace_test_p6nop, MCOUNT_INSN_SIZE); 152 break; 153 case 1: 154 pr_info("ftrace: converting mcount calls to 66 66 66 66 90\n"); 155 memcpy(ftrace_nop, ftrace_test_nop5, MCOUNT_INSN_SIZE); 156 break; 157 case 2: 158 pr_info("ftrace: converting mcount calls to jmp . + 5\n"); 159 memcpy(ftrace_nop, ftrace_test_jmp, MCOUNT_INSN_SIZE); 160 break; 161 } 162 163 /* The return code is retured via data */ 164 *(unsigned long *)data = 0; 165 166 return 0; 167 } 168