root/tools/testing/selftests/bpf/prog_tests/tailcalls.c
// SPDX-License-Identifier: GPL-2.0
#include <unistd.h>
#include <test_progs.h>
#include <network_helpers.h>
#include "tailcall_poke.skel.h"
#include "tailcall_bpf2bpf_hierarchy2.skel.h"
#include "tailcall_bpf2bpf_hierarchy3.skel.h"
#include "tailcall_freplace.skel.h"
#include "tc_bpf2bpf.skel.h"
#include "tailcall_fail.skel.h"
#include "tailcall_sleepable.skel.h"

/* test_tailcall_1 checks basic functionality by patching multiple locations
 * in a single program for a single tail call slot with nop->jmp, jmp->nop
 * and jmp->jmp rewrites. Also checks for nop->nop.
 */
static void test_tailcall_1(void)
{
        int err, map_fd, prog_fd, main_fd, i, j;
        struct bpf_map *prog_array;
        struct bpf_program *prog;
        struct bpf_object *obj;
        char prog_name[32];
        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("tailcall1.bpf.o", BPF_PROG_TYPE_SCHED_CLS, &obj,
                                 &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                err = bpf_prog_test_run_opts(main_fd, &topts);
                ASSERT_OK(err, "tailcall");
                ASSERT_EQ(topts.retval, i, "tailcall retval");

                err = bpf_map_delete_elem(map_fd, &i);
                if (CHECK_FAIL(err))
                        goto out;
        }

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 3, "tailcall retval");

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_OK(topts.retval, "tailcall retval");

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                j = bpf_map__max_entries(prog_array) - 1 - i;
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", j);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                j = bpf_map__max_entries(prog_array) - 1 - i;

                err = bpf_prog_test_run_opts(main_fd, &topts);
                ASSERT_OK(err, "tailcall");
                ASSERT_EQ(topts.retval, j, "tailcall retval");

                err = bpf_map_delete_elem(map_fd, &i);
                if (CHECK_FAIL(err))
                        goto out;
        }

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 3, "tailcall retval");

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                err = bpf_map_delete_elem(map_fd, &i);
                if (CHECK_FAIL(err >= 0 || errno != ENOENT))
                        goto out;

                err = bpf_prog_test_run_opts(main_fd, &topts);
                ASSERT_OK(err, "tailcall");
                ASSERT_EQ(topts.retval, 3, "tailcall retval");
        }

out:
        bpf_object__close(obj);
}

/* test_tailcall_2 checks that patching multiple programs for a single
 * tail call slot works. It also jumps through several programs and tests
 * the tail call limit counter.
 */
static void test_tailcall_2(void)
{
        int err, map_fd, prog_fd, main_fd, i;
        struct bpf_map *prog_array;
        struct bpf_program *prog;
        struct bpf_object *obj;
        char prog_name[32];
        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("tailcall2.bpf.o", BPF_PROG_TYPE_SCHED_CLS, &obj,
                                 &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 2, "tailcall retval");

        i = 2;
        err = bpf_map_delete_elem(map_fd, &i);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 1, "tailcall retval");

        i = 0;
        err = bpf_map_delete_elem(map_fd, &i);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 3, "tailcall retval");
out:
        bpf_object__close(obj);
}

static void test_tailcall_count(const char *which, bool test_fentry,
                                bool test_fexit)
{
        struct bpf_object *obj = NULL, *fentry_obj = NULL, *fexit_obj = NULL;
        struct bpf_link *fentry_link = NULL, *fexit_link = NULL;
        int err, map_fd, prog_fd, main_fd, data_fd, i, val;
        struct bpf_map *prog_array, *data_map;
        struct bpf_program *prog;
        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(which, BPF_PROG_TYPE_SCHED_CLS, &obj,
                            &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        prog = bpf_object__find_program_by_name(obj, "classifier_0");
        if (CHECK_FAIL(!prog))
                goto out;

        prog_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(prog_fd < 0))
                goto out;

        i = 0;
        err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
        if (CHECK_FAIL(err))
                goto out;

        if (test_fentry) {
                fentry_obj = bpf_object__open_file("tailcall_bpf2bpf_fentry.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");
                if (!ASSERT_OK_PTR(prog, "find fentry prog"))
                        goto out;

                err = bpf_program__set_attach_target(prog, prog_fd,
                                                     "subprog_tail");
                if (!ASSERT_OK(err, "set_attach_target subprog_tail"))
                        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;
        }

        if (test_fexit) {
                fexit_obj = bpf_object__open_file("tailcall_bpf2bpf_fexit.bpf.o",
                                                  NULL);
                if (!ASSERT_OK_PTR(fexit_obj, "open fexit_obj file"))
                        goto out;

                prog = bpf_object__find_program_by_name(fexit_obj, "fexit");
                if (!ASSERT_OK_PTR(prog, "find fexit prog"))
                        goto out;

                err = bpf_program__set_attach_target(prog, prog_fd,
                                                     "subprog_tail");
                if (!ASSERT_OK(err, "set_attach_target subprog_tail"))
                        goto out;

                err = bpf_object__load(fexit_obj);
                if (!ASSERT_OK(err, "load fexit_obj"))
                        goto out;

                fexit_link = bpf_program__attach_trace(prog);
                if (!ASSERT_OK_PTR(fexit_link, "attach_trace"))
                        goto out;
        }

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 1, "tailcall retval");

        data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
        if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
                goto out;

        data_fd = bpf_map__fd(data_map);
        if (CHECK_FAIL(data_fd < 0))
                goto out;

        i = 0;
        err = bpf_map_lookup_elem(data_fd, &i, &val);
        ASSERT_OK(err, "tailcall count");
        ASSERT_EQ(val, 33, "tailcall count");

        if (test_fentry) {
                data_map = bpf_object__find_map_by_name(fentry_obj, ".bss");
                if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map),
                                  "find tailcall_bpf2bpf_fentry.bss map"))
                        goto out;

                data_fd = bpf_map__fd(data_map);
                if (!ASSERT_FALSE(data_fd < 0,
                                  "find tailcall_bpf2bpf_fentry.bss map fd"))
                        goto out;

                i = 0;
                err = bpf_map_lookup_elem(data_fd, &i, &val);
                ASSERT_OK(err, "fentry count");
                ASSERT_EQ(val, 33, "fentry count");
        }

        if (test_fexit) {
                data_map = bpf_object__find_map_by_name(fexit_obj, ".bss");
                if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map),
                                  "find tailcall_bpf2bpf_fexit.bss map"))
                        goto out;

                data_fd = bpf_map__fd(data_map);
                if (!ASSERT_FALSE(data_fd < 0,
                                  "find tailcall_bpf2bpf_fexit.bss map fd"))
                        goto out;

                i = 0;
                err = bpf_map_lookup_elem(data_fd, &i, &val);
                ASSERT_OK(err, "fexit count");
                ASSERT_EQ(val, 33, "fexit count");
        }

        i = 0;
        err = bpf_map_delete_elem(map_fd, &i);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_OK(topts.retval, "tailcall retval");
