selftests/bpf: Add uprobe/usdt syscall tests

Adding tests for optimized uprobe/usdt probes.

Checking that we get expected trampoline and attached bpf programs
get executed properly.

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-15-jolsa@kernel.org
This commit is contained in:
Jiri Olsa
2025-08-21 20:09:24 +02:00
committed by Peter Zijlstra
parent 7932c4cf57
commit d5c86c3370
2 changed files with 335 additions and 1 deletions
@@ -8,6 +8,7 @@
#include <asm/ptrace.h>
#include <linux/compiler.h>
#include <linux/stringify.h>
#include <linux/kernel.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
@@ -15,6 +16,11 @@
#include "uprobe_syscall.skel.h"
#include "uprobe_syscall_executed.skel.h"
#define USDT_NOP .byte 0x0f, 0x1f, 0x44, 0x00, 0x00
#include "usdt.h"
#pragma GCC diagnostic ignored "-Wattributes"
__naked unsigned long uretprobe_regs_trigger(void)
{
asm volatile (
@@ -305,6 +311,265 @@ cleanup:
close(go[0]);
}
#define TRAMP "[uprobes-trampoline]"
__attribute__((aligned(16)))
__nocf_check __weak __naked void uprobe_test(void)
{
asm volatile (" \n"
".byte 0x0f, 0x1f, 0x44, 0x00, 0x00 \n"
"ret \n"
);
}
__attribute__((aligned(16)))
__nocf_check __weak void usdt_test(void)
{
USDT(optimized_uprobe, usdt);
}
static int find_uprobes_trampoline(void *tramp_addr)
{
void *start, *end;
char line[128];
int ret = -1;
FILE *maps;
maps = fopen("/proc/self/maps", "r");
if (!maps) {
fprintf(stderr, "cannot open maps\n");
return -1;
}
while (fgets(line, sizeof(line), maps)) {
int m = -1;
/* We care only about private r-x mappings. */
if (sscanf(line, "%p-%p r-xp %*x %*x:%*x %*u %n", &start, &end, &m) != 2)
continue;
if (m < 0)
continue;
if (!strncmp(&line[m], TRAMP, sizeof(TRAMP)-1) && (start == tramp_addr)) {
ret = 0;
break;
}
}
fclose(maps);
return ret;
}
static unsigned char nop5[5] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 };
static void *find_nop5(void *fn)
{
int i;
for (i = 0; i < 10; i++) {
if (!memcmp(nop5, fn + i, 5))
return fn + i;
}
return NULL;
}
typedef void (__attribute__((nocf_check)) *trigger_t)(void);
static bool shstk_is_enabled;
static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigger,
void *addr, int executed)
{
struct __arch_relative_insn {
__u8 op;
__s32 raddr;
} __packed *call;
void *tramp = NULL;
__u8 *bp;
/* Uprobe gets optimized after first trigger, so let's press twice. */
trigger();
trigger();
/* Make sure bpf program got executed.. */
ASSERT_EQ(skel->bss->executed, executed, "executed");
if (shstk_is_enabled) {
/* .. and check optimization is disabled under shadow stack. */
bp = (__u8 *) addr;
ASSERT_EQ(*bp, 0xcc, "int3");
} else {
/* .. and check the trampoline is as expected. */
call = (struct __arch_relative_insn *) addr;
tramp = (void *) (call + 1) + call->raddr;
ASSERT_EQ(call->op, 0xe8, "call");
ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline");
}
return tramp;
}
static void check_detach(void *addr, void *tramp)
{
/* [uprobes_trampoline] stays after detach */
ASSERT_OK(!shstk_is_enabled && find_uprobes_trampoline(tramp), "uprobes_trampoline");
ASSERT_OK(memcmp(addr, nop5, 5), "nop5");
}
static void check(struct uprobe_syscall_executed *skel, struct bpf_link *link,
trigger_t trigger, void *addr, int executed)
{
void *tramp;
tramp = check_attach(skel, trigger, addr, executed);
bpf_link__destroy(link);
check_detach(addr, tramp);
}
static void test_uprobe_legacy(void)
{
struct uprobe_syscall_executed *skel = NULL;
LIBBPF_OPTS(bpf_uprobe_opts, opts,
.retprobe = true,
);
struct bpf_link *link;
unsigned long offset;
offset = get_uprobe_offset(&uprobe_test);
if (!ASSERT_GE(offset, 0, "get_uprobe_offset"))
goto cleanup;
/* uprobe */
skel = uprobe_syscall_executed__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_syscall_executed__open_and_load"))
return;
skel->bss->pid = getpid();
link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe,
0, "/proc/self/exe", offset, NULL);
if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts"))
goto cleanup;
check(skel, link, uprobe_test, uprobe_test, 2);
/* uretprobe */
skel->bss->executed = 0;
link = bpf_program__attach_uprobe_opts(skel->progs.test_uretprobe,
0, "/proc/self/exe", offset, &opts);
if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts"))
goto cleanup;
check(skel, link, uprobe_test, uprobe_test, 2);
cleanup:
uprobe_syscall_executed__destroy(skel);
}
static void test_uprobe_multi(void)
{
struct uprobe_syscall_executed *skel = NULL;
LIBBPF_OPTS(bpf_uprobe_multi_opts, opts);
struct bpf_link *link;
unsigned long offset;
offset = get_uprobe_offset(&uprobe_test);
if (!ASSERT_GE(offset, 0, "get_uprobe_offset"))
goto cleanup;
opts.offsets = &offset;
opts.cnt = 1;
skel = uprobe_syscall_executed__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_syscall_executed__open_and_load"))
return;
skel->bss->pid = getpid();
/* uprobe.multi */
link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_multi,
0, "/proc/self/exe", NULL, &opts);
if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi"))
goto cleanup;
check(skel, link, uprobe_test, uprobe_test, 2);
/* uretprobe.multi */
skel->bss->executed = 0;
opts.retprobe = true;
link = bpf_program__attach_uprobe_multi(skel->progs.test_uretprobe_multi,
0, "/proc/self/exe", NULL, &opts);
if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi"))
goto cleanup;
check(skel, link, uprobe_test, uprobe_test, 2);
cleanup:
uprobe_syscall_executed__destroy(skel);
}
static void test_uprobe_session(void)
{
struct uprobe_syscall_executed *skel = NULL;
LIBBPF_OPTS(bpf_uprobe_multi_opts, opts,
.session = true,
);
struct bpf_link *link;
unsigned long offset;
offset = get_uprobe_offset(&uprobe_test);
if (!ASSERT_GE(offset, 0, "get_uprobe_offset"))
goto cleanup;
opts.offsets = &offset;
opts.cnt = 1;
skel = uprobe_syscall_executed__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_syscall_executed__open_and_load"))
return;
skel->bss->pid = getpid();
link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_session,
0, "/proc/self/exe", NULL, &opts);
if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi"))
goto cleanup;
check(skel, link, uprobe_test, uprobe_test, 4);
cleanup:
uprobe_syscall_executed__destroy(skel);
}
static void test_uprobe_usdt(void)
{
struct uprobe_syscall_executed *skel;
struct bpf_link *link;
void *addr;
errno = 0;
addr = find_nop5(usdt_test);
if (!ASSERT_OK_PTR(addr, "find_nop5"))
return;
skel = uprobe_syscall_executed__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_syscall_executed__open_and_load"))
return;
skel->bss->pid = getpid();
link = bpf_program__attach_usdt(skel->progs.test_usdt,
-1 /* all PIDs */, "/proc/self/exe",
"optimized_uprobe", "usdt", NULL);
if (!ASSERT_OK_PTR(link, "bpf_program__attach_usdt"))
goto cleanup;
check(skel, link, usdt_test, addr, 2);
cleanup:
uprobe_syscall_executed__destroy(skel);
}
/*
* Borrowed from tools/testing/selftests/x86/test_shadow_stack.c.
*
@@ -347,11 +612,20 @@ static void test_uretprobe_shadow_stack(void)
return;
}
/* Run all of the uretprobe tests. */
/* Run all the tests with shadow stack in place. */
shstk_is_enabled = true;
test_uretprobe_regs_equal();
test_uretprobe_regs_change();
test_uretprobe_syscall_call();
test_uprobe_legacy();
test_uprobe_multi();
test_uprobe_session();
test_uprobe_usdt();
shstk_is_enabled = false;
ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
}
@@ -365,6 +639,14 @@ static void __test_uprobe_syscall(void)
test_uretprobe_syscall_call();
if (test__start_subtest("uretprobe_shadow_stack"))
test_uretprobe_shadow_stack();
if (test__start_subtest("uprobe_legacy"))
test_uprobe_legacy();
if (test__start_subtest("uprobe_multi"))
test_uprobe_multi();
if (test__start_subtest("uprobe_session"))
test_uprobe_session();
if (test__start_subtest("uprobe_usdt"))
test_uprobe_usdt();
}
#else
static void __test_uprobe_syscall(void)
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/usdt.bpf.h>
#include <string.h>
struct pt_regs regs;
@@ -10,6 +12,36 @@ char _license[] SEC("license") = "GPL";
int executed = 0;
int pid;
SEC("uprobe")
int BPF_UPROBE(test_uprobe)
{
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
executed++;
return 0;
}
SEC("uretprobe")
int BPF_URETPROBE(test_uretprobe)
{
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
executed++;
return 0;
}
SEC("uprobe.multi")
int test_uprobe_multi(struct pt_regs *ctx)
{
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
executed++;
return 0;
}
SEC("uretprobe.multi")
int test_uretprobe_multi(struct pt_regs *ctx)
{
@@ -19,3 +51,23 @@ int test_uretprobe_multi(struct pt_regs *ctx)
executed++;
return 0;
}
SEC("uprobe.session")
int test_uprobe_session(struct pt_regs *ctx)
{
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
executed++;
return 0;
}
SEC("usdt")
int test_usdt(struct pt_regs *ctx)
{
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
executed++;
return 0;
}