diff mbox series

[RFC,v3,3/3] selftests/bpf: verify bpftool emits preserve_static_offset

Message ID 20231220133411.22978-4-eddyz87@gmail.com (mailing list archive)
State RFC
Delegated to: BPF
Headers show
Series use preserve_static_offset in bpf uapi headers | expand

Checks

Context Check Description
netdev/tree_selection success Not a local patch, async
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-13 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-18 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-42 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc

Commit Message

Eduard Zingerman Dec. 20, 2023, 1:34 p.m. UTC
Extend test_bpftool.py with following test cases:
- Load a small program that has some context types in it's BTF,
  verify that "bpftool btf dump file ... format c" emits
  preserve_static_offset attribute.
- Load a small program that has no context types in it's BTF,
  verify that "bpftool btf dump file ... format c" does not emit
  preserve_static_offset attribute.
- Load a small program that uses a map,
  verify that "bpftool btf dump map pinned ... value format c"
  emit preserve_static_offset for expected types.

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 .../bpf/progs/dummy_no_context_btf.c          |  12 +++
 .../selftests/bpf/progs/dummy_prog_with_map.c |  65 ++++++++++++
 .../selftests/bpf/progs/dummy_sk_buff_user.c  |  29 +++++
 tools/testing/selftests/bpf/test_bpftool.py   | 100 ++++++++++++++++++
 4 files changed, 206 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/dummy_no_context_btf.c
 create mode 100644 tools/testing/selftests/bpf/progs/dummy_prog_with_map.c
 create mode 100644 tools/testing/selftests/bpf/progs/dummy_sk_buff_user.c
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/progs/dummy_no_context_btf.c b/tools/testing/selftests/bpf/progs/dummy_no_context_btf.c
new file mode 100644
index 000000000000..5a1df4984dce
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/dummy_no_context_btf.c
@@ -0,0 +1,12 @@ 
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* A dummy program that does not reference context types in it's BTF */
+SEC("tc")
+__u32 dummy_prog(void *ctx)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/dummy_prog_with_map.c b/tools/testing/selftests/bpf/progs/dummy_prog_with_map.c
new file mode 100644
index 000000000000..0268317494ef
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/dummy_prog_with_map.c
@@ -0,0 +1,65 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+#if __has_attribute(btf_decl_tag)
+#define __decl_tag_bpf_ctx __attribute__((btf_decl_tag(("preserve_static_offset"))))
+#endif
+
+struct test_struct_a {
+	int v;
+} __decl_tag_bpf_ctx;
+
+struct test_struct_b {
+	int v;
+} __decl_tag_bpf_ctx;
+
+struct test_struct_c {
+	int v;
+} __decl_tag_bpf_ctx;
+
+struct test_struct_d {
+	int v;
+} __decl_tag_bpf_ctx;
+
+struct test_struct_e {
+	int v;
+} __decl_tag_bpf_ctx;
+
+struct test_struct_f {
+	int v;
+} __decl_tag_bpf_ctx;
+
+typedef struct test_struct_c test_struct_c_td;
+
+struct map_value {
+	struct test_struct_a a;
+	struct test_struct_b b[2];
+	test_struct_c_td c;
+	const struct test_struct_d *(*d)(volatile struct test_struct_e *);
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 4);
+	__type(key, int);
+	__type(value, struct map_value);
+} test_map1 SEC(".maps");
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 4);
+	__type(key, int);
+	__type(value, struct test_struct_f);
+} test_map2 SEC(".maps");
+
+/* A dummy program that references map 'test_map', used by test_bpftool.py */
+SEC("tc")
+int dummy_prog_with_map(void *ctx)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/dummy_sk_buff_user.c b/tools/testing/selftests/bpf/progs/dummy_sk_buff_user.c
new file mode 100644
index 000000000000..8c10aebe8689
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/dummy_sk_buff_user.c
@@ -0,0 +1,29 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+/* In linux/bpf.h __bpf_ctx macro is defined differently for BPF and
+ * non-BPF targets:
+ * - for BPF it is __attribute__((preserve_static_offset))
+ * - for non-BPF it is __attribute__((btf_decl_tag("preserve_static_offset")))
+ *
+ * bpftool uses decl tag as a signal to emit preserve_static_offset,
+ * thus additional declaration is needed in this test.
+ */
+#if __has_attribute(btf_decl_tag)
+#define __decl_tag_bpf_ctx __attribute__((btf_decl_tag(("preserve_static_offset"))))
+#endif
+
+struct __decl_tag_bpf_ctx __sk_buff;
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* A dummy program that references __sk_buff type in it's BTF,
+ * used by test_bpftool.py.
+ */
+SEC("tc")
+int sk_buff_user(struct __sk_buff *skb)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_bpftool.py b/tools/testing/selftests/bpf/test_bpftool.py
index 1c2408ee1f5d..d3a8b71afcc1 100644
--- a/tools/testing/selftests/bpf/test_bpftool.py
+++ b/tools/testing/selftests/bpf/test_bpftool.py
@@ -3,10 +3,12 @@ 
 
 import collections
 import functools
+import io
 import json
 import os
 import socket
 import subprocess
+import tempfile
 import unittest
 
 
@@ -25,6 +27,10 @@  class UnprivilegedUserError(Exception):
     pass
 
 
+class MissingDependencyError(Exception):
+    pass
+
+
 def _bpftool(args, json=True):
     _args = ["bpftool"]
     if json:
