aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Rostedt <srostedt@redhat.com>2008-11-14 23:47:03 -0500
committerSteven Rostedt <srostedt@redhat.com>2008-11-20 13:52:28 -0500
commitf48cb8b48b0b10025ca9c451b9b32cac3fcd33ba (patch)
treee95f858bcf155d2000e57d2a6cda78dc0ffc791f
parente4486fe316895e87672a563c4f36393218f84ff1 (diff)
powerpc/ppc64: ftrace, handle module trampolines for dyn ftrace
Impact: Allow 64 bit PowerPC to trace modules with dynamic ftrace This adds code to handle the PPC64 module trampolines, and allows for PPC64 to use dynamic ftrace. Thanks to Paul Mackerras for these updates: - fix the mod and rec->arch.mod NULL checks. - fix to is_bl_op compare. Thanks to Milton Miller for: - finding the nasty race with using two nops, and recommending instead that I use a branch 8 forward. Signed-off-by: Steven Rostedt <srostedt@redhat.com>
-rw-r--r--arch/powerpc/include/asm/ftrace.h2
-rw-r--r--arch/powerpc/include/asm/module.h11
-rw-r--r--arch/powerpc/kernel/ftrace.c278
-rw-r--r--arch/powerpc/kernel/module_64.c13
4 files changed, 293 insertions, 11 deletions
diff --git a/arch/powerpc/include/asm/ftrace.h b/arch/powerpc/include/asm/ftrace.h
index 17efecc2bf03..e5f2ae8362f7 100644
--- a/arch/powerpc/include/asm/ftrace.h
+++ b/arch/powerpc/include/asm/ftrace.h
@@ -16,7 +16,7 @@ static inline unsigned long ftrace_call_adjust(unsigned long addr)
16} 16}
17 17
18struct dyn_arch_ftrace { 18struct dyn_arch_ftrace {
19 /* nothing yet */ 19 struct module *mod;
20}; 20};
21#endif /* CONFIG_DYNAMIC_FTRACE */ 21#endif /* CONFIG_DYNAMIC_FTRACE */
22#endif /* __ASSEMBLY__ */ 22#endif /* __ASSEMBLY__ */
diff --git a/arch/powerpc/include/asm/module.h b/arch/powerpc/include/asm/module.h
index e5f14b13ccf0..340bc699b620 100644
--- a/arch/powerpc/include/asm/module.h
+++ b/arch/powerpc/include/asm/module.h
@@ -34,6 +34,11 @@ struct mod_arch_specific {
34#ifdef __powerpc64__ 34#ifdef __powerpc64__
35 unsigned int stubs_section; /* Index of stubs section in module */ 35 unsigned int stubs_section; /* Index of stubs section in module */
36 unsigned int toc_section; /* What section is the TOC? */ 36 unsigned int toc_section; /* What section is the TOC? */
37#ifdef CONFIG_DYNAMIC_FTRACE
38 unsigned long toc;
39 unsigned long tramp;
40#endif
41
37#else 42#else
38 /* Indices of PLT sections within module. */ 43 /* Indices of PLT sections within module. */
39 unsigned int core_plt_section; 44 unsigned int core_plt_section;
@@ -68,6 +73,12 @@ struct mod_arch_specific {
68# endif /* MODULE */ 73# endif /* MODULE */
69#endif 74#endif
70 75
76#ifdef CONFIG_DYNAMIC_FTRACE
77# ifdef MODULE
78 asm(".section .ftrace.tramp,\"ax\",@nobits; .align 3; .previous");
79# endif /* MODULE */
80#endif
81
71 82
72struct exception_table_entry; 83struct exception_table_entry;
73void sort_ex_table(struct exception_table_entry *start, 84void sort_ex_table(struct exception_table_entry *start,
diff --git a/arch/powerpc/kernel/ftrace.c b/arch/powerpc/kernel/ftrace.c
index 1adfbb268d8e..1aec559bdfcb 100644
--- a/arch/powerpc/kernel/ftrace.c
+++ b/arch/powerpc/kernel/ftrace.c
@@ -10,22 +10,29 @@
10#include <linux/spinlock.h> 10#include <linux/spinlock.h>
11#include <linux/hardirq.h> 11#include <linux/hardirq.h>
12#include <linux/uaccess.h> 12#include <linux/uaccess.h>
13#include <linux/module.h>
13#include <linux/ftrace.h> 14#include <linux/ftrace.h>
14#include <linux/percpu.h> 15#include <linux/percpu.h>
15#include <linux/init.h> 16#include <linux/init.h>
16#include <linux/list.h> 17#include <linux/list.h>
17 18
18#include <asm/cacheflush.h> 19#include <asm/cacheflush.h>
20#include <asm/code-patching.h>
19#include <asm/ftrace.h> 21#include <asm/ftrace.h>
20 22
23#if 0
24#define DEBUGP printk
25#else
26#define DEBUGP(fmt , ...) do { } while (0)
27#endif
21 28
22static unsigned int ftrace_nop = 0x60000000; 29static unsigned int ftrace_nop = PPC_NOP_INSTR;
23 30
24#ifdef CONFIG_PPC32 31#ifdef CONFIG_PPC32
25# define GET_ADDR(addr) addr 32# define GET_ADDR(addr) addr
26#else 33#else
27/* PowerPC64's functions are data that points to the functions */ 34/* PowerPC64's functions are data that points to the functions */
28# define GET_ADDR(addr) *(unsigned long *)addr 35# define GET_ADDR(addr) (*(unsigned long *)addr)
29#endif 36#endif
30 37
31 38
@@ -102,6 +109,9 @@ ftrace_modify_code(unsigned long ip, unsigned char *old_code,
102 return 0; 109 return 0;
103} 110}
104 111
112/*
113 * Helper functions that are the same for both PPC64 and PPC32.
114 */
105static int test_24bit_addr(unsigned long ip, unsigned long addr) 115static int test_24bit_addr(unsigned long ip, unsigned long addr)
106{ 116{
107 long diff; 117 long diff;
@@ -119,43 +129,292 @@ static int test_24bit_addr(unsigned long ip, unsigned long addr)
119 return (diff < (1 << 25)) && (diff > (-1 << 26)); 129 return (diff < (1 << 25)) && (diff > (-1 << 26));
120} 130}
121 131
132static int is_bl_op(unsigned int op)
133{
134 return (op & 0xfc000003) == 0x48000001;
135}
136
137static int test_offset(unsigned long offset)
138{
139 return (offset + 0x2000000 > 0x3ffffff) || ((offset & 3) != 0);
140}
141
142static unsigned long find_bl_target(unsigned long ip, unsigned int op)
143{
144 static int offset;
145
146 offset = (op & 0x03fffffc);
147 /* make it signed */
148 if (offset & 0x02000000)
149 offset |= 0xfe000000;
150
151 return ip + (long)offset;
152}
153
154static unsigned int branch_offset(unsigned long offset)
155{
156 /* return "bl ip+offset" */
157 return 0x48000001 | (offset & 0x03fffffc);
158}
159
160#ifdef CONFIG_PPC64
161static int
162__ftrace_make_nop(struct module *mod,
163 struct dyn_ftrace *rec, unsigned long addr)
164{
165 unsigned char replaced[MCOUNT_INSN_SIZE * 2];
166 unsigned int *op = (unsigned *)&replaced;
167 unsigned char jmp[8];
168 unsigned long *ptr = (unsigned long *)&jmp;
169 unsigned long ip = rec->ip;
170 unsigned long tramp;
171 int offset;
172
173 /* read where this goes */
174 if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE))
175 return -EFAULT;
176
177 /* Make sure that that this is still a 24bit jump */
178 if (!is_bl_op(*op)) {
179 printk(KERN_ERR "Not expected bl: opcode is %x\n", *op);
180 return -EINVAL;
181 }
182
183 /* lets find where the pointer goes */
184 tramp = find_bl_target(ip, *op);
185
186 /*
187 * On PPC64 the trampoline looks like:
188 * 0x3d, 0x82, 0x00, 0x00, addis r12,r2, <high>
189 * 0x39, 0x8c, 0x00, 0x00, addi r12,r12, <low>
190 * Where the bytes 2,3,6 and 7 make up the 32bit offset
191 * to the TOC that holds the pointer.
192 * to jump to.
193 * 0xf8, 0x41, 0x00, 0x28, std r2,40(r1)
194 * 0xe9, 0x6c, 0x00, 0x20, ld r11,32(r12)
195 * The actually address is 32 bytes from the offset
196 * into the TOC.
197 * 0xe8, 0x4c, 0x00, 0x28, ld r2,40(r12)
198 */
199
200 DEBUGP("ip:%lx jumps to %lx r2: %lx", ip, tramp, mod->arch.toc);
201
202 /* Find where the trampoline jumps to */
203 if (probe_kernel_read(jmp, (void *)tramp, 8)) {
204 printk(KERN_ERR "Failed to read %lx\n", tramp);
205 return -EFAULT;
206 }
207
208 DEBUGP(" %08x %08x",
209 (unsigned)(*ptr >> 32),
210 (unsigned)*ptr);
211
212 offset = (unsigned)jmp[2] << 24 |
213 (unsigned)jmp[3] << 16 |
214 (unsigned)jmp[6] << 8 |
215 (unsigned)jmp[7];
216
217 DEBUGP(" %x ", offset);
218
219 /* get the address this jumps too */
220 tramp = mod->arch.toc + offset + 32;
221 DEBUGP("toc: %lx", tramp);
222
223 if (probe_kernel_read(jmp, (void *)tramp, 8)) {
224 printk(KERN_ERR "Failed to read %lx\n", tramp);
225 return -EFAULT;
226 }
227
228 DEBUGP(" %08x %08x\n",
229 (unsigned)(*ptr >> 32),
230 (unsigned)*ptr);
231
232 /* This should match what was called */
233 if (*ptr != GET_ADDR(addr)) {
234 printk(KERN_ERR "addr does not match %lx\n", *ptr);
235 return -EINVAL;
236 }
237
238 /*
239 * We want to nop the line, but the next line is
240 * 0xe8, 0x41, 0x00, 0x28 ld r2,40(r1)
241 * This needs to be turned to a nop too.
242 */
243 if (probe_kernel_read(replaced, (void *)(ip+4), MCOUNT_INSN_SIZE))
244 return -EFAULT;
245
246 if (*op != 0xe8410028) {
247 printk(KERN_ERR "Next line is not ld! (%08x)\n", *op);
248 return -EINVAL;
249 }
250
251 /*
252 * Milton Miller pointed out that we can not blindly do nops.
253 * If a task was preempted when calling a trace function,
254 * the nops will remove the way to restore the TOC in r2
255 * and the r2 TOC will get corrupted.
256 */
257
258 /*
259 * Replace:
260 * bl <tramp> <==== will be replaced with "b 1f"
261 * ld r2,40(r1)
262 * 1:
263 */
264 op[0] = 0x48000008; /* b +8 */
265
266 if (probe_kernel_write((void *)ip, replaced, MCOUNT_INSN_SIZE))
267 return -EPERM;
268
269 return 0;
270}
271
272#else /* !PPC64 */
273static int
274__ftrace_make_nop(struct module *mod,
275 struct dyn_ftrace *rec, unsigned long addr)
276{
277 /* Ignore modules for PPC32 (for now) */
278 return 0;
279}
280#endif /* PPC64 */
281
122int ftrace_make_nop(struct module *mod, 282int ftrace_make_nop(struct module *mod,
123 struct dyn_ftrace *rec, unsigned long addr) 283 struct dyn_ftrace *rec, unsigned long addr)
124{ 284{
125 unsigned char *old, *new; 285 unsigned char *old, *new;
286 unsigned long ip = rec->ip;
126 287
127 /* 288 /*
128 * If the calling address is more that 24 bits away, 289 * If the calling address is more that 24 bits away,
129 * then we had to use a trampoline to make the call. 290 * then we had to use a trampoline to make the call.
130 * Otherwise just update the call site. 291 * Otherwise just update the call site.
131 */ 292 */
132 if (test_24bit_addr(rec->ip, addr)) { 293 if (test_24bit_addr(ip, addr)) {
133 /* within range */ 294 /* within range */
134 old = ftrace_call_replace(rec->ip, addr); 295 old = ftrace_call_replace(ip, addr);
135 new = ftrace_nop_replace(); 296 new = ftrace_nop_replace();
136 return ftrace_modify_code(rec->ip, old, new); 297 return ftrace_modify_code(ip, old, new);
298 }
299
300#ifdef CONFIG_PPC64
301 /*
302 * Out of range jumps are called from modules.
303 * We should either already have a pointer to the module
304 * or it has been passed in.
305 */
306 if (!rec->arch.mod) {
307 if (!mod) {
308 printk(KERN_ERR "No module loaded addr=%lx\n",
309 addr);
310 return -EFAULT;
311 }
312 rec->arch.mod = mod;
313 } else if (mod) {
314 if (mod != rec->arch.mod) {
315 printk(KERN_ERR
316 "Record mod %p not equal to passed in mod %p\n",
317 rec->arch.mod, mod);
318 return -EINVAL;
319 }
320 /* nothing to do if mod == rec->arch.mod */
321 } else
322 mod = rec->arch.mod;
323#endif /* CONFIG_PPC64 */
324
325 return __ftrace_make_nop(mod, rec, addr);
326
327}
328
329#ifdef CONFIG_PPC64
330static int
331__ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
332{
333 unsigned char replaced[MCOUNT_INSN_SIZE * 2];
334 unsigned int *op = (unsigned *)&replaced;
335 unsigned long ip = rec->ip;
336 unsigned long offset;
337
338 /* read where this goes */
339 if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE * 2))
340 return -EFAULT;
341
342 /*
343 * It should be pointing to two nops or
344 * b +8; ld r2,40(r1)
345 */
346 if (((op[0] != 0x48000008) || (op[1] != 0xe8410028)) &&
347 ((op[0] != PPC_NOP_INSTR) || (op[1] != PPC_NOP_INSTR))) {
348 printk(KERN_ERR "Expected NOPs but have %x %x\n", op[0], op[1]);
349 return -EINVAL;
350 }
351
352 /* If we never set up a trampoline to ftrace_caller, then bail */
353 if (!rec->arch.mod->arch.tramp) {
354 printk(KERN_ERR "No ftrace trampoline\n");
355 return -EINVAL;
356 }
357
358 /* now calculate a jump to the ftrace caller trampoline */
359 offset = rec->arch.mod->arch.tramp - ip;
360
361 if (test_offset(offset)) {
362 printk(KERN_ERR "REL24 %li out of range!\n",
363 (long int)offset);
364 return -EINVAL;
137 } 365 }
138 366
367 /* Set to "bl addr" */
368 op[0] = branch_offset(offset);
369 /* ld r2,40(r1) */
370 op[1] = 0xe8410028;
371
372 DEBUGP("write to %lx\n", rec->ip);
373
374 if (probe_kernel_write((void *)ip, replaced, MCOUNT_INSN_SIZE * 2))
375 return -EPERM;
376
139 return 0; 377 return 0;
140} 378}
379#else
380static int
381__ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
382{
383 /* PPC32 ignores modules for now */
384 return 0;
385}
386#endif /* CONFIG_PPC64 */
141 387
142int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) 388int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
143{ 389{
144 unsigned char *old, *new; 390 unsigned char *old, *new;
391 unsigned long ip = rec->ip;
145 392
146 /* 393 /*
147 * If the calling address is more that 24 bits away, 394 * If the calling address is more that 24 bits away,
148 * then we had to use a trampoline to make the call. 395 * then we had to use a trampoline to make the call.
149 * Otherwise just update the call site. 396 * Otherwise just update the call site.
150 */ 397 */
151 if (test_24bit_addr(rec->ip, addr)) { 398 if (test_24bit_addr(ip, addr)) {
152 /* within range */ 399 /* within range */
153 old = ftrace_nop_replace(); 400 old = ftrace_nop_replace();
154 new = ftrace_call_replace(rec->ip, addr); 401 new = ftrace_call_replace(ip, addr);
155 return ftrace_modify_code(rec->ip, old, new); 402 return ftrace_modify_code(ip, old, new);
156 } 403 }
157 404
158 return 0; 405#ifdef CONFIG_PPC64
406 /*
407 * Out of range jumps are called from modules.
408 * Being that we are converting from nop, it had better
409 * already have a module defined.
410 */
411 if (!rec->arch.mod) {
412 printk(KERN_ERR "No module loaded\n");
413 return -EINVAL;
414 }
415#endif
416
417 return __ftrace_make_call(rec, addr);
159} 418}
160 419
161int ftrace_update_ftrace_func(ftrace_func_t func) 420int ftrace_update_ftrace_func(ftrace_func_t func)
@@ -180,4 +439,3 @@ int __init ftrace_dyn_arch_init(void *data)
180 439
181 return 0; 440 return 0;
182} 441}
183
diff --git a/arch/powerpc/kernel/module_64.c b/arch/powerpc/kernel/module_64.c
index 1af2377e4992..8992b031a7b6 100644
--- a/arch/powerpc/kernel/module_64.c
+++ b/arch/powerpc/kernel/module_64.c
@@ -20,6 +20,7 @@
20#include <linux/moduleloader.h> 20#include <linux/moduleloader.h>
21#include <linux/err.h> 21#include <linux/err.h>
22#include <linux/vmalloc.h> 22#include <linux/vmalloc.h>
23#include <linux/ftrace.h>
23#include <linux/bug.h> 24#include <linux/bug.h>
24#include <asm/module.h> 25#include <asm/module.h>
25#include <asm/firmware.h> 26#include <asm/firmware.h>
@@ -163,6 +164,11 @@ static unsigned long get_stubs_size(const Elf64_Ehdr *hdr,
163 } 164 }
164 } 165 }
165 166
167#ifdef CONFIG_DYNAMIC_FTRACE
168 /* make the trampoline to the ftrace_caller */
169 relocs++;
170#endif
171
166 DEBUGP("Looks like a total of %lu stubs, max\n", relocs); 172 DEBUGP("Looks like a total of %lu stubs, max\n", relocs);
167 return relocs * sizeof(struct ppc64_stub_entry); 173 return relocs * sizeof(struct ppc64_stub_entry);
168} 174}
@@ -441,5 +447,12 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
441 } 447 }
442 } 448 }
443 449
450#ifdef CONFIG_DYNAMIC_FTRACE
451 me->arch.toc = my_r2(sechdrs, me);
452 me->arch.tramp = stub_for_addr(sechdrs,
453 (unsigned long)ftrace_caller,
454 me);
455#endif
456
444 return 0; 457 return 0;
445} 458}