out:
        bpf_link__destroy(fentry_link);
        bpf_link__destroy(fexit_link);
        bpf_object__close(fentry_obj);
        bpf_object__close(fexit_obj);
        bpf_object__close(obj);
}

/* test_tailcall_3 checks that the count value of the tail call limit
 * enforcement matches with expectations. JIT uses direct jump.
 */
static void test_tailcall_3(void)
{
        test_tailcall_count("tailcall3.bpf.o", false, false);
}

/* test_tailcall_6 checks that the count value of the tail call limit
 * enforcement matches with expectations. JIT uses indirect jump.
 */
static void test_tailcall_6(void)
{
        test_tailcall_count("tailcall6.bpf.o", false, false);
}

/* test_tailcall_4 checks that the kernel properly selects indirect jump
 * for the case where the key is not known. Latter is passed via global
 * data to select different targets we can compare return value of.
 */
static void test_tailcall_4(void)
{
        int err, map_fd, prog_fd, main_fd, data_fd, i;
        struct bpf_map *prog_array, *data_map;
        struct bpf_program *prog;
        struct bpf_object *obj;
        static const int zero = 0;
        char buff[128] = {};
        char prog_name[32];
        LIBBPF_OPTS(bpf_test_run_opts, topts,
                .data_in = buff,
                .data_size_in = sizeof(buff),
                .repeat = 1,
        );

        err = bpf_prog_test_load("tailcall4.bpf.o", BPF_PROG_TYPE_SCHED_CLS, &obj,
                                 &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
        if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
                goto out;

        data_fd = bpf_map__fd(data_map);
        if (CHECK_FAIL(data_fd < 0))
                goto out;

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                err = bpf_map_update_elem(data_fd, &zero, &i, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;

                err = bpf_prog_test_run_opts(main_fd, &topts);
                ASSERT_OK(err, "tailcall");
                ASSERT_EQ(topts.retval, i, "tailcall retval");
        }

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                err = bpf_map_update_elem(data_fd, &zero, &i, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;

                err = bpf_map_delete_elem(map_fd, &i);
                if (CHECK_FAIL(err))
                        goto out;

                err = bpf_prog_test_run_opts(main_fd, &topts);
                ASSERT_OK(err, "tailcall");
                ASSERT_EQ(topts.retval, 3, "tailcall retval");
        }
out:
        bpf_object__close(obj);
}

/* test_tailcall_5 probes similarly to test_tailcall_4 that the kernel generates
 * an indirect jump when the keys are const but different from different branches.
 */
static void test_tailcall_5(void)
{
        int err, map_fd, prog_fd, main_fd, data_fd, i, key[] = { 1111, 1234, 5678 };
        struct bpf_map *prog_array, *data_map;
        struct bpf_program *prog;
        struct bpf_object *obj;
        static const int zero = 0;
        char buff[128] = {};
        char prog_name[32];
        LIBBPF_OPTS(bpf_test_run_opts, topts,
                .data_in = buff,
                .data_size_in = sizeof(buff),
                .repeat = 1,
        );

        err = bpf_prog_test_load("tailcall5.bpf.o", BPF_PROG_TYPE_SCHED_CLS, &obj,
                                 &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
        if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
                goto out;

        data_fd = bpf_map__fd(data_map);
        if (CHECK_FAIL(data_fd < 0))
                goto out;

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                err = bpf_map_update_elem(data_fd, &zero, &key[i], BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;

                err = bpf_prog_test_run_opts(main_fd, &topts);
                ASSERT_OK(err, "tailcall");
                ASSERT_EQ(topts.retval, i, "tailcall retval");
        }

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                err = bpf_map_update_elem(data_fd, &zero, &key[i], BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;

                err = bpf_map_delete_elem(map_fd, &i);
                if (CHECK_FAIL(err))
                        goto out;

                err = bpf_prog_test_run_opts(main_fd, &topts);
                ASSERT_OK(err, "tailcall");
                ASSERT_EQ(topts.retval, 3, "tailcall retval");
        }
out:
        bpf_object__close(obj);
}

/* test_tailcall_bpf2bpf_1 purpose is to make sure that tailcalls are working
 * correctly in correlation with BPF subprograms
 */
static void test_tailcall_bpf2bpf_1(void)
{
        int err, map_fd, prog_fd, main_fd, i;
        struct bpf_map *prog_array;
        struct bpf_program *prog;
        struct bpf_object *obj;
        char prog_name[32];
        LIBBPF_OPTS(bpf_test_run_opts, topts,
                .data_in = &pkt_v4,
                .data_size_in = sizeof(pkt_v4),
                .repeat = 1,
        );

        err = bpf_prog_test_load("tailcall_bpf2bpf1.bpf.o", BPF_PROG_TYPE_SCHED_CLS,
                                 &obj, &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        /* nop -> jmp */
        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 1, "tailcall retval");

        /* jmp -> nop, call subprog that will do tailcall */
        i = 1;
        err = bpf_map_delete_elem(map_fd, &i);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_OK(topts.retval, "tailcall retval");

        /* make sure that subprog can access ctx and entry prog that
         * called this subprog can properly return
         */
        i = 0;
        err = bpf_map_delete_elem(map_fd, &i);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, sizeof(pkt_v4) * 2, "tailcall retval");
out:
        bpf_object__close(obj);
}

/* test_tailcall_bpf2bpf_2 checks that the count value of the tail call limit
 * enforcement matches with expectations when tailcall is preceded with
 * bpf2bpf call.
 */
static void test_tailcall_bpf2bpf_2(void)
{
        int err, map_fd, prog_fd, main_fd, data_fd, i, val;
        struct bpf_map *prog_array, *data_map;
        struct bpf_program *prog;
        struct bpf_object *obj;
        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_bpf2bpf2.bpf.o", BPF_PROG_TYPE_SCHED_CLS,
                                 &obj, &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        prog = bpf_object__find_program_by_name(obj, "classifier_0");
        if (CHECK_FAIL(!prog))
                goto out;

        prog_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(prog_fd < 0))
                goto out;

        i = 0;
        err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 1, "tailcall retval");

        data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
        if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
                goto out;

        data_fd = bpf_map__fd(data_map);
        if (CHECK_FAIL(data_fd < 0))
                goto out;

        i = 0;
        err = bpf_map_lookup_elem(data_fd, &i, &val);
        ASSERT_OK(err, "tailcall count");
        ASSERT_EQ(val, 33, "tailcall count");

        i = 0;
        err = bpf_map_delete_elem(map_fd, &i);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_OK(topts.retval, "tailcall retval");
