diff mbox series

[bpf-next,6/6] selftests/bpf: add tests for libbpf-provided externs

Message ID 20191117070807.251360-7-andriin@fb.com
State Changes Requested
Delegated to: BPF Maintainers
Headers show
Series Add libbpf-provided extern variables support | expand

Commit Message

Andrii Nakryiko Nov. 17, 2019, 7:08 a.m. UTC
Add a set of tests validating libbpf-provided extern variables. One crucial
feature that's tested is dead code elimination together with using invalid BPF
helper. CONFIG_MISSING is not supposed to exist and should always be specified
by libbpf as zero, which allows BPF verifier to correctly do branch pruning
and not fail validation, when invalid BPF helper is called from dead if branch.

Signed-off-by: Andrii Nakryiko <andriin@fb.com>
---
 .../selftests/bpf/prog_tests/core_extern.c    | 186 ++++++++++++++++++
 .../selftests/bpf/progs/test_core_extern.c    |  43 ++++
 2 files changed, 229 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/core_extern.c
 create mode 100644 tools/testing/selftests/bpf/progs/test_core_extern.c
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/prog_tests/core_extern.c b/tools/testing/selftests/bpf/prog_tests/core_extern.c
new file mode 100644
index 000000000000..2a87a5f1de4d
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/core_extern.c
@@ -0,0 +1,186 @@ 
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#include <linux/version.h>
+
+static size_t roundup_page(size_t sz)
+{
+	long page_size = sysconf(_SC_PAGE_SIZE);
+	return (sz + page_size - 1) / page_size * page_size;
+}
+
+static uint32_t get_kernel_version(void)
+{
+	uint32_t major, minor, patch;
+	struct utsname info;
+
+	uname(&info);
+	if (sscanf(info.release, "%u.%u.%u", &major, &minor, &patch) != 3)
+		return 0;
+	return KERNEL_VERSION(major, minor, patch);
+}
+
+struct data {
+	uint64_t kern_ver;
+	uint64_t tristate_val;
+	uint64_t bool_val;
+	uint64_t int_val;
+	uint64_t missing_val;
+};
+
+static struct test_case {
+	const char *name;
+	const char *cfg;
+	const char *cfg_path;
+	bool fails;
+	struct data data;
+} test_cases[] = {
+	{ .name = "default search path", .cfg_path = NULL },
+	{ .name = "/proc/config.gz", .cfg_path = "/proc/config.gz" },
+	{ .name = "missing config", .fails = true,
+	  .cfg_path = "/proc/invalid-config.gz" },
+	{
+		.name = "custom values",
+		.cfg = "CONFIG_TRISTATE=m\n"
+		       "CONFIG_BOOL=y\n"
+		       "CONFIG_INT=123456\n",
+		.data = {
+			.tristate_val = 2,
+			.bool_val = 1,
+			.int_val = 123456,
+		},
+	},
+	{
+		/* there is no real typing, so any valid value is accepted */
+		.name = "mixed up types",
+		.cfg = "CONFIG_TRISTATE=123\n"
+		       "CONFIG_BOOL=m\n"
+		       "CONFIG_INT=y\n",
+		.data = {
+			.tristate_val = 123,
+			.bool_val = 2,
+			.int_val = 1,
+		},
+	},
+	{
+		/* somewhat weird behavior of strtoull */
+		.name = "negative int",
+		.cfg = "CONFIG_INT=-12\n",
+		.data = { .int_val = (uint64_t)-12 },
+	},
+	{ .name = "bad tristate", .fails = true, .cfg = "CONFIG_TRISTATE=M" },
+	{ .name = "bad bool", .fails = true, .cfg = "CONFIG_BOOL=X" },
+	{ .name = "int (not int)", .fails = true, .cfg = "CONFIG_INT=abc" },
+	{ .name = "int (string)", .fails = true, .cfg = "CONFIG_INT=\"abc\"" },
+	{ .name = "int (empty)", .fails = true, .cfg = "CONFIG_INT=" },
+	{ .name = "int (mixed up 1)", .fails = true, .cfg = "CONFIG_INT=123abc",
+	  .fails = true, },
+	{ .name = "int (mixed up 2)", .fails = true, .cfg = "CONFIG_INT=123abc\n",
+	  .fails = true, },
+	{ .name = "int (too big)", .fails = true,
+	  .cfg = "CONFIG_INT=123456789123456789123\n" },
+};
+
+void test_core_extern(void)
+{
+	const char *file = "test_core_extern.o";
+	const char *probe_name = "raw_tp/sys_enter";
+	const char *tp_name = "sys_enter";
+	const size_t bss_sz = roundup_page(sizeof(struct data));
+	const uint32_t kern_ver = get_kernel_version();
+	int err, duration = 0, i;
+	struct bpf_program *prog;
+	struct bpf_object *obj;
+	struct bpf_link *link = NULL;
+	struct bpf_map *bss_map;
+	void *bss_mmaped = NULL;
+	volatile struct data *data;
+
+	for (i = 0; i < ARRAY_SIZE(test_cases); i++) {
+		char tmp_cfg_path[] = "/tmp/test_core_extern_cfg.XXXXXX";
+		const struct test_case *t = &test_cases[i];
+		DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+			.kernel_config_path = t->cfg_path,
+		);
+
+		if (!test__start_subtest(t->name))
+			continue;
+
+		if (t->cfg) {
+			size_t n = strlen(t->cfg) + 1;
+			int fd = mkstemp(tmp_cfg_path);
+
+			if (CHECK(fd < 0, "mkstemp", "errno: %d\n", errno))
+				continue;
+			printf("using '%s' as config file\n", tmp_cfg_path);
+			if (CHECK_FAIL(write(fd, t->cfg, n) != n))
+				continue;
+			close(fd);
+			opts.kernel_config_path = tmp_cfg_path;
+		}
+
+		obj = bpf_object__open_file("test_core_extern.o", &opts);
+		if (t->fails) {
+			CHECK(!IS_ERR(obj), "obj_open",
+			      "shouldn't succeed opening '%s'!\n", file);
+			goto cleanup;
+		} else {
+			if (CHECK(IS_ERR(obj), "obj_open",
+				  "failed to open '%s': %ld\n",
+				  file, PTR_ERR(obj)))
+				goto cleanup;
+		}
+		prog = bpf_object__find_program_by_title(obj, probe_name);
+		if (CHECK(!prog, "find_prog", "prog %s missing\n", probe_name))
+			goto cleanup;
+		err = bpf_object__load(obj);
+		if (CHECK(err, "obj_load", "failed to load prog '%s': %d\n",
+			  probe_name, err))
+			goto cleanup;
+		bss_map = bpf_object__find_map_by_name(obj, "test_cor.bss");
+		if (CHECK(!bss_map, "find_bss_map", ".bss map not found\n"))
+			goto cleanup;
+		bss_mmaped = mmap(NULL, bss_sz, PROT_READ | PROT_WRITE,
+				  MAP_SHARED, bpf_map__fd(bss_map), 0);
+		if (CHECK(bss_mmaped == MAP_FAILED, "bss_mmap",
+			  ".bss mmap failed: %d\n", errno)) {
+			bss_mmaped = NULL;
+			goto cleanup;
+		}
+		data = bss_mmaped;
+
+		link = bpf_program__attach_raw_tracepoint(prog, tp_name);
+		if (CHECK(IS_ERR(link), "attach_raw_tp", "err %ld\n", PTR_ERR(link)))
+			goto cleanup;
+
+		usleep(1);
+
+		CHECK(data->kern_ver != kern_ver, "kern_ver",
+		      "exp %x, got %lx\n", kern_ver, data->kern_ver);
+		CHECK(data->missing_val != 0xDEADC0DE, "missing_val",
+		      "exp %x, got %lx\n", 0xDEADC0DE, data->missing_val);
+		CHECK(data->bool_val != t->data.bool_val, "bool_val",
+		      "exp %lx, got %lx\n", t->data.bool_val, data->bool_val);
+		CHECK(data->tristate_val != t->data.tristate_val,
+		      "tristate_val", "exp %lx, got %lx\n",
+		      t->data.tristate_val, data->tristate_val);
+		CHECK(data->int_val != t->data.int_val, "int_val",
+		      "exp %lx, got %lx\n", t->data.int_val, data->int_val);
+
+cleanup:
+		if (t->cfg)
+			unlink(tmp_cfg_path);
+		if (bss_mmaped) {
+			CHECK_FAIL(munmap(bss_mmaped, bss_sz));
+			bss_mmaped = NULL;
+			data = NULL;
+		}
+		if (!IS_ERR_OR_NULL(link)) {
+			bpf_link__destroy(link);
+			link = NULL;
+		}
+		if (!IS_ERR_OR_NULL(obj))
+			bpf_object__close(obj);
+	}
+}
diff --git a/tools/testing/selftests/bpf/progs/test_core_extern.c b/tools/testing/selftests/bpf/progs/test_core_extern.c
new file mode 100644
index 000000000000..adbc74113265
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_core_extern.c
@@ -0,0 +1,43 @@ 
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2017 Facebook
+
+#include <stdint.h>
+#include <linux/ptrace.h>
+#include <linux/bpf.h>
+#include "bpf_helpers.h"
+
+/* non-existing BPF helper, to test dead code elimination */
+static int (*bpf_missing_helper)(const void *arg1, int arg2) = (void *) 999;
+
+extern uint64_t LINUX_KERNEL_VERSION;
+extern uint64_t CONFIG_TRISTATE;
+extern uint64_t CONFIG_BOOL;
+extern uint64_t CONFIG_INT;
+extern uint64_t CONFIG_MISSING;
+
+volatile struct {
+	uint64_t kern_ver;
+	uint64_t tristate_val;
+	uint64_t bool_val;
+	uint64_t int_val;
+	uint64_t missing_val;
+} out = {};
+
+SEC("raw_tp/sys_enter")
+int handle_sys_enter(struct pt_regs *ctx)
+{
+	out.kern_ver = LINUX_KERNEL_VERSION;
+	out.tristate_val = CONFIG_TRISTATE;
+	out.bool_val = CONFIG_BOOL;
+	out.int_val = CONFIG_INT;
+
+	if (CONFIG_MISSING)
+		/* invalid, but dead code - never executed */
+		out.missing_val = bpf_missing_helper(ctx, 123);
+	else
+		out.missing_val = 0xDEADC0DE;
+
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";