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:
committed by
Peter Zijlstra
parent
7932c4cf57
commit
d5c86c3370
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user