out:
        bpf_object__close(obj);
}

/* test_tailcall_bpf2bpf_3 checks that non-trivial amount of stack (up to
 * 256 bytes) can be used within bpf subprograms that have the tailcalls
 * in them
 */
static void test_tailcall_bpf2bpf_3(void)
{
        int err, map_fd, prog_fd, main_fd, i;
        struct bpf_map *prog_array;
        struct bpf_program *prog;
        struct bpf_object *obj;
        char prog_name[32];
        LIBBPF_OPTS(bpf_test_run_opts, topts,
                .data_in = &pkt_v4,
                .data_size_in = sizeof(pkt_v4),
                .repeat = 1,
        );

        err = bpf_prog_test_load("tailcall_bpf2bpf3.bpf.o", BPF_PROG_TYPE_SCHED_CLS,
                                 &obj, &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, sizeof(pkt_v4) * 3, "tailcall retval");

        i = 1;
        err = bpf_map_delete_elem(map_fd, &i);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, sizeof(pkt_v4), "tailcall retval");

        i = 0;
        err = bpf_map_delete_elem(map_fd, &i);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, sizeof(pkt_v4) * 2, "tailcall retval");
out:
        bpf_object__close(obj);
}

#include "tailcall_bpf2bpf4.skel.h"

/* test_tailcall_bpf2bpf_4 checks that tailcall counter is correctly preserved
 * across tailcalls combined with bpf2bpf calls. for making sure that tailcall
 * counter behaves correctly, bpf program will go through following flow:
 *
 * entry -> entry_subprog -> tailcall0 -> bpf_func0 -> subprog0 ->
 * -> tailcall1 -> bpf_func1 -> subprog1 -> tailcall2 -> bpf_func2 ->
 * subprog2 [here bump global counter] --------^
 *
 * We go through first two tailcalls and start counting from the subprog2 where
 * the loop begins. At the end of the test make sure that the global counter is
 * equal to 31, because tailcall counter includes the first two tailcalls
 * whereas global counter is incremented only on loop presented on flow above.
 *
 * The noise parameter is used to insert bpf_map_update calls into the logic
 * to force verifier to patch instructions. This allows us to ensure jump
 * logic remains correct with instruction movement.
 */
static void test_tailcall_bpf2bpf_4(bool noise)
{
        int err, map_fd, prog_fd, main_fd, data_fd, i;
        struct tailcall_bpf2bpf4__bss val;
        struct bpf_map *prog_array, *data_map;
        struct bpf_program *prog;
        struct bpf_object *obj;
        char prog_name[32];
        LIBBPF_OPTS(bpf_test_run_opts, topts,
                .data_in = &pkt_v4,
                .data_size_in = sizeof(pkt_v4),
                .repeat = 1,
        );

        err = bpf_prog_test_load("tailcall_bpf2bpf4.bpf.o", BPF_PROG_TYPE_SCHED_CLS,
                                 &obj, &prog_fd);
        if (CHECK_FAIL(err))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (CHECK_FAIL(!prog))
                goto out;

        main_fd = bpf_program__fd(prog);
        if (CHECK_FAIL(main_fd < 0))
                goto out;

        prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
        if (CHECK_FAIL(!prog_array))
                goto out;

        map_fd = bpf_map__fd(prog_array);
        if (CHECK_FAIL(map_fd < 0))
                goto out;

        for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
                snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);

                prog = bpf_object__find_program_by_name(obj, prog_name);
                if (CHECK_FAIL(!prog))
                        goto out;

                prog_fd = bpf_program__fd(prog);
                if (CHECK_FAIL(prog_fd < 0))
                        goto out;

                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (CHECK_FAIL(err))
                        goto out;
        }

        data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
        if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
                goto out;

        data_fd = bpf_map__fd(data_map);
        if (CHECK_FAIL(data_fd < 0))
                goto out;

        i = 0;
        val.noise = noise;
        val.count = 0;
        err = bpf_map_update_elem(data_fd, &i, &val, BPF_ANY);
        if (CHECK_FAIL(err))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, sizeof(pkt_v4) * 3, "tailcall retval");

        i = 0;
        err = bpf_map_lookup_elem(data_fd, &i, &val);
        ASSERT_OK(err, "tailcall count");
        ASSERT_EQ(val.count, 31, "tailcall count");

out:
        bpf_object__close(obj);
}

#include "tailcall_bpf2bpf6.skel.h"

/* Tail call counting works even when there is data on stack which is
 * not aligned to 8 bytes.
 */
