Add a small tailcall target/probe pair and a tailcalls subtest that
attaches fentry to the callee program.
The test first runs the callee directly and checks that fentry fires
exactly once, then runs the entry program, tail-calls into the callee
and checks that the same fentry also fires exactly once on the tail-call
path.
This covers both sides of the x86 change: direct entry must not
double-fire, while tail-called entry must now be observable.
Signed-off-by: Takeru Hayasaka <hayatake396@gmail.com>
---
.../selftests/bpf/prog_tests/tailcalls.c | 110 ++++++++++++++++++
.../bpf/progs/tailcall_fentry_probe.c | 16 +++
.../bpf/progs/tailcall_fentry_target.c | 27 +++++
3 files changed, 153 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c
create mode 100644 tools/testing/selftests/bpf/progs/tailcall_fentry_target.c
diff --git a/tools/testing/selftests/bpf/prog_tests/tailcalls.c b/tools/testing/selftests/bpf/prog_tests/tailcalls.c
index 7d534fde0af9..ac05df65b666 100644
--- a/tools/testing/selftests/bpf/prog_tests/tailcalls.c
+++ b/tools/testing/selftests/bpf/prog_tests/tailcalls.c
@@ -1113,6 +1113,114 @@ static void test_tailcall_bpf2bpf_fentry_entry(void)
bpf_object__close(tgt_obj);
}
+static void test_tailcall_fentry_tailcallee(void)
+{
+ struct bpf_object *tgt_obj = NULL, *fentry_obj = NULL;
+ struct bpf_map *prog_array, *data_map;
+ struct bpf_link *fentry_link = NULL;
+ struct bpf_program *prog;
+ int err, map_fd, callee_fd, main_fd, data_fd, i, val;
+ char buff[128] = {};
+
+ LIBBPF_OPTS(bpf_test_run_opts, topts,
+ .data_in = buff,
+ .data_size_in = sizeof(buff),
+ .repeat = 1,
+ );
+
+ err = bpf_prog_test_load("tailcall_fentry_target.bpf.o",
+ BPF_PROG_TYPE_SCHED_CLS,
+ &tgt_obj, &main_fd);
+ if (!ASSERT_OK(err, "load tgt_obj"))
+ return;
+
+ prog_array = bpf_object__find_map_by_name(tgt_obj, "jmp_table");
+ if (!ASSERT_OK_PTR(prog_array, "find jmp_table map"))
+ goto out;
+
+ map_fd = bpf_map__fd(prog_array);
+ if (!ASSERT_FALSE(map_fd < 0, "find jmp_table map fd"))
+ goto out;
+
+ prog = bpf_object__find_program_by_name(tgt_obj, "entry");
+ if (!ASSERT_OK_PTR(prog, "find entry prog"))
+ goto out;
+
+ main_fd = bpf_program__fd(prog);
+ if (!ASSERT_FALSE(main_fd < 0, "find entry prog fd"))
+ goto out;
+
+ prog = bpf_object__find_program_by_name(tgt_obj, "callee");
+ if (!ASSERT_OK_PTR(prog, "find callee prog"))
+ goto out;
+
+ callee_fd = bpf_program__fd(prog);
+ if (!ASSERT_FALSE(callee_fd < 0, "find callee prog fd"))
+ goto out;
+
+ i = 0;
+ err = bpf_map_update_elem(map_fd, &i, &callee_fd, BPF_ANY);
+ if (!ASSERT_OK(err, "update jmp_table"))
+ goto out;
+
+ fentry_obj = bpf_object__open_file("tailcall_fentry_probe.bpf.o", NULL);
+ if (!ASSERT_OK_PTR(fentry_obj, "open fentry_obj file"))
+ goto out;
+
+ prog = bpf_object__find_program_by_name(fentry_obj, "fentry_callee");
+ if (!ASSERT_OK_PTR(prog, "find fentry prog"))
+ goto out;
+
+ err = bpf_program__set_attach_target(prog, callee_fd, "callee");
+ if (!ASSERT_OK(err, "set_attach_target callee"))
+ goto out;
+
+ err = bpf_object__load(fentry_obj);
+ if (!ASSERT_OK(err, "load fentry_obj"))
+ goto out;
+
+ fentry_link = bpf_program__attach_trace(prog);
+ if (!ASSERT_OK_PTR(fentry_link, "attach_trace"))
+ goto out;
+
+ data_map = bpf_object__find_map_by_name(fentry_obj, ".bss");
+ if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map),
+ "find tailcall_fentry_probe.bss map"))
+ goto out;
+
+ data_fd = bpf_map__fd(data_map);
+ if (!ASSERT_FALSE(data_fd < 0,
+ "find tailcall_fentry_probe.bss map fd"))
+ goto out;
+
+ err = bpf_prog_test_run_opts(callee_fd, &topts);
+ ASSERT_OK(err, "direct callee");
+ ASSERT_EQ(topts.retval, 7, "direct callee retval");
+
+ i = 0;
+ err = bpf_map_lookup_elem(data_fd, &i, &val);
+ ASSERT_OK(err, "direct fentry count");
+ ASSERT_EQ(val, 1, "direct fentry count");
+
+ val = 0;
+ err = bpf_map_update_elem(data_fd, &i, &val, BPF_ANY);
+ ASSERT_OK(err, "reset fentry count");
+
+ err = bpf_prog_test_run_opts(main_fd, &topts);
+ ASSERT_OK(err, "tailcall");
+ ASSERT_EQ(topts.retval, 7, "tailcall retval");
+
+ i = 0;
+ err = bpf_map_lookup_elem(data_fd, &i, &val);
+ ASSERT_OK(err, "fentry count");
+ ASSERT_EQ(val, 1, "fentry count");
+
+out:
+ bpf_link__destroy(fentry_link);
+ bpf_object__close(fentry_obj);
+ bpf_object__close(tgt_obj);
+}
+
#define JMP_TABLE "/sys/fs/bpf/jmp_table"
static int poke_thread_exit;
@@ -1759,6 +1867,8 @@ void test_tailcalls(void)
test_tailcall_bpf2bpf_fentry_fexit();
if (test__start_subtest("tailcall_bpf2bpf_fentry_entry"))
test_tailcall_bpf2bpf_fentry_entry();
+ if (test__start_subtest("tailcall_fentry_tailcallee"))
+ test_tailcall_fentry_tailcallee();
if (test__start_subtest("tailcall_poke"))
test_tailcall_poke();
if (test__start_subtest("tailcall_bpf2bpf_hierarchy_1"))
diff --git a/tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c b/tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c
new file mode 100644
index 000000000000..b784aeffb316
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_fentry_probe.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+int count = 0;
+
+SEC("fentry/callee")
+int BPF_PROG(fentry_callee, struct sk_buff *skb)
+{
+ count++;
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tailcall_fentry_target.c b/tools/testing/selftests/bpf/progs/tailcall_fentry_target.c
new file mode 100644
index 000000000000..46da06ce323c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_fentry_target.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+
+#include <bpf/bpf_helpers.h>
+#include "bpf_legacy.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __uint(value_size, sizeof(__u32));
+} jmp_table SEC(".maps");
+
+SEC("tc")
+int callee(struct __sk_buff *skb)
+{
+ return 7;
+}
+
+SEC("tc")
+int entry(struct __sk_buff *skb)
+{
+ bpf_tail_call_static(skb, &jmp_table, 0);
+ return 0;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.43.0
© 2016 - 2026 Red Hat, Inc.