summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c
blob: 3f9949e8227dfd2e8051d0733eb097960b959d62 (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// SPDX-License-Identifier: GPL-2.0

#include <linux/err.h>
#include <string.h>
#include <bpf/btf.h>
#include <bpf/libbpf.h>
#include <linux/btf.h>
#include <linux/kernel.h>
#define CONFIG_DEBUG_INFO_BTF
#include <linux/btf_ids.h>
#include "test_progs.h"

#define BTF_DATA_FILE "resolve_btfids.test.o.BTF"

#define DECL_TAG_FASTCALL "bpf_fastcall"
#define DECL_TAG_KFUNC "bpf_kfunc"
#define TYPE_ATTR_ARENA "address_space(1)"
#define ARENA_ARG(n) (1U << (n))

#ifndef KF_FASTCALL
#define KF_FASTCALL (1 << 12)
#endif
#ifndef KF_ARENA_RET
#define KF_ARENA_RET  (1 << 13)
#endif
#ifndef KF_ARENA_ARG1
#define KF_ARENA_ARG1 (1 << 14)
#endif
#ifndef KF_ARENA_ARG2
#define KF_ARENA_ARG2 (1 << 15)
#endif

struct symbol {
	const char	*name;
	int		 type;
	int		 id;
};

struct symbol test_symbols[] = {
	{ "unused",  BTF_KIND_UNKN,     0 },
	{ "S",       BTF_KIND_TYPEDEF, -1 },
	{ "T",       BTF_KIND_TYPEDEF, -1 },
	{ "U",       BTF_KIND_TYPEDEF, -1 },
	{ "S",       BTF_KIND_STRUCT,  -1 },
	{ "U",       BTF_KIND_UNION,   -1 },
	{ "func",    BTF_KIND_FUNC,    -1 },
};

struct kfunc_symbol {
	const char	*name;
	s32		 id;
	u32		 flags;
	u32		 arena_args;
	bool		 arena_ret;
};

static struct kfunc_symbol kfunc_symbols[] = {
	{ "kfunc_a", -1, 0, 0, false },
	{ "kfunc_b", -1, KF_FASTCALL, 0, false },
	{ "kfunc_c", -1, KF_ARENA_RET | KF_ARENA_ARG1 | KF_ARENA_ARG2,
	  ARENA_ARG(0) | ARENA_ARG(1), true },
	{ "kfunc_d", -1, KF_ARENA_ARG2, ARENA_ARG(1), false },
	{ "kfunc_e", -1, 0, ARENA_ARG(0) | ARENA_ARG(1) | ARENA_ARG(2) |
	  ARENA_ARG(3) | ARENA_ARG(4), false },
	{ "kfunc_f", -1, 0, ARENA_ARG(1), false },
	{ "kfunc_g", -1, KF_ARENA_RET, ARENA_ARG(0) | ARENA_ARG(1), true },
};

/* Align the .BTF_ids section to 4 bytes */
asm (
".pushsection " BTF_IDS_SECTION " ,\"a\"; \n"
".balign 4, 0;                            \n"
".popsection;                             \n");

/*
 * test_list_local, test_set and test_kfunc_set are .local symbols placed
 * in .BTF_ids by inline asm, and are read here directly by C name. To the
 * compiler they are plain, default-visibility extern objects.
 *
 * When test_progs is linked as a position-independent executable (PIE),
 * taking the address of such an extern is routed through the GOT. The
 * GNU assembler on aarch64 unconditionally converts references to .local
 * symbols into section + addend form (".BTF_ids + <offset>"), but a GOT
 * slot cannot carry an addend (the AArch64 ELF spec mandates zero), so
 * the linker resolves it to the .BTF_ids base.
 *
 * Mark them hidden so the compiler treats them as non-interposable and
 * emits a direct, addend-preserving PC-relative access instead of a GOT
 * load, in both PIE and non-PIE builds. test_list_global is .globl and
 * not affected, so it is left at default visibility.
 */
#pragma GCC visibility push(hidden)
BTF_ID_LIST(test_list_local)
BTF_ID_UNUSED
BTF_ID(typedef, S)
BTF_ID(typedef, T)
BTF_ID(typedef, U)
BTF_ID(struct,  S)
BTF_ID(union,   U)
BTF_ID(func,    func)

BTF_SET_START(test_set)
BTF_ID(typedef, S)
BTF_ID(typedef, T)
BTF_ID(typedef, U)
BTF_ID(struct,  S)
BTF_ID(union,   U)
BTF_ID(func,    func)
BTF_SET_END(test_set)

BTF_KFUNCS_START(test_kfunc_set)
BTF_ID_FLAGS(func, kfunc_a)
BTF_ID_FLAGS(func, kfunc_b, KF_FASTCALL)
BTF_ID_FLAGS(func, kfunc_c, KF_ARENA_RET | KF_ARENA_ARG1 | KF_ARENA_ARG2)
BTF_ID_FLAGS(func, kfunc_d, KF_ARENA_ARG2)
BTF_ID_FLAGS(func, kfunc_e)
BTF_ID_FLAGS(func, kfunc_f)
BTF_ID_FLAGS(func, kfunc_g, KF_ARENA_RET)
BTF_KFUNCS_END(test_kfunc_set)

/*
 * Same kfuncs in reverse declaration order, so resolve_btfids has to
 * actually sort at least one of the two sets.
 */
BTF_KFUNCS_START(test_kfunc_set_rev)
BTF_ID_FLAGS(func, kfunc_g, KF_ARENA_RET)
BTF_ID_FLAGS(func, kfunc_f)
BTF_ID_FLAGS(func, kfunc_e)
BTF_ID_FLAGS(func, kfunc_d, KF_ARENA_ARG2)
BTF_ID_FLAGS(func, kfunc_c, KF_ARENA_RET | KF_ARENA_ARG1 | KF_ARENA_ARG2)
BTF_ID_FLAGS(func, kfunc_b, KF_FASTCALL)
BTF_ID_FLAGS(func, kfunc_a)
BTF_KFUNCS_END(test_kfunc_set_rev)
#pragma GCC visibility pop

extern __u32 test_list_global[];
BTF_ID_LIST_GLOBAL(test_list_global, 1)
BTF_ID_UNUSED
BTF_ID(typedef, S)
BTF_ID(typedef, T)
BTF_ID(typedef, U)
BTF_ID(struct,  S)
BTF_ID(union,   U)
BTF_ID(func,    func)

static int
__resolve_symbol(struct btf *btf, int type_id)
{
	const struct btf_type *type;
	const char *str;
	unsigned int i;

	type = btf__type_by_id(btf, type_id);
	if (!ASSERT_OK_PTR(type, "btf__type_by_id"))
		return -1;

	str = btf__name_by_offset(btf, type->name_off);

	for (i = 0; i < ARRAY_SIZE(test_symbols); i++) {
		if (test_symbols[i].id >= 0)
			continue;

		if (BTF_INFO_KIND(type->info) != test_symbols[i].type)
			continue;

		if (!strcmp(str, test_symbols[i].name))
			test_symbols[i].id = type_id;
	}

	if (!btf_is_func(type))
		return 0;

	for (i = 0; i < ARRAY_SIZE(kfunc_symbols); i++) {
		if (kfunc_symbols[i].id >= 0)
			continue;
		if (!strcmp(str, kfunc_symbols[i].name))
			kfunc_symbols[i].id = type_id;
	}

	return 0;
}

static int resolve_symbols(struct btf *btf)
{
	__u32 nr = btf__type_cnt(btf);
	int type_id;

	for (type_id = 1; type_id < nr; type_id++) {
		if (__resolve_symbol(btf, type_id))
			return -1;
	}
	return 0;
}

static bool btf_has_decl_tag(struct btf *btf, const char *tag_name, s32 target_id)
{
	const struct btf_type *t;
	const char *name;
	int nr, id;

	nr = btf__type_cnt(btf);
	for (id = 1; id < nr; id++) {
		t = btf__type_by_id(btf, id);
		if (!btf_is_decl_tag(t))
			continue;
		if (t->type != (__u32)target_id)
			continue;
		if (btf_decl_tag(t)->component_idx != -1)
			continue;
		name = btf__name_by_offset(btf, t->name_off);
		if (strcmp(name, tag_name) == 0)
			return true;
	}
	return false;
}

static void check_kfunc_set(struct btf_id_set8 *set)
{
	unsigned int i, j;

	ASSERT_EQ(set->flags, BTF_SET8_KFUNCS, "kfunc_set_flags");
	ASSERT_EQ(set->cnt, ARRAY_SIZE(kfunc_symbols), "kfunc_set_cnt");

	for (i = 0; i < set->cnt; i++) {
		for (j = 0; j < ARRAY_SIZE(kfunc_symbols); j++) {
			if (kfunc_symbols[j].id == (s32)set->pairs[i].id) {
				ASSERT_EQ(set->pairs[i].flags,
					  kfunc_symbols[j].flags, "kfunc_flags_check");
				break;
			}
		}

		ASSERT_TRUE(j < ARRAY_SIZE(kfunc_symbols), "kfunc_id_found");

		if (i > 0) {
			ASSERT_LE(set->pairs[i - 1].id,
				  set->pairs[i].id, "kfunc_sort_check");
		}
	}
}

/* True if @id is PTR -> TYPE_TAG(kflag=1, "address_space(1)") -> pointee */
static bool is_arena_tagged_ptr(struct btf *btf, __u32 id)
{
	const struct btf_type *ptr, *tag;
	const char *name;

	ptr = btf__type_by_id(btf, id);
	if (!btf_is_ptr(ptr))
		return false;
	tag = btf__type_by_id(btf, ptr->type);
	if (!btf_is_type_tag(tag) || !btf_kflag(tag))
		return false;
	name = btf__name_by_offset(btf, tag->name_off);
	return strcmp(name, TYPE_ATTR_ARENA) == 0;
}

void test_resolve_btfids(void)
{
	__u32 *test_list, *test_lists[] = { test_list_local, test_list_global };
	unsigned int i, j;
	struct btf *btf;

	btf = btf__parse_raw(BTF_DATA_FILE);
	if (!ASSERT_OK_PTR(btf, "btf_parse"))
		return;

	if (resolve_symbols(btf))
		goto out;

	/* Check BTF_ID_LIST(test_list_local) and
	 * BTF_ID_LIST_GLOBAL(test_list_global) IDs
	 */
	for (j = 0; j < ARRAY_SIZE(test_lists); j++) {
		test_list = test_lists[j];
		for (i = 0; i < ARRAY_SIZE(test_symbols); i++)
			ASSERT_EQ(test_list[i], test_symbols[i].id, test_symbols[i].name);
	}

	/* Check BTF_SET_START(test_set) IDs */
	for (i = 0; i < test_set.cnt; i++) {
		bool found = false;

		for (j = 0; j < ARRAY_SIZE(test_symbols); j++) {
			if (test_symbols[j].id != test_set.ids[i])
				continue;
			found = true;
			break;
		}

		if (!ASSERT_TRUE(found, "id_in_test_symbols"))
			break;

		if (i > 0)
			ASSERT_LE(test_set.ids[i - 1], test_set.ids[i], "sort_check");
	}

	check_kfunc_set(&test_kfunc_set);
	check_kfunc_set(&test_kfunc_set_rev);

	/* Check resolve_btfids emitted a bpf_kfunc decl_tag for each kfunc */
	for (i = 0; i < ARRAY_SIZE(kfunc_symbols); i++) {
		ASSERT_TRUE(btf_has_decl_tag(btf, DECL_TAG_KFUNC,
					     kfunc_symbols[i].id),
			    kfunc_symbols[i].name);
	}

	/* Check resolve_btfids emitted bpf_fastcall for KF_FASTCALL kfuncs */
	for (i = 0; i < ARRAY_SIZE(kfunc_symbols); i++) {
		if (kfunc_symbols[i].flags & KF_FASTCALL) {
			ASSERT_TRUE(btf_has_decl_tag(btf, DECL_TAG_FASTCALL,
						     kfunc_symbols[i].id),
				    kfunc_symbols[i].name);
		}
	}

	/*
	 * Check resolve_btfids wrapped exactly the arena-flagged or suffixed
	 * return/args with the address_space(1) type attribute, and left other
	 * pointers/returns untouched.
	 */
	for (i = 0; i < ARRAY_SIZE(kfunc_symbols); i++) {
		const struct btf_type *fn, *proto;
		const struct btf_param *params;
		const char *name = kfunc_symbols[i].name;
		u32 arena_args = kfunc_symbols[i].arena_args;
		__u32 nr;

		fn = btf__type_by_id(btf, kfunc_symbols[i].id);
		if (!ASSERT_TRUE(btf_is_func(fn), name))
			continue;
		proto = btf__type_by_id(btf, fn->type);
		if (!ASSERT_TRUE(btf_is_func_proto(proto), name))
			continue;
		params = btf_params(proto);
		nr = btf_vlen(proto);

		ASSERT_EQ(is_arena_tagged_ptr(btf, proto->type),
			  kfunc_symbols[i].arena_ret, name);
		for (j = 0; j < nr; j++)
			ASSERT_EQ(is_arena_tagged_ptr(btf, params[j].type),
				  !!(arena_args & ARENA_ARG(j)), name);
	}

out:
	btf__free(btf);
}