static void test_tailcall_bpf2bpf_6(void)
{
        struct tailcall_bpf2bpf6 *obj;
        int err, map_fd, prog_fd, main_fd, data_fd, i, val;
        LIBBPF_OPTS(bpf_test_run_opts, topts,
                .data_in = &pkt_v4,
                .data_size_in = sizeof(pkt_v4),
                .repeat = 1,
        );

        obj = tailcall_bpf2bpf6__open_and_load();
        if (!ASSERT_OK_PTR(obj, "open and load"))
                return;

        main_fd = bpf_program__fd(obj->progs.entry);
        if (!ASSERT_GE(main_fd, 0, "entry prog fd"))
                goto out;

        map_fd = bpf_map__fd(obj->maps.jmp_table);
        if (!ASSERT_GE(map_fd, 0, "jmp_table map fd"))
                goto out;

        prog_fd = bpf_program__fd(obj->progs.classifier_0);
        if (!ASSERT_GE(prog_fd, 0, "classifier_0 prog fd"))
                goto out;

        i = 0;
        err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
        if (!ASSERT_OK(err, "jmp_table map update"))
                goto out;

        err = bpf_prog_test_run_opts(main_fd, &topts);
        ASSERT_OK(err, "entry prog test run");
        ASSERT_EQ(topts.retval, 0, "tailcall retval");

        data_fd = bpf_map__fd(obj->maps.bss);
        if (!ASSERT_GE(data_fd, 0, "bss map fd"))
                goto out;

        i = 0;
        err = bpf_map_lookup_elem(data_fd, &i, &val);
        ASSERT_OK(err, "bss map lookup");
        ASSERT_EQ(val, 1, "done flag is set");

out:
        tailcall_bpf2bpf6__destroy(obj);
}

/* test_tailcall_bpf2bpf_fentry checks that the count value of the tail call
 * limit enforcement matches with expectations when tailcall is preceded with
 * bpf2bpf call, and the bpf2bpf call is traced by fentry.
 */
static void test_tailcall_bpf2bpf_fentry(void)
{
        test_tailcall_count("tailcall_bpf2bpf2.bpf.o", true, false);
}

/* test_tailcall_bpf2bpf_fexit checks that the count value of the tail call
 * limit enforcement matches with expectations when tailcall is preceded with
 * bpf2bpf call, and the bpf2bpf call is traced by fexit.
 */
static void test_tailcall_bpf2bpf_fexit(void)
{
        test_tailcall_count("tailcall_bpf2bpf2.bpf.o", false, true);
}

/* test_tailcall_bpf2bpf_fentry_fexit checks that the count value of the tail
 * call limit enforcement matches with expectations when tailcall is preceded
 * with bpf2bpf call, and the bpf2bpf call is traced by both fentry and fexit.
 */
static void test_tailcall_bpf2bpf_fentry_fexit(void)
{
        test_tailcall_count("tailcall_bpf2bpf2.bpf.o", true, true);
}

/* test_tailcall_bpf2bpf_fentry_entry checks that the count value of the tail
 * call limit enforcement matches with expectations when tailcall is preceded
 * with bpf2bpf call, and the bpf2bpf caller is traced by fentry.
 */
static void test_tailcall_bpf2bpf_fentry_entry(void)
{
        struct bpf_object *tgt_obj = NULL, *fentry_obj = NULL;
        int err, map_fd, prog_fd, data_fd, i, val;
        struct bpf_map *prog_array, *data_map;
        struct bpf_link *fentry_link = NULL;
        struct bpf_program *prog;
        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_bpf2bpf2.bpf.o",
                                 BPF_PROG_TYPE_SCHED_CLS,
                                 &tgt_obj, &prog_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, "classifier_0");
        if (!ASSERT_OK_PTR(prog, "find classifier_0 prog"))
                goto out;

        prog_fd = bpf_program__fd(prog);
        if (!ASSERT_FALSE(prog_fd < 0, "find classifier_0 prog fd"))
                goto out;

        i = 0;
        err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
        if (!ASSERT_OK(err, "update jmp_table"))
                goto out;

        fentry_obj = bpf_object__open_file("tailcall_bpf2bpf_fentry.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");
        if (!ASSERT_OK_PTR(prog, "find fentry prog"))
                goto out;

        err = bpf_program__set_attach_target(prog, prog_fd, "classifier_0");
        if (!ASSERT_OK(err, "set_attach_target classifier_0"))
                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;

        err = bpf_prog_test_run_opts(prog_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 1, "tailcall retval");

        data_map = bpf_object__find_map_by_name(tgt_obj, "tailcall.bss");
        if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map),
                          "find tailcall.bss map"))
                goto out;

        data_fd = bpf_map__fd(data_map);
        if (!ASSERT_FALSE(data_fd < 0, "find tailcall.bss map fd"))
                goto out;

        i = 0;
        err = bpf_map_lookup_elem(data_fd, &i, &val);
        ASSERT_OK(err, "tailcall count");
        ASSERT_EQ(val, 34, "tailcall count");

        data_map = bpf_object__find_map_by_name(fentry_obj, ".bss");
        if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map),
                          "find tailcall_bpf2bpf_fentry.bss map"))
                goto out;

        data_fd = bpf_map__fd(data_map);
        if (!ASSERT_FALSE(data_fd < 0,
                          "find tailcall_bpf2bpf_fentry.bss map fd"))
                goto out;

        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;

static void *poke_update(void *arg)
{
        __u32 zero = 0, prog1_fd, prog2_fd, map_fd;
        struct tailcall_poke *call = arg;

        map_fd = bpf_map__fd(call->maps.jmp_table);
        prog1_fd = bpf_program__fd(call->progs.call1);
        prog2_fd = bpf_program__fd(call->progs.call2);

        while (!poke_thread_exit) {
                bpf_map_update_elem(map_fd, &zero, &prog1_fd, BPF_ANY);
                bpf_map_update_elem(map_fd, &zero, &prog2_fd, BPF_ANY);
        }

        return NULL;
}

/*
 * We are trying to hit prog array update during another program load
 * that shares the same prog array map.
 *
 * For that we share the jmp_table map between two skeleton instances
 * by pinning the jmp_table to same path. Then first skeleton instance
 * periodically updates jmp_table in 'poke update' thread while we load
 * the second skeleton instance in the main thread.
 */
