THIS_MODULE }
 
 extern struct kfunc_btf_id_list bpf_tcp_ca_kfunc_list;
+extern struct kfunc_btf_id_list prog_test_kfunc_list;
 
 #endif
 
        EXPORT_SYMBOL_GPL(name)
 
 DEFINE_KFUNC_BTF_ID_LIST(bpf_tcp_ca_kfunc_list);
+DEFINE_KFUNC_BTF_ID_LIST(prog_test_kfunc_list);
 
 /* Copyright (c) 2017 Facebook
  */
 #include <linux/bpf.h>
+#include <linux/btf.h>
 #include <linux/btf_ids.h>
 #include <linux/slab.h>
 #include <linux/vmalloc.h>
 
 bool bpf_prog_test_check_kfunc_call(u32 kfunc_id, struct module *owner)
 {
-       return btf_id_set_contains(&test_sk_kfunc_ids, kfunc_id);
+       if (btf_id_set_contains(&test_sk_kfunc_ids, kfunc_id))
+               return true;
+       return bpf_check_mod_kfunc_call(&prog_test_kfunc_list, kfunc_id, owner);
 }
 
 static void *bpf_test_init(const union bpf_attr *kattr, u32 size,
 
                linked_vars.skel.h linked_maps.skel.h
 
 LSKELS := kfunc_call_test.c fentry_test.c fexit_test.c fexit_sleep.c \
-       test_ksyms_module.c test_ringbuf.c atomics.c trace_printk.c \
-       trace_vprintk.c
+       test_ringbuf.c atomics.c trace_printk.c trace_vprintk.c
+# Generate both light skeleton and libbpf skeleton for these
+LSKELS_EXTRA := test_ksyms_module.c
 SKEL_BLACKLIST += $$(LSKELS)
 
 test_static_linked.skel.h-deps := test_static_linked1.o test_static_linked2.o
 TRUNNER_BPF_SKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.skel.h,      \
                                 $$(filter-out $(SKEL_BLACKLIST) $(LINKED_BPF_SRCS),\
                                               $$(TRUNNER_BPF_SRCS)))
-TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS))
+TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS) $$(LSKELS_EXTRA))
 TRUNNER_BPF_SKELS_LINKED := $$(addprefix $$(TRUNNER_OUTPUT)/,$(LINKED_SKELS))
 TEST_GEN_FILES += $$(TRUNNER_BPF_OBJS)
 
 
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright (c) 2020 Facebook */
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
 #include <linux/error-injection.h>
 #include <linux/init.h>
 #include <linux/module.h>
 
 DEFINE_PER_CPU(int, bpf_testmod_ksym_percpu) = 123;
 
+noinline void
+bpf_testmod_test_mod_kfunc(int i)
+{
+       *(int *)this_cpu_ptr(&bpf_testmod_ksym_percpu) = i;
+}
+
 noinline int bpf_testmod_loop_test(int n)
 {
        int i, sum = 0;
        .write = bpf_testmod_test_write,
 };
 
+BTF_SET_START(bpf_testmod_kfunc_ids)
+BTF_ID(func, bpf_testmod_test_mod_kfunc)
+BTF_SET_END(bpf_testmod_kfunc_ids)
+
+static DEFINE_KFUNC_BTF_ID_SET(&bpf_testmod_kfunc_ids, bpf_testmod_kfunc_btf_set);
+
 static int bpf_testmod_init(void)
 {
-       return sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
+       int ret;
+
+       ret = sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
+       if (ret)
+               return ret;
+       register_kfunc_btf_id_set(&prog_test_kfunc_list, &bpf_testmod_kfunc_btf_set);
+       return 0;
 }
 
 static void bpf_testmod_exit(void)
 {
+       unregister_kfunc_btf_id_set(&prog_test_kfunc_list, &bpf_testmod_kfunc_btf_set);
        return sysfs_remove_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
 }
 
 
 /* Copyright (c) 2021 Facebook */
 
 #include <test_progs.h>
-#include <bpf/libbpf.h>
-#include <bpf/btf.h>
+#include <network_helpers.h>
 #include "test_ksyms_module.lskel.h"
 
