diff options
Diffstat (limited to 'arch/x86/kernel/ftrace.c')
| -rw-r--r-- | arch/x86/kernel/ftrace.c | 73 |
1 files changed, 69 insertions, 4 deletions
diff --git a/arch/x86/kernel/ftrace.c b/arch/x86/kernel/ftrace.c index c3a7cb4bf6e6..1d414029f1d8 100644 --- a/arch/x86/kernel/ftrace.c +++ b/arch/x86/kernel/ftrace.c | |||
| @@ -206,6 +206,21 @@ static int | |||
| 206 | ftrace_modify_code(unsigned long ip, unsigned const char *old_code, | 206 | ftrace_modify_code(unsigned long ip, unsigned const char *old_code, |
| 207 | unsigned const char *new_code); | 207 | unsigned const char *new_code); |
| 208 | 208 | ||
| 209 | /* | ||
| 210 | * Should never be called: | ||
| 211 | * As it is only called by __ftrace_replace_code() which is called by | ||
| 212 | * ftrace_replace_code() that x86 overrides, and by ftrace_update_code() | ||
| 213 | * which is called to turn mcount into nops or nops into function calls | ||
| 214 | * but not to convert a function from not using regs to one that uses | ||
| 215 | * regs, which ftrace_modify_call() is for. | ||
| 216 | */ | ||
| 217 | int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr, | ||
| 218 | unsigned long addr) | ||
| 219 | { | ||
| 220 | WARN_ON(1); | ||
| 221 | return -EINVAL; | ||
| 222 | } | ||
| 223 | |||
| 209 | int ftrace_update_ftrace_func(ftrace_func_t func) | 224 | int ftrace_update_ftrace_func(ftrace_func_t func) |
| 210 | { | 225 | { |
| 211 | unsigned long ip = (unsigned long)(&ftrace_call); | 226 | unsigned long ip = (unsigned long)(&ftrace_call); |
| @@ -220,6 +235,14 @@ int ftrace_update_ftrace_func(ftrace_func_t func) | |||
| 220 | 235 | ||
| 221 | ret = ftrace_modify_code(ip, old, new); | 236 | ret = ftrace_modify_code(ip, old, new); |
| 222 | 237 | ||
| 238 | /* Also update the regs callback function */ | ||
| 239 | if (!ret) { | ||
| 240 | ip = (unsigned long)(&ftrace_regs_call); | ||
| 241 | memcpy(old, &ftrace_regs_call, MCOUNT_INSN_SIZE); | ||
| 242 | new = ftrace_call_replace(ip, (unsigned long)func); | ||
| 243 | ret = ftrace_modify_code(ip, old, new); | ||
| 244 | } | ||
| 245 | |||
| 223 | atomic_dec(&modifying_ftrace_code); | 246 | atomic_dec(&modifying_ftrace_code); |
| 224 | 247 | ||
| 225 | return ret; | 248 | return ret; |
| @@ -299,6 +322,32 @@ static int add_brk_on_nop(struct dyn_ftrace *rec) | |||
| 299 | return add_break(rec->ip, old); | 322 | return add_break(rec->ip, old); |
| 300 | } | 323 | } |
| 301 | 324 | ||
| 325 | /* | ||
| 326 | * If the record has the FTRACE_FL_REGS set, that means that it | ||
| 327 | * wants to convert to a callback that saves all regs. If FTRACE_FL_REGS | ||
| 328 | * is not not set, then it wants to convert to the normal callback. | ||
| 329 | */ | ||
| 330 | static unsigned long get_ftrace_addr(struct dyn_ftrace *rec) | ||
| 331 | { | ||
| 332 | if (rec->flags & FTRACE_FL_REGS) | ||
| 333 | return (unsigned long)FTRACE_REGS_ADDR; | ||
| 334 | else | ||
| 335 | return (unsigned long)FTRACE_ADDR; | ||
| 336 | } | ||
| 337 | |||
| 338 | /* | ||
| 339 | * The FTRACE_FL_REGS_EN is set when the record already points to | ||
| 340 | * a function that saves all the regs. Basically the '_EN' version | ||
| 341 | * represents the current state of the function. | ||
| 342 | */ | ||
| 343 | static unsigned long get_ftrace_old_addr(struct dyn_ftrace *rec) | ||
| 344 | { | ||
| 345 | if (rec->flags & FTRACE_FL_REGS_EN) | ||
| 346 | return (unsigned long)FTRACE_REGS_ADDR; | ||
| 347 | else | ||
| 348 | return (unsigned long)FTRACE_ADDR; | ||
| 349 | } | ||
| 350 | |||
| 302 | static int add_breakpoints(struct dyn_ftrace *rec, int enable) | 351 | static int add_breakpoints(struct dyn_ftrace *rec, int enable) |
| 303 | { | 352 | { |
| 304 | unsigned long ftrace_addr; | 353 | unsigned long ftrace_addr; |
| @@ -306,7 +355,7 @@ static int add_breakpoints(struct dyn_ftrace *rec, int enable) | |||
| 306 | 355 | ||
| 307 | ret = ftrace_test_record(rec, enable); | 356 | ret = ftrace_test_record(rec, enable); |
| 308 | 357 | ||
| 309 | ftrace_addr = (unsigned long)FTRACE_ADDR; | 358 | ftrace_addr = get_ftrace_addr(rec); |
| 310 | 359 | ||
| 311 | switch (ret) { | 360 | switch (ret) { |
| 312 | case FTRACE_UPDATE_IGNORE: | 361 | case FTRACE_UPDATE_IGNORE: |
| @@ -316,6 +365,10 @@ static int add_breakpoints(struct dyn_ftrace *rec, int enable) | |||
| 316 | /* converting nop to call */ | 365 | /* converting nop to call */ |
| 317 | return add_brk_on_nop(rec); | 366 | return add_brk_on_nop(rec); |
| 318 | 367 | ||
| 368 | case FTRACE_UPDATE_MODIFY_CALL_REGS: | ||
| 369 | case FTRACE_UPDATE_MODIFY_CALL: | ||
| 370 | ftrace_addr = get_ftrace_old_addr(rec); | ||
| 371 | /* fall through */ | ||
| 319 | case FTRACE_UPDATE_MAKE_NOP: | 372 | case FTRACE_UPDATE_MAKE_NOP: |
| 320 | /* converting a call to a nop */ | 373 | /* converting a call to a nop */ |
| 321 | return add_brk_on_call(rec, ftrace_addr); | 374 | return add_brk_on_call(rec, ftrace_addr); |
| @@ -360,13 +413,21 @@ static int remove_breakpoint(struct dyn_ftrace *rec) | |||
| 360 | * If not, don't touch the breakpoint, we make just create | 413 | * If not, don't touch the breakpoint, we make just create |
| 361 | * a disaster. | 414 | * a disaster. |
| 362 | */ | 415 | */ |
| 363 | ftrace_addr = (unsigned long)FTRACE_ADDR; | 416 | ftrace_addr = get_ftrace_addr(rec); |
| 417 | nop = ftrace_call_replace(ip, ftrace_addr); | ||
| 418 | |||
| 419 | if (memcmp(&ins[1], &nop[1], MCOUNT_INSN_SIZE - 1) == 0) | ||
| 420 | goto update; | ||
| 421 | |||
| 422 | /* Check both ftrace_addr and ftrace_old_addr */ | ||
| 423 | ftrace_addr = get_ftrace_old_addr(rec); | ||
| 364 | nop = ftrace_call_replace(ip, ftrace_addr); | 424 | nop = ftrace_call_replace(ip, ftrace_addr); |
| 365 | 425 | ||
| 366 | if (memcmp(&ins[1], &nop[1], MCOUNT_INSN_SIZE - 1) != 0) | 426 | if (memcmp(&ins[1], &nop[1], MCOUNT_INSN_SIZE - 1) != 0) |
| 367 | return -EINVAL; | 427 | return -EINVAL; |
| 368 | } | 428 | } |
| 369 | 429 | ||
| 430 | update: | ||
| 370 | return probe_kernel_write((void *)ip, &nop[0], 1); | 431 | return probe_kernel_write((void *)ip, &nop[0], 1); |
| 371 | } | 432 | } |
| 372 | 433 | ||
| @@ -405,12 +466,14 @@ static int add_update(struct dyn_ftrace *rec, int enable) | |||
| 405 | 466 | ||
| 406 | ret = ftrace_test_record(rec, enable); | 467 | ret = ftrace_test_record(rec, enable); |
| 407 | 468 | ||
| 408 | ftrace_addr = (unsigned long)FTRACE_ADDR; | 469 | ftrace_addr = get_ftrace_addr(rec); |
| 409 | 470 | ||
| 410 | switch (ret) { | 471 | switch (ret) { |
| 411 | case FTRACE_UPDATE_IGNORE: | 472 | case FTRACE_UPDATE_IGNORE: |
| 412 | return 0; | 473 | return 0; |
| 413 | 474 | ||
| 475 | case FTRACE_UPDATE_MODIFY_CALL_REGS: | ||
| 476 | case FTRACE_UPDATE_MODIFY_CALL: | ||
| 414 | case FTRACE_UPDATE_MAKE_CALL: | 477 | case FTRACE_UPDATE_MAKE_CALL: |
| 415 | /* converting nop to call */ | 478 | /* converting nop to call */ |
| 416 | return add_update_call(rec, ftrace_addr); | 479 | return add_update_call(rec, ftrace_addr); |
| @@ -455,12 +518,14 @@ static int finish_update(struct dyn_ftrace *rec, int enable) | |||
| 455 | 518 | ||
| 456 | ret = ftrace_update_record(rec, enable); | 519 | ret = ftrace_update_record(rec, enable); |
| 457 | 520 | ||
| 458 | ftrace_addr = (unsigned long)FTRACE_ADDR; | 521 | ftrace_addr = get_ftrace_addr(rec); |
| 459 | 522 | ||
| 460 | switch (ret) { | 523 | switch (ret) { |
| 461 | case FTRACE_UPDATE_IGNORE: | 524 | case FTRACE_UPDATE_IGNORE: |
| 462 | return 0; | 525 | return 0; |
| 463 | 526 | ||
| 527 | case FTRACE_UPDATE_MODIFY_CALL_REGS: | ||
| 528 | case FTRACE_UPDATE_MODIFY_CALL: | ||
| 464 | case FTRACE_UPDATE_MAKE_CALL: | 529 | case FTRACE_UPDATE_MAKE_CALL: |
| 465 | /* converting nop to call */ | 530 | /* converting nop to call */ |
| 466 | return finish_update_call(rec, ftrace_addr); | 531 | return finish_update_call(rec, ftrace_addr); |