static void test_tailcall_poke(void)
{
        struct tailcall_poke *call, *test;
        int err, cnt = 10;
        pthread_t thread;

        unlink(JMP_TABLE);

        call = tailcall_poke__open_and_load();
        if (!ASSERT_OK_PTR(call, "tailcall_poke__open"))
                return;

        err = bpf_map__pin(call->maps.jmp_table, JMP_TABLE);
        if (!ASSERT_OK(err, "bpf_map__pin"))
                goto out;

        err = pthread_create(&thread, NULL, poke_update, call);
        if (!ASSERT_OK(err, "new toggler"))
                goto out;

        while (cnt--) {
                test = tailcall_poke__open();
                if (!ASSERT_OK_PTR(test, "tailcall_poke__open"))
                        break;

                err = bpf_map__set_pin_path(test->maps.jmp_table, JMP_TABLE);
                if (!ASSERT_OK(err, "bpf_map__pin")) {
                        tailcall_poke__destroy(test);
                        break;
                }

                bpf_program__set_autoload(test->progs.test, true);
                bpf_program__set_autoload(test->progs.call1, false);
                bpf_program__set_autoload(test->progs.call2, false);

                err = tailcall_poke__load(test);
                tailcall_poke__destroy(test);
                if (!ASSERT_OK(err, "tailcall_poke__load"))
                        break;
        }

        poke_thread_exit = 1;
        ASSERT_OK(pthread_join(thread, NULL), "pthread_join");

out:
        bpf_map__unpin(call->maps.jmp_table, JMP_TABLE);
        tailcall_poke__destroy(call);
}

static void test_tailcall_hierarchy_count(const char *which, bool test_fentry,
                                          bool test_fexit,
                                          bool test_fentry_entry)
{
        int err, map_fd, prog_fd, main_data_fd, fentry_data_fd = 0, fexit_data_fd = 0, i, val;
        struct bpf_object *obj = NULL, *fentry_obj = NULL, *fexit_obj = NULL;
        struct bpf_link *fentry_link = NULL, *fexit_link = NULL;
        struct bpf_program *prog, *fentry_prog;
        struct bpf_map *prog_array, *data_map;
        int fentry_prog_fd;
        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(which, BPF_PROG_TYPE_SCHED_CLS, &obj,
                                 &prog_fd);
        if (!ASSERT_OK(err, "load obj"))
                return;

        prog = bpf_object__find_program_by_name(obj, "entry");
        if (!ASSERT_OK_PTR(prog, "find entry prog"))
                goto out;

        prog_fd = bpf_program__fd(prog);
        if (!ASSERT_GE(prog_fd, 0, "prog_fd"))
                goto out;

        if (test_fentry_entry) {
                fentry_obj = bpf_object__open_file("tailcall_bpf2bpf_hierarchy_fentry.bpf.o",
                                                   NULL);
                if (!ASSERT_OK_PTR(fentry_obj, "open fentry_obj file"))
                        goto out;

                fentry_prog = bpf_object__find_program_by_name(fentry_obj,
                                                               "fentry");
                if (!ASSERT_OK_PTR(prog, "find fentry prog"))
                        goto out;

                err = bpf_program__set_attach_target(fentry_prog, prog_fd,
                                                     "entry");
                if (!ASSERT_OK(err, "set_attach_target entry"))
                        goto out;

                err = bpf_object__load(fentry_obj);
                if (!ASSERT_OK(err, "load fentry_obj"))
                        goto out;

                fentry_link = bpf_program__attach_trace(fentry_prog);
                if (!ASSERT_OK_PTR(fentry_link, "attach_trace"))
                        goto out;

                fentry_prog_fd = bpf_program__fd(fentry_prog);
                if (!ASSERT_GE(fentry_prog_fd, 0, "fentry_prog_fd"))
                        goto out;

                prog_array = bpf_object__find_map_by_name(fentry_obj, "jmp_table");
                if (!ASSERT_OK_PTR(prog_array, "find jmp_table"))
                        goto out;

                map_fd = bpf_map__fd(prog_array);
                if (!ASSERT_GE(map_fd, 0, "map_fd"))
                        goto out;

                i = 0;
                err = bpf_map_update_elem(map_fd, &i, &fentry_prog_fd, BPF_ANY);
                if (!ASSERT_OK(err, "update jmp_table"))
                        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 data_map"))
                        goto out;

        } else {
                prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
                if (!ASSERT_OK_PTR(prog_array, "find jmp_table"))
                        goto out;

                map_fd = bpf_map__fd(prog_array);
                if (!ASSERT_GE(map_fd, 0, "map_fd"))
                        goto out;

                i = 0;
                err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
                if (!ASSERT_OK(err, "update jmp_table"))
                        goto out;

                data_map = bpf_object__find_map_by_name(obj, ".bss");
                if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map),
                                  "find data_map"))
                        goto out;
        }

        if (test_fentry) {
                fentry_obj = bpf_object__open_file("tailcall_bpf2bpf_fentry.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");
                if (!ASSERT_OK_PTR(prog, "find fentry prog"))
                        goto out;

                err = bpf_program__set_attach_target(prog, prog_fd,
                                                     "subprog_tail");
                if (!ASSERT_OK(err, "set_attach_target subprog_tail"))
                        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;
        }

        if (test_fexit) {
                fexit_obj = bpf_object__open_file("tailcall_bpf2bpf_fexit.bpf.o",
                                                  NULL);
                if (!ASSERT_OK_PTR(fexit_obj, "open fexit_obj file"))
                        goto out;

                prog = bpf_object__find_program_by_name(fexit_obj, "fexit");
                if (!ASSERT_OK_PTR(prog, "find fexit prog"))
                        goto out;

                err = bpf_program__set_attach_target(prog, prog_fd,
                                                     "subprog_tail");
                if (!ASSERT_OK(err, "set_attach_target subprog_tail"))
                        goto out;

                err = bpf_object__load(fexit_obj);
                if (!ASSERT_OK(err, "load fexit_obj"))
                        goto out;

                fexit_link = bpf_program__attach_trace(prog);
                if (!ASSERT_OK_PTR(fexit_link, "attach_trace"))
                        goto out;
        }

        err = bpf_prog_test_run_opts(prog_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 1, "tailcall retval");

        main_data_fd = bpf_map__fd(data_map);
        if (!ASSERT_GE(main_data_fd, 0, "main_data_fd"))
                goto out;

        i = 0;
        err = bpf_map_lookup_elem(main_data_fd, &i, &val);
        ASSERT_OK(err, "tailcall count");
        ASSERT_EQ(val, 34, "tailcall count");

        if (test_fentry) {
                data_map = bpf_object__find_map_by_name(fentry_obj, ".bss");
                if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map),
                                  "find tailcall_bpf2bpf_fentry.bss map"))
                        goto out;

                fentry_data_fd = bpf_map__fd(data_map);
                if (!ASSERT_GE(fentry_data_fd, 0,
                                  "find tailcall_bpf2bpf_fentry.bss map fd"))
                        goto out;

                i = 0;
                err = bpf_map_lookup_elem(fentry_data_fd, &i, &val);
                ASSERT_OK(err, "fentry count");
                ASSERT_EQ(val, 68, "fentry count");
        }

        if (test_fexit) {
                data_map = bpf_object__find_map_by_name(fexit_obj, ".bss");
                if (!ASSERT_FALSE(!data_map || !bpf_map__is_internal(data_map),
                                  "find tailcall_bpf2bpf_fexit.bss map"))
                        goto out;

                fexit_data_fd = bpf_map__fd(data_map);
                if (!ASSERT_GE(fexit_data_fd, 0,
                                  "find tailcall_bpf2bpf_fexit.bss map fd"))
                        goto out;

                i = 0;
                err = bpf_map_lookup_elem(fexit_data_fd, &i, &val);
                ASSERT_OK(err, "fexit count");
                ASSERT_EQ(val, 68, "fexit count");
        }

        i = 0;
        err = bpf_map_delete_elem(map_fd, &i);
        if (!ASSERT_OK(err, "delete_elem from jmp_table"))
                goto out;

        err = bpf_prog_test_run_opts(prog_fd, &topts);
        ASSERT_OK(err, "tailcall");
        ASSERT_EQ(topts.retval, 1, "tailcall retval");

        i = 0;
        err = bpf_map_lookup_elem(main_data_fd, &i, &val);
        ASSERT_OK(err, "tailcall count");
        ASSERT_EQ(val, 35, "tailcall count");

        if (test_fentry) {
                i = 0;
                err = bpf_map_lookup_elem(fentry_data_fd, &i, &val);
                ASSERT_OK(err, "fentry count");
                ASSERT_EQ(val, 70, "fentry count");
        }

        if (test_fexit) {
                i = 0;
                err = bpf_map_lookup_elem(fexit_data_fd, &i, &val);
                ASSERT_OK(err, "fexit count");
                ASSERT_EQ(val, 70, "fexit count");
        }

