selftests/bpf: Add tests for open-coded task_vma iter
authorDave Marchevsky <davemarchevsky@fb.com>
Fri, 13 Oct 2023 20:44:25 +0000 (13:44 -0700)
committerAndrii Nakryiko <andrii@kernel.org>
Fri, 13 Oct 2023 22:48:58 +0000 (15:48 -0700)
The open-coded task_vma iter added earlier in this series allows for
natural iteration over a task's vmas using existing open-coded iter
infrastructure, specifically bpf_for_each.

This patch adds a test demonstrating this pattern and validating
correctness. The vma->vm_start and vma->vm_end addresses of the first
1000 vmas are recorded and compared to /proc/PID/maps output. As
expected, both see the same vmas and addresses - with the exception of
the [vsyscall] vma - which is explained in a comment in the prog_tests
program.

Signed-off-by: Dave Marchevsky <davemarchevsky@fb.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20231013204426.1074286-5-davemarchevsky@fb.com
tools/testing/selftests/bpf/bpf_experimental.h
tools/testing/selftests/bpf/prog_tests/iters.c
tools/testing/selftests/bpf/progs/iters_task_vma.c [new file with mode: 0644]

index 9aa29564bd7407ab1b46fe56ef93ce7d69b60aeb..2c8cb3f615293071e81f532d6b124b7b72f805de 100644 (file)
@@ -159,6 +159,14 @@ extern void *bpf_percpu_obj_new_impl(__u64 local_type_id, void *meta) __ksym;
  */
 extern void bpf_percpu_obj_drop_impl(void *kptr, void *meta) __ksym;
 
+struct bpf_iter_task_vma;
+
+extern int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it,
+                                struct task_struct *task,
+                                unsigned long addr) __ksym;
+extern struct vm_area_struct *bpf_iter_task_vma_next(struct bpf_iter_task_vma *it) __ksym;
+extern void bpf_iter_task_vma_destroy(struct bpf_iter_task_vma *it) __ksym;
+
 /* Convenience macro to wrap over bpf_obj_drop_impl */
 #define bpf_percpu_obj_drop(kptr) bpf_percpu_obj_drop_impl(kptr, NULL)
 
index 10804ae5ae972bea66cd03fae138c4c519fadd38..b696873c54556b5b5aa82279d4b56bfc9d0f02f1 100644 (file)
@@ -8,6 +8,7 @@
 #include "iters_looping.skel.h"
 #include "iters_num.skel.h"
 #include "iters_testmod_seq.skel.h"
+#include "iters_task_vma.skel.h"
 
 static void subtest_num_iters(void)
 {
@@ -90,6 +91,61 @@ cleanup:
        iters_testmod_seq__destroy(skel);
 }
 
+static void subtest_task_vma_iters(void)
+{
+       unsigned long start, end, bpf_iter_start, bpf_iter_end;
+       struct iters_task_vma *skel;
+       char rest_of_line[1000];
+       unsigned int seen;
+       FILE *f = NULL;
+       int err;
+
+       skel = iters_task_vma__open_and_load();
+       if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+               return;
+
+       skel->bss->target_pid = getpid();
+
+       err = iters_task_vma__attach(skel);
+       if (!ASSERT_OK(err, "skel_attach"))
+               goto cleanup;
+
+       getpgid(skel->bss->target_pid);
+       iters_task_vma__detach(skel);
+
+       if (!ASSERT_GT(skel->bss->vmas_seen, 0, "vmas_seen_gt_zero"))
+               goto cleanup;
+
+       f = fopen("/proc/self/maps", "r");
+       if (!ASSERT_OK_PTR(f, "proc_maps_fopen"))
+               goto cleanup;
+
+       seen = 0;
+       while (fscanf(f, "%lx-%lx %[^\n]\n", &start, &end, rest_of_line) == 3) {
+               /* [vsyscall] vma isn't _really_ part of task->mm vmas.
+                * /proc/PID/maps returns it when out of vmas - see get_gate_vma
+                * calls in fs/proc/task_mmu.c
+                */
+               if (strstr(rest_of_line, "[vsyscall]"))
+                       continue;
+
+               bpf_iter_start = skel->bss->vm_ranges[seen].vm_start;
+               bpf_iter_end = skel->bss->vm_ranges[seen].vm_end;
+
+               ASSERT_EQ(bpf_iter_start, start, "vma->vm_start match");
+               ASSERT_EQ(bpf_iter_end, end, "vma->vm_end match");
+               seen++;
+       }
+
+       if (!ASSERT_EQ(skel->bss->vmas_seen, seen, "vmas_seen_eq"))
+               goto cleanup;
+
+cleanup:
+       if (f)
+               fclose(f);
+       iters_task_vma__destroy(skel);
+}
+
 void test_iters(void)
 {
        RUN_TESTS(iters_state_safety);
@@ -103,4 +159,6 @@ void test_iters(void)
                subtest_num_iters();
        if (test__start_subtest("testmod_seq"))
                subtest_testmod_seq_iters();
+       if (test__start_subtest("task_vma"))
+               subtest_task_vma_iters();
 }
diff --git a/tools/testing/selftests/bpf/progs/iters_task_vma.c b/tools/testing/selftests/bpf/progs/iters_task_vma.c
new file mode 100644 (file)
index 0000000..44edecf
--- /dev/null
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include "bpf_experimental.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+pid_t target_pid = 0;
+unsigned int vmas_seen = 0;
+
+struct {
+       __u64 vm_start;
+       __u64 vm_end;
+} vm_ranges[1000];
+
+SEC("raw_tp/sys_enter")
+int iter_task_vma_for_each(const void *ctx)
+{
+       struct task_struct *task = bpf_get_current_task_btf();
+       struct vm_area_struct *vma;
+       unsigned int seen = 0;
+
+       if (task->pid != target_pid)
+               return 0;
+
+       if (vmas_seen)
+               return 0;
+
+       bpf_for_each(task_vma, vma, task, 0) {
+               if (seen >= 1000)
+                       break;
+
+               vm_ranges[seen].vm_start = vma->vm_start;
+               vm_ranges[seen].vm_end = vma->vm_end;
+               seen++;
+       }
+
+       vmas_seen = seen;
+       return 0;
+}
+
+char _license[] SEC("license") = "GPL";