diff options
Diffstat (limited to 'arch/powerpc/kernel/ftrace.c')
-rw-r--r-- | arch/powerpc/kernel/ftrace.c | 182 |
1 files changed, 85 insertions, 97 deletions
diff --git a/arch/powerpc/kernel/ftrace.c b/arch/powerpc/kernel/ftrace.c index 3271cd698e4c..5355244c99ff 100644 --- a/arch/powerpc/kernel/ftrace.c +++ b/arch/powerpc/kernel/ftrace.c | |||
@@ -114,19 +114,9 @@ ftrace_modify_code(unsigned long ip, unsigned char *old_code, | |||
114 | */ | 114 | */ |
115 | static int test_24bit_addr(unsigned long ip, unsigned long addr) | 115 | static int test_24bit_addr(unsigned long ip, unsigned long addr) |
116 | { | 116 | { |
117 | long diff; | ||
118 | 117 | ||
119 | /* | 118 | /* use the create_branch to verify that this offset can be branched */ |
120 | * Can we get to addr from ip in 24 bits? | 119 | return create_branch((unsigned int *)ip, addr, 0); |
121 | * (26 really, since we mulitply by 4 for 4 byte alignment) | ||
122 | */ | ||
123 | diff = addr - ip; | ||
124 | |||
125 | /* | ||
126 | * Return true if diff is less than 1 << 25 | ||
127 | * and greater than -1 << 26. | ||
128 | */ | ||
129 | return (diff < (1 << 25)) && (diff > (-1 << 26)); | ||
130 | } | 120 | } |
131 | 121 | ||
132 | static int is_bl_op(unsigned int op) | 122 | static int is_bl_op(unsigned int op) |
@@ -134,11 +124,6 @@ static int is_bl_op(unsigned int op) | |||
134 | return (op & 0xfc000003) == 0x48000001; | 124 | return (op & 0xfc000003) == 0x48000001; |
135 | } | 125 | } |
136 | 126 | ||
137 | static int test_offset(unsigned long offset) | ||
138 | { | ||
139 | return (offset + 0x2000000 > 0x3ffffff) || ((offset & 3) != 0); | ||
140 | } | ||
141 | |||
142 | static unsigned long find_bl_target(unsigned long ip, unsigned int op) | 127 | static unsigned long find_bl_target(unsigned long ip, unsigned int op) |
143 | { | 128 | { |
144 | static int offset; | 129 | static int offset; |
@@ -151,37 +136,30 @@ static unsigned long find_bl_target(unsigned long ip, unsigned int op) | |||
151 | return ip + (long)offset; | 136 | return ip + (long)offset; |
152 | } | 137 | } |
153 | 138 | ||
154 | static unsigned int branch_offset(unsigned long offset) | ||
155 | { | ||
156 | /* return "bl ip+offset" */ | ||
157 | return 0x48000001 | (offset & 0x03fffffc); | ||
158 | } | ||
159 | |||
160 | #ifdef CONFIG_PPC64 | 139 | #ifdef CONFIG_PPC64 |
161 | static int | 140 | static int |
162 | __ftrace_make_nop(struct module *mod, | 141 | __ftrace_make_nop(struct module *mod, |
163 | struct dyn_ftrace *rec, unsigned long addr) | 142 | struct dyn_ftrace *rec, unsigned long addr) |
164 | { | 143 | { |
165 | unsigned char replaced[MCOUNT_INSN_SIZE * 2]; | 144 | unsigned int op; |
166 | unsigned int *op = (unsigned *)&replaced; | 145 | unsigned int jmp[5]; |
167 | unsigned char jmp[8]; | 146 | unsigned long ptr; |
168 | unsigned long *ptr = (unsigned long *)&jmp; | ||
169 | unsigned long ip = rec->ip; | 147 | unsigned long ip = rec->ip; |
170 | unsigned long tramp; | 148 | unsigned long tramp; |
171 | int offset; | 149 | int offset; |
172 | 150 | ||
173 | /* read where this goes */ | 151 | /* read where this goes */ |
174 | if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE)) | 152 | if (probe_kernel_read(&op, (void *)ip, sizeof(int))) |
175 | return -EFAULT; | 153 | return -EFAULT; |
176 | 154 | ||
177 | /* Make sure that that this is still a 24bit jump */ | 155 | /* Make sure that that this is still a 24bit jump */ |
178 | if (!is_bl_op(*op)) { | 156 | if (!is_bl_op(op)) { |
179 | printk(KERN_ERR "Not expected bl: opcode is %x\n", *op); | 157 | printk(KERN_ERR "Not expected bl: opcode is %x\n", op); |
180 | return -EINVAL; | 158 | return -EINVAL; |
181 | } | 159 | } |
182 | 160 | ||
183 | /* lets find where the pointer goes */ | 161 | /* lets find where the pointer goes */ |
184 | tramp = find_bl_target(ip, *op); | 162 | tramp = find_bl_target(ip, op); |
185 | 163 | ||
186 | /* | 164 | /* |
187 | * On PPC64 the trampoline looks like: | 165 | * On PPC64 the trampoline looks like: |
@@ -200,19 +178,25 @@ __ftrace_make_nop(struct module *mod, | |||
200 | DEBUGP("ip:%lx jumps to %lx r2: %lx", ip, tramp, mod->arch.toc); | 178 | DEBUGP("ip:%lx jumps to %lx r2: %lx", ip, tramp, mod->arch.toc); |
201 | 179 | ||
202 | /* Find where the trampoline jumps to */ | 180 | /* Find where the trampoline jumps to */ |
203 | if (probe_kernel_read(jmp, (void *)tramp, 8)) { | 181 | if (probe_kernel_read(jmp, (void *)tramp, sizeof(jmp))) { |
204 | printk(KERN_ERR "Failed to read %lx\n", tramp); | 182 | printk(KERN_ERR "Failed to read %lx\n", tramp); |
205 | return -EFAULT; | 183 | return -EFAULT; |
206 | } | 184 | } |
207 | 185 | ||
208 | DEBUGP(" %08x %08x", | 186 | DEBUGP(" %08x %08x", jmp[0], jmp[1]); |
209 | (unsigned)(*ptr >> 32), | 187 | |
210 | (unsigned)*ptr); | 188 | /* verify that this is what we expect it to be */ |
189 | if (((jmp[0] & 0xffff0000) != 0x3d820000) || | ||
190 | ((jmp[1] & 0xffff0000) != 0x398c0000) || | ||
191 | (jmp[2] != 0xf8410028) || | ||
192 | (jmp[3] != 0xe96c0020) || | ||
193 | (jmp[4] != 0xe84c0028)) { | ||
194 | printk(KERN_ERR "Not a trampoline\n"); | ||
195 | return -EINVAL; | ||
196 | } | ||
211 | 197 | ||
212 | offset = (unsigned)jmp[2] << 24 | | 198 | offset = (unsigned)((unsigned short)jmp[0]) << 16 | |
213 | (unsigned)jmp[3] << 16 | | 199 | (unsigned)((unsigned short)jmp[1]); |
214 | (unsigned)jmp[6] << 8 | | ||
215 | (unsigned)jmp[7]; | ||
216 | 200 | ||
217 | DEBUGP(" %x ", offset); | 201 | DEBUGP(" %x ", offset); |
218 | 202 | ||
@@ -225,13 +209,13 @@ __ftrace_make_nop(struct module *mod, | |||
225 | return -EFAULT; | 209 | return -EFAULT; |
226 | } | 210 | } |
227 | 211 | ||
228 | DEBUGP(" %08x %08x\n", | 212 | DEBUGP(" %08x %08x\n", jmp[0], jmp[1]); |
229 | (unsigned)(*ptr >> 32), | 213 | |
230 | (unsigned)*ptr); | 214 | ptr = ((unsigned long)jmp[0] << 32) + jmp[1]; |
231 | 215 | ||
232 | /* This should match what was called */ | 216 | /* This should match what was called */ |
233 | if (*ptr != GET_ADDR(addr)) { | 217 | if (ptr != GET_ADDR(addr)) { |
234 | printk(KERN_ERR "addr does not match %lx\n", *ptr); | 218 | printk(KERN_ERR "addr does not match %lx\n", ptr); |
235 | return -EINVAL; | 219 | return -EINVAL; |
236 | } | 220 | } |
237 | 221 | ||
@@ -240,11 +224,11 @@ __ftrace_make_nop(struct module *mod, | |||
240 | * 0xe8, 0x41, 0x00, 0x28 ld r2,40(r1) | 224 | * 0xe8, 0x41, 0x00, 0x28 ld r2,40(r1) |
241 | * This needs to be turned to a nop too. | 225 | * This needs to be turned to a nop too. |
242 | */ | 226 | */ |
243 | if (probe_kernel_read(replaced, (void *)(ip+4), MCOUNT_INSN_SIZE)) | 227 | if (probe_kernel_read(&op, (void *)(ip+4), MCOUNT_INSN_SIZE)) |
244 | return -EFAULT; | 228 | return -EFAULT; |
245 | 229 | ||
246 | if (*op != 0xe8410028) { | 230 | if (op != 0xe8410028) { |
247 | printk(KERN_ERR "Next line is not ld! (%08x)\n", *op); | 231 | printk(KERN_ERR "Next line is not ld! (%08x)\n", op); |
248 | return -EINVAL; | 232 | return -EINVAL; |
249 | } | 233 | } |
250 | 234 | ||
@@ -261,11 +245,14 @@ __ftrace_make_nop(struct module *mod, | |||
261 | * ld r2,40(r1) | 245 | * ld r2,40(r1) |
262 | * 1: | 246 | * 1: |
263 | */ | 247 | */ |
264 | op[0] = 0x48000008; /* b +8 */ | 248 | op = 0x48000008; /* b +8 */ |
265 | 249 | ||
266 | if (probe_kernel_write((void *)ip, replaced, MCOUNT_INSN_SIZE)) | 250 | if (probe_kernel_write((void *)ip, &op, MCOUNT_INSN_SIZE)) |
267 | return -EPERM; | 251 | return -EPERM; |
268 | 252 | ||
253 | |||
254 | flush_icache_range(ip, ip + 8); | ||
255 | |||
269 | return 0; | 256 | return 0; |
270 | } | 257 | } |
271 | 258 | ||
@@ -274,46 +261,52 @@ static int | |||
274 | __ftrace_make_nop(struct module *mod, | 261 | __ftrace_make_nop(struct module *mod, |
275 | struct dyn_ftrace *rec, unsigned long addr) | 262 | struct dyn_ftrace *rec, unsigned long addr) |
276 | { | 263 | { |
277 | unsigned char replaced[MCOUNT_INSN_SIZE]; | 264 | unsigned int op; |
278 | unsigned int *op = (unsigned *)&replaced; | 265 | unsigned int jmp[4]; |
279 | unsigned char jmp[8]; | ||
280 | unsigned int *ptr = (unsigned int *)&jmp; | ||
281 | unsigned long ip = rec->ip; | 266 | unsigned long ip = rec->ip; |
282 | unsigned long tramp; | 267 | unsigned long tramp; |
283 | int offset; | ||
284 | 268 | ||
285 | if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE)) | 269 | if (probe_kernel_read(&op, (void *)ip, MCOUNT_INSN_SIZE)) |
286 | return -EFAULT; | 270 | return -EFAULT; |
287 | 271 | ||
288 | /* Make sure that that this is still a 24bit jump */ | 272 | /* Make sure that that this is still a 24bit jump */ |
289 | if (!is_bl_op(*op)) { | 273 | if (!is_bl_op(op)) { |
290 | printk(KERN_ERR "Not expected bl: opcode is %x\n", *op); | 274 | printk(KERN_ERR "Not expected bl: opcode is %x\n", op); |
291 | return -EINVAL; | 275 | return -EINVAL; |
292 | } | 276 | } |
293 | 277 | ||
294 | /* lets find where the pointer goes */ | 278 | /* lets find where the pointer goes */ |
295 | tramp = find_bl_target(ip, *op); | 279 | tramp = find_bl_target(ip, op); |
296 | 280 | ||
297 | /* | 281 | /* |
298 | * On PPC32 the trampoline looks like: | 282 | * On PPC32 the trampoline looks like: |
299 | * lis r11,sym@ha | 283 | * 0x3d, 0x60, 0x00, 0x00 lis r11,sym@ha |
300 | * addi r11,r11,sym@l | 284 | * 0x39, 0x6b, 0x00, 0x00 addi r11,r11,sym@l |
301 | * mtctr r11 | 285 | * 0x7d, 0x69, 0x03, 0xa6 mtctr r11 |
302 | * bctr | 286 | * 0x4e, 0x80, 0x04, 0x20 bctr |
303 | */ | 287 | */ |
304 | 288 | ||
305 | DEBUGP("ip:%lx jumps to %lx", ip, tramp); | 289 | DEBUGP("ip:%lx jumps to %lx", ip, tramp); |
306 | 290 | ||
307 | /* Find where the trampoline jumps to */ | 291 | /* Find where the trampoline jumps to */ |
308 | if (probe_kernel_read(jmp, (void *)tramp, 8)) { | 292 | if (probe_kernel_read(jmp, (void *)tramp, sizeof(jmp))) { |
309 | printk(KERN_ERR "Failed to read %lx\n", tramp); | 293 | printk(KERN_ERR "Failed to read %lx\n", tramp); |
310 | return -EFAULT; | 294 | return -EFAULT; |
311 | } | 295 | } |
312 | 296 | ||
313 | DEBUGP(" %08x %08x ", ptr[0], ptr[1]); | 297 | DEBUGP(" %08x %08x ", jmp[0], jmp[1]); |
298 | |||
299 | /* verify that this is what we expect it to be */ | ||
300 | if (((jmp[0] & 0xffff0000) != 0x3d600000) || | ||
301 | ((jmp[1] & 0xffff0000) != 0x396b0000) || | ||
302 | (jmp[2] != 0x7d6903a6) || | ||
303 | (jmp[3] != 0x4e800420)) { | ||
304 | printk(KERN_ERR "Not a trampoline\n"); | ||
305 | return -EINVAL; | ||
306 | } | ||
314 | 307 | ||
315 | tramp = (ptr[1] & 0xffff) | | 308 | tramp = (jmp[1] & 0xffff) | |
316 | ((ptr[0] & 0xffff) << 16); | 309 | ((jmp[0] & 0xffff) << 16); |
317 | if (tramp & 0x8000) | 310 | if (tramp & 0x8000) |
318 | tramp -= 0x10000; | 311 | tramp -= 0x10000; |
319 | 312 | ||
@@ -326,11 +319,13 @@ __ftrace_make_nop(struct module *mod, | |||
326 | return -EINVAL; | 319 | return -EINVAL; |
327 | } | 320 | } |
328 | 321 | ||
329 | op[0] = PPC_NOP_INSTR; | 322 | op = PPC_NOP_INSTR; |
330 | 323 | ||
331 | if (probe_kernel_write((void *)ip, replaced, MCOUNT_INSN_SIZE)) | 324 | if (probe_kernel_write((void *)ip, &op, MCOUNT_INSN_SIZE)) |
332 | return -EPERM; | 325 | return -EPERM; |
333 | 326 | ||
327 | flush_icache_range(ip, ip + 8); | ||
328 | |||
334 | return 0; | 329 | return 0; |
335 | } | 330 | } |
336 | #endif /* PPC64 */ | 331 | #endif /* PPC64 */ |
@@ -384,13 +379,11 @@ int ftrace_make_nop(struct module *mod, | |||
384 | static int | 379 | static int |
385 | __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | 380 | __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) |
386 | { | 381 | { |
387 | unsigned char replaced[MCOUNT_INSN_SIZE * 2]; | 382 | unsigned int op[2]; |
388 | unsigned int *op = (unsigned *)&replaced; | ||
389 | unsigned long ip = rec->ip; | 383 | unsigned long ip = rec->ip; |
390 | unsigned long offset; | ||
391 | 384 | ||
392 | /* read where this goes */ | 385 | /* read where this goes */ |
393 | if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE * 2)) | 386 | if (probe_kernel_read(op, (void *)ip, MCOUNT_INSN_SIZE * 2)) |
394 | return -EFAULT; | 387 | return -EFAULT; |
395 | 388 | ||
396 | /* | 389 | /* |
@@ -409,43 +402,40 @@ __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | |||
409 | return -EINVAL; | 402 | return -EINVAL; |
410 | } | 403 | } |
411 | 404 | ||
412 | /* now calculate a jump to the ftrace caller trampoline */ | 405 | /* create the branch to the trampoline */ |
413 | offset = rec->arch.mod->arch.tramp - ip; | 406 | op[0] = create_branch((unsigned int *)ip, |
414 | 407 | rec->arch.mod->arch.tramp, BRANCH_SET_LINK); | |
415 | if (test_offset(offset)) { | 408 | if (!op[0]) { |
416 | printk(KERN_ERR "REL24 %li out of range!\n", | 409 | printk(KERN_ERR "REL24 out of range!\n"); |
417 | (long int)offset); | ||
418 | return -EINVAL; | 410 | return -EINVAL; |
419 | } | 411 | } |
420 | 412 | ||
421 | /* Set to "bl addr" */ | ||
422 | op[0] = branch_offset(offset); | ||
423 | /* ld r2,40(r1) */ | 413 | /* ld r2,40(r1) */ |
424 | op[1] = 0xe8410028; | 414 | op[1] = 0xe8410028; |
425 | 415 | ||
426 | DEBUGP("write to %lx\n", rec->ip); | 416 | DEBUGP("write to %lx\n", rec->ip); |
427 | 417 | ||
428 | if (probe_kernel_write((void *)ip, replaced, MCOUNT_INSN_SIZE * 2)) | 418 | if (probe_kernel_write((void *)ip, op, MCOUNT_INSN_SIZE * 2)) |
429 | return -EPERM; | 419 | return -EPERM; |
430 | 420 | ||
421 | flush_icache_range(ip, ip + 8); | ||
422 | |||
431 | return 0; | 423 | return 0; |
432 | } | 424 | } |
433 | #else | 425 | #else |
434 | static int | 426 | static int |
435 | __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | 427 | __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) |
436 | { | 428 | { |
437 | unsigned char replaced[MCOUNT_INSN_SIZE]; | 429 | unsigned int op; |
438 | unsigned int *op = (unsigned *)&replaced; | ||
439 | unsigned long ip = rec->ip; | 430 | unsigned long ip = rec->ip; |
440 | unsigned long offset; | ||
441 | 431 | ||
442 | /* read where this goes */ | 432 | /* read where this goes */ |
443 | if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE)) | 433 | if (probe_kernel_read(&op, (void *)ip, MCOUNT_INSN_SIZE)) |
444 | return -EFAULT; | 434 | return -EFAULT; |
445 | 435 | ||
446 | /* It should be pointing to a nop */ | 436 | /* It should be pointing to a nop */ |
447 | if (op[0] != PPC_NOP_INSTR) { | 437 | if (op != PPC_NOP_INSTR) { |
448 | printk(KERN_ERR "Expected NOP but have %x\n", op[0]); | 438 | printk(KERN_ERR "Expected NOP but have %x\n", op); |
449 | return -EINVAL; | 439 | return -EINVAL; |
450 | } | 440 | } |
451 | 441 | ||
@@ -455,23 +445,21 @@ __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | |||
455 | return -EINVAL; | 445 | return -EINVAL; |
456 | } | 446 | } |
457 | 447 | ||
458 | /* now calculate a jump to the ftrace caller trampoline */ | 448 | /* create the branch to the trampoline */ |
459 | offset = rec->arch.mod->arch.tramp - ip; | 449 | op = create_branch((unsigned int *)ip, |
460 | 450 | rec->arch.mod->arch.tramp, BRANCH_SET_LINK); | |
461 | if (test_offset(offset)) { | 451 | if (!op) { |
462 | printk(KERN_ERR "REL24 %li out of range!\n", | 452 | printk(KERN_ERR "REL24 out of range!\n"); |
463 | (long int)offset); | ||
464 | return -EINVAL; | 453 | return -EINVAL; |
465 | } | 454 | } |
466 | 455 | ||
467 | /* Set to "bl addr" */ | ||
468 | op[0] = branch_offset(offset); | ||
469 | |||
470 | DEBUGP("write to %lx\n", rec->ip); | 456 | DEBUGP("write to %lx\n", rec->ip); |
471 | 457 | ||
472 | if (probe_kernel_write((void *)ip, replaced, MCOUNT_INSN_SIZE)) | 458 | if (probe_kernel_write((void *)ip, &op, MCOUNT_INSN_SIZE)) |
473 | return -EPERM; | 459 | return -EPERM; |
474 | 460 | ||
461 | flush_icache_range(ip, ip + 8); | ||
462 | |||
475 | return 0; | 463 | return 0; |
476 | } | 464 | } |
477 | #endif /* CONFIG_PPC64 */ | 465 | #endif /* CONFIG_PPC64 */ |