out:
        bpf_link__destroy(fentry_link);
        bpf_link__destroy(fexit_link);
        bpf_object__close(fentry_obj);
        bpf_object__close(fexit_obj);
        bpf_object__close(obj);
}

/* test_tailcall_bpf2bpf_hierarchy_1 checks that the count value of the tail
 * call limit enforcement matches with expectations when tailcalls are preceded
 * with two bpf2bpf calls.
 *
 *         subprog --tailcall-> entry
 * entry <
 *         subprog --tailcall-> entry
 */
static void test_tailcall_bpf2bpf_hierarchy_1(void)
{
        test_tailcall_hierarchy_count("tailcall_bpf2bpf_hierarchy1.bpf.o",
                                      false, false, false);
}

/* test_tailcall_bpf2bpf_hierarchy_fentry checks that the count value of the
 * tail call limit enforcement matches with expectations when tailcalls are
 * preceded with two bpf2bpf calls, and the two subprogs are traced by fentry.
 */
static void test_tailcall_bpf2bpf_hierarchy_fentry(void)
{
        test_tailcall_hierarchy_count("tailcall_bpf2bpf_hierarchy1.bpf.o",
                                      true, false, false);
}

/* test_tailcall_bpf2bpf_hierarchy_fexit checks that the count value of the tail
 * call limit enforcement matches with expectations when tailcalls are preceded
 * with two bpf2bpf calls, and the two subprogs are traced by fexit.
 */
static void test_tailcall_bpf2bpf_hierarchy_fexit(void)
{
        test_tailcall_hierarchy_count("tailcall_bpf2bpf_hierarchy1.bpf.o",
                                      false, true, false);
}

/* test_tailcall_bpf2bpf_hierarchy_fentry_fexit checks that the count value of
 * the tail call limit enforcement matches with expectations when tailcalls are
 * preceded with two bpf2bpf calls, and the two subprogs are traced by both
 * fentry and fexit.
 */
static void test_tailcall_bpf2bpf_hierarchy_fentry_fexit(void)
{
        test_tailcall_hierarchy_count("tailcall_bpf2bpf_hierarchy1.bpf.o",
                                      true, true, false);
}

/* test_tailcall_bpf2bpf_hierarchy_fentry_entry checks that the count value of
 * the tail call limit enforcement matches with expectations when tailcalls are
 * preceded with two bpf2bpf calls in fentry.
 */
static void test_tailcall_bpf2bpf_hierarchy_fentry_entry(void)
{
        test_tailcall_hierarchy_count("tc_dummy.bpf.o", false, false, true);
}

/* test_tailcall_bpf2bpf_hierarchy_2 checks that the count value of the tail
 * call limit enforcement matches with expectations:
 *
 *         subprog_tail0 --tailcall-> classifier_0 -> subprog_tail0
 * entry <
 *         subprog_tail1 --tailcall-> classifier_1 -> subprog_tail1
 */
static void test_tailcall_bpf2bpf_hierarchy_2(void)
{
        RUN_TESTS(tailcall_bpf2bpf_hierarchy2);
}

/* test_tailcall_bpf2bpf_hierarchy_3 checks that the count value of the tail
 * call limit enforcement matches with expectations:
 *
 *                                   subprog with jmp_table0 to classifier_0
 * entry --tailcall-> classifier_0 <
 *                                   subprog with jmp_table1 to classifier_0
 */
