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 Thu, 3 Jul 2025 at 21:57, Emil Tsalapatis <emil@xxxxxxxxxxxxxxx> wrote:
>
> 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.

Ack.

>
> > +}
> > +
> > +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?

Yep, will drop.

>
> > +
> > +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.

Good catch, will fix. We should definitely not return 0.

>
> > +       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.

Yeah, I can add one more, and this is the only interface to streams
visible to the program.

>
>
> > +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