diff options
Diffstat (limited to 'arch/x86/kernel/ftrace.c')
-rw-r--r-- | arch/x86/kernel/ftrace.c | 83 |
1 files changed, 47 insertions, 36 deletions
diff --git a/arch/x86/kernel/ftrace.c b/arch/x86/kernel/ftrace.c index d4bdd253fea7..e6253195a301 100644 --- a/arch/x86/kernel/ftrace.c +++ b/arch/x86/kernel/ftrace.c | |||
@@ -77,8 +77,7 @@ within(unsigned long addr, unsigned long start, unsigned long end) | |||
77 | return addr >= start && addr < end; | 77 | return addr >= start && addr < end; |
78 | } | 78 | } |
79 | 79 | ||
80 | static int | 80 | static unsigned long text_ip_addr(unsigned long ip) |
81 | do_ftrace_mod_code(unsigned long ip, const void *new_code) | ||
82 | { | 81 | { |
83 | /* | 82 | /* |
84 | * On x86_64, kernel text mappings are mapped read-only with | 83 | * On x86_64, kernel text mappings are mapped read-only with |
@@ -91,7 +90,7 @@ do_ftrace_mod_code(unsigned long ip, const void *new_code) | |||
91 | if (within(ip, (unsigned long)_text, (unsigned long)_etext)) | 90 | if (within(ip, (unsigned long)_text, (unsigned long)_etext)) |
92 | ip = (unsigned long)__va(__pa_symbol(ip)); | 91 | ip = (unsigned long)__va(__pa_symbol(ip)); |
93 | 92 | ||
94 | return probe_kernel_write((void *)ip, new_code, MCOUNT_INSN_SIZE); | 93 | return ip; |
95 | } | 94 | } |
96 | 95 | ||
97 | static const unsigned char *ftrace_nop_replace(void) | 96 | static const unsigned char *ftrace_nop_replace(void) |
@@ -123,8 +122,10 @@ ftrace_modify_code_direct(unsigned long ip, unsigned const char *old_code, | |||
123 | if (memcmp(replaced, old_code, MCOUNT_INSN_SIZE) != 0) | 122 | if (memcmp(replaced, old_code, MCOUNT_INSN_SIZE) != 0) |
124 | return -EINVAL; | 123 | return -EINVAL; |
125 | 124 | ||
125 | ip = text_ip_addr(ip); | ||
126 | |||
126 | /* replace the text with the new text */ | 127 | /* replace the text with the new text */ |
127 | if (do_ftrace_mod_code(ip, new_code)) | 128 | if (probe_kernel_write((void *)ip, new_code, MCOUNT_INSN_SIZE)) |
128 | return -EPERM; | 129 | return -EPERM; |
129 | 130 | ||
130 | sync_core(); | 131 | sync_core(); |
@@ -221,37 +222,51 @@ int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr, | |||
221 | return -EINVAL; | 222 | return -EINVAL; |
222 | } | 223 | } |
223 | 224 | ||
224 | int ftrace_update_ftrace_func(ftrace_func_t func) | 225 | static unsigned long ftrace_update_func; |
226 | |||
227 | static int update_ftrace_func(unsigned long ip, void *new) | ||
225 | { | 228 | { |
226 | unsigned long ip = (unsigned long)(&ftrace_call); | 229 | unsigned char old[MCOUNT_INSN_SIZE]; |
227 | unsigned char old[MCOUNT_INSN_SIZE], *new; | ||
228 | int ret; | 230 | int ret; |
229 | 231 | ||
230 | memcpy(old, &ftrace_call, MCOUNT_INSN_SIZE); | 232 | memcpy(old, (void *)ip, MCOUNT_INSN_SIZE); |
231 | new = ftrace_call_replace(ip, (unsigned long)func); | 233 | |
234 | ftrace_update_func = ip; | ||
235 | /* Make sure the breakpoints see the ftrace_update_func update */ | ||
236 | smp_wmb(); | ||
232 | 237 | ||
233 | /* See comment above by declaration of modifying_ftrace_code */ | 238 | /* See comment above by declaration of modifying_ftrace_code */ |
234 | atomic_inc(&modifying_ftrace_code); | 239 | atomic_inc(&modifying_ftrace_code); |
235 | 240 | ||
236 | ret = ftrace_modify_code(ip, old, new); | 241 | ret = ftrace_modify_code(ip, old, new); |
237 | 242 | ||
243 | atomic_dec(&modifying_ftrace_code); | ||
244 | |||
245 | return ret; | ||
246 | } | ||
247 | |||
248 | int ftrace_update_ftrace_func(ftrace_func_t func) | ||
249 | { | ||
250 | unsigned long ip = (unsigned long)(&ftrace_call); | ||
251 | unsigned char *new; | ||
252 | int ret; | ||
253 | |||
254 | new = ftrace_call_replace(ip, (unsigned long)func); | ||
255 | ret = update_ftrace_func(ip, new); | ||
256 | |||
238 | /* Also update the regs callback function */ | 257 | /* Also update the regs callback function */ |
239 | if (!ret) { | 258 | if (!ret) { |
240 | ip = (unsigned long)(&ftrace_regs_call); | 259 | ip = (unsigned long)(&ftrace_regs_call); |
241 | memcpy(old, &ftrace_regs_call, MCOUNT_INSN_SIZE); | ||
242 | new = ftrace_call_replace(ip, (unsigned long)func); | 260 | new = ftrace_call_replace(ip, (unsigned long)func); |
243 | ret = ftrace_modify_code(ip, old, new); | 261 | ret = update_ftrace_func(ip, new); |
244 | } | 262 | } |
245 | 263 | ||
246 | atomic_dec(&modifying_ftrace_code); | ||
247 | |||
248 | return ret; | 264 | return ret; |
249 | } | 265 | } |
250 | 266 | ||
251 | static int is_ftrace_caller(unsigned long ip) | 267 | static int is_ftrace_caller(unsigned long ip) |
252 | { | 268 | { |
253 | if (ip == (unsigned long)(&ftrace_call) || | 269 | if (ip == ftrace_update_func) |
254 | ip == (unsigned long)(&ftrace_regs_call)) | ||
255 | return 1; | 270 | return 1; |
256 | 271 | ||
257 | return 0; | 272 | return 0; |
@@ -677,45 +692,41 @@ int __init ftrace_dyn_arch_init(void *data) | |||
677 | #ifdef CONFIG_DYNAMIC_FTRACE | 692 | #ifdef CONFIG_DYNAMIC_FTRACE |
678 | extern void ftrace_graph_call(void); | 693 | extern void ftrace_graph_call(void); |
679 | 694 | ||
680 | static int ftrace_mod_jmp(unsigned long ip, | 695 | static unsigned char *ftrace_jmp_replace(unsigned long ip, unsigned long addr) |
681 | int old_offset, int new_offset) | ||
682 | { | 696 | { |
683 | unsigned char code[MCOUNT_INSN_SIZE]; | 697 | static union ftrace_code_union calc; |
684 | 698 | ||
685 | if (probe_kernel_read(code, (void *)ip, MCOUNT_INSN_SIZE)) | 699 | /* Jmp not a call (ignore the .e8) */ |
686 | return -EFAULT; | 700 | calc.e8 = 0xe9; |
701 | calc.offset = ftrace_calc_offset(ip + MCOUNT_INSN_SIZE, addr); | ||
687 | 702 | ||
688 | if (code[0] != 0xe9 || old_offset != *(int *)(&code[1])) | 703 | /* |
689 | return -EINVAL; | 704 | * ftrace external locks synchronize the access to the static variable. |
705 | */ | ||
706 | return calc.code; | ||
707 | } | ||
690 | 708 | ||
691 | *(int *)(&code[1]) = new_offset; | 709 | static int ftrace_mod_jmp(unsigned long ip, void *func) |
710 | { | ||
711 | unsigned char *new; | ||
692 | 712 | ||
693 | if (do_ftrace_mod_code(ip, &code)) | 713 | new = ftrace_jmp_replace(ip, (unsigned long)func); |
694 | return -EPERM; | ||
695 | 714 | ||
696 | return 0; | 715 | return update_ftrace_func(ip, new); |
697 | } | 716 | } |
698 | 717 | ||
699 | int ftrace_enable_ftrace_graph_caller(void) | 718 | int ftrace_enable_ftrace_graph_caller(void) |
700 | { | 719 | { |
701 | unsigned long ip = (unsigned long)(&ftrace_graph_call); | 720 | unsigned long ip = (unsigned long)(&ftrace_graph_call); |
702 | int old_offset, new_offset; | ||
703 | 721 | ||
704 | old_offset = (unsigned long)(&ftrace_stub) - (ip + MCOUNT_INSN_SIZE); | 722 | return ftrace_mod_jmp(ip, &ftrace_graph_caller); |
705 | new_offset = (unsigned long)(&ftrace_graph_caller) - (ip + MCOUNT_INSN_SIZE); | ||
706 | |||
707 | return ftrace_mod_jmp(ip, old_offset, new_offset); | ||
708 | } | 723 | } |
709 | 724 | ||
710 | int ftrace_disable_ftrace_graph_caller(void) | 725 | int ftrace_disable_ftrace_graph_caller(void) |
711 | { | 726 | { |
712 | unsigned long ip = (unsigned long)(&ftrace_graph_call); | 727 | unsigned long ip = (unsigned long)(&ftrace_graph_call); |
713 | int old_offset, new_offset; | ||
714 | |||
715 | old_offset = (unsigned long)(&ftrace_graph_caller) - (ip + MCOUNT_INSN_SIZE); | ||
716 | new_offset = (unsigned long)(&ftrace_stub) - (ip + MCOUNT_INSN_SIZE); | ||
717 | 728 | ||
718 | return ftrace_mod_jmp(ip, old_offset, new_offset); | 729 | return ftrace_mod_jmp(ip, &ftrace_stub); |
719 | } | 730 | } |
720 | 731 | ||
721 | #endif /* !CONFIG_DYNAMIC_FTRACE */ | 732 | #endif /* !CONFIG_DYNAMIC_FTRACE */ |