Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
fork

Configure Feed

Select the types of activity you want to include in your feed.

ftrace/x86: Use breakpoints for converting function graph caller

When the conversion was made to remove stop machine and use the breakpoint
logic instead, the modification of the function graph caller is still
done directly as though it was being done under stop machine.

As it is not converted via stop machine anymore, there is a possibility
that the code could be layed across cache lines and if another CPU is
accessing that function graph call when it is being updated, it could
cause a General Protection Fault.

Convert the update of the function graph caller to use the breakpoint
method as well.

Cc: H. Peter Anvin <hpa@zytor.com>
Cc: stable@vger.kernel.org # 3.5+
Fixes: 08d636b6d4fb "ftrace/x86: Have arch x86_64 use breakpoints instead of stop machine"
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>

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