static void test_tailcall_bpf2bpf_hierarchy_3(void)
{
        RUN_TESTS(tailcall_bpf2bpf_hierarchy3);
}

/* test_tailcall_freplace checks that the freplace prog fails to update the
 * prog_array map, no matter whether the freplace prog attaches to its target.
 */
static void test_tailcall_freplace(void)
{
        struct tailcall_freplace *freplace_skel = NULL;
        struct bpf_link *freplace_link = NULL;
        struct bpf_program *freplace_prog;
        struct tc_bpf2bpf *tc_skel = NULL;
        int prog_fd, tc_prog_fd, map_fd;
        char buff[128] = {};
        int err, key;

        LIBBPF_OPTS(bpf_test_run_opts, topts,
                    .data_in = buff,
                    .data_size_in = sizeof(buff),
                    .repeat = 1,
        );

        freplace_skel = tailcall_freplace__open();
        if (!ASSERT_OK_PTR(freplace_skel, "tailcall_freplace__open"))
                return;

        tc_skel = tc_bpf2bpf__open_and_load();
        if (!ASSERT_OK_PTR(tc_skel, "tc_bpf2bpf__open_and_load"))
                goto out;

        tc_prog_fd = bpf_program__fd(tc_skel->progs.entry_tc);
        freplace_prog = freplace_skel->progs.entry_freplace;
        err = bpf_program__set_attach_target(freplace_prog, tc_prog_fd,
                                             "subprog_tc");
        if (!ASSERT_OK(err, "set_attach_target"))
                goto out;

        err = tailcall_freplace__load(freplace_skel);
        if (!ASSERT_OK(err, "tailcall_freplace__load"))
                goto out;

        map_fd = bpf_map__fd(freplace_skel->maps.jmp_table);
        prog_fd = bpf_program__fd(freplace_prog);
        key = 0;
        err = bpf_map_update_elem(map_fd, &key, &prog_fd, BPF_ANY);
        ASSERT_ERR(err, "update jmp_table failure");

        freplace_link = bpf_program__attach_freplace(freplace_prog, tc_prog_fd,
                                                     "subprog_tc");
        if (!ASSERT_OK_PTR(freplace_link, "attach_freplace"))
                goto out;

        err = bpf_map_update_elem(map_fd, &key, &prog_fd, BPF_ANY);
        ASSERT_ERR(err, "update jmp_table failure");

out:
        bpf_link__destroy(freplace_link);
        tailcall_freplace__destroy(freplace_skel);
        tc_bpf2bpf__destroy(tc_skel);
}

/* test_tailcall_bpf2bpf_freplace checks the failure that fails to attach a tail
 * callee prog with freplace prog or fails to update an extended prog to
 * prog_array map.
 */
static void test_tailcall_bpf2bpf_freplace(void)
{
        struct tailcall_freplace *freplace_skel = NULL;
        struct bpf_link *freplace_link = NULL;
        struct tc_bpf2bpf *tc_skel = NULL;
        char buff[128] = {};
        int prog_fd, map_fd;
        int err, key;

        LIBBPF_OPTS(bpf_test_run_opts, topts,
                    .data_in = buff,
                    .data_size_in = sizeof(buff),
                    .repeat = 1,
        );

        tc_skel = tc_bpf2bpf__open_and_load();
        if (!ASSERT_OK_PTR(tc_skel, "tc_bpf2bpf__open_and_load"))
                goto out;

        prog_fd = bpf_program__fd(tc_skel->progs.entry_tc);
        freplace_skel = tailcall_freplace__open();
        if (!ASSERT_OK_PTR(freplace_skel, "tailcall_freplace__open"))
                goto out;

        err = bpf_program__set_attach_target(freplace_skel->progs.entry_freplace,
                                             prog_fd, "subprog_tc");
        if (!ASSERT_OK(err, "set_attach_target"))
                goto out;

        err = tailcall_freplace__load(freplace_skel);
        if (!ASSERT_OK(err, "tailcall_freplace__load"))
                goto out;

        /* OK to attach then detach freplace prog. */

        freplace_link = bpf_program__attach_freplace(freplace_skel->progs.entry_freplace,
                                                     prog_fd, "subprog_tc");
        if (!ASSERT_OK_PTR(freplace_link, "attach_freplace"))
                goto out;

        err = bpf_link__destroy(freplace_link);
        freplace_link = NULL;
        if (!ASSERT_OK(err, "destroy link"))
                goto out;

        /* OK to update prog_array map then delete element from the map. */

        key = 0;
        map_fd = bpf_map__fd(freplace_skel->maps.jmp_table);
        err = bpf_map_update_elem(map_fd, &key, &prog_fd, BPF_ANY);
        if (!ASSERT_OK(err, "update jmp_table"))
                goto out;

        err = bpf_map_delete_elem(map_fd, &key);
        if (!ASSERT_OK(err, "delete_elem from jmp_table"))
                goto out;

        /* Fail to attach a tail callee prog with freplace prog. */

        err = bpf_map_update_elem(map_fd, &key, &prog_fd, BPF_ANY);
        if (!ASSERT_OK(err, "update jmp_table"))
                goto out;

        freplace_link = bpf_program__attach_freplace(freplace_skel->progs.entry_freplace,
                                                     prog_fd, "subprog_tc");
        if (!ASSERT_ERR_PTR(freplace_link, "attach_freplace failure"))
                goto out;

        err = bpf_map_delete_elem(map_fd, &key);
        if (!ASSERT_OK(err, "delete_elem from jmp_table"))
                goto out;

        /* Fail to update an extended prog to prog_array map. */

        freplace_link = bpf_program__attach_freplace(freplace_skel->progs.entry_freplace,
                                                     prog_fd, "subprog_tc");
        if (!ASSERT_OK_PTR(freplace_link, "attach_freplace"))
                goto out;

        err = bpf_map_update_elem(map_fd, &key, &prog_fd, BPF_ANY);
        if (!ASSERT_ERR(err, "update jmp_table failure"))
                goto out;

out:
        bpf_link__destroy(freplace_link);
        tailcall_freplace__destroy(freplace_skel);
        tc_bpf2bpf__destroy(tc_skel);
}