-static int duration;
-
 void test_ksyms_module(void)
 {
-       struct test_ksyms_module* skel;
+       struct test_ksyms_module *skel;
+       int retval;
        int err;
 
-       skel = test_ksyms_module__open_and_load();
-       if (CHECK(!skel, "skel_open", "failed to open skeleton\n"))
+       if (!env.has_testmod) {
+               test__skip();
                return;
+       }
 
-       err = test_ksyms_module__attach(skel);
-       if (CHECK(err, "skel_attach", "skeleton attach failed: %d\n", err))
+       skel = test_ksyms_module__open_and_load();
+       if (!ASSERT_OK_PTR(skel, "test_ksyms_module__open_and_load"))
+               return;
+       err = bpf_prog_test_run(skel->progs.load.prog_fd, 1, &pkt_v4, sizeof(pkt_v4),
+                               NULL, NULL, (__u32 *)&retval, NULL);
+       if (!ASSERT_OK(err, "bpf_prog_test_run"))
                goto cleanup;
-
-       usleep(1);
-
-       ASSERT_EQ(skel->bss->triggered, true, "triggered");
-       ASSERT_EQ(skel->bss->out_mod_ksym_global, 123, "global_ksym_val");
-
+       ASSERT_EQ(retval, 0, "retval");
+       ASSERT_EQ(skel->bss->out_bpf_testmod_ksym, 42, "bpf_testmod_ksym");
 cleanup:
        test_ksyms_module__destroy(skel);
 }
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <network_helpers.h>
+#include "test_ksyms_module.skel.h"
+
+void test_ksyms_module_libbpf(void)
+{
+       struct test_ksyms_module *skel;
+       int retval, err;
+
+       if (!env.has_testmod) {
+               test__skip();
+               return;
+       }
+
+       skel = test_ksyms_module__open_and_load();
+       if (!ASSERT_OK_PTR(skel, "test_ksyms_module__open"))
+               return;
+       err = bpf_prog_test_run(bpf_program__fd(skel->progs.load), 1, &pkt_v4,
+                               sizeof(pkt_v4), NULL, NULL, (__u32 *)&retval, NULL);
+       if (!ASSERT_OK(err, "bpf_prog_test_run"))
+               goto cleanup;
+       ASSERT_EQ(retval, 0, "retval");
+       ASSERT_EQ(skel->bss->out_bpf_testmod_ksym, 42, "bpf_testmod_ksym");
+cleanup:
+       test_ksyms_module__destroy(skel);
+}
 
 /* Copyright (c) 2021 Facebook */
 
 #include "vmlinux.h"
-
 #include <bpf/bpf_helpers.h>
 
+#define X_0(x)
+#define X_1(x) x X_0(x)
+#define X_2(x) x X_1(x)
+#define X_3(x) x X_2(x)
+#define X_4(x) x X_3(x)
+#define X_5(x) x X_4(x)
+#define X_6(x) x X_5(x)
+#define X_7(x) x X_6(x)
+#define X_8(x) x X_7(x)
+#define X_9(x) x X_8(x)
+#define X_10(x) x X_9(x)
+#define REPEAT_256(Y) X_2(X_10(X_10(Y))) X_5(X_10(Y)) X_6(Y)
+
 extern const int bpf_testmod_ksym_percpu __ksym;
+extern void bpf_testmod_test_mod_kfunc(int i) __ksym;
+extern void bpf_testmod_invalid_mod_kfunc(void) __ksym __weak;
 
-int out_mod_ksym_global = 0;
-bool triggered = false;
+int out_bpf_testmod_ksym = 0;
+const volatile int x = 0;
 
-SEC("raw_tp/sys_enter")
-int handler(const void *ctx)
+SEC("tc")
+int load(struct __sk_buff *skb)
 {
-       int *val;
-       __u32 cpu;
-
-       val = (int *)bpf_this_cpu_ptr(&bpf_testmod_ksym_percpu);
-       out_mod_ksym_global = *val;
-       triggered = true;
+       /* This will be kept by clang, but removed by verifier. Since it is
+        * marked as __weak, libbpf and gen_loader don't error out if BTF ID
+        * is not found for it, instead imm and off is set to 0 for it.
+        */
+       if (x)
+               bpf_testmod_invalid_mod_kfunc();
+       bpf_testmod_test_mod_kfunc(42);
+       out_bpf_testmod_ksym = *(int *)bpf_this_cpu_ptr(&bpf_testmod_ksym_percpu);
+       return 0;
+}
 
+SEC("tc")
+int load_256(struct __sk_buff *skb)
+{
+       /* this will fail if kfunc doesn't reuse its own btf fd index */
+       REPEAT_256(bpf_testmod_test_mod_kfunc(42););
+       bpf_testmod_test_mod_kfunc(42);
        return 0;
 }
 
 
+{
+       "calls: invalid kfunc call not eliminated",
+       .insns = {
+       BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 0, 0),
+       BPF_MOV64_IMM(BPF_REG_0, 1),
+       BPF_EXIT_INSN(),
+       },
+       .prog_type = BPF_PROG_TYPE_TRACEPOINT,
+       .result  = REJECT,
+       .errstr = "invalid kernel function call not eliminated in verifier pass",
+},
+{
+       "calls: invalid kfunc call unreachable",
+       .insns = {
+       BPF_MOV64_IMM(BPF_REG_0, 1),
+       BPF_JMP_IMM(BPF_JGT, BPF_REG_0, 0, 2),
+       BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 0, 0),
+       BPF_MOV64_IMM(BPF_REG_0, 1),
+       BPF_EXIT_INSN(),
+       },
+       .prog_type = BPF_PROG_TYPE_TRACEPOINT,
+       .result  = ACCEPT,
+},
 {
        "calls: basic sanity",
        .insns = {