@@ -63,12 +69,26 @@  DMESG_EMITTING_HELPERS = [
         "bpf_trace_vprintk",
     ]
 
+BPFFS_MOUNT = "/sys/fs/bpf/"
+
+DUMMY_SK_BUFF_USER_OBJ = cur_dir + "/dummy_sk_buff_user.bpf.o"
+DUMMY_NO_CONTEXT_BTF_OBJ = cur_dir + "/dummy_no_context_btf.bpf.o"
+DUMMY_PROG_WITH_MAP_OBJ = cur_dir + "/dummy_prog_with_map.bpf.o"
+
 class TestBpftool(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         if os.getuid() != 0:
             raise UnprivilegedUserError(
                 "This test suite needs root privileges")
+        objs = [DUMMY_SK_BUFF_USER_OBJ,
+                DUMMY_NO_CONTEXT_BTF_OBJ,
+                DUMMY_PROG_WITH_MAP_OBJ]
+        for obj in objs:
+            if os.path.exists(obj):
+                continue
+            raise MissingDependencyError(
+                "File " + obj + " does not exist, make sure progs/*.c are compiled")
 
     @default_iface
     def test_feature_dev_json(self, iface):
@@ -172,3 +192,83 @@  class TestBpftool(unittest.TestCase):
         res = bpftool(["feature", "probe", "macros"])
         for pattern in expected_patterns:
             self.assertRegex(res, pattern)
+
+    def assertStringsPresent(self, text, patterns):
+        pos = 0
+        for i, pat in enumerate(patterns):
+            m = text.find(pat, pos)
+            if m == -1:
+                with io.StringIO() as msg:
+                    print("Can't find expected string:", file=msg)
+                    for s in patterns[0:i]:
+                        print("    MATCHED: " + s, file=msg)
+                    print("NOT MATCHED: " + pat, file=msg)
+                    print("", file=msg)
+                    print("Searching in:", file=msg)
+                    print(text, file=msg)
+                    self.fail(msg.getvalue())
+            pos += len(pat)
+
+    def assertPreserveStaticOffset(self, btf_dump, types):
+        self.assertStringsPresent(btf_dump, [
+            "#if !defined(BPF_NO_PRESERVE_STATIC_OFFSET) && " +
+              "__has_attribute(preserve_static_offset)",
+            "#pragma clang attribute push " +
+              "(__attribute__((preserve_static_offset)), apply_to = record)"
+        ] + ["struct " + t + ";" for t in types] + [
+            "#endif /* BPF_NO_PRESERVE_STATIC_OFFSET */"
+        ])
+
+    # Load a small program that has some context types in it's BTF,
+    # verify that "bpftool btf dump file ... format c" emits
+    # preserve_static_offset attribute.
+    def test_c_dump_preserve_static_offset_present(self):
+        res = bpftool(["btf", "dump", "file", DUMMY_SK_BUFF_USER_OBJ, "format", "c"])
+        self.assertPreserveStaticOffset(res, ["__sk_buff"])
+
+    # Load a small program that has no context types in it's BTF,
+    # verify that "bpftool btf dump file ... format c" does not emit
+    # preserve_static_offset attribute.
+    def test_c_dump_no_preserve_static_offset(self):
+        res = bpftool(["btf", "dump", "file", DUMMY_NO_CONTEXT_BTF_OBJ, "format", "c"])
+        self.assertNotRegex(res, "preserve_static_offset")
+        self.assertStringsPresent(res, [
+            "preserve_access_index",
+            "typedef unsigned int __u32;"
+        ])
+
+    # When BTF is dumped for maps bpftool follows a slightly different
+    # code path, that filters which BTF types would be printed.
+    # Test this code path here:
+    # - load a program that uses a map, value type of which references
+    #   a number of structs annotated with preserve_static_offset;
+    # - dump BTF for that map and check preserve_static_offset annotation
+    #   for expected structs.
+    def test_c_dump_preserve_static_offset_map(self):
+        prog_pin = tempfile.mktemp(prefix="dummy_prog_with_map", dir=BPFFS_MOUNT)
+        maps_dir = tempfile.mktemp(prefix="dummy_prog_with_map_maps", dir=BPFFS_MOUNT)
+        map_pin1 = maps_dir + "/test_map1"
+        map_pin2 = maps_dir + "/test_map2"
+
+        bpftool(["prog", "load", DUMMY_PROG_WITH_MAP_OBJ, prog_pin, "pinmaps", maps_dir])
+        try:
+            map1 = bpftool(["btf", "dump", "map", "pinned", map_pin1, "value", "format", "c"])
+            map2 = bpftool(["btf", "dump", "map", "pinned", map_pin2, "value", "format", "c"])
+        finally:
+            os.remove(prog_pin)
+            os.remove(map_pin1)
+            os.remove(map_pin2)
+            os.rmdir(maps_dir)
+
+        # test_map1 should have all types except struct test_struct_f
+        self.assertPreserveStaticOffset(map1, [
+            "test_struct_a", "test_struct_b", "test_struct_c",
+            "test_struct_d", "test_struct_e",
+        ])
+        self.assertNotRegex(map1, "test_struct_f")
+
+        # test_map2 should have only struct test_struct_f
+        self.assertPreserveStaticOffset(map2, [
+            "test_struct_f"
+        ])
+        self.assertNotRegex(map2, "struct test_struct_a")