OSDN Git Service

selftests/bpf: add bpf_for_each(), bpf_for(), and bpf_repeat() macros
authorAndrii Nakryiko <andrii@kernel.org>
Wed, 8 Mar 2023 18:41:18 +0000 (10:41 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Thu, 9 Mar 2023 00:19:51 +0000 (16:19 -0800)
Add bpf_for_each(), bpf_for(), and bpf_repeat() macros that make writing
open-coded iterator-based loops much more convenient and natural. These
macros utilize cleanup attribute to ensure proper destruction of the
iterator and thanks to that manage to provide the ergonomics that is
very close to C language's for() construct. Typical loop would look like:

  int i;
  int arr[N];

  bpf_for(i, 0, N) {
      /* verifier will know that i >= 0 && i < N, so could be used to
       * directly access array elements with no extra checks
       */
       arr[i] = i;
  }

bpf_repeat() is very similar, but it doesn't expose iteration number and
is meant as a simple "repeat action N times" loop:

  bpf_repeat(N) { /* whatever, N times */ }

Note that `break` and `continue` statements inside the {} block work as
expected.

bpf_for_each() is a generalization over any kind of BPF open-coded
iterator allowing to use for-each-like approach instead of calling
low-level bpf_iter_<type>_{new,next,destroy}() APIs explicitly. E.g.:

  struct cgroup *cg;

  bpf_for_each(cgroup, cg, some, input, args) {
      /* do something with each cg */
  }

would call (not-yet-implemented) bpf_iter_cgroup_{new,next,destroy}()
functions to form a loop over cgroups, where `some, input, args` are
passed verbatim into constructor as

  bpf_iter_cgroup_new(&it, some, input, args).

As a first demonstration, add pyperf variant based on the bpf_for() loop.

Also clean up a few tests that either included bpf_misc.h header
unnecessarily from the user-space, which is unsupported, or included it
before any common types are defined (and thus leading to unnecessary
compilation warnings, potentially).

Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230308184121.1165081-6-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/prog_tests/bpf_verif_scale.c
tools/testing/selftests/bpf/prog_tests/uprobe_autoattach.c
tools/testing/selftests/bpf/progs/bpf_misc.h
tools/testing/selftests/bpf/progs/lsm.c
tools/testing/selftests/bpf/progs/pyperf.h
tools/testing/selftests/bpf/progs/pyperf600_iter.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/pyperf600_nounroll.c

index 5ca2528..731c343 100644 (file)
@@ -144,6 +144,12 @@ void test_verif_scale_pyperf600_nounroll()
        scale_test("pyperf600_nounroll.bpf.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
 }
 
+void test_verif_scale_pyperf600_iter()
+{
+       /* open-coded BPF iterator version */
+       scale_test("pyperf600_iter.bpf.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
+}
+
 void test_verif_scale_loop1()
 {
        scale_test("loop1.bpf.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
index 6558c85..d5b3377 100644 (file)
@@ -3,7 +3,6 @@
 
 #include <test_progs.h>
 #include "test_uprobe_autoattach.skel.h"
-#include "progs/bpf_misc.h"
 
 /* uprobe attach point */
 static noinline int autoattach_trigger_func(int arg1, int arg2, int arg3,
index f704885..597688a 100644 (file)
 #define FUNC_REG_ARG_CNT 5
 #endif
 
+struct bpf_iter_num;
+
+extern int bpf_iter_num_new(struct bpf_iter_num *it, int start, int end) __ksym;
+extern int *bpf_iter_num_next(struct bpf_iter_num *it) __ksym;
+extern void bpf_iter_num_destroy(struct bpf_iter_num *it) __ksym;
+
+#ifndef bpf_for_each
+/* bpf_for_each(iter_type, cur_elem, args...) provides generic construct for
+ * using BPF open-coded iterators without having to write mundane explicit
+ * low-level loop logic. Instead, it provides for()-like generic construct
+ * that can be used pretty naturally. E.g., for some hypothetical cgroup
+ * iterator, you'd write:
+ *
+ * struct cgroup *cg, *parent_cg = <...>;
+ *
+ * bpf_for_each(cgroup, cg, parent_cg, CG_ITER_CHILDREN) {
+ *     bpf_printk("Child cgroup id = %d", cg->cgroup_id);
+ *     if (cg->cgroup_id == 123)
+ *         break;
+ * }
+ *
+ * I.e., it looks almost like high-level for each loop in other languages,
+ * supports continue/break, and is verifiable by BPF verifier.
+ *
+ * For iterating integers, the difference betwen bpf_for_each(num, i, N, M)
+ * and bpf_for(i, N, M) is in that bpf_for() provides additional proof to
+ * verifier that i is in [N, M) range, and in bpf_for_each() case i is `int
+ * *`, not just `int`. So for integers bpf_for() is more convenient.
+ *
+ * Note: this macro relies on C99 feature of allowing to declare variables
+ * inside for() loop, bound to for() loop lifetime. It also utilizes GCC
+ * extension: __attribute__((cleanup(<func>))), supported by both GCC and
+ * Clang.
+ */
+#define bpf_for_each(type, cur, args...) for (                                                 \
+       /* initialize and define destructor */                                                  \
+       struct bpf_iter_##type ___it __attribute__((aligned(8), /* enforce, just in case */,    \
+                                                   cleanup(bpf_iter_##type##_destroy))),       \
+       /* ___p pointer is just to call bpf_iter_##type##_new() *once* to init ___it */         \
+                              *___p = (bpf_iter_##type##_new(&___it, ##args),                  \
+       /* this is a workaround for Clang bug: it currently doesn't emit BTF */                 \
+       /* for bpf_iter_##type##_destroy() when used from cleanup() attribute */                \
+                                       (void)bpf_iter_##type##_destroy, (void *)0);            \
+       /* iteration and termination check */                                                   \
+       (((cur) = bpf_iter_##type##_next(&___it)));                                             \
+)
+#endif /* bpf_for_each */
+
+#ifndef bpf_for
+/* bpf_for(i, start, end) implements a for()-like looping construct that sets
+ * provided integer variable *i* to values starting from *start* through,
+ * but not including, *end*. It also proves to BPF verifier that *i* belongs
+ * to range [start, end), so this can be used for accessing arrays without
+ * extra checks.
+ *
+ * Note: *start* and *end* are assumed to be expressions with no side effects
+ * and whose values do not change throughout bpf_for() loop execution. They do
+ * not have to be statically known or constant, though.
+ *
+ * Note: similarly to bpf_for_each(), it relies on C99 feature of declaring for()
+ * loop bound variables and cleanup attribute, supported by GCC and Clang.
+ */
+#define bpf_for(i, start, end) for (                                                           \
+       /* initialize and define destructor */                                                  \
+       struct bpf_iter_num ___it __attribute__((aligned(8), /* enforce, just in case */        \
+                                                cleanup(bpf_iter_num_destroy))),               \
+       /* ___p pointer is necessary to call bpf_iter_num_new() *once* to init ___it */         \
+                           *___p = (bpf_iter_num_new(&___it, (start), (end)),                  \
+       /* this is a workaround for Clang bug: it currently doesn't emit BTF */                 \
+       /* for bpf_iter_num_destroy() when used from cleanup() attribute */                     \
+                               (void)bpf_iter_num_destroy, (void *)0);                         \
+       ({                                                                                      \
+               /* iteration step */                                                            \
+               int *___t = bpf_iter_num_next(&___it);                                          \
+               /* termination and bounds check */                                              \
+               (___t && ((i) = *___t, (i) >= (start) && (i) < (end)));                         \
+       });                                                                                     \
+)
+#endif /* bpf_for */
+
+#ifndef bpf_repeat
+/* bpf_repeat(N) performs N iterations without exposing iteration number
+ *
+ * Note: similarly to bpf_for_each(), it relies on C99 feature of declaring for()
+ * loop bound variables and cleanup attribute, supported by GCC and Clang.
+ */
+#define bpf_repeat(N) for (                                                                    \
+       /* initialize and define destructor */                                                  \
+       struct bpf_iter_num ___it __attribute__((aligned(8), /* enforce, just in case */        \
+                                                cleanup(bpf_iter_num_destroy))),               \
+       /* ___p pointer is necessary to call bpf_iter_num_new() *once* to init ___it */         \
+                           *___p = (bpf_iter_num_new(&___it, 0, (N)),                          \
+       /* this is a workaround for Clang bug: it currently doesn't emit BTF */                 \
+       /* for bpf_iter_num_destroy() when used from cleanup() attribute */                     \
+                               (void)bpf_iter_num_destroy, (void *)0);                         \
+       bpf_iter_num_next(&___it);                                                              \
+       /* nothing here  */                                                                     \
+)
+#endif /* bpf_repeat */
 
 #endif
index dc93887..fadfdd9 100644 (file)
@@ -4,12 +4,12 @@
  * Copyright 2020 Google LLC.
  */
 
-#include "bpf_misc.h"
 #include "vmlinux.h"
+#include <errno.h>
 #include <bpf/bpf_core_read.h>
 #include <bpf/bpf_helpers.h>
 #include <bpf/bpf_tracing.h>
-#include <errno.h>
+#include "bpf_misc.h"
 
 struct {
        __uint(type, BPF_MAP_TYPE_ARRAY);
index 6c7b1fb..f2e7a31 100644 (file)
@@ -7,6 +7,7 @@
 #include <stdbool.h>
 #include <linux/bpf.h>
 #include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
 
 #define FUNCTION_NAME_LEN 64
 #define FILE_NAME_LEN 128
@@ -294,17 +295,22 @@ int __on_event(struct bpf_raw_tracepoint_args *ctx)
        if (ctx.done)
                return 0;
 #else
-#ifdef NO_UNROLL
+#if defined(USE_ITER)
+/* no for loop, no unrolling */
+#elif defined(NO_UNROLL)
 #pragma clang loop unroll(disable)
-#else
-#ifdef UNROLL_COUNT
+#elif defined(UNROLL_COUNT)
 #pragma clang loop unroll_count(UNROLL_COUNT)
 #else
 #pragma clang loop unroll(full)
-#endif
 #endif /* NO_UNROLL */
                /* Unwind python stack */
+#ifdef USE_ITER
+               int i;
+               bpf_for(i, 0, STACK_MAX_LEN) {
+#else /* !USE_ITER */
                for (int i = 0; i < STACK_MAX_LEN; ++i) {
+#endif
                        if (frame_ptr && get_frame_data(frame_ptr, pidData, &frame, &sym)) {
                                int32_t new_symbol_id = *symbol_counter * 64 + cur_cpu;
                                int32_t *symbol_id = bpf_map_lookup_elem(&symbolmap, &sym);
diff --git a/tools/testing/selftests/bpf/progs/pyperf600_iter.c b/tools/testing/selftests/bpf/progs/pyperf600_iter.c
new file mode 100644 (file)
index 0000000..d62e1b2
--- /dev/null
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
+#define STACK_MAX_LEN 600
+#define SUBPROGS
+#define NO_UNROLL
+#define USE_ITER
+#include "pyperf.h"
index 6beff75..520b58c 100644 (file)
@@ -2,7 +2,4 @@
 // Copyright (c) 2019 Facebook
 #define STACK_MAX_LEN 600
 #define NO_UNROLL
-/* clang will not unroll at all.
- * Total program size is around 2k insns
- */
 #include "pyperf.h"