diff options
author | Steven Rostedt (VMware) <rostedt@goodmis.org> | 2017-04-14 17:45:45 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-04-21 03:31:22 -0400 |
commit | 666452ffdbf700d8ca15d0b75070f3487ccae4fc (patch) | |
tree | 524cae7c3a2e48411bc3c8756634284d983c8e2b /kernel | |
parent | fdaa36c75c5a1bf7cdd2f463f8d320c98c6f0761 (diff) |
ftrace: Fix removing of second function probe
commit 82cc4fc2e70ec5baeff8f776f2773abc8b2cc0ae upstream.
When two function probes are added to set_ftrace_filter, and then one of
them is removed, the update to the function locations is not performed, and
the record keeping of the function states are corrupted, and causes an
ftrace_bug() to occur.
This is easily reproducable by adding two probes, removing one, and then
adding it back again.
# cd /sys/kernel/debug/tracing
# echo schedule:traceoff > set_ftrace_filter
# echo do_IRQ:traceoff > set_ftrace_filter
# echo \!do_IRQ:traceoff > /debug/tracing/set_ftrace_filter
# echo do_IRQ:traceoff > set_ftrace_filter
Causes:
------------[ cut here ]------------
WARNING: CPU: 2 PID: 1098 at kernel/trace/ftrace.c:2369 ftrace_get_addr_curr+0x143/0x220
Modules linked in: [...]
CPU: 2 PID: 1098 Comm: bash Not tainted 4.10.0-test+ #405
Hardware name: Hewlett-Packard HP Compaq Pro 6300 SFF/339A, BIOS K01 v02.05 05/07/2012
Call Trace:
dump_stack+0x68/0x9f
__warn+0x111/0x130
? trace_irq_work_interrupt+0xa0/0xa0
warn_slowpath_null+0x1d/0x20
ftrace_get_addr_curr+0x143/0x220
? __fentry__+0x10/0x10
ftrace_replace_code+0xe3/0x4f0
? ftrace_int3_handler+0x90/0x90
? printk+0x99/0xb5
? 0xffffffff81000000
ftrace_modify_all_code+0x97/0x110
arch_ftrace_update_code+0x10/0x20
ftrace_run_update_code+0x1c/0x60
ftrace_run_modify_code.isra.48.constprop.62+0x8e/0xd0
register_ftrace_function_probe+0x4b6/0x590
? ftrace_startup+0x310/0x310
? debug_lockdep_rcu_enabled.part.4+0x1a/0x30
? update_stack_state+0x88/0x110
? ftrace_regex_write.isra.43.part.44+0x1d3/0x320
? preempt_count_sub+0x18/0xd0
? mutex_lock_nested+0x104/0x800
? ftrace_regex_write.isra.43.part.44+0x1d3/0x320
? __unwind_start+0x1c0/0x1c0
? _mutex_lock_nest_lock+0x800/0x800
ftrace_trace_probe_callback.isra.3+0xc0/0x130
? func_set_flag+0xe0/0xe0
? __lock_acquire+0x642/0x1790
? __might_fault+0x1e/0x20
? trace_get_user+0x398/0x470
? strcmp+0x35/0x60
ftrace_trace_onoff_callback+0x48/0x70
ftrace_regex_write.isra.43.part.44+0x251/0x320
? match_records+0x420/0x420
ftrace_filter_write+0x2b/0x30
__vfs_write+0xd7/0x330
? do_loop_readv_writev+0x120/0x120
? locks_remove_posix+0x90/0x2f0
? do_lock_file_wait+0x160/0x160
? __lock_is_held+0x93/0x100
? rcu_read_lock_sched_held+0x5c/0xb0
? preempt_count_sub+0x18/0xd0
? __sb_start_write+0x10a/0x230
? vfs_write+0x222/0x240
vfs_write+0xef/0x240
SyS_write+0xab/0x130
? SyS_read+0x130/0x130
? trace_hardirqs_on_caller+0x182/0x280
? trace_hardirqs_on_thunk+0x1a/0x1c
entry_SYSCALL_64_fastpath+0x18/0xad
RIP: 0033:0x7fe61c157c30
RSP: 002b:00007ffe87890258 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: ffffffff8114a410 RCX: 00007fe61c157c30
RDX: 0000000000000010 RSI: 000055814798f5e0 RDI: 0000000000000001
RBP: ffff8800c9027f98 R08: 00007fe61c422740 R09: 00007fe61ca53700
R10: 0000000000000073 R11: 0000000000000246 R12: 0000558147a36400
R13: 00007ffe8788f160 R14: 0000000000000024 R15: 00007ffe8788f15c
? trace_hardirqs_off_caller+0xc0/0x110
---[ end trace 99fa09b3d9869c2c ]---
Bad trampoline accounting at: ffffffff81cc3b00 (do_IRQ+0x0/0x150)
Fixes: 59df055f1991 ("ftrace: trace different functions with a different tracer")
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/trace/ftrace.c | 20 |
1 files changed, 16 insertions, 4 deletions
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index da87b3cba5b3..3bd975c8d9c9 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c | |||
@@ -3736,23 +3736,24 @@ static void __enable_ftrace_function_probe(struct ftrace_ops_hash *old_hash) | |||
3736 | ftrace_probe_registered = 1; | 3736 | ftrace_probe_registered = 1; |
3737 | } | 3737 | } |
3738 | 3738 | ||
3739 | static void __disable_ftrace_function_probe(void) | 3739 | static bool __disable_ftrace_function_probe(void) |
3740 | { | 3740 | { |
3741 | int i; | 3741 | int i; |
3742 | 3742 | ||
3743 | if (!ftrace_probe_registered) | 3743 | if (!ftrace_probe_registered) |
3744 | return; | 3744 | return false; |
3745 | 3745 | ||
3746 | for (i = 0; i < FTRACE_FUNC_HASHSIZE; i++) { | 3746 | for (i = 0; i < FTRACE_FUNC_HASHSIZE; i++) { |
3747 | struct hlist_head *hhd = &ftrace_func_hash[i]; | 3747 | struct hlist_head *hhd = &ftrace_func_hash[i]; |
3748 | if (hhd->first) | 3748 | if (hhd->first) |
3749 | return; | 3749 | return false; |
3750 | } | 3750 | } |
3751 | 3751 | ||
3752 | /* no more funcs left */ | 3752 | /* no more funcs left */ |
3753 | ftrace_shutdown(&trace_probe_ops, 0); | 3753 | ftrace_shutdown(&trace_probe_ops, 0); |
3754 | 3754 | ||
3755 | ftrace_probe_registered = 0; | 3755 | ftrace_probe_registered = 0; |
3756 | return true; | ||
3756 | } | 3757 | } |
3757 | 3758 | ||
3758 | 3759 | ||
@@ -3882,6 +3883,7 @@ static void | |||
3882 | __unregister_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops, | 3883 | __unregister_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops, |
3883 | void *data, int flags) | 3884 | void *data, int flags) |
3884 | { | 3885 | { |
3886 | struct ftrace_ops_hash old_hash_ops; | ||
3885 | struct ftrace_func_entry *rec_entry; | 3887 | struct ftrace_func_entry *rec_entry; |
3886 | struct ftrace_func_probe *entry; | 3888 | struct ftrace_func_probe *entry; |
3887 | struct ftrace_func_probe *p; | 3889 | struct ftrace_func_probe *p; |
@@ -3893,6 +3895,7 @@ __unregister_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops, | |||
3893 | struct hlist_node *tmp; | 3895 | struct hlist_node *tmp; |
3894 | char str[KSYM_SYMBOL_LEN]; | 3896 | char str[KSYM_SYMBOL_LEN]; |
3895 | int i, ret; | 3897 | int i, ret; |
3898 | bool disabled; | ||
3896 | 3899 | ||
3897 | if (glob && (strcmp(glob, "*") == 0 || !strlen(glob))) | 3900 | if (glob && (strcmp(glob, "*") == 0 || !strlen(glob))) |
3898 | func_g.search = NULL; | 3901 | func_g.search = NULL; |
@@ -3911,6 +3914,10 @@ __unregister_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops, | |||
3911 | 3914 | ||
3912 | mutex_lock(&trace_probe_ops.func_hash->regex_lock); | 3915 | mutex_lock(&trace_probe_ops.func_hash->regex_lock); |
3913 | 3916 | ||
3917 | old_hash_ops.filter_hash = old_hash; | ||
3918 | /* Probes only have filters */ | ||
3919 | old_hash_ops.notrace_hash = NULL; | ||
3920 | |||
3914 | hash = alloc_and_copy_ftrace_hash(FTRACE_HASH_DEFAULT_BITS, *orig_hash); | 3921 | hash = alloc_and_copy_ftrace_hash(FTRACE_HASH_DEFAULT_BITS, *orig_hash); |
3915 | if (!hash) | 3922 | if (!hash) |
3916 | /* Hmm, should report this somehow */ | 3923 | /* Hmm, should report this somehow */ |
@@ -3948,12 +3955,17 @@ __unregister_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops, | |||
3948 | } | 3955 | } |
3949 | } | 3956 | } |
3950 | mutex_lock(&ftrace_lock); | 3957 | mutex_lock(&ftrace_lock); |
3951 | __disable_ftrace_function_probe(); | 3958 | disabled = __disable_ftrace_function_probe(); |
3952 | /* | 3959 | /* |
3953 | * Remove after the disable is called. Otherwise, if the last | 3960 | * Remove after the disable is called. Otherwise, if the last |
3954 | * probe is removed, a null hash means *all enabled*. | 3961 | * probe is removed, a null hash means *all enabled*. |
3955 | */ | 3962 | */ |
3956 | ret = ftrace_hash_move(&trace_probe_ops, 1, orig_hash, hash); | 3963 | ret = ftrace_hash_move(&trace_probe_ops, 1, orig_hash, hash); |
3964 | |||
3965 | /* still need to update the function call sites */ | ||
3966 | if (ftrace_enabled && !disabled) | ||
3967 | ftrace_run_modify_code(&trace_probe_ops, FTRACE_UPDATE_CALLS, | ||
3968 | &old_hash_ops); | ||
3957 | synchronize_sched(); | 3969 | synchronize_sched(); |
3958 | if (!ret) | 3970 | if (!ret) |
3959 | free_ftrace_hash_rcu(old_hash); | 3971 | free_ftrace_hash_rcu(old_hash); |