summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/bpf/prog_tests/btf_inline.c
blob: e630e5f0f5fa50016f9b8e39a1704687d782ade2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026, Oracle and/or its affiliates. */

#include <test_progs.h>
#include <bpf/btf.h>
#include <bpf/libbpf.h>

#define BTF_SYSFS_DIR		"/sys/kernel/btf"
#define BTF_INLINE_SUFFIX	".inline"

/*
 * For a specific inline site, verify we have the right function,
 * loc proto and loc param representation and that the offset is
 * reasonable given the caller where it was inlined.
 */
void test_btf_inline(void)
{
	struct btf *inline_btf = NULL, *btf = NULL, *vmlinux_btf = NULL;
	const char *inline_caller = "bpf_testmod_test_write";
	const char *inline_func = "testmod_inline_len";
	unsigned long long caller_addr, base_addr;
	bool skip = false, found_loc = false;
	const struct btf_loc_param *lp;
	int locsec_id, func_id, n, i;
	const struct btf_type *t;
	struct btf_loc *l;
	const __u32 *p;
	int err = 0;

	if (!env.has_testmod) {
		test__skip();
		return;
	}

	/* LOCSEC offsets are relative to the ELF section base. */
	base_addr = module_get_base_addr_sec("bpf_testmod", ".text");
	if (!ASSERT_NEQ(base_addr, 0, "base_addr_nonzero"))
		return;

	if (!ASSERT_OK(kallsyms_find(inline_caller, &caller_addr),
		       "find_inline_caller"))
		return;
	if (!ASSERT_NEQ(caller_addr, 0, "caller_addr_nonzero"))
		return;
	if (!ASSERT_GE(caller_addr, base_addr, "caller_addr_ge_base_addr"))
		return;
	caller_addr -= base_addr;

	vmlinux_btf = btf__load_vmlinux_btf();
	if (!ASSERT_OK_PTR(vmlinux_btf, "vmlinux_btf"))
		return;

	btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod", vmlinux_btf);
	if (!ASSERT_OK_PTR(btf, "bpf_testmod_btf"))
		goto out;

	inline_btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod" BTF_INLINE_SUFFIX,
				      btf);
	err = libbpf_get_error(inline_btf);
	/* pahole may not have inline BTF feature support. */
	if (err == -ENOENT) {
		skip = true;
		goto out;
	}
	if (!ASSERT_OK(err, "parse_inline_btf"))
		goto out;
	locsec_id = btf__find_by_name_kind(inline_btf, "inline.text", BTF_KIND_LOCSEC);
	if (locsec_id < 0) {
		skip = true;
		goto out;
	}
	func_id = btf__find_by_name_kind(inline_btf, inline_func, BTF_KIND_FUNC);
	if (!ASSERT_GT(func_id, 0, "inline_caller_func"))
		goto out;
	t = btf__type_by_id(inline_btf, locsec_id);
	n = btf_vlen(t);
	for (i = 0, l = btf_locsec_locs(t); i < n; i++, l++) {
		if (l->func == func_id) {
			found_loc = true;
			break;
		}
	}
	if (!ASSERT_TRUE(found_loc, "found_loc"))
		goto out;
	if (!ASSERT_GT(l->loc_proto, 0, "loc_proto_id"))
		goto out;
	if (!ASSERT_GT(l->offset, 0, "loc_offset"))
		goto out;
	t = btf__type_by_id(inline_btf, l->loc_proto);
	if (!ASSERT_OK_PTR(t, "loc_proto_ptr"))
		goto out;
	if (!ASSERT_EQ(btf_vlen(t), 1, "loc_proto_one_param"))
		goto out;
	p = btf_loc_proto_params(t);
	if (!ASSERT_GT(*p, 0, "loc_proto_has_loc_param"))
		goto out;
	t = btf__type_by_id(inline_btf, *p);
	if (!ASSERT_OK_PTR(t, "loc_param_ptr"))
		goto out;
	lp = btf_loc_param(t);
	if (!ASSERT_TRUE(lp->flags & BTF_LOC_PARAM_REG, "param_has_reg"))
		goto out;
	if (!ASSERT_GT(l->offset, caller_addr, "inline_gt_caller"))
		goto out;
	/* simple sanity test to roughly ensure inline site still in function */
	if (!ASSERT_LT(l->offset, caller_addr + 512, "inline_in_caller"))
		goto out;
out:
	btf__free(inline_btf);
	btf__free(btf);
	btf__free(vmlinux_btf);
	if (skip)
		test__skip();
}