From: Eduard Zingerman <eddyz87@gmail.com>
To: bpf@vger.kernel.org, ast@kernel.org
Cc: andrii@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev,
kernel-team@fb.com, yonghong.song@linux.dev,
quentin@isovalent.com, alan.maguire@oracle.com,
Eduard Zingerman <eddyz87@gmail.com>
Subject: [RFC v3 3/3] selftests/bpf: verify bpftool emits preserve_static_offset
Date: Wed, 20 Dec 2023 15:34:11 +0200 [thread overview]
Message-ID: <20231220133411.22978-4-eddyz87@gmail.com> (raw)
In-Reply-To: <20231220133411.22978-1-eddyz87@gmail.com>
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 --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")
--
2.42.1
next prev parent reply other threads:[~2023-12-20 13:34 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-12-20 13:34 [RFC v3 0/3] use preserve_static_offset in bpf uapi headers Eduard Zingerman
2023-12-20 13:34 ` [RFC v3 1/3] bpf: Mark virtual BPF context structures as preserve_static_offset Eduard Zingerman
2023-12-20 13:34 ` [RFC v3 2/3] bpftool: add attribute preserve_static_offset for context types Eduard Zingerman
2023-12-20 13:34 ` Eduard Zingerman [this message]
2023-12-20 19:20 ` [RFC v3 0/3] use preserve_static_offset in bpf uapi headers Alexei Starovoitov
2023-12-20 20:19 ` Eduard Zingerman
2024-01-03 13:06 ` Quentin Monnet
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20231220133411.22978-4-eddyz87@gmail.com \
--to=eddyz87@gmail.com \
--cc=alan.maguire@oracle.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=kernel-team@fb.com \
--cc=martin.lau@linux.dev \
--cc=quentin@isovalent.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox