diff options
| author | Abhishek Sagar <sagar.abhishek@gmail.com> | 2008-06-01 12:17:30 -0400 |
|---|---|---|
| committer | Ingo Molnar <mingo@elte.hu> | 2008-06-10 05:56:57 -0400 |
| commit | 0eb967012ea15e6e8cfab483d9fa37bc602d400c (patch) | |
| tree | 0e9c026a2d83f313cdc3f9f235d58ff522cee090 | |
| parent | e0773410247f1e5fc6f7c52a4c5f3c6c9873d527 (diff) | |
ftrace: prevent freeing of all failed updates
Prevent freeing of records which cause problems and correspond to function from
core kernel text. A new flag, FTRACE_FL_CONVERTED is used to mark a record
as "converted". All other records are patched lazily to NOPs. Failed records
now also remain on frace_hash table. Each invocation of ftrace_record_ip now
checks whether the traced function has ever been recorded (including past
failures) and doesn't re-record it again.
Signed-off-by: Abhishek Sagar <sagar.abhishek@gmail.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
| -rw-r--r-- | include/linux/ftrace.h | 1 | ||||
| -rw-r--r-- | kernel/trace/ftrace.c | 76 |
2 files changed, 47 insertions, 30 deletions
diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h index 623819433ed5..20e14d0093c7 100644 --- a/include/linux/ftrace.h +++ b/include/linux/ftrace.h | |||
| @@ -49,6 +49,7 @@ enum { | |||
| 49 | FTRACE_FL_FILTER = (1 << 2), | 49 | FTRACE_FL_FILTER = (1 << 2), |
| 50 | FTRACE_FL_ENABLED = (1 << 3), | 50 | FTRACE_FL_ENABLED = (1 << 3), |
| 51 | FTRACE_FL_NOTRACE = (1 << 4), | 51 | FTRACE_FL_NOTRACE = (1 << 4), |
| 52 | FTRACE_FL_CONVERTED = (1 << 5), | ||
| 52 | }; | 53 | }; |
| 53 | 54 | ||
| 54 | struct dyn_ftrace { | 55 | struct dyn_ftrace { |
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index f762f5a2d331..ec54cb7d69d6 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c | |||
| @@ -216,6 +216,12 @@ ftrace_add_hash(struct dyn_ftrace *node, unsigned long key) | |||
| 216 | hlist_add_head_rcu(&node->node, &ftrace_hash[key]); | 216 | hlist_add_head_rcu(&node->node, &ftrace_hash[key]); |
| 217 | } | 217 | } |
| 218 | 218 | ||
| 219 | /* called from kstop_machine */ | ||
| 220 | static inline void ftrace_del_hash(struct dyn_ftrace *node) | ||
| 221 | { | ||
| 222 | hlist_del(&node->node); | ||
| 223 | } | ||
| 224 | |||
| 219 | static void ftrace_free_rec(struct dyn_ftrace *rec) | 225 | static void ftrace_free_rec(struct dyn_ftrace *rec) |
| 220 | { | 226 | { |
| 221 | /* no locking, only called from kstop_machine */ | 227 | /* no locking, only called from kstop_machine */ |
| @@ -332,12 +338,11 @@ ftrace_record_ip(unsigned long ip) | |||
| 332 | #define FTRACE_ADDR ((long)(ftrace_caller)) | 338 | #define FTRACE_ADDR ((long)(ftrace_caller)) |
| 333 | #define MCOUNT_ADDR ((long)(mcount)) | 339 | #define MCOUNT_ADDR ((long)(mcount)) |
| 334 | 340 | ||
| 335 | static void | 341 | static int |
| 336 | __ftrace_replace_code(struct dyn_ftrace *rec, | 342 | __ftrace_replace_code(struct dyn_ftrace *rec, |
| 337 | unsigned char *old, unsigned char *new, int enable) | 343 | unsigned char *old, unsigned char *new, int enable) |
| 338 | { | 344 | { |
| 339 | unsigned long ip, fl; | 345 | unsigned long ip, fl; |
| 340 | int failed; | ||
| 341 | 346 | ||
| 342 | ip = rec->ip; | 347 | ip = rec->ip; |
| 343 | 348 | ||
| @@ -364,7 +369,7 @@ __ftrace_replace_code(struct dyn_ftrace *rec, | |||
| 364 | 369 | ||
| 365 | if ((fl == (FTRACE_FL_FILTER | FTRACE_FL_ENABLED)) || | 370 | if ((fl == (FTRACE_FL_FILTER | FTRACE_FL_ENABLED)) || |
| 366 | (fl == 0) || (rec->flags & FTRACE_FL_NOTRACE)) | 371 | (fl == 0) || (rec->flags & FTRACE_FL_NOTRACE)) |
| 367 | return; | 372 | return 0; |
| 368 | 373 | ||
| 369 | /* | 374 | /* |
| 370 | * If it is enabled disable it, | 375 | * If it is enabled disable it, |
| @@ -388,7 +393,7 @@ __ftrace_replace_code(struct dyn_ftrace *rec, | |||
| 388 | */ | 393 | */ |
| 389 | fl = rec->flags & (FTRACE_FL_NOTRACE | FTRACE_FL_ENABLED); | 394 | fl = rec->flags & (FTRACE_FL_NOTRACE | FTRACE_FL_ENABLED); |
| 390 | if (fl == FTRACE_FL_NOTRACE) | 395 | if (fl == FTRACE_FL_NOTRACE) |
| 391 | return; | 396 | return 0; |
| 392 | 397 | ||
| 393 | new = ftrace_call_replace(ip, FTRACE_ADDR); | 398 | new = ftrace_call_replace(ip, FTRACE_ADDR); |
| 394 | } else | 399 | } else |
| @@ -396,34 +401,24 @@ __ftrace_replace_code(struct dyn_ftrace *rec, | |||
| 396 | 401 | ||
| 397 | if (enable) { | 402 | if (enable) { |
| 398 | if (rec->flags & FTRACE_FL_ENABLED) | 403 | if (rec->flags & FTRACE_FL_ENABLED) |
| 399 | return; | 404 | return 0; |
| 400 | rec->flags |= FTRACE_FL_ENABLED; | 405 | rec->flags |= FTRACE_FL_ENABLED; |
| 401 | } else { | 406 | } else { |
| 402 | if (!(rec->flags & FTRACE_FL_ENABLED)) | 407 | if (!(rec->flags & FTRACE_FL_ENABLED)) |
| 403 | return; | 408 | return 0; |
| 404 | rec->flags &= ~FTRACE_FL_ENABLED; | 409 | rec->flags &= ~FTRACE_FL_ENABLED; |
| 405 | } | 410 | } |
| 406 | } | 411 | } |
| 407 | 412 | ||
| 408 | failed = ftrace_modify_code(ip, old, new); | 413 | return ftrace_modify_code(ip, old, new); |
| 409 | if (failed) { | ||
| 410 | unsigned long key; | ||
| 411 | /* It is possible that the function hasn't been converted yet */ | ||
| 412 | key = hash_long(ip, FTRACE_HASHBITS); | ||
| 413 | if (!ftrace_ip_in_hash(ip, key)) { | ||
| 414 | rec->flags |= FTRACE_FL_FAILED; | ||
| 415 | ftrace_free_rec(rec); | ||
| 416 | } | ||
| 417 | |||
| 418 | } | ||
| 419 | } | 414 | } |
| 420 | 415 | ||
| 421 | static void ftrace_replace_code(int enable) | 416 | static void ftrace_replace_code(int enable) |
| 422 | { | 417 | { |
| 418 | int i, failed; | ||
| 423 | unsigned char *new = NULL, *old = NULL; | 419 | unsigned char *new = NULL, *old = NULL; |
| 424 | struct dyn_ftrace *rec; | 420 | struct dyn_ftrace *rec; |
| 425 | struct ftrace_page *pg; | 421 | struct ftrace_page *pg; |
| 426 | int i; | ||
| 427 | 422 | ||
| 428 | if (enable) | 423 | if (enable) |
| 429 | old = ftrace_nop_replace(); | 424 | old = ftrace_nop_replace(); |
| @@ -438,7 +433,15 @@ static void ftrace_replace_code(int enable) | |||
| 438 | if (rec->flags & FTRACE_FL_FAILED) | 433 | if (rec->flags & FTRACE_FL_FAILED) |
| 439 | continue; | 434 | continue; |
| 440 | 435 | ||
| 441 | __ftrace_replace_code(rec, old, new, enable); | 436 | failed = __ftrace_replace_code(rec, old, new, enable); |
| 437 | if (failed && (rec->flags & FTRACE_FL_CONVERTED)) { | ||
| 438 | rec->flags |= FTRACE_FL_FAILED; | ||
| 439 | if ((system_state == SYSTEM_BOOTING) || | ||
| 440 | !kernel_text_address(rec->ip)) { | ||
| 441 | ftrace_del_hash(rec); | ||
| 442 | ftrace_free_rec(rec); | ||
| 443 | } | ||
| 444 | } | ||
| 442 | } | 445 | } |
| 443 | } | 446 | } |
| 444 | } | 447 | } |
| @@ -467,7 +470,6 @@ ftrace_code_disable(struct dyn_ftrace *rec) | |||
| 467 | failed = ftrace_modify_code(ip, call, nop); | 470 | failed = ftrace_modify_code(ip, call, nop); |
| 468 | if (failed) { | 471 | if (failed) { |
| 469 | rec->flags |= FTRACE_FL_FAILED; | 472 | rec->flags |= FTRACE_FL_FAILED; |
| 470 | ftrace_free_rec(rec); | ||
| 471 | return 0; | 473 | return 0; |
| 472 | } | 474 | } |
| 473 | return 1; | 475 | return 1; |
| @@ -621,8 +623,7 @@ unsigned long ftrace_update_tot_cnt; | |||
| 621 | static int __ftrace_update_code(void *ignore) | 623 | static int __ftrace_update_code(void *ignore) |
| 622 | { | 624 | { |
| 623 | struct dyn_ftrace *p; | 625 | struct dyn_ftrace *p; |
| 624 | struct hlist_head head; | 626 | struct hlist_node *t, *n; |
| 625 | struct hlist_node *t; | ||
| 626 | int save_ftrace_enabled; | 627 | int save_ftrace_enabled; |
| 627 | cycle_t start, stop; | 628 | cycle_t start, stop; |
| 628 | int i; | 629 | int i; |
| @@ -637,18 +638,33 @@ static int __ftrace_update_code(void *ignore) | |||
| 637 | 638 | ||
| 638 | /* No locks needed, the machine is stopped! */ | 639 | /* No locks needed, the machine is stopped! */ |
| 639 | for (i = 0; i < FTRACE_HASHSIZE; i++) { | 640 | for (i = 0; i < FTRACE_HASHSIZE; i++) { |
| 640 | if (hlist_empty(&ftrace_hash[i])) | 641 | /* all CPUS are stopped, we are safe to modify code */ |
| 641 | continue; | 642 | hlist_for_each_entry_safe(p, t, n, &ftrace_hash[i], node) { |
| 643 | /* Skip over failed records which have not been | ||
| 644 | * freed. */ | ||
| 645 | if (p->flags & FTRACE_FL_FAILED) | ||
| 646 | continue; | ||
| 642 | 647 | ||
| 643 | head = ftrace_hash[i]; | 648 | /* Unconverted records are always at the head of the |
| 644 | INIT_HLIST_HEAD(&ftrace_hash[i]); | 649 | * hash bucket. Once we encounter a converted record, |
| 650 | * simply skip over to the next bucket. Saves ftraced | ||
| 651 | * some processor cycles (ftrace does its bid for | ||
| 652 | * global warming :-p ). */ | ||
| 653 | if (p->flags & (FTRACE_FL_CONVERTED)) | ||
| 654 | break; | ||
| 645 | 655 | ||
| 646 | /* all CPUS are stopped, we are safe to modify code */ | 656 | if (ftrace_code_disable(p)) { |
| 647 | hlist_for_each_entry(p, t, &head, node) { | 657 | p->flags |= FTRACE_FL_CONVERTED; |
| 648 | if (ftrace_code_disable(p)) | ||
| 649 | ftrace_update_cnt++; | 658 | ftrace_update_cnt++; |
| 650 | } | 659 | } else { |
| 660 | if ((system_state == SYSTEM_BOOTING) || | ||
| 661 | !kernel_text_address(p->ip)) { | ||
| 662 | ftrace_del_hash(p); | ||
| 663 | ftrace_free_rec(p); | ||
| 651 | 664 | ||
| 665 | } | ||
| 666 | } | ||
| 667 | } | ||
| 652 | } | 668 | } |
| 653 | 669 | ||
| 654 | stop = ftrace_now(raw_smp_processor_id()); | 670 | stop = ftrace_now(raw_smp_processor_id()); |
