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