diff options
Diffstat (limited to 'tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c')
-rw-r--r-- | tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c | 529 |
1 files changed, 469 insertions, 60 deletions
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c index bf6ca8e3eb13..844f6fc8487b 100644 --- a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c @@ -6,6 +6,8 @@ #include "uprobe_multi.skel.h" #include "uprobe_multi_bench.skel.h" #include "uprobe_multi_usdt.skel.h" +#include "uprobe_multi_consumers.skel.h" +#include "uprobe_multi_pid_filter.skel.h" #include "bpf/libbpf_internal.h" #include "testing_helpers.h" #include "../sdt.h" @@ -38,6 +40,7 @@ struct child { int pid; int tid; pthread_t thread; + char stack[65536]; }; static void release_child(struct child *child) @@ -67,41 +70,54 @@ static void kick_child(struct child *child) fflush(NULL); } -static struct child *spawn_child(void) +static int child_func(void *arg) { - static struct child child; - int err; - int c; - - /* pipe to notify child to execute the trigger functions */ - if (pipe(child.go)) - return NULL; + struct child *child = arg; + int err, c; - child.pid = child.tid = fork(); - if (child.pid < 0) { - release_child(&child); - errno = EINVAL; - return NULL; - } + close(child->go[1]); - /* child */ - if (child.pid == 0) { - close(child.go[1]); + /* wait for parent's kick */ + err = read(child->go[0], &c, 1); + if (err != 1) + exit(err); - /* wait for parent's kick */ - err = read(child.go[0], &c, 1); - if (err != 1) - exit(err); + uprobe_multi_func_1(); + uprobe_multi_func_2(); + uprobe_multi_func_3(); + usdt_trigger(); - uprobe_multi_func_1(); - uprobe_multi_func_2(); - uprobe_multi_func_3(); - usdt_trigger(); + exit(errno); +} - exit(errno); +static int spawn_child_flag(struct child *child, bool clone_vm) +{ + /* pipe to notify child to execute the trigger functions */ + if (pipe(child->go)) + return -1; + + if (clone_vm) { + child->pid = child->tid = clone(child_func, child->stack + sizeof(child->stack)/2, + CLONE_VM|SIGCHLD, child); + } else { + child->pid = child->tid = fork(); + } + if (child->pid < 0) { + release_child(child); + errno = EINVAL; + return -1; } - return &child; + /* fork-ed child */ + if (!clone_vm && child->pid == 0) + child_func(child); + + return 0; +} + +static int spawn_child(struct child *child) +{ + return spawn_child_flag(child, false); } static void *child_thread(void *ctx) @@ -130,39 +146,38 @@ static void *child_thread(void *ctx) pthread_exit(&err); } -static struct child *spawn_thread(void) +static int spawn_thread(struct child *child) { - static struct child child; int c, err; /* pipe to notify child to execute the trigger functions */ - if (pipe(child.go)) - return NULL; + if (pipe(child->go)) + return -1; /* pipe to notify parent that child thread is ready */ - if (pipe(child.c2p)) { - close(child.go[0]); - close(child.go[1]); - return NULL; + if (pipe(child->c2p)) { + close(child->go[0]); + close(child->go[1]); + return -1; } - child.pid = getpid(); + child->pid = getpid(); - err = pthread_create(&child.thread, NULL, child_thread, &child); + err = pthread_create(&child->thread, NULL, child_thread, child); if (err) { err = -errno; - close(child.go[0]); - close(child.go[1]); - close(child.c2p[0]); - close(child.c2p[1]); + close(child->go[0]); + close(child->go[1]); + close(child->c2p[0]); + close(child->c2p[1]); errno = -err; - return NULL; + return -1; } - err = read(child.c2p[0], &c, 1); + err = read(child->c2p[0], &c, 1); if (!ASSERT_EQ(err, 1, "child_thread_ready")) - return NULL; + return -1; - return &child; + return 0; } static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child) @@ -198,7 +213,7 @@ static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child /* * There are 2 entry and 2 exit probe called for each uprobe_multi_func_[123] - * function and each slepable probe (6) increments uprobe_multi_sleep_result. + * function and each sleepable probe (6) increments uprobe_multi_sleep_result. */ ASSERT_EQ(skel->bss->uprobe_multi_func_1_result, 2, "uprobe_multi_func_1_result"); ASSERT_EQ(skel->bss->uprobe_multi_func_2_result, 2, "uprobe_multi_func_2_result"); @@ -303,24 +318,22 @@ cleanup: static void test_attach_api(const char *binary, const char *pattern, struct bpf_uprobe_multi_opts *opts) { - struct child *child; + static struct child child; /* no pid filter */ __test_attach_api(binary, pattern, opts, NULL); /* pid filter */ - child = spawn_child(); - if (!ASSERT_OK_PTR(child, "spawn_child")) + if (!ASSERT_OK(spawn_child(&child), "spawn_child")) return; - __test_attach_api(binary, pattern, opts, child); + __test_attach_api(binary, pattern, opts, &child); /* pid filter (thread) */ - child = spawn_thread(); - if (!ASSERT_OK_PTR(child, "spawn_thread")) + if (!ASSERT_OK(spawn_thread(&child), "spawn_thread")) return; - __test_attach_api(binary, pattern, opts, child); + __test_attach_api(binary, pattern, opts, &child); } static void test_attach_api_pattern(void) @@ -516,6 +529,122 @@ cleanup: uprobe_multi__destroy(skel); } +#ifdef __x86_64__ +noinline void uprobe_multi_error_func(void) +{ + /* + * If --fcf-protection=branch is enabled the gcc generates endbr as + * first instruction, so marking the exact address of int3 with the + * symbol to be used in the attach_uprobe_fail_trap test below. + */ + asm volatile ( + ".globl uprobe_multi_error_func_int3; \n" + "uprobe_multi_error_func_int3: \n" + "int3 \n" + ); +} + +/* + * Attaching uprobe on uprobe_multi_error_func results in error + * because it already starts with int3 instruction. + */ +static void attach_uprobe_fail_trap(struct uprobe_multi *skel) +{ + LIBBPF_OPTS(bpf_uprobe_multi_opts, opts); + const char *syms[4] = { + "uprobe_multi_func_1", + "uprobe_multi_func_2", + "uprobe_multi_func_3", + "uprobe_multi_error_func_int3", + }; + + opts.syms = syms; + opts.cnt = ARRAY_SIZE(syms); + + skel->links.uprobe = bpf_program__attach_uprobe_multi(skel->progs.uprobe, -1, + "/proc/self/exe", NULL, &opts); + if (!ASSERT_ERR_PTR(skel->links.uprobe, "bpf_program__attach_uprobe_multi")) { + bpf_link__destroy(skel->links.uprobe); + skel->links.uprobe = NULL; + } +} +#else +static void attach_uprobe_fail_trap(struct uprobe_multi *skel) { } +#endif + +short sema_1 __used, sema_2 __used; + +static void attach_uprobe_fail_refctr(struct uprobe_multi *skel) +{ + unsigned long *tmp_offsets = NULL, *tmp_ref_ctr_offsets = NULL; + unsigned long offsets[3], ref_ctr_offsets[3]; + LIBBPF_OPTS(bpf_link_create_opts, opts); + const char *path = "/proc/self/exe"; + const char *syms[3] = { + "uprobe_multi_func_1", + "uprobe_multi_func_2", + }; + const char *sema[3] = { + "sema_1", + "sema_2", + }; + int prog_fd, link_fd, err; + + prog_fd = bpf_program__fd(skel->progs.uprobe_extra); + + err = elf_resolve_syms_offsets("/proc/self/exe", 2, (const char **) &syms, + &tmp_offsets, STT_FUNC); + if (!ASSERT_OK(err, "elf_resolve_syms_offsets_func")) + return; + + err = elf_resolve_syms_offsets("/proc/self/exe", 2, (const char **) &sema, + &tmp_ref_ctr_offsets, STT_OBJECT); + if (!ASSERT_OK(err, "elf_resolve_syms_offsets_sema")) + goto cleanup; + + /* + * We attach to 3 uprobes on 2 functions, so 2 uprobes share single function, + * but with different ref_ctr_offset which is not allowed and results in fail. + */ + offsets[0] = tmp_offsets[0]; /* uprobe_multi_func_1 */ + offsets[1] = tmp_offsets[1]; /* uprobe_multi_func_2 */ + offsets[2] = tmp_offsets[1]; /* uprobe_multi_func_2 */ + + ref_ctr_offsets[0] = tmp_ref_ctr_offsets[0]; /* sema_1 */ + ref_ctr_offsets[1] = tmp_ref_ctr_offsets[1]; /* sema_2 */ + ref_ctr_offsets[2] = tmp_ref_ctr_offsets[0]; /* sema_1, error */ + + opts.uprobe_multi.path = path; + opts.uprobe_multi.offsets = (const unsigned long *) &offsets; + opts.uprobe_multi.ref_ctr_offsets = (const unsigned long *) &ref_ctr_offsets; + opts.uprobe_multi.cnt = 3; + + link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts); + if (!ASSERT_ERR(link_fd, "link_fd")) + close(link_fd); + +cleanup: + free(tmp_ref_ctr_offsets); + free(tmp_offsets); +} + +static void test_attach_uprobe_fails(void) +{ + struct uprobe_multi *skel = NULL; + + skel = uprobe_multi__open_and_load(); + if (!ASSERT_OK_PTR(skel, "uprobe_multi__open_and_load")) + return; + + /* attach fails due to adding uprobe on trap instruction, x86_64 only */ + attach_uprobe_fail_trap(skel); + + /* attach fail due to wrong ref_ctr_offs on one of the uprobes */ + attach_uprobe_fail_refctr(skel); + + uprobe_multi__destroy(skel); +} + static void __test_link_api(struct child *child) { int prog_fd, link1_fd = -1, link2_fd = -1, link3_fd = -1, link4_fd = -1; @@ -595,24 +724,296 @@ cleanup: static void test_link_api(void) { - struct child *child; + static struct child child; /* no pid filter */ __test_link_api(NULL); /* pid filter */ - child = spawn_child(); - if (!ASSERT_OK_PTR(child, "spawn_child")) + if (!ASSERT_OK(spawn_child(&child), "spawn_child")) return; - __test_link_api(child); + __test_link_api(&child); /* pid filter (thread) */ - child = spawn_thread(); - if (!ASSERT_OK_PTR(child, "spawn_thread")) + if (!ASSERT_OK(spawn_thread(&child), "spawn_thread")) + return; + + __test_link_api(&child); +} + +static struct bpf_program * +get_program(struct uprobe_multi_consumers *skel, int prog) +{ + switch (prog) { + case 0: + return skel->progs.uprobe_0; + case 1: + return skel->progs.uprobe_1; + case 2: + return skel->progs.uprobe_2; + case 3: + return skel->progs.uprobe_3; + default: + ASSERT_FAIL("get_program"); + return NULL; + } +} + +static struct bpf_link ** +get_link(struct uprobe_multi_consumers *skel, int link) +{ + switch (link) { + case 0: + return &skel->links.uprobe_0; + case 1: + return &skel->links.uprobe_1; + case 2: + return &skel->links.uprobe_2; + case 3: + return &skel->links.uprobe_3; + default: + ASSERT_FAIL("get_link"); + return NULL; + } +} + +static int uprobe_attach(struct uprobe_multi_consumers *skel, int idx) +{ + struct bpf_program *prog = get_program(skel, idx); + struct bpf_link **link = get_link(skel, idx); + LIBBPF_OPTS(bpf_uprobe_multi_opts, opts); + + if (!prog || !link) + return -1; + + /* + * bit/prog: 0,1 uprobe entry + * bit/prog: 2,3 uprobe return + */ + opts.retprobe = idx == 2 || idx == 3; + + *link = bpf_program__attach_uprobe_multi(prog, 0, "/proc/self/exe", + "uprobe_consumer_test", + &opts); + if (!ASSERT_OK_PTR(*link, "bpf_program__attach_uprobe_multi")) + return -1; + return 0; +} + +static void uprobe_detach(struct uprobe_multi_consumers *skel, int idx) +{ + struct bpf_link **link = get_link(skel, idx); + + bpf_link__destroy(*link); + *link = NULL; +} + +static bool test_bit(int bit, unsigned long val) +{ + return val & (1 << bit); +} + +noinline int +uprobe_consumer_test(struct uprobe_multi_consumers *skel, + unsigned long before, unsigned long after) +{ + int idx; + + /* detach uprobe for each unset programs in 'before' state ... */ + for (idx = 0; idx < 4; idx++) { + if (test_bit(idx, before) && !test_bit(idx, after)) + uprobe_detach(skel, idx); + } + + /* ... and attach all new programs in 'after' state */ + for (idx = 0; idx < 4; idx++) { + if (!test_bit(idx, before) && test_bit(idx, after)) { + if (!ASSERT_OK(uprobe_attach(skel, idx), "uprobe_attach_after")) + return -1; + } + } + return 0; +} + +static void consumer_test(struct uprobe_multi_consumers *skel, + unsigned long before, unsigned long after) +{ + int err, idx; + + printf("consumer_test before %lu after %lu\n", before, after); + + /* 'before' is each, we attach uprobe for every set idx */ + for (idx = 0; idx < 4; idx++) { + if (test_bit(idx, before)) { + if (!ASSERT_OK(uprobe_attach(skel, idx), "uprobe_attach_before")) + goto cleanup; + } + } + + err = uprobe_consumer_test(skel, before, after); + if (!ASSERT_EQ(err, 0, "uprobe_consumer_test")) + goto cleanup; + + for (idx = 0; idx < 4; idx++) { + const char *fmt = "BUG"; + __u64 val = 0; + + if (idx < 2) { + /* + * uprobe entry + * +1 if define in 'before' + */ + if (test_bit(idx, before)) + val++; + fmt = "prog 0/1: uprobe"; + } else { + /* + * uprobe return is tricky ;-) + * + * to trigger uretprobe consumer, the uretprobe needs to be installed, + * which means one of the 'return' uprobes was alive when probe was hit: + * + * idxs: 2/3 uprobe return in 'installed' mask + * + * in addition if 'after' state removes everything that was installed in + * 'before' state, then uprobe kernel object goes away and return uprobe + * is not installed and we won't hit it even if it's in 'after' state. + */ + unsigned long had_uretprobes = before & 0b1100; /* is uretprobe installed */ + unsigned long probe_preserved = before & after; /* did uprobe go away */ + + if (had_uretprobes && probe_preserved && test_bit(idx, after)) + val++; + fmt = "idx 2/3: uretprobe"; + } + + ASSERT_EQ(skel->bss->uprobe_result[idx], val, fmt); + skel->bss->uprobe_result[idx] = 0; + } + +cleanup: + for (idx = 0; idx < 4; idx++) + uprobe_detach(skel, idx); +} + +static void test_consumers(void) +{ + struct uprobe_multi_consumers *skel; + int before, after; + + skel = uprobe_multi_consumers__open_and_load(); + if (!ASSERT_OK_PTR(skel, "uprobe_multi_consumers__open_and_load")) + return; + + /* + * The idea of this test is to try all possible combinations of + * uprobes consumers attached on single function. + * + * - 2 uprobe entry consumer + * - 2 uprobe exit consumers + * + * The test uses 4 uprobes attached on single function, but that + * translates into single uprobe with 4 consumers in kernel. + * + * The before/after values present the state of attached consumers + * before and after the probed function: + * + * bit/prog 0,1 : uprobe entry + * bit/prog 2,3 : uprobe return + * + * For example for: + * + * before = 0b0101 + * after = 0b0110 + * + * it means that before we call 'uprobe_consumer_test' we attach + * uprobes defined in 'before' value: + * + * - bit/prog 0: uprobe entry + * - bit/prog 2: uprobe return + * + * uprobe_consumer_test is called and inside it we attach and detach + * uprobes based on 'after' value: + * + * - bit/prog 0: stays untouched + * - bit/prog 2: uprobe return is detached + * + * uprobe_consumer_test returns and we check counters values increased + * by bpf programs on each uprobe to match the expected count based on + * before/after bits. + */ + + for (before = 0; before < 16; before++) { + for (after = 0; after < 16; after++) + consumer_test(skel, before, after); + } + + uprobe_multi_consumers__destroy(skel); +} + +static struct bpf_program *uprobe_multi_program(struct uprobe_multi_pid_filter *skel, int idx) +{ + switch (idx) { + case 0: return skel->progs.uprobe_multi_0; + case 1: return skel->progs.uprobe_multi_1; + case 2: return skel->progs.uprobe_multi_2; + } + return NULL; +} + +#define TASKS 3 + +static void run_pid_filter(struct uprobe_multi_pid_filter *skel, bool clone_vm, bool retprobe) +{ + LIBBPF_OPTS(bpf_uprobe_multi_opts, opts, .retprobe = retprobe); + struct bpf_link *link[TASKS] = {}; + struct child child[TASKS] = {}; + int i; + + memset(skel->bss->test, 0, sizeof(skel->bss->test)); + + for (i = 0; i < TASKS; i++) { + if (!ASSERT_OK(spawn_child_flag(&child[i], clone_vm), "spawn_child")) + goto cleanup; + skel->bss->pids[i] = child[i].pid; + } + + for (i = 0; i < TASKS; i++) { + link[i] = bpf_program__attach_uprobe_multi(uprobe_multi_program(skel, i), + child[i].pid, "/proc/self/exe", + "uprobe_multi_func_1", &opts); + if (!ASSERT_OK_PTR(link[i], "bpf_program__attach_uprobe_multi")) + goto cleanup; + } + + for (i = 0; i < TASKS; i++) + kick_child(&child[i]); + + for (i = 0; i < TASKS; i++) { + ASSERT_EQ(skel->bss->test[i][0], 1, "pid"); + ASSERT_EQ(skel->bss->test[i][1], 0, "unknown"); + } + +cleanup: + for (i = 0; i < TASKS; i++) + bpf_link__destroy(link[i]); + for (i = 0; i < TASKS; i++) + release_child(&child[i]); +} + +static void test_pid_filter_process(bool clone_vm) +{ + struct uprobe_multi_pid_filter *skel; + + skel = uprobe_multi_pid_filter__open_and_load(); + if (!ASSERT_OK_PTR(skel, "uprobe_multi_pid_filter__open_and_load")) return; - __test_link_api(child); + run_pid_filter(skel, clone_vm, false); + run_pid_filter(skel, clone_vm, true); + + uprobe_multi_pid_filter__destroy(skel); } static void test_bench_attach_uprobe(void) @@ -703,4 +1104,12 @@ void test_uprobe_multi_test(void) test_bench_attach_usdt(); if (test__start_subtest("attach_api_fails")) test_attach_api_fails(); + if (test__start_subtest("attach_uprobe_fails")) + test_attach_uprobe_fails(); + if (test__start_subtest("consumers")) + test_consumers(); + if (test__start_subtest("filter_fork")) + test_pid_filter_process(false); + if (test__start_subtest("filter_clone_vm")) + test_pid_filter_process(true); } |