static void test_tailcall_failure()
{
        RUN_TESTS(tailcall_fail);
}

noinline void uprobe_sleepable_trigger(void)
{
        asm volatile ("");
}

static void test_tailcall_sleepable(void)
{
        LIBBPF_OPTS(bpf_uprobe_opts, opts);
        struct tailcall_sleepable *skel;
        int prog_fd, map_fd;
        int err, key;

        skel = tailcall_sleepable__open();
        if (!ASSERT_OK_PTR(skel, "tailcall_sleepable__open"))
                return;

        /*
         * Test that we can't load uprobe_normal and uprobe_sleepable_1,
         * because they share tailcall map.
         */
        bpf_program__set_autoload(skel->progs.uprobe_normal, true);
        bpf_program__set_autoload(skel->progs.uprobe_sleepable_1, true);

        err = tailcall_sleepable__load(skel);
        if (!ASSERT_ERR(err, "tailcall_sleepable__load"))
                goto out;

        tailcall_sleepable__destroy(skel);

        /*
         * Test that we can tail call from sleepable to sleepable program.
         */
        skel = tailcall_sleepable__open();
        if (!ASSERT_OK_PTR(skel, "tailcall_sleepable__open"))
                return;

        bpf_program__set_autoload(skel->progs.uprobe_sleepable_1, true);
        bpf_program__set_autoload(skel->progs.uprobe_sleepable_2, true);

        err = tailcall_sleepable__load(skel);
        if (!ASSERT_OK(err, "tailcall_sleepable__load"))
                goto out;

        /* Add sleepable uprobe_sleepable_2 to jmp_table[0]. */
        key = 0;
        prog_fd = bpf_program__fd(skel->progs.uprobe_sleepable_2);
        map_fd = bpf_map__fd(skel->maps.jmp_table);
        err = bpf_map_update_elem(map_fd, &key, &prog_fd, BPF_ANY);
        if (!ASSERT_OK(err, "update jmp_table"))
                goto out;

        skel->bss->my_pid = getpid();

        /* Attach uprobe_sleepable_1 to uprobe_sleepable_trigger and hit it.  */
        opts.func_name = "uprobe_sleepable_trigger";
        skel->links.uprobe_sleepable_1 = bpf_program__attach_uprobe_opts(
                                                skel->progs.uprobe_sleepable_1,
                                                -1,
                                                "/proc/self/exe",
                                                0 /* offset */,
                                                &opts);
        if (!ASSERT_OK_PTR(skel->links.uprobe_sleepable_1, "bpf_program__attach_uprobe_opts"))
                goto out;

        uprobe_sleepable_trigger();
        ASSERT_EQ(skel->bss->executed, 1, "executed");

out:
        tailcall_sleepable__destroy(skel);
}

void test_tailcalls(void)
{
        if (test__start_subtest("tailcall_1"))
                test_tailcall_1();
        if (test__start_subtest("tailcall_2"))
                test_tailcall_2();
        if (test__start_subtest("tailcall_3"))
                test_tailcall_3();
        if (test__start_subtest("tailcall_4"))
                test_tailcall_4();
        if (test__start_subtest("tailcall_5"))
                test_tailcall_5();
        if (test__start_subtest("tailcall_6"))
                test_tailcall_6();
        if (test__start_subtest("tailcall_bpf2bpf_1"))
                test_tailcall_bpf2bpf_1();
        if (test__start_subtest("tailcall_bpf2bpf_2"))
                test_tailcall_bpf2bpf_2();
        if (test__start_subtest("tailcall_bpf2bpf_3"))
                test_tailcall_bpf2bpf_3();
        if (test__start_subtest("tailcall_bpf2bpf_4"))
                test_tailcall_bpf2bpf_4(false);
        if (test__start_subtest("tailcall_bpf2bpf_5"))
                test_tailcall_bpf2bpf_4(true);
        if (test__start_subtest("tailcall_bpf2bpf_6"))
                test_tailcall_bpf2bpf_6();
        if (test__start_subtest("tailcall_bpf2bpf_fentry"))
                test_tailcall_bpf2bpf_fentry();
        if (test__start_subtest("tailcall_bpf2bpf_fexit"))
                test_tailcall_bpf2bpf_fexit();
        if (test__start_subtest("tailcall_bpf2bpf_fentry_fexit"))
                test_tailcall_bpf2bpf_fentry_fexit();
        if (test__start_subtest("tailcall_bpf2bpf_fentry_entry"))
                test_tailcall_bpf2bpf_fentry_entry();
        if (test__start_subtest("tailcall_poke"))
                test_tailcall_poke();
        if (test__start_subtest("tailcall_bpf2bpf_hierarchy_1"))
                test_tailcall_bpf2bpf_hierarchy_1();
        if (test__start_subtest("tailcall_bpf2bpf_hierarchy_fentry"))
                test_tailcall_bpf2bpf_hierarchy_fentry();
        if (test__start_subtest("tailcall_bpf2bpf_hierarchy_fexit"))
                test_tailcall_bpf2bpf_hierarchy_fexit();
        if (test__start_subtest("tailcall_bpf2bpf_hierarchy_fentry_fexit"))
                test_tailcall_bpf2bpf_hierarchy_fentry_fexit();
        if (test__start_subtest("tailcall_bpf2bpf_hierarchy_fentry_entry"))
                test_tailcall_bpf2bpf_hierarchy_fentry_entry();
        test_tailcall_bpf2bpf_hierarchy_2();
        test_tailcall_bpf2bpf_hierarchy_3();
        if (test__start_subtest("tailcall_freplace"))
                test_tailcall_freplace();
        if (test__start_subtest("tailcall_bpf2bpf_freplace"))
                test_tailcall_bpf2bpf_freplace();
        if (test__start_subtest("tailcall_failure"))
                test_tailcall_failure();
        if (test__start_subtest("tailcall_sleepable"))
                test_tailcall_sleepable();
}