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
|
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <test_progs.h>
#include <pthread.h>
#include <sched.h>
#include <linux/perf_event.h>
#include <sys/syscall.h>
#include "arena_stack.skel.h"
#include "arena_stack_nomem.skel.h"
#include "arena_stack_fentry.skel.h"
#include "arena_stack_loop.skel.h"
#include "arena_stack_grow.skel.h"
#include "arena_stack_throw.skel.h"
static int run_prog(int fd)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
if (bpf_prog_test_run_opts(fd, &opts))
return -1;
return opts.retval;
}
static int run_tc_prog(int fd)
{
char pkt[64] = {};
LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = pkt, .data_size_in = sizeof(pkt));
if (bpf_prog_test_run_opts(fd, &opts))
return -1;
return opts.retval;
}
static int pin_to_cpu0(cpu_set_t *old)
{
cpu_set_t set;
if (sched_getaffinity(0, sizeof(*old), old))
return -1;
CPU_ZERO(&set);
CPU_SET(0, &set);
return sched_setaffinity(0, sizeof(set), &set);
}
#define NR_THREADS 8
#define NR_RUNS 20
static int preempted_fd;
static void *preempted_thread(void *arg)
{
long bad = 0;
int i;
for (i = 0; i < NR_RUNS; i++)
bad += run_prog(preempted_fd) != 0;
return (void *)bad;
}
/* Programs on one cpu preempt each other. They don't exit in the order they started. */
static void test_preemption(struct arena_stack *skel)
{
pthread_t thread[NR_THREADS];
long bad = 0;
cpu_set_t old;
void *ret;
int i, n;
if (!ASSERT_OK(pin_to_cpu0(&old), "pin"))
return;
preempted_fd = bpf_program__fd(skel->progs.preempted);
/* threads inherit the affinity */
for (n = 0; n < NR_THREADS; n++)
if (!ASSERT_OK(pthread_create(&thread[n], NULL, preempted_thread, NULL), "thread"))
break;
for (i = 0; i < n; i++) {
pthread_join(thread[i], &ret);
bad += (long)ret;
}
ASSERT_EQ(bad, 0, "runs with a corrupted frame");
sched_setaffinity(0, sizeof(old), &old);
}
/*
* A program that runs with preemption disabled is interrupted by a program in
* the timer interrupt
*/
static void test_nesting(struct arena_stack *skel)
{
struct perf_event_attr attr = {
.size = sizeof(attr),
.type = PERF_TYPE_SOFTWARE,
.config = PERF_COUNT_SW_CPU_CLOCK,
.sample_period = 50000, /* nsec */
};
struct bpf_link *link = NULL;
int i, pfd, bad = 0;
cpu_set_t old;
if (!ASSERT_OK(pin_to_cpu0(&old), "pin"))
return;
pfd = syscall(__NR_perf_event_open, &attr, -1, 0, -1, 0);
if (!ASSERT_GE(pfd, 0, "perf_event_open"))
goto out;
link = bpf_program__attach_perf_event(skel->progs.in_interrupt, pfd);
if (!ASSERT_OK_PTR(link, "attach_perf_event")) {
close(pfd);
goto out;
}
for (i = 0; i < 200; i++)
bad += run_tc_prog(bpf_program__fd(skel->progs.interrupted)) != 0;
ASSERT_EQ(bad, 0, "runs with a corrupted frame");
/* the program was interrupted */
ASSERT_GT(skel->bss->interrupts, 10, "interrupts");
bpf_link__destroy(link);
out:
sched_setaffinity(0, sizeof(old), &old);
}
/* bpf_throw() doesn't return the stack. Programs run when all stacks of the cpu are taken. */
static void test_throw(struct arena_stack *skel)
{
cpu_set_t old;
int i;
if (!ASSERT_OK(pin_to_cpu0(&old), "pin"))
return;
for (i = 0; i < 40; i++)
run_prog(bpf_program__fd(skel->progs.throws));
for (i = 0; i < 40; i++)
if (!ASSERT_EQ(run_prog(bpf_program__fd(skel->progs.prog_returns_7)), 7, "returns_7"))
break;
/* the same with preemption disabled */
for (i = 0; i < 200; i++)
run_tc_prog(bpf_program__fd(skel->progs.throws_nested));
for (i = 0; i < 40; i++)
if (!ASSERT_EQ(run_tc_prog(bpf_program__fd(skel->progs.returns_8_nested)), 8,
"returns_8_nested"))
break;
sched_setaffinity(0, sizeof(old), &old);
}
/* A callback that the kernel calls takes a stack of its own */
static void test_timer(struct arena_stack *skel)
{
int i, j;
for (i = 0; i < 100; i++) {
if (!ASSERT_OK(run_prog(bpf_program__fd(skel->progs.start_timer)), "start_timer"))
return;
for (j = 0; j < 1000 && skel->bss->timer_runs <= i; j++)
usleep(100);
}
ASSERT_EQ(skel->bss->timer_runs, 100, "runs of the callback");
ASSERT_EQ(skel->bss->timer_bad, 0, "runs with a wrong frame");
}
static void test_attach_to_subprog(struct arena_stack *skel)
{
struct arena_stack_fentry *fentry;
int tgt_fd = bpf_program__fd(skel->progs.preempted);
/* the main program is fine */
fentry = arena_stack_fentry__open();
if (!ASSERT_OK_PTR(fentry, "open"))
return;
ASSERT_OK(bpf_program__set_attach_target(fentry->progs.on_entry, tgt_fd, "preempted"),
"set_attach_target");
ASSERT_OK(arena_stack_fentry__load(fentry), "load for the main program");
arena_stack_fentry__destroy(fentry);
fentry = arena_stack_fentry__open();
if (!ASSERT_OK_PTR(fentry, "open"))
return;
ASSERT_OK(bpf_program__set_attach_target(fentry->progs.on_entry, tgt_fd, "spin"),
"set_attach_target");
ASSERT_ERR(arena_stack_fentry__load(fentry), "load for a subprog");
arena_stack_fentry__destroy(fentry);
}
/* The insns that take the stack are in front of the first insn. A jump to it doesn't run them. */
static void test_jump_to_first_insn(void)
{
struct arena_stack_loop *skel;
cpu_set_t old;
int i;
skel = arena_stack_loop__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
if (!ASSERT_OK(pin_to_cpu0(&old), "pin"))
goto out;
for (i = 0; i < 1000; i++) {
if (!ASSERT_EQ(run_tc_prog(bpf_program__fd(skel->progs.loop)), 7, "loop"))
break;
if (!ASSERT_EQ(run_tc_prog(bpf_program__fd(skel->progs.nop_loop)), 7, "nop_loop"))
break;
if (!ASSERT_EQ(run_tc_prog(bpf_program__fd(skel->progs.gotox_loop)), 7,
"gotox_loop"))
break;
}
sched_setaffinity(0, sizeof(old), &old);
out:
arena_stack_loop__destroy(skel);
}
/*
* The stack that a callback finds is too small. It gets a new one and the pages of the old
* one are freed. Before the callback starts another program gets these pages as its stack
* and gives it back. The stack of the callback has to stay taken.
*/
static void test_slot_grows(void)
{
struct arena_stack_grow *skel;
cpu_set_t old;
/* the compiler may inline it */
if (libbpf_find_vmlinux_btf_id("arena_free_pages", BPF_TRACE_FEXIT) <= 0) {
test__skip();
return;
}
skel = arena_stack_grow__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
if (!ASSERT_OK(pin_to_cpu0(&old), "pin"))
goto out;
/* the stack of the cpu is large enough for both programs */
ASSERT_OK(run_prog(bpf_program__fd(skel->progs.big)), "big");
ASSERT_OK(run_prog(bpf_program__fd(skel->progs.small)), "small");
ASSERT_OK(arena_stack_grow__attach(skel), "attach");
skel->bss->nest = 1;
ASSERT_OK(run_prog(bpf_program__fd(skel->progs.big)), "big with callbacks");
ASSERT_EQ(skel->bss->frame, 1, "frame of outer_cb");
/* the stack did not grow, or arena_free_pages() is inlined where it grows */
if (!skel->bss->waited)
test__skip();
sched_setaffinity(0, sizeof(old), &old);
out:
arena_stack_grow__destroy(skel);
}
static void test_no_memory(void)
{
struct bpf_prog_info info = {};
__u32 info_len = sizeof(info);
struct arena_stack_nomem *skel;
char buf[256] = {};
int fd;
skel = arena_stack_nomem__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
ASSERT_EQ(run_prog(bpf_program__fd(skel->progs.take_page)), 0, "take_page");
fd = bpf_program__fd(skel->progs.returns_1);
ASSERT_EQ(run_prog(fd), 0, "retval");
ASSERT_EQ(skel->bss->runs, 0, "runs");
ASSERT_OK(bpf_prog_get_info_by_fd(fd, &info, &info_len), "info");
ASSERT_EQ(info.recursion_misses, 1, "misses");
ASSERT_GT(bpf_prog_stream_read(fd, BPF_STREAM_STDERR, buf, sizeof(buf) - 1, NULL), 0,
"stream_read");
ASSERT_HAS_SUBSTR(buf, "No memory for the stack in arena", "stderr");
arena_stack_nomem__destroy(skel);
}
/*
* bpf_throw() in a main program that has stack arguments. The exception callback restores the
* registers of the caller.
*/
static void test_throw_stack_args(void)
{
struct arena_stack_throw *skel;
int i;
skel = arena_stack_throw__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
if (!skel->rodata->has_stack_arg) {
test__skip();
goto out;
}
if (!ASSERT_OK(arena_stack_throw__attach(skel), "attach"))
goto out;
ASSERT_OK(run_prog(bpf_program__fd(skel->progs.calls_kfunc)), "calls_kfunc");
ASSERT_GE(skel->bss->throws, 1, "throws");
for (i = 0; i < 4; i++)
ASSERT_EQ(skel->bss->regs[i], 6 + i, "register");
out:
arena_stack_throw__destroy(skel);
}
void serial_test_arena_stack(void)
{
struct arena_stack *skel;
#if !defined(__x86_64__) && !defined(__aarch64__)
/* other JITs don't support r12 */
test__skip();
return;
#endif
skel = arena_stack__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
if (test__start_subtest("preemption"))
test_preemption(skel);
if (test__start_subtest("nesting"))
test_nesting(skel);
if (test__start_subtest("attach_to_subprog"))
test_attach_to_subprog(skel);
if (test__start_subtest("timer"))
test_timer(skel);
if (test__start_subtest("throw"))
test_throw(skel);
arena_stack__destroy(skel);
if (test__start_subtest("throw_stack_args"))
test_throw_stack_args();
if (test__start_subtest("jump_to_first_insn"))
test_jump_to_first_insn();
if (test__start_subtest("slot_grows"))
test_slot_grows();
if (test__start_subtest("no_memory"))
test_no_memory();
}
|