// SPDX-License-Identifier: GPL-2.0
+#include <bpf/btf.h>
+#include <test_btf.h>
+#include <linux/btf.h>
 #include <test_progs.h>
 #include <network_helpers.h>
 
        linked_list__destroy(skel);
 }
 
+#define SPIN_LOCK 2
+#define LIST_HEAD 3
+#define LIST_NODE 4
+
+static struct btf *init_btf(void)
+{
+       int id, lid, hid, nid;
+       struct btf *btf;
+
+       btf = btf__new_empty();
+       if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
+               return NULL;
+       id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+       if (!ASSERT_EQ(id, 1, "btf__add_int"))
+               goto end;
+       lid = btf__add_struct(btf, "bpf_spin_lock", 4);
+       if (!ASSERT_EQ(lid, SPIN_LOCK, "btf__add_struct bpf_spin_lock"))
+               goto end;
+       hid = btf__add_struct(btf, "bpf_list_head", 16);
+       if (!ASSERT_EQ(hid, LIST_HEAD, "btf__add_struct bpf_list_head"))
+               goto end;
+       nid = btf__add_struct(btf, "bpf_list_node", 16);
+       if (!ASSERT_EQ(nid, LIST_NODE, "btf__add_struct bpf_list_node"))
+               goto end;
+       return btf;
+end:
+       btf__free(btf);
+       return NULL;
+}
+
+static void test_btf(void)
+{
+       struct btf *btf = NULL;
+       int id, err;
+
+       while (test__start_subtest("btf: too many locks")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 24);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", SPIN_LOCK, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_struct foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", SPIN_LOCK, 32, 0);
+               if (!ASSERT_OK(err, "btf__add_struct foo::a"))
+                       break;
+               err = btf__add_field(btf, "c", LIST_HEAD, 64, 0);
+               if (!ASSERT_OK(err, "btf__add_struct foo::a"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -E2BIG, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: missing lock")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 16);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_struct foo::a"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:baz:a", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:baz:a"))
+                       break;
+               id = btf__add_struct(btf, "baz", 16);
+               if (!ASSERT_EQ(id, 7, "btf__add_struct baz"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_NODE, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field baz::a"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -EINVAL, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: bad offset")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 36);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:foo:b", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:foo:b"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -EEXIST, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: missing contains:")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 24);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", SPIN_LOCK, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_HEAD, 64, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -EINVAL, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: missing struct")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 24);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", SPIN_LOCK, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_HEAD, 64, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:bar:bar", 5, 1);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:bar:bar"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -ENOENT, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: missing node")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 24);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", SPIN_LOCK, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_HEAD, 64, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:foo:c", 5, 1);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:foo:c"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               btf__free(btf);
+               ASSERT_EQ(err, -ENOENT, "check btf");
+               break;
+       }
+
+       while (test__start_subtest("btf: node incorrect type")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 20);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", SPIN_LOCK, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:bar:a", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:bar:a"))
+                       break;
+               id = btf__add_struct(btf, "bar", 4);
+               if (!ASSERT_EQ(id, 7, "btf__add_struct bar"))
+                       break;
+               err = btf__add_field(btf, "a", SPIN_LOCK, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::a"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -EINVAL, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: multiple bpf_list_node with name b")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 52);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::c"))
+                       break;
+               err = btf__add_field(btf, "d", SPIN_LOCK, 384, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::d"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:foo:b", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:foo:b"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -EINVAL, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: owning | owned AA cycle")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 36);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:foo:b", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:foo:b"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -ELOOP, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: owning | owned ABA cycle")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 36);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:bar:b", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:bar:b"))
+                       break;
+               id = btf__add_struct(btf, "bar", 36);
+               if (!ASSERT_EQ(id, 7, "btf__add_struct bar"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:foo:b", 7, 0);
+               if (!ASSERT_EQ(id, 8, "btf__add_decl_tag contains:foo:b"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -ELOOP, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: owning -> owned")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 20);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", SPIN_LOCK, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:bar:a", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:bar:a"))
+                       break;
+               id = btf__add_struct(btf, "bar", 16);
+               if (!ASSERT_EQ(id, 7, "btf__add_struct bar"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_NODE, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::a"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, 0, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: owning -> owning | owned -> owned")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 20);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", SPIN_LOCK, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:bar:b", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:bar:b"))
+                       break;
+               id = btf__add_struct(btf, "bar", 36);
+               if (!ASSERT_EQ(id, 7, "btf__add_struct bar"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:baz:a", 7, 0);
+               if (!ASSERT_EQ(id, 8, "btf__add_decl_tag contains:baz:a"))
+                       break;
+               id = btf__add_struct(btf, "baz", 16);
+               if (!ASSERT_EQ(id, 9, "btf__add_struct baz"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_NODE, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field baz:a"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, 0, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: owning | owned -> owning | owned -> owned")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 36);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:bar:b", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:bar:b"))
+                       break;
+               id = btf__add_struct(btf, "bar", 36);
+               if (!ASSERT_EQ(id, 7, "btf__add_struct bar"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar:a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar:b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar:c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:baz:a", 7, 0);
+               if (!ASSERT_EQ(id, 8, "btf__add_decl_tag contains:baz:a"))
+                       break;
+               id = btf__add_struct(btf, "baz", 16);
+               if (!ASSERT_EQ(id, 9, "btf__add_struct baz"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_NODE, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field baz:a"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -ELOOP, "check btf");
+               btf__free(btf);
+               break;
+       }
+
+       while (test__start_subtest("btf: owning -> owning | owned -> owning | owned -> owned")) {
+               btf = init_btf();
+               if (!ASSERT_OK_PTR(btf, "init_btf"))
+                       break;
+               id = btf__add_struct(btf, "foo", 20);
+               if (!ASSERT_EQ(id, 5, "btf__add_struct foo"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::a"))
+                       break;
+               err = btf__add_field(btf, "b", SPIN_LOCK, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field foo::b"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:bar:b", 5, 0);
+               if (!ASSERT_EQ(id, 6, "btf__add_decl_tag contains:bar:b"))
+                       break;
+               id = btf__add_struct(btf, "bar", 36);
+               if (!ASSERT_EQ(id, 7, "btf__add_struct bar"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:baz:b", 7, 0);
+               if (!ASSERT_EQ(id, 8, "btf__add_decl_tag"))
+                       break;
+               id = btf__add_struct(btf, "baz", 36);
+               if (!ASSERT_EQ(id, 9, "btf__add_struct baz"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_HEAD, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::a"))
+                       break;
+               err = btf__add_field(btf, "b", LIST_NODE, 128, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::b"))
+                       break;
+               err = btf__add_field(btf, "c", SPIN_LOCK, 256, 0);
+               if (!ASSERT_OK(err, "btf__add_field bar::c"))
+                       break;
+               id = btf__add_decl_tag(btf, "contains:bam:a", 9, 0);
+               if (!ASSERT_EQ(id, 10, "btf__add_decl_tag contains:bam:a"))
+                       break;
+               id = btf__add_struct(btf, "bam", 16);
+               if (!ASSERT_EQ(id, 11, "btf__add_struct bam"))
+                       break;
+               err = btf__add_field(btf, "a", LIST_NODE, 0, 0);
+               if (!ASSERT_OK(err, "btf__add_field bam::a"))
+                       break;
+
+               err = btf__load_into_kernel(btf);
+               ASSERT_EQ(err, -ELOOP, "check btf");
+               btf__free(btf);
+               break;
+       }
+}
+
 void test_linked_list(void)
 {
        int i;
                test_linked_list_fail_prog(linked_list_fail_tests[i].prog_name,
                                           linked_list_fail_tests[i].err_msg);
        }
+       test_btf();
        test_linked_list_success(PUSH_POP, false);
        test_linked_list_success(PUSH_POP, true);
        test_linked_list_success(PUSH_POP_MULT, false);