On Tue, Jul 1, 2025 at 11:17 PM Kumar Kartikeya Dwivedi <memxor@xxxxxxxxx> wrote: > > Add selftests to stress test the various facets of the stream API, > memory allocation pattern, and ensuring dumping support is tested and > functional. > > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@xxxxxxxxx> > --- > .../testing/selftests/bpf/prog_tests/stream.c | 141 ++++++++++++++++++ > tools/testing/selftests/bpf/progs/stream.c | 81 ++++++++++ > .../testing/selftests/bpf/progs/stream_fail.c | 17 +++ > 3 files changed, 239 insertions(+) > create mode 100644 tools/testing/selftests/bpf/prog_tests/stream.c > create mode 100644 tools/testing/selftests/bpf/progs/stream.c > create mode 100644 tools/testing/selftests/bpf/progs/stream_fail.c > Reviewed-by: Emil Tsalapatis <emil@xxxxxxxxxxxxxxx> > diff --git a/tools/testing/selftests/bpf/prog_tests/stream.c b/tools/testing/selftests/bpf/prog_tests/stream.c > new file mode 100644 > index 000000000000..d9f0185dca61 > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/stream.c > @@ -0,0 +1,141 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ > +#include <test_progs.h> > +#include <sys/mman.h> > +#include <regex.h> > + > +#include "stream.skel.h" > +#include "stream_fail.skel.h" > + > +void test_stream_failure(void) > +{ > + RUN_TESTS(stream_fail); > +} > + > +void test_stream_success(void) > +{ > + RUN_TESTS(stream); > + return; > +} > + > +struct { > + int prog_off; > + const char *errstr; > +} stream_error_arr[] = { > + { > + offsetof(struct stream, progs.stream_cond_break), > + "ERROR: Timeout detected for may_goto instruction\n" > + "CPU: [0-9]+ UID: 0 PID: [0-9]+ Comm: .*\n" > + "Call trace:\n" > + "([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" > + "|[ \t]+[^\n]+\n)*", > + }, > + { > + offsetof(struct stream, progs.stream_deadlock), > + "ERROR: AA or ABBA deadlock detected for bpf_res_spin_lock\n" > + "Attempted lock = (0x[0-9a-fA-F]+)\n" > + "Total held locks = 1\n" > + "Held lock\\[ 0\\] = \\1\n" // Lock address must match > + "CPU: [0-9]+ UID: 0 PID: [0-9]+ Comm: .*\n" > + "Call trace:\n" > + "([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" > + "|[ \t]+[^\n]+\n)*", > + }, > +}; > + > +static int match_regex(const char *pattern, const char *string) > +{ > + int err, rc; > + regex_t re; > + > + err = regcomp(&re, pattern, REG_EXTENDED | REG_NEWLINE); > + if (err) > + return -1; > + rc = regexec(&re, string, 0, NULL, 0); > + regfree(&re); > + return rc == 0 ? 1 : 0; Nit: You can just return rc and do ASSERT_TRUE(ret > 0) for the result in test_stream_errors. > +} > + > +void test_stream_errors(void) > +{ > + LIBBPF_OPTS(bpf_test_run_opts, opts); > + LIBBPF_OPTS(bpf_prog_stream_read_opts, ropts); > + struct stream *skel; > + int ret, prog_fd; > + char buf[1024]; > + > + skel = stream__open_and_load(); > + if (!ASSERT_OK_PTR(skel, "stream__open_and_load")) > + return; > + > + for (int i = 0; i < ARRAY_SIZE(stream_error_arr); i++) { > + struct bpf_program **prog; > + > + prog = (struct bpf_program **)(((char *)skel) + stream_error_arr[i].prog_off); > + prog_fd = bpf_program__fd(*prog); > + ret = bpf_prog_test_run_opts(prog_fd, &opts); > + ASSERT_OK(ret, "ret"); > + ASSERT_OK(opts.retval, "retval"); > + > +#if !defined(__x86_64__) > + ASSERT_TRUE(1, "Timed may_goto unsupported, skip."); > + if (i == 0) { > + ret = bpf_prog_stream_read(prog_fd, 2, buf, sizeof(buf), &ropts); > + ASSERT_EQ(ret, 0, "stream read"); > + continue; > + } > +#endif > + > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDERR, buf, sizeof(buf), &ropts); > + ASSERT_GT(ret, 0, "stream read"); > + ASSERT_LE(ret, 1023, "len for buf"); > + buf[ret] = '\0'; > + > + ret = match_regex(stream_error_arr[i].errstr, buf); > + if (!ASSERT_TRUE(ret == 1, "regex match")) > + fprintf(stderr, "Output from stream:\n%s\n", buf); > + } > + > + stream__destroy(skel); > +} > + > +void test_stream_syscall(void) > +{ > + LIBBPF_OPTS(bpf_test_run_opts, opts); > + LIBBPF_OPTS(bpf_prog_stream_read_opts, ropts); > + struct stream *skel; > + int ret, prog_fd; > + char buf[64]; > + > + skel = stream__open_and_load(); > + if (!ASSERT_OK_PTR(skel, "stream__open_and_load")) > + return; > + > + prog_fd = bpf_program__fd(skel->progs.stream_syscall); > + ret = bpf_prog_test_run_opts(prog_fd, &opts); > + ASSERT_OK(ret, "ret"); > + ASSERT_OK(opts.retval, "retval"); > + > + ASSERT_LT(bpf_prog_stream_read(0, BPF_STREAM_STDOUT, buf, sizeof(buf), &ropts), 0, "error"); > + ret = -errno; > + ASSERT_EQ(ret, -EINVAL, "bad prog_fd"); > + > + ASSERT_LT(bpf_prog_stream_read(prog_fd, 0, buf, sizeof(buf), &ropts), 0, "error"); > + ret = -errno; > + ASSERT_EQ(ret, -ENOENT, "bad stream id"); > + > + ASSERT_LT(bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, NULL, sizeof(buf), NULL), 0, "error"); > + ret = -errno; > + ASSERT_EQ(ret, -EFAULT, "bad stream buf"); > + > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, buf, 2, NULL); > + ASSERT_EQ(ret, 2, "bytes"); > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, buf, 2, NULL); > + ASSERT_EQ(ret, 1, "bytes"); > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, buf, 1, &ropts); > + ASSERT_EQ(ret, 0, "no bytes stdout"); > + ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDERR, buf, 1, &ropts); > + ASSERT_EQ(ret, 0, "no bytes stderr"); > + > + stream__destroy(skel); > +} > diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c > new file mode 100644 > index 000000000000..ae163a656082 > --- /dev/null > +++ b/tools/testing/selftests/bpf/progs/stream.c > @@ -0,0 +1,81 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ > +#include <vmlinux.h> > +#include <bpf/bpf_tracing.h> > +#include <bpf/bpf_helpers.h> > +#include "bpf_misc.h" > +#include "bpf_experimental.h" > + > +struct arr_elem { > + struct bpf_res_spin_lock lock; > +}; > + > +struct { > + __uint(type, BPF_MAP_TYPE_ARRAY); > + __uint(max_entries, 1); > + __type(key, int); > + __type(value, struct arr_elem); > +} arrmap SEC(".maps"); > + > +#define ENOSPC 28 > +#define _STR "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" > + > +#define STREAM_STR (u64)(_STR _STR _STR _STR) Is this unused? > + > +int size; > + > +SEC("syscall") > +__success __retval(0) > +int stream_exhaust(void *ctx) > +{ > + /* Use global variable for loop convergence. */ > + size = 0; > + bpf_repeat(BPF_MAX_LOOPS) { > + if (bpf_stream_printk(BPF_STDOUT, _STR) == -ENOSPC && size == 99954) > + return 0; > + size += sizeof(_STR) - 1; > + } > + return 1; > +} > + > +SEC("syscall") > +__success __retval(0) > +int stream_cond_break(void *ctx) > +{ > + while (can_loop) > + ; > + return 0; > +} > + > +SEC("syscall") > +__success __retval(0) > +int stream_deadlock(void *ctx) > +{ > + struct bpf_res_spin_lock *lock, *nlock; > + > + lock = bpf_map_lookup_elem(&arrmap, &(int){0}); > + if (!lock) > + return 0; Nit: Maybe change the unexpected failure paths to non-zero? If they are followed then the test stderr output will fail the regex in userspace, but still it would be nice to immediately be able to see which step broke. > + nlock = bpf_map_lookup_elem(&arrmap, &(int){0}); > + if (!nlock) > + return 0; > + if (bpf_res_spin_lock(lock)) > + return 0; > + if (bpf_res_spin_lock(nlock)) { > + bpf_res_spin_unlock(lock); > + return 0; > + } > + bpf_res_spin_unlock(nlock); > + bpf_res_spin_unlock(lock); > + return 0; > +} > + > +SEC("syscall") > +__success __retval(0) > +int stream_syscall(void *ctx) > +{ > + bpf_stream_printk(BPF_STDOUT, "foo"); > + return 0; > +} > + > +char _license[] SEC("license") = "GPL"; > diff --git a/tools/testing/selftests/bpf/progs/stream_fail.c b/tools/testing/selftests/bpf/progs/stream_fail.c > new file mode 100644 > index 000000000000..12004d5092b7 > --- /dev/null > +++ b/tools/testing/selftests/bpf/progs/stream_fail.c > @@ -0,0 +1,17 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ > +#include <vmlinux.h> > +#include <bpf/bpf_tracing.h> > +#include <bpf/bpf_helpers.h> > +#include <bpf/bpf_core_read.h> > +#include "bpf_misc.h" > + > +SEC("syscall") > +__failure __msg("Possibly NULL pointer passed") > +int stream_vprintk_null_arg(void *ctx) > +{ > + bpf_stream_vprintk(BPF_STDOUT, "", NULL, 0, NULL); > + return 0; > +} > + Possibly add a test passing a random scalar as the pointer? Though if the test above succeeds, the verifier properly identifies the argument as a pointer arg so it wouldn't give any extra signal. > +char _license[] SEC("license") = "GPL"; > -- > 2.47.1 >