Masami, This series passed all my tests, are you comfortable with me pushing them to linux-next? -- Steve On Mon, 03 Jun 2024 15:07:04 -0400 Steven Rostedt <rostedt@xxxxxxxxxxx> wrote: > This is a continuation of the function graph multi user code. > I wrote a proof of concept back in 2019 of this code[1] and > Masami started cleaning it up. I started from Masami's work v10 > that can be found here: > > https://lore.kernel.org/linux-trace-kernel/171509088006.162236.7227326999861366050.stgit@devnote2/ > > This is *only* the code that allows multiple users of function > graph tracing. This is not the fprobe work that Masami is working > to add on top of it. As Masami took my proof of concept, there > was still several things I disliked about that code. Instead of > having Masami clean it up even more, I decided to take over on just > my code and change it up a bit. > > Changes since v2: https://lore.kernel.org/linux-trace-kernel/20240602033744.563858532@xxxxxxxxxxx > > - Added comments describing which hashes the append and intersect > functions were used for. > > - Replaced checks of (NULL or EMPTY_HASH) with ftrace_hash_empty() > helper function. > > - Added check at the end of intersect_hash() to convert the hash > to EMPTY hash if it doesn't have any functions. > > - Renamed compare_ops() to ops_equal() and return boolean (inversed return > value). > > - Broke out __ftrace_hash_move_and_update_ops() to use in both > ftrace_hash_move_and_update_ops() and ftrace_hash_move_and_update_subops(). > > Diff between last version at end of this email. > > Masami Hiramatsu (Google) (3): > function_graph: Handle tail calls for stack unwinding > function_graph: Use a simple LRU for fgraph_array index number > ftrace: Add multiple fgraph storage selftest > > Steven Rostedt (Google) (9): > ftrace: Add subops logic to allow one ops to manage many > ftrace: Allow subops filtering to be modified > function_graph: Add pid tracing back to function graph tracer > function_graph: Use for_each_set_bit() in __ftrace_return_to_handler() > function_graph: Use bitmask to loop on fgraph entry > function_graph: Use static_call and branch to optimize entry function > function_graph: Use static_call and branch to optimize return function > selftests/ftrace: Add function_graph tracer to func-filter-pid test > selftests/ftrace: Add fgraph-multi.tc test > > Steven Rostedt (VMware) (15): > function_graph: Convert ret_stack to a series of longs > fgraph: Use BUILD_BUG_ON() to make sure we have structures divisible by long > function_graph: Add an array structure that will allow multiple callbacks > function_graph: Allow multiple users to attach to function graph > function_graph: Remove logic around ftrace_graph_entry and return > ftrace/function_graph: Pass fgraph_ops to function graph callbacks > ftrace: Allow function_graph tracer to be enabled in instances > ftrace: Allow ftrace startup flags to exist without dynamic ftrace > function_graph: Have the instances use their own ftrace_ops for filtering > function_graph: Add "task variables" per task for fgraph_ops > function_graph: Move set_graph_function tests to shadow stack global var > function_graph: Move graph depth stored data to shadow stack global var > function_graph: Move graph notrace bit to shadow stack global var > function_graph: Implement fgraph_reserve_data() and fgraph_retrieve_data() > function_graph: Add selftest for passing local variables > > ---- > include/linux/ftrace.h | 43 +- > include/linux/sched.h | 2 +- > include/linux/trace_recursion.h | 39 - > kernel/trace/fgraph.c | 1044 ++++++++++++++++---- > kernel/trace/ftrace.c | 522 +++++++++- > kernel/trace/ftrace_internal.h | 5 +- > kernel/trace/trace.h | 94 +- > kernel/trace/trace_functions.c | 8 + > kernel/trace/trace_functions_graph.c | 96 +- > kernel/trace/trace_irqsoff.c | 10 +- > kernel/trace/trace_sched_wakeup.c | 10 +- > kernel/trace/trace_selftest.c | 259 ++++- > .../selftests/ftrace/test.d/ftrace/fgraph-multi.tc | 103 ++ > .../ftrace/test.d/ftrace/func-filter-pid.tc | 27 +- > 14 files changed, 1945 insertions(+), 317 deletions(-) > create mode 100644 tools/testing/selftests/ftrace/test.d/ftrace/fgraph-multi.tc > > > diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c > index 41fabc6d30e4..da7e6abf48b4 100644 > --- a/kernel/trace/ftrace.c > +++ b/kernel/trace/ftrace.c > @@ -3170,7 +3170,7 @@ int ftrace_shutdown(struct ftrace_ops *ops, int command) > /* Simply make a copy of @src and return it */ > static struct ftrace_hash *copy_hash(struct ftrace_hash *src) > { > - if (!src || src == EMPTY_HASH) > + if (ftrace_hash_empty(src)) > return EMPTY_HASH; > > return alloc_and_copy_ftrace_hash(src->size_bits, src); > @@ -3187,6 +3187,9 @@ static struct ftrace_hash *copy_hash(struct ftrace_hash *src) > * > * Otherwise, go through all of @new_hash and add anything that @hash > * doesn't already have, to @hash. > + * > + * The filter_hash updates uses just the append_hash() function > + * and the notrace_hash does not. > */ > static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash) > { > @@ -3195,11 +3198,11 @@ static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash) > int i; > > /* An empty hash does everything */ > - if (!*hash || *hash == EMPTY_HASH) > + if (ftrace_hash_empty(*hash)) > return 0; > > /* If new_hash has everything make hash have everything */ > - if (!new_hash || new_hash == EMPTY_HASH) { > + if (ftrace_hash_empty(new_hash)) { > free_ftrace_hash(*hash); > *hash = EMPTY_HASH; > return 0; > @@ -3217,7 +3220,12 @@ static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash) > return 0; > } > > -/* Add to @hash only those that are in both @new_hash1 and @new_hash2 */ > +/* > + * Add to @hash only those that are in both @new_hash1 and @new_hash2 > + * > + * The notrace_hash updates uses just the intersect_hash() function > + * and the filter_hash does not. > + */ > static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash1, > struct ftrace_hash *new_hash2) > { > @@ -3229,8 +3237,7 @@ static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_has > * If new_hash1 or new_hash2 is the EMPTY_HASH then make the hash > * empty as well as empty for notrace means none are notraced. > */ > - if (!new_hash1 || new_hash1 == EMPTY_HASH || > - !new_hash2 || new_hash2 == EMPTY_HASH) { > + if (ftrace_hash_empty(new_hash1) || ftrace_hash_empty(new_hash2)) { > free_ftrace_hash(*hash); > *hash = EMPTY_HASH; > return 0; > @@ -3245,6 +3252,11 @@ static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_has > return -ENOMEM; > } > } > + /* If nothing intersects, make it the empty set */ > + if (ftrace_hash_empty(*hash)) { > + free_ftrace_hash(*hash); > + *hash = EMPTY_HASH; > + } > return 0; > } > > @@ -3266,7 +3278,7 @@ static struct ftrace_hash *append_hashes(struct ftrace_ops *ops) > return NULL; > } > /* Nothing more to do if new_hash is empty */ > - if (new_hash == EMPTY_HASH) > + if (ftrace_hash_empty(new_hash)) > break; > } > return new_hash; > @@ -3300,59 +3312,76 @@ static struct ftrace_hash *intersect_hashes(struct ftrace_ops *ops) > return NULL; > } > /* Nothing more to do if new_hash is empty */ > - if (new_hash == EMPTY_HASH) > + if (ftrace_hash_empty(new_hash)) > break; > } > return new_hash; > } > > -/* Returns 0 on equal or non-zero on non-equal */ > -static int compare_ops(struct ftrace_hash *A, struct ftrace_hash *B) > +static bool ops_equal(struct ftrace_hash *A, struct ftrace_hash *B) > { > struct ftrace_func_entry *entry; > int size; > int i; > > - if (!A || A == EMPTY_HASH) > - return !(!B || B == EMPTY_HASH); > + if (ftrace_hash_empty(A)) > + return ftrace_hash_empty(B); > > - if (!B || B == EMPTY_HASH) > - return !(!A || A == EMPTY_HASH); > + if (ftrace_hash_empty(B)) > + return ftrace_hash_empty(A); > > if (A->count != B->count) > - return 1; > + return false; > > size = 1 << A->size_bits; > for (i = 0; i < size; i++) { > hlist_for_each_entry(entry, &A->buckets[i], hlist) { > if (!__ftrace_lookup_ip(B, entry->ip)) > - return 1; > + return false; > } > } > > - return 0; > + return true; > } > > -static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops, > - struct ftrace_hash **orig_hash, > - struct ftrace_hash *hash, > - int enable); > +static void ftrace_ops_update_code(struct ftrace_ops *ops, > + struct ftrace_ops_hash *old_hash); > + > +static int __ftrace_hash_move_and_update_ops(struct ftrace_ops *ops, > + struct ftrace_hash **orig_hash, > + struct ftrace_hash *hash, > + int enable) > +{ > + struct ftrace_ops_hash old_hash_ops; > + struct ftrace_hash *old_hash; > + int ret; > + > + old_hash = *orig_hash; > + old_hash_ops.filter_hash = ops->func_hash->filter_hash; > + old_hash_ops.notrace_hash = ops->func_hash->notrace_hash; > + ret = ftrace_hash_move(ops, enable, orig_hash, hash); > + if (!ret) { > + ftrace_ops_update_code(ops, &old_hash_ops); > + free_ftrace_hash_rcu(old_hash); > + } > + return ret; > +} > > static int ftrace_update_ops(struct ftrace_ops *ops, struct ftrace_hash *filter_hash, > struct ftrace_hash *notrace_hash) > { > int ret; > > - if (compare_ops(filter_hash, ops->func_hash->filter_hash)) { > - ret = ftrace_hash_move_and_update_ops(ops, &ops->func_hash->filter_hash, > - filter_hash, 1); > + if (!ops_equal(filter_hash, ops->func_hash->filter_hash)) { > + ret = __ftrace_hash_move_and_update_ops(ops, &ops->func_hash->filter_hash, > + filter_hash, 1); > if (ret < 0) > return ret; > } > > - if (compare_ops(notrace_hash, ops->func_hash->notrace_hash)) { > - ret = ftrace_hash_move_and_update_ops(ops, &ops->func_hash->notrace_hash, > - notrace_hash, 0); > + if (!ops_equal(notrace_hash, ops->func_hash->notrace_hash)) { > + ret = __ftrace_hash_move_and_update_ops(ops, &ops->func_hash->notrace_hash, > + notrace_hash, 0); > if (ret < 0) > return ret; > } > @@ -3438,8 +3467,8 @@ int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int > * o If either notrace_hash is empty then the final stays empty > * o Otherwise, the final is an intersection between the hashes > */ > - if (ops->func_hash->filter_hash == EMPTY_HASH || > - subops->func_hash->filter_hash == EMPTY_HASH) { > + if (ftrace_hash_empty(ops->func_hash->filter_hash) || > + ftrace_hash_empty(subops->func_hash->filter_hash)) { > filter_hash = EMPTY_HASH; > } else { > size_bits = max(ops->func_hash->filter_hash->size_bits, > @@ -3454,8 +3483,8 @@ int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int > } > } > > - if (ops->func_hash->notrace_hash == EMPTY_HASH || > - subops->func_hash->notrace_hash == EMPTY_HASH) { > + if (ftrace_hash_empty(ops->func_hash->notrace_hash) || > + ftrace_hash_empty(subops->func_hash->notrace_hash)) { > notrace_hash = EMPTY_HASH; > } else { > size_bits = max(ops->func_hash->filter_hash->size_bits, > @@ -3591,7 +3620,7 @@ static int ftrace_hash_move_and_update_subops(struct ftrace_ops *subops, > } > > /* Move the hash over to the new hash */ > - ret = ftrace_hash_move_and_update_ops(ops, orig_hash, new_hash, enable); > + ret = __ftrace_hash_move_and_update_ops(ops, orig_hash, new_hash, enable); > > free_ftrace_hash(new_hash); > > @@ -4822,11 +4851,6 @@ static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops, > struct ftrace_hash *hash, > int enable) > { > - struct ftrace_ops_hash old_hash_ops; > - struct ftrace_hash *old_hash; > - struct ftrace_ops *op; > - int ret; > - > if (ops->flags & FTRACE_OPS_FL_SUBOP) > return ftrace_hash_move_and_update_subops(ops, orig_hash, hash, enable); > > @@ -4838,6 +4862,8 @@ static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops, > * it will not affect subops that share it. > */ > if (!(ops->flags & FTRACE_OPS_FL_ENABLED)) { > + struct ftrace_ops *op; > + > /* Check if any other manager subops maps to this hash */ > do_for_each_ftrace_op(op, ftrace_ops_list) { > struct ftrace_ops *subops; > @@ -4851,15 +4877,7 @@ static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops, > } while_for_each_ftrace_op(op); > } > > - old_hash = *orig_hash; > - old_hash_ops.filter_hash = ops->func_hash->filter_hash; > - old_hash_ops.notrace_hash = ops->func_hash->notrace_hash; > - ret = ftrace_hash_move(ops, enable, orig_hash, hash); > - if (!ret) { > - ftrace_ops_update_code(ops, &old_hash_ops); > - free_ftrace_hash_rcu(old_hash); > - } > - return ret; > + return __ftrace_hash_move_and_update_ops(ops, orig_hash, hash, enable); > } > > static bool module_exists(const char *module)