Skip to content

Commit b8c8c14

Browse files
committed
Merge tag 'ftrace-v6.14-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace
Pull tracing fixes from Steven Rostedt: "Function graph accounting fixes: - Fix the manage ops hashes The function graph registers a "manager ops" and "sub-ops" to ftrace. The manager ops does not have any callback but calls the sub-ops callbacks. The manage ops hashes (what is used to tell ftrace what functions to attach to) is built on the sub-ops it manages. There was an error in the way it built the hash. An empty hash means to attach to all functions. When the manager ops had one sub-ops it properly copied its hash. But when the manager ops had more than one sub-ops, it went into a loop to make a set of all functions it needed to add to the hash. If any of the subops hashes was empty, that would mean to attach to all functions. The error was that the first iteration of the loop passed in an empty hash to start with in order to add the other hashes. That starting hash was mistaken as to attach to all functions. This made the manage ops attach to all functions whenever it had two or more sub-ops, even if each sub-op was attached to only a single function. - Do not add duplicate entries to the manager ops hash If two or more subops hashes trace the same function, an entry for that function will be added to the manager ops for each subops. This causes waste and extra overhead. Fprobe accounting fixes: - Remove last function from fprobe hash Fprobes has a ftrace hash to manage which functions an fprobe is attached to. It also has a counter of how many fprobes are attached. When the last fprobe is removed, it unregisters the fprobe from ftrace but does not remove the functions the last fprobe was attached to from the hash. This leaves the old functions attached. When a new fprobe is added, the fprobe infrastructure attaches to not only the functions of the new fprobe, but also to the functions of the last fprobe. - Fix accounting of the fprobe counter When a fprobe is added, it updates a counter. If the counter goes from zero to one, it attaches its ops to ftrace. When an fprobe is removed, the counter is decremented. If the counter goes from 1 to zero, it removes the fprobes ops from ftrace. There was an issue where if two fprobes trace the same function, the addition of each fprobe would increment the counter. But when removing the first of the fprobes, it would notice that another fprobe is still attached to one of its functions no it does not remove the functions from the ftrace ops. But it also did not decrement the counter, so when the last fprobe is removed, the counter is still one. This leaves the fprobes callback still registered with ftrace and it being called by the functions defined by the fprobes ops hash. Worse yet, because all the functions from the fprobe ops hash have been removed, that tells ftrace that it wants to trace all functions. Thus, this puts the state of the system where every function is calling the fprobe callback handler (which does nothing as there are no registered fprobes), but this causes a good 13% slow down of the entire system. Other updates: - Add a selftest to test the above issues to prevent regressions. - Fix preempt count accounting in function tracing Better recursion protection was added to function tracing which added another layer of preempt disable. As the preempt_count gets traced in the event, it needs to subtract the amount of preempt disabling the tracer does to record what the preempt_count was when the trace was triggered. - Fix memory leak in output of set_event A variable is passed by the seq_file functions in the location that is set by the return of the next() function. The start() function allocates it and the stop() function frees it. But when the last item is found, the next() returns NULL which leaks the data that was allocated in start(). The m->private is used for something else, so have next() free the data when it returns NULL, as stop() will then just receive NULL in that case" * tag 'ftrace-v6.14-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: tracing: Fix memory leak when reading set_event file ftrace: Correct preemption accounting for function tracing. selftests/ftrace: Update fprobe test to check enabled_functions file fprobe: Fix accounting of when to unregister from function graph fprobe: Always unregister fgraph function from ops ftrace: Do not add duplicate entries in subops manager ops ftrace: Fix accounting of adding subops to a manager ops
2 parents ff202c5 + 2fa6a01 commit b8c8c14

File tree

5 files changed

+95
-24
lines changed

5 files changed

+95
-24
lines changed

kernel/trace/fprobe.c

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -403,13 +403,12 @@ static void fprobe_graph_remove_ips(unsigned long *addrs, int num)
403403
lockdep_assert_held(&fprobe_mutex);
404404

405405
fprobe_graph_active--;
406-
if (!fprobe_graph_active) {
407-
/* Q: should we unregister it ? */
406+
/* Q: should we unregister it ? */
407+
if (!fprobe_graph_active)
408408
unregister_ftrace_graph(&fprobe_graph_ops);
409-
return;
410-
}
411409

412-
ftrace_set_filter_ips(&fprobe_graph_ops.ops, addrs, num, 1, 0);
410+
if (num)
411+
ftrace_set_filter_ips(&fprobe_graph_ops.ops, addrs, num, 1, 0);
413412
}
414413

