On Sun, 11 Sep 2022 05:28:04 +0800 David Gow <davidgow@xxxxxxxxxx> wrote: > +++ b/lib/kunit/ftrace_stub.c > @@ -0,0 +1,137 @@ > +// SPDX-License-Identifier: GPL-2.0 > +#include <kunit/test.h> > + > +#include <linux/typecheck.h> > + > +#include <linux/ftrace.h> > +#include <linux/sched.h> > + > +struct kunit_ftrace_stub_ctx { > + struct kunit *test; > + unsigned long real_fn_addr; /* used as a key to lookup the stub */ > + unsigned long replacement_addr; > + struct ftrace_ops ops; /* a copy of kunit_stub_base_ops with .private set */ > +}; > + > +static void kunit_stub_trampoline(unsigned long ip, unsigned long parent_ip, > + struct ftrace_ops *ops, > + struct ftrace_regs *fregs) > +{ > + struct kunit_ftrace_stub_ctx *ctx = ops->private; > + int lock_bit; > + > + if (current->kunit_test != ctx->test) > + return; > + > + lock_bit = ftrace_test_recursion_trylock(ip, parent_ip); > + KUNIT_ASSERT_GE(ctx->test, lock_bit, 0); > + > + ftrace_instruction_pointer_set(fregs, ctx->replacement_addr); > + > + ftrace_test_recursion_unlock(lock_bit); > +} > + > +static struct ftrace_ops kunit_stub_base_ops = { > + .func = &kunit_stub_trampoline, > + .flags = FTRACE_OPS_FL_IPMODIFY | > +#ifndef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS > + FTRACE_OPS_FL_SAVE_REGS | > +#endif > + FTRACE_OPS_FL_DYNAMIC > +}; > + > +static void __kunit_ftrace_stub_resource_free(struct kunit_resource *res) > +{ > + struct kunit_ftrace_stub_ctx *ctx = res->data; > + > + unregister_ftrace_function(&ctx->ops); > + kfree(ctx); > +} > + > +/* Matching function for kunit_find_resource(). match_data is real_fn_addr. */ > +static bool __kunit_ftrace_stub_resource_match(struct kunit *test, > + struct kunit_resource *res, > + void *match_real_fn_addr) > +{ > + /* This pointer is only valid if res is a static stub resource. */ > + struct kunit_ftrace_stub_ctx *ctx = res->data; > + > + /* Make sure the resource is a static stub resource. */ > + if (res->free != &__kunit_ftrace_stub_resource_free) > + return false; > + > + return ctx->real_fn_addr == (unsigned long)match_real_fn_addr; > +} > + > +void kunit_deactivate_ftrace_stub(struct kunit *test, void *real_fn_addr) > +{ > + struct kunit_resource *res; > + > + KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL, > + "Tried to deactivate a NULL stub."); > + > + /* Look up the existing stub for this function. */ > + res = kunit_find_resource(test, > + __kunit_ftrace_stub_resource_match, > + real_fn_addr); > + > + /* Error out if the stub doesn't exist. */ > + KUNIT_ASSERT_PTR_NE_MSG(test, res, NULL, > + "Tried to deactivate a nonexistent stub."); > + > + /* Free the stub. We 'put' twice, as we got a reference > + * from kunit_find_resource(). The free function will deactivate the > + * ftrace stub. > + */ > + kunit_remove_resource(test, res); > + kunit_put_resource(res); > +} > +EXPORT_SYMBOL_GPL(kunit_deactivate_ftrace_stub); > + > +void __kunit_activate_ftrace_stub(struct kunit *test, > + const char *name, > + void *real_fn_addr, > + void *replacement_addr) > +{ > + unsigned long ftrace_ip; > + struct kunit_ftrace_stub_ctx *ctx; > + int ret; > + > + ftrace_ip = ftrace_location((unsigned long)real_fn_addr); > + if (!ftrace_ip) > + KUNIT_ASSERT_FAILURE(test, "%s ip is invalid: not a function, or is marked notrace or inline", name); > + > + /* Allocate the stub context, which contains pointers to the replacement > + * function and the test object. It's also registered as a KUnit > + * resource which can be looked up by address (to deactivate manually) > + * and is destroyed automatically on test exit. > + */ > + ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); > + KUNIT_ASSERT_NOT_ERR_OR_NULL_MSG(test, ctx, "failed to allocate kunit stub for %s", name); > + > + ctx->test = test; > + ctx->ops = kunit_stub_base_ops; > + ctx->ops.private = ctx; > + ctx->real_fn_addr = (unsigned long)real_fn_addr; > + ctx->replacement_addr = (unsigned long)replacement_addr; > + > + ret = ftrace_set_filter_ip(&ctx->ops, ftrace_ip, 0, 0); > + if (ret) { > + kfree(ctx); > + KUNIT_ASSERT_FAILURE(test, "failed to set filter ip for %s: %d", name, ret); I don't know the KUNIT_ASSERT content, but I'm guessing that any failure is just to crash the kernel or something where you do not need to bail out of the function? > + } > + > + ret = register_ftrace_function(&ctx->ops); > + if (ret) { > + kfree(ctx); > + if (ret == -EBUSY) > + KUNIT_ASSERT_FAILURE(test, "failed to register stub (-EBUSY) for %s, likely due to already stubbing it?", name); > + KUNIT_ASSERT_FAILURE(test, "failed to register stub for %s: %d", name, ret); > + } > + > + /* Register the stub as a resource with a cleanup function */ > + kunit_alloc_resource(test, NULL, > + __kunit_ftrace_stub_resource_free, > + GFP_KERNEL, ctx); And I'm also guessing that there's no race concern with registering the free resource after enabling the function code? Other than that, looks good to me. Reviewed-by: Steven Rostedt (Google) <rostedt@xxxxxxxxxxx> -- Steve > +}