Re: [PATCH bpf-next v4 12/12] selftests/bpf: Add tests for prog streams

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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
>





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux