selftests/bpf: Add hit/attach/detach race optimized uprobe test

Adding test that makes sure parallel execution of the uprobe and
attach/detach of optimized uprobe on it works properly.

By default the test runs for 500ms, which is adjustable by using
BPF_SELFTESTS_UPROBE_SYSCALL_RACE_MSEC env variable.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20250720112133.244369-16-jolsa@kernel.org
This commit is contained in:
Jiri Olsa 2025-07-20 13:21:25 +02:00 committed by Peter Zijlstra
parent d5c86c3370
commit c8be59667c

View File

@ -15,6 +15,7 @@
#include <asm/prctl.h>
#include "uprobe_syscall.skel.h"
#include "uprobe_syscall_executed.skel.h"
#include "bpf/libbpf_internal.h"
#define USDT_NOP .byte 0x0f, 0x1f, 0x44, 0x00, 0x00
#include "usdt.h"
@ -629,6 +630,111 @@ static void test_uretprobe_shadow_stack(void)
ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
}
static volatile bool race_stop;
static USDT_DEFINE_SEMA(race);
static void *worker_trigger(void *arg)
{
unsigned long rounds = 0;
while (!race_stop) {
uprobe_test();
rounds++;
}
printf("tid %d trigger rounds: %lu\n", gettid(), rounds);
return NULL;
}
static void *worker_attach(void *arg)
{
LIBBPF_OPTS(bpf_uprobe_opts, opts);
struct uprobe_syscall_executed *skel;
unsigned long rounds = 0, offset;
const char *sema[2] = {
__stringify(USDT_SEMA(race)),
NULL,
};
unsigned long *ref;
int err;
offset = get_uprobe_offset(&uprobe_test);
if (!ASSERT_GE(offset, 0, "get_uprobe_offset"))
return NULL;
err = elf_resolve_syms_offsets("/proc/self/exe", 1, (const char **) &sema, &ref, STT_OBJECT);
if (!ASSERT_OK(err, "elf_resolve_syms_offsets_sema"))
return NULL;
opts.ref_ctr_offset = *ref;
skel = uprobe_syscall_executed__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_syscall_executed__open_and_load"))
return NULL;
skel->bss->pid = getpid();
while (!race_stop) {
skel->links.test_uprobe = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe,
0, "/proc/self/exe", offset, &opts);
if (!ASSERT_OK_PTR(skel->links.test_uprobe, "bpf_program__attach_uprobe_opts"))
break;
bpf_link__destroy(skel->links.test_uprobe);
skel->links.test_uprobe = NULL;
rounds++;
}
printf("tid %d attach rounds: %lu hits: %d\n", gettid(), rounds, skel->bss->executed);
uprobe_syscall_executed__destroy(skel);
free(ref);
return NULL;
}
static useconds_t race_msec(void)
{
char *env;
env = getenv("BPF_SELFTESTS_UPROBE_SYSCALL_RACE_MSEC");
if (env)
return atoi(env);
/* default duration is 500ms */
return 500;
}
static void test_uprobe_race(void)
{
int err, i, nr_threads;
pthread_t *threads;
nr_threads = libbpf_num_possible_cpus();
if (!ASSERT_GT(nr_threads, 0, "libbpf_num_possible_cpus"))
return;
nr_threads = max(2, nr_threads);
threads = alloca(sizeof(*threads) * nr_threads);
if (!ASSERT_OK_PTR(threads, "malloc"))
return;
for (i = 0; i < nr_threads; i++) {
err = pthread_create(&threads[i], NULL, i % 2 ? worker_trigger : worker_attach,
NULL);
if (!ASSERT_OK(err, "pthread_create"))
goto cleanup;
}
usleep(race_msec() * 1000);
cleanup:
race_stop = true;
for (nr_threads = i, i = 0; i < nr_threads; i++)
pthread_join(threads[i], NULL);
ASSERT_FALSE(USDT_SEMA_IS_ACTIVE(race), "race_semaphore");
}
static void __test_uprobe_syscall(void)
{
if (test__start_subtest("uretprobe_regs_equal"))
@ -647,6 +753,8 @@ static void __test_uprobe_syscall(void)
test_uprobe_session();
if (test__start_subtest("uprobe_usdt"))
test_uprobe_usdt();
if (test__start_subtest("uprobe_race"))
test_uprobe_race();
}
#else
static void __test_uprobe_syscall(void)