415414
static int symbols_cmp(const void *a, const void *b)
@@ -679,8 +678,7 @@ int unregister_fprobe(struct fprobe *fp)
679678
}
680679
del_fprobe_hash(fp);
681680

682-
if (count)
683-
fprobe_graph_remove_ips(addrs, count);
681+
fprobe_graph_remove_ips(addrs, count);
684682

685683
kfree_rcu(hlist_array, rcu);
686684
fp->hlist_array = NULL;

kernel/trace/ftrace.c

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3220,15 +3220,22 @@ static struct ftrace_hash *copy_hash(struct ftrace_hash *src)
32203220
* The filter_hash updates uses just the append_hash() function
32213221
* and the notrace_hash does not.
32223222
*/
3223-
static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash)
3223+
static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash,
3224+
int size_bits)
32243225
{
32253226
struct ftrace_func_entry *entry;
32263227
int size;
32273228
int i;
32283229

3229-
/* An empty hash does everything */
3230-
if (ftrace_hash_empty(*hash))
3231-
return 0;
3230+
if (*hash) {
3231+
/* An empty hash does everything */
3232+
if (ftrace_hash_empty(*hash))
3233+
return 0;
3234+
} else {
3235+
*hash = alloc_ftrace_hash(size_bits);
3236+
if (!*hash)
3237+
return -ENOMEM;
3238+
}
32323239

32333240
/* If new_hash has everything make hash have everything */
32343241
if (ftrace_hash_empty(new_hash)) {
@@ -3292,16 +3299,18 @@ static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_has
32923299
/* Return a new hash that has a union of all @ops->filter_hash entries */
32933300
static struct ftrace_hash *append_hashes(struct ftrace_ops *ops)
32943301
{
3295-
struct ftrace_hash *new_hash;
3302+
struct ftrace_hash *new_hash = NULL;
32963303
struct ftrace_ops *subops;
3304+
int size_bits;
32973305
int ret;
32983306

3299-
new_hash = alloc_ftrace_hash(ops->func_hash->filter_hash->size_bits);
3300-
if (!new_hash)
3301-
return NULL;
3307+
if (ops->func_hash->filter_hash)
3308+
size_bits = ops->func_hash->filter_hash->size_bits;
3309+
else
3310+
size_bits = FTRACE_HASH_DEFAULT_BITS;
33023311

33033312
list_for_each_entry(subops, &ops->subop_list, list) {
3304-
ret = append_hash(&new_hash, subops->func_hash->filter_hash);
3313+
ret = append_hash(&new_hash, subops->func_hash->filter_hash, size_bits);
33053314
if (ret < 0) {
33063315
free_ftrace_hash(new_hash);
33073316
return NULL;
@@ -3310,7 +3319,8 @@ static struct ftrace_hash *append_hashes(struct ftrace_ops *ops)
33103319
if (ftrace_hash_empty(new_hash))
33113320
break;
33123321
}
3313-
return new_hash;
3322+
/* Can't return NULL as that means this failed */
3323+
return new_hash ? : EMPTY_HASH;
33143324
}
33153325

33163326
/* Make @ops trace evenything except what all its subops do not trace */
@@ -3505,7 +3515,8 @@ int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int
35053515
filter_hash = alloc_and_copy_ftrace_hash(size_bits, ops->func_hash->filter_hash);
35063516
if (!filter_hash)
35073517
return -ENOMEM;
3508-
ret = append_hash(&filter_hash, subops->func_hash->filter_hash);
3518+
ret = append_hash(&filter_hash, subops->func_hash->filter_hash,
3519+
size_bits);
35093520
if (ret < 0) {
35103521
free_ftrace_hash(filter_hash);
35113522
return ret;
@@ -5707,6 +5718,9 @@ __ftrace_match_addr(struct ftrace_hash *hash, unsigned long ip, int remove)
57075718
return -ENOENT;
57085719
free_hash_entry(hash, entry);
57095720
return 0;
5721+
} else if (__ftrace_lookup_ip(hash, ip) != NULL) {
5722+
/* Already exists */
5723+
return 0;
57105724
}
57115725

57125726
entry = add_hash_entry(hash, ip);

kernel/trace/trace_events.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,13 @@ s_next(struct seq_file *m, void *v, loff_t *pos)
15911591
return iter;
15921592
#endif
15931593

1594+
/*
1595+
* The iter is allocated in s_start() and passed via the 'v'
1596+
* parameter. To stop the iterator, NULL must be returned. But
1597+
* the return value is what the 'v' parameter in s_stop() receives
1598+
* and frees. Free iter here as it will no longer be used.
1599+
*/
1600+
kfree(iter);
15941601
return NULL;
15951602
}
15961603

@@ -1667,9 +1674,9 @@ static int s_show(struct seq_file *m, void *v)
16671674
}
16681675
#endif
16691676

1670-
static void s_stop(struct seq_file *m, void *p)
1677+
static void s_stop(struct seq_file *m, void *v)
16711678
{
1672-
kfree(p);
1679+
kfree(v);
16731680
t_stop(m, NULL);
16741681
}
16751682

kernel/trace/trace_functions.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ function_trace_call(unsigned long ip, unsigned long parent_ip,
216216

217217
parent_ip = function_get_true_parent_ip(parent_ip, fregs);
218218

219-
trace_ctx = tracing_gen_ctx();
219+
trace_ctx = tracing_gen_ctx_dec();
220220

221221
data = this_cpu_ptr(tr->array_buffer.data);
222222
if (!atomic_read(&data->disabled))
@@ -321,7 +321,6 @@ function_no_repeats_trace_call(unsigned long ip, unsigned long parent_ip,
321321
struct trace_array *tr = op->private;
322322
struct trace_array_cpu *data;
323323
unsigned int trace_ctx;
324-
unsigned long flags;
325324
int bit;
326325

327326
if (unlikely(!tr->function_enabled))
@@ -347,8 +346,7 @@ function_no_repeats_trace_call(unsigned long ip, unsigned long parent_ip,
347346
if (is_repeat_check(tr, last_info, ip, parent_ip))
348347
goto out;
349348

350-
local_save_flags(flags);
351-
trace_ctx = tracing_gen_ctx_flags(flags);
349+
trace_ctx = tracing_gen_ctx_dec();
352350
process_repeats(tr, ip, parent_ip, last_info, trace_ctx);
353351

354352
trace_function(tr, ip, parent_ip, trace_ctx);

tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe.tc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,38 @@ echo 0 > events/enable
77
echo > dynamic_events
88

99
PLACE=$FUNCTION_FORK
10+
PLACE2="kmem_cache_free"
11+
PLACE3="schedule_timeout"
1012

1113
echo "f:myevent1 $PLACE" >> dynamic_events
14+
15+
# Make sure the event is attached and is the only one
16+
grep -q $PLACE enabled_functions
17+
cnt=`cat enabled_functions | wc -l`
18+
if [ $cnt -ne 1 ]; then
19+
exit_fail
20+
fi
21+
1222
echo "f:myevent2 $PLACE%return" >> dynamic_events
1323

24+
# It should till be the only attached function
25+
cnt=`cat enabled_functions | wc -l`
26+
if [ $cnt -ne 1 ]; then
27+
exit_fail
28+
fi
29+
30+
# add another event
31+
echo "f:myevent3 $PLACE2" >> dynamic_events
32+
33+
grep -q $PLACE2 enabled_functions
34+
cnt=`cat enabled_functions | wc -l`
35+
if [ $cnt -ne 2 ]; then
36+
exit_fail
37+
fi
38+
1439
grep -q myevent1 dynamic_events
1540
grep -q myevent2 dynamic_events
41+
grep -q myevent3 dynamic_events
1642
test -d events/fprobes/myevent1
1743
test -d events/fprobes/myevent2
1844

@@ -21,6 +47,34 @@ echo "-:myevent2" >> dynamic_events
2147
grep -q myevent1 dynamic_events
2248
! grep -q myevent2 dynamic_events
2349

50+
# should still have 2 left
51+
cnt=`cat enabled_functions | wc -l`
52+
if [ $cnt -ne 2 ]; then
53+
exit_fail
54+
fi
55+
2456
echo > dynamic_events
2557

58+
# Should have none left
59+
cnt=`cat enabled_functions | wc -l`
60+
if [ $cnt -ne 0 ]; then
61+
exit_fail
62+
fi
63+
64+
echo "f:myevent4 $PLACE" >> dynamic_events
65+
66+
# Should only have one enabled
67+
cnt=`cat enabled_functions | wc -l`
68+
if [ $cnt -ne 1 ]; then
69+
exit_fail
70+
fi
71+
72+
echo > dynamic_events
73+
74+
# Should have none left
75+
cnt=`cat enabled_functions | wc -l`
76+
if [ $cnt -ne 0 ]; then
77+
exit_fail
78+
fi
79+
2680
clear_trace

0 commit comments

Comments
 (0)