Merge tag 'perf-core-2025-09-26' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull performance events updates from Ingo Molnar:
 "Core perf code updates:

   - Convert mmap() related reference counts to refcount_t. This is in
     reaction to the recently fixed refcount bugs, which could have been
     detected earlier and could have mitigated the bug somewhat (Thomas
     Gleixner, Peter Zijlstra)

   - Clean up and simplify the callchain code, in preparation for
     sframes (Steven Rostedt, Josh Poimboeuf)

  Uprobes updates:

   - Add support to optimize usdt probes on x86-64, which gives a
     substantial speedup (Jiri Olsa)

   - Cleanups and fixes on x86 (Peter Zijlstra)

  PMU driver updates:

   - Various optimizations and fixes to the Intel PMU driver (Dapeng Mi)

  Misc cleanups and fixes:

   - Remove redundant __GFP_NOWARN (Qianfeng Rong)"

* tag 'perf-core-2025-09-26' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (57 commits)
  selftests/bpf: Fix uprobe_sigill test for uprobe syscall error value
  uprobes/x86: Return error from uprobe syscall when not called from trampoline
  perf: Skip user unwind if the task is a kernel thread
  perf: Simplify get_perf_callchain() user logic
  perf: Use current->flags & PF_KTHREAD|PF_USER_WORKER instead of current->mm == NULL
  perf: Have get_perf_callchain() return NULL if crosstask and user are set
  perf: Remove get_perf_callchain() init_nr argument
  perf/x86: Print PMU counters bitmap in x86_pmu_show_pmu_cap()
  perf/x86/intel: Add ICL_FIXED_0_ADAPTIVE bit into INTEL_FIXED_BITS_MASK
  perf/x86/intel: Change macro GLOBAL_CTRL_EN_PERF_METRICS to BIT_ULL(48)
  perf/x86: Add PERF_CAP_PEBS_TIMING_INFO flag
  perf/x86/intel: Fix IA32_PMC_x_CFG_B MSRs access error
  perf/x86/intel: Use early_initcall() to hook bts_init()
  uprobes: Remove redundant __GFP_NOWARN
  selftests/seccomp: validate uprobe syscall passes through seccomp
  seccomp: passthrough uprobe systemcall without filtering
  selftests/bpf: Fix uprobe syscall shadow stack test
  selftests/bpf: Change test_uretprobe_regs_change for uprobe and uretprobe
  selftests/bpf: Add uprobe_regs_equal test
  selftests/bpf: Add optimized usdt variant for basic usdt test
  ...
This commit is contained in:
Linus Torvalds
2025-09-30 11:11:21 -07:00
32 changed files with 2262 additions and 416 deletions
@@ -8,22 +8,31 @@
#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>
#include <asm/prctl.h>
#include "uprobe_syscall.skel.h"
#include "uprobe_syscall_executed.skel.h"
#include "bpf/libbpf_internal.h"
__naked unsigned long uretprobe_regs_trigger(void)
#define USDT_NOP .byte 0x0f, 0x1f, 0x44, 0x00, 0x00
#include "usdt.h"
#pragma GCC diagnostic ignored "-Wattributes"
__attribute__((aligned(16)))
__nocf_check __weak __naked unsigned long uprobe_regs_trigger(void)
{
asm volatile (
".byte 0x0f, 0x1f, 0x44, 0x00, 0x00\n" /* nop5 */
"movq $0xdeadbeef, %rax\n"
"ret\n"
);
}
__naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
__naked void uprobe_regs(struct pt_regs *before, struct pt_regs *after)
{
asm volatile (
"movq %r15, 0(%rdi)\n"
@@ -44,15 +53,17 @@ __naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
"movq $0, 120(%rdi)\n" /* orig_rax */
"movq $0, 128(%rdi)\n" /* rip */
"movq $0, 136(%rdi)\n" /* cs */
"pushq %rax\n"
"pushf\n"
"pop %rax\n"
"movq %rax, 144(%rdi)\n" /* eflags */
"pop %rax\n"
"movq %rsp, 152(%rdi)\n" /* rsp */
"movq $0, 160(%rdi)\n" /* ss */
/* save 2nd argument */
"pushq %rsi\n"
"call uretprobe_regs_trigger\n"
"call uprobe_regs_trigger\n"
/* save return value and load 2nd argument pointer to rax */
"pushq %rax\n"
@@ -92,25 +103,37 @@ __naked void uretprobe_regs(struct pt_regs *before, struct pt_regs *after)
);
}
static void test_uretprobe_regs_equal(void)
static void test_uprobe_regs_equal(bool retprobe)
{
LIBBPF_OPTS(bpf_uprobe_opts, opts,
.retprobe = retprobe,
);
struct uprobe_syscall *skel = NULL;
struct pt_regs before = {}, after = {};
unsigned long *pb = (unsigned long *) &before;
unsigned long *pa = (unsigned long *) &after;
unsigned long *pp;
unsigned long offset;
unsigned int i, cnt;
int err;
offset = get_uprobe_offset(&uprobe_regs_trigger);
if (!ASSERT_GE(offset, 0, "get_uprobe_offset"))
return;
skel = uprobe_syscall__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_syscall__open_and_load"))
goto cleanup;
err = uprobe_syscall__attach(skel);
if (!ASSERT_OK(err, "uprobe_syscall__attach"))
skel->links.probe = bpf_program__attach_uprobe_opts(skel->progs.probe,
0, "/proc/self/exe", offset, &opts);
if (!ASSERT_OK_PTR(skel->links.probe, "bpf_program__attach_uprobe_opts"))
goto cleanup;
uretprobe_regs(&before, &after);
/* make sure uprobe gets optimized */
if (!retprobe)
uprobe_regs_trigger();
uprobe_regs(&before, &after);
pp = (unsigned long *) &skel->bss->regs;
cnt = sizeof(before)/sizeof(*pb);
@@ -119,7 +142,7 @@ static void test_uretprobe_regs_equal(void)
unsigned int offset = i * sizeof(unsigned long);
/*
* Check register before and after uretprobe_regs_trigger call
* Check register before and after uprobe_regs_trigger call
* that triggers the uretprobe.
*/
switch (offset) {
@@ -133,7 +156,7 @@ static void test_uretprobe_regs_equal(void)
/*
* Check register seen from bpf program and register after
* uretprobe_regs_trigger call
* uprobe_regs_trigger call (with rax exception, check below).
*/
switch (offset) {
/*
@@ -146,6 +169,15 @@ static void test_uretprobe_regs_equal(void)
case offsetof(struct pt_regs, rsp):
case offsetof(struct pt_regs, ss):
break;
/*
* uprobe does not see return value in rax, it needs to see the
* original (before) rax value
*/
case offsetof(struct pt_regs, rax):
if (!retprobe) {
ASSERT_EQ(pp[i], pb[i], "uprobe rax prog-before value check");
break;
}
default:
if (!ASSERT_EQ(pp[i], pa[i], "register prog-after value check"))
fprintf(stdout, "failed register offset %u\n", offset);
@@ -175,7 +207,7 @@ static int write_bpf_testmod_uprobe(unsigned long offset)
return ret != n ? (int) ret : 0;
}
static void test_uretprobe_regs_change(void)
static void test_regs_change(void)
{
struct pt_regs before = {}, after = {};
unsigned long *pb = (unsigned long *) &before;
@@ -183,13 +215,16 @@ static void test_uretprobe_regs_change(void)
unsigned long cnt = sizeof(before)/sizeof(*pb);
unsigned int i, err, offset;
offset = get_uprobe_offset(uretprobe_regs_trigger);
offset = get_uprobe_offset(uprobe_regs_trigger);
err = write_bpf_testmod_uprobe(offset);
if (!ASSERT_OK(err, "register_uprobe"))
return;
uretprobe_regs(&before, &after);
/* make sure uprobe gets optimized */
uprobe_regs_trigger();
uprobe_regs(&before, &after);
err = write_bpf_testmod_uprobe(0);
if (!ASSERT_OK(err, "unregister_uprobe"))
@@ -252,6 +287,7 @@ static void test_uretprobe_syscall_call(void)
);
struct uprobe_syscall_executed *skel;
int pid, status, err, go[2], c = 0;
struct bpf_link *link;
if (!ASSERT_OK(pipe(go), "pipe"))
return;
@@ -277,11 +313,14 @@ static void test_uretprobe_syscall_call(void)
_exit(0);
}
skel->links.test = bpf_program__attach_uprobe_multi(skel->progs.test, pid,
"/proc/self/exe",
"uretprobe_syscall_call", &opts);
if (!ASSERT_OK_PTR(skel->links.test, "bpf_program__attach_uprobe_multi"))
skel->bss->pid = pid;
link = bpf_program__attach_uprobe_multi(skel->progs.test_uretprobe_multi,
pid, "/proc/self/exe",
"uretprobe_syscall_call", &opts);
if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi"))
goto cleanup;
skel->links.test_uretprobe_multi = link;
/* kick the child */
write(go[1], &c, 1);
@@ -301,6 +340,256 @@ 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 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;
/* 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");
/* .. 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(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.
*
@@ -343,30 +632,166 @@ static void test_uretprobe_shadow_stack(void)
return;
}
/* Run all of the uretprobe tests. */
test_uretprobe_regs_equal();
test_uretprobe_regs_change();
/* Run all the tests with shadow stack in place. */
test_uprobe_regs_equal(false);
test_uprobe_regs_equal(true);
test_uretprobe_syscall_call();
test_uprobe_legacy();
test_uprobe_multi();
test_uprobe_session();
test_uprobe_usdt();
test_regs_change();
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");
}
#ifndef __NR_uprobe
#define __NR_uprobe 336
#endif
static void test_uprobe_error(void)
{
long err = syscall(__NR_uprobe);
ASSERT_EQ(err, -1, "error");
ASSERT_EQ(errno, ENXIO, "errno");
}
static void __test_uprobe_syscall(void)
{
if (test__start_subtest("uretprobe_regs_equal"))
test_uprobe_regs_equal(true);
if (test__start_subtest("uretprobe_syscall_call"))
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();
if (test__start_subtest("uprobe_race"))
test_uprobe_race();
if (test__start_subtest("uprobe_error"))
test_uprobe_error();
if (test__start_subtest("uprobe_regs_equal"))
test_uprobe_regs_equal(false);
if (test__start_subtest("regs_change"))
test_regs_change();
}
#else
static void test_uretprobe_regs_equal(void)
{
test__skip();
}
static void test_uretprobe_regs_change(void)
{
test__skip();
}
static void test_uretprobe_syscall_call(void)
{
test__skip();
}
static void test_uretprobe_shadow_stack(void)
static void __test_uprobe_syscall(void)
{
test__skip();
}
@@ -374,12 +799,5 @@ static void test_uretprobe_shadow_stack(void)
void test_uprobe_syscall(void)
{
if (test__start_subtest("uretprobe_regs_equal"))
test_uretprobe_regs_equal();
if (test__start_subtest("uretprobe_regs_change"))
test_uretprobe_regs_change();
if (test__start_subtest("uretprobe_syscall_call"))
test_uretprobe_syscall_call();
if (test__start_subtest("uretprobe_shadow_stack"))
test_uretprobe_shadow_stack();
__test_uprobe_syscall();
}
+25 -13
View File
@@ -40,12 +40,19 @@ static void __always_inline trigger_func(int x) {
}
}
static void subtest_basic_usdt(void)
static void subtest_basic_usdt(bool optimized)
{
LIBBPF_OPTS(bpf_usdt_opts, opts);
struct test_usdt *skel;
struct test_usdt__bss *bss;
int err, i;
int err, i, called;
#define TRIGGER(x) ({ \
trigger_func(x); \
if (optimized) \
trigger_func(x); \
optimized ? 2 : 1; \
})
skel = test_usdt__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open"))
@@ -66,11 +73,11 @@ static void subtest_basic_usdt(void)
if (!ASSERT_OK_PTR(skel->links.usdt0, "usdt0_link"))
goto cleanup;
trigger_func(1);
called = TRIGGER(1);
ASSERT_EQ(bss->usdt0_called, 1, "usdt0_called");
ASSERT_EQ(bss->usdt3_called, 1, "usdt3_called");
ASSERT_EQ(bss->usdt12_called, 1, "usdt12_called");
ASSERT_EQ(bss->usdt0_called, called, "usdt0_called");
ASSERT_EQ(bss->usdt3_called, called, "usdt3_called");
ASSERT_EQ(bss->usdt12_called, called, "usdt12_called");
ASSERT_EQ(bss->usdt0_cookie, 0xcafedeadbeeffeed, "usdt0_cookie");
ASSERT_EQ(bss->usdt0_arg_cnt, 0, "usdt0_arg_cnt");
@@ -119,11 +126,11 @@ static void subtest_basic_usdt(void)
* bpf_program__attach_usdt() handles this properly and attaches to
* all possible places of USDT invocation.
*/
trigger_func(2);
called += TRIGGER(2);
ASSERT_EQ(bss->usdt0_called, 2, "usdt0_called");
ASSERT_EQ(bss->usdt3_called, 2, "usdt3_called");
ASSERT_EQ(bss->usdt12_called, 2, "usdt12_called");
ASSERT_EQ(bss->usdt0_called, called, "usdt0_called");
ASSERT_EQ(bss->usdt3_called, called, "usdt3_called");
ASSERT_EQ(bss->usdt12_called, called, "usdt12_called");
/* only check values that depend on trigger_func()'s input value */
ASSERT_EQ(bss->usdt3_args[0], 2, "usdt3_arg1");
@@ -142,9 +149,9 @@ static void subtest_basic_usdt(void)
if (!ASSERT_OK_PTR(skel->links.usdt3, "usdt3_reattach"))
goto cleanup;
trigger_func(3);
called += TRIGGER(3);
ASSERT_EQ(bss->usdt3_called, 3, "usdt3_called");
ASSERT_EQ(bss->usdt3_called, called, "usdt3_called");
/* this time usdt3 has custom cookie */
ASSERT_EQ(bss->usdt3_cookie, 0xBADC00C51E, "usdt3_cookie");
ASSERT_EQ(bss->usdt3_arg_cnt, 3, "usdt3_arg_cnt");
@@ -158,6 +165,7 @@ static void subtest_basic_usdt(void)
cleanup:
test_usdt__destroy(skel);
#undef TRIGGER
}
unsigned short test_usdt_100_semaphore SEC(".probes");
@@ -425,7 +433,11 @@ cleanup:
void test_usdt(void)
{
if (test__start_subtest("basic"))
subtest_basic_usdt();
subtest_basic_usdt(false);
#ifdef __x86_64__
if (test__start_subtest("basic_optimized"))
subtest_basic_usdt(true);
#endif
if (test__start_subtest("multispec"))
subtest_multispec_usdt();
if (test__start_subtest("urand_auto_attach"))
@@ -7,8 +7,8 @@ struct pt_regs regs;
char _license[] SEC("license") = "GPL";
SEC("uretprobe//proc/self/exe:uretprobe_regs_trigger")
int uretprobe(struct pt_regs *ctx)
SEC("uprobe")
int probe(struct pt_regs *ctx)
{
__builtin_memcpy(&regs, ctx, sizeof(regs));
return 0;
@@ -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;
@@ -8,10 +10,64 @@ struct pt_regs regs;
char _license[] SEC("license") = "GPL";
int executed = 0;
int pid;
SEC("uretprobe.multi")
int test(struct pt_regs *regs)
SEC("uprobe")
int BPF_UPROBE(test_uprobe)
{
executed = 1;
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)
{
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
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;
}
@@ -500,15 +500,21 @@ static struct bin_attribute bin_attr_bpf_testmod_file __ro_after_init = {
*/
#ifdef __x86_64__
static int
uprobe_handler(struct uprobe_consumer *self, struct pt_regs *regs, __u64 *data)
{
regs->cx = 0x87654321feebdaed;
return 0;
}
static int
uprobe_ret_handler(struct uprobe_consumer *self, unsigned long func,
struct pt_regs *regs, __u64 *data)
{
regs->ax = 0x12345678deadbeef;
regs->cx = 0x87654321feebdaed;
regs->r11 = (u64) -1;
return true;
return 0;
}
struct testmod_uprobe {
@@ -520,6 +526,7 @@ struct testmod_uprobe {
static DEFINE_MUTEX(testmod_uprobe_mutex);
static struct testmod_uprobe uprobe = {
.consumer.handler = uprobe_handler,
.consumer.ret_handler = uprobe_ret_handler,
};
+545
View File
@@ -0,0 +1,545 @@
// SPDX-License-Identifier: BSD-2-Clause
/*
* This single-header library defines a collection of variadic macros for
* defining and triggering USDTs (User Statically-Defined Tracepoints):
*
* - For USDTs without associated semaphore:
* USDT(group, name, args...)
*
* - For USDTs with implicit (transparent to the user) semaphore:
* USDT_WITH_SEMA(group, name, args...)
* USDT_IS_ACTIVE(group, name)
*
* - For USDTs with explicit (user-defined and provided) semaphore:
* USDT_WITH_EXPLICIT_SEMA(sema, group, name, args...)
* USDT_SEMA_IS_ACTIVE(sema)
*
* all of which emit a NOP instruction into the instruction stream, and so
* have *zero* overhead for the surrounding code. USDTs are identified by
* a combination of `group` and `name` identifiers, which is used by external
* tracing tooling (tracers) for identifying exact USDTs of interest.
*
* USDTs can have an associated (2-byte) activity counter (USDT semaphore),
* automatically maintained by Linux kernel whenever any correctly written
* BPF-based tracer is attached to the USDT. This USDT semaphore can be used
* to check whether there is a need to do any extra data collection and
* processing for a given USDT (if necessary), and otherwise avoid extra work
* for a common case of USDT not being traced ("active").
*
* See documentation for USDT_WITH_SEMA()/USDT_IS_ACTIVE() or
* USDT_WITH_EXPLICIT_SEMA()/USDT_SEMA_IS_ACTIVE() APIs below for details on
* working with USDTs with implicitly or explicitly associated
* USDT semaphores, respectively.
*
* There is also some additional data recorded into an auxiliary note
* section. The data in the note section describes the operands, in terms of
* size and location, used by tracing tooling to know where to find USDT
* arguments. Each location is encoded as an assembler operand string.
* Tracing tools (bpftrace and BPF-based tracers, systemtap, etc) insert
* breakpoints on top of the nop, and decode the location operand-strings,
* like an assembler, to find the values being passed.
*
* The operand strings are selected by the compiler for each operand.
* They are constrained by inline-assembler codes.The default is:
*
* #define USDT_ARG_CONSTRAINT nor
*
* This is a good default if the operands tend to be integral and
* moderate in number (smaller than number of registers). In other
* cases, the compiler may report "'asm' requires impossible reload" or
* similar. In this case, consider simplifying the macro call (fewer
* and simpler operands), reduce optimization, or override the default
* constraints string via:
*
* #define USDT_ARG_CONSTRAINT g
* #include <usdt.h>
*
* For some historical description of USDT v3 format (the one used by this
* library and generally recognized and assumed by BPF-based tracing tools)
* see [0]. The more formal specification can be found at [1]. Additional
* argument constraints information can be found at [2].
*
* Original SystemTap's sys/sdt.h implementation ([3]) was used as a base for
* this USDT library implementation. Current implementation differs *a lot* in
* terms of exposed user API and general usability, which was the main goal
* and focus of the reimplementation work. Nevertheless, underlying recorded
* USDT definitions are fully binary compatible and any USDT-based tooling
* should work equally well with USDTs defined by either SystemTap's or this
* library's USDT implementation.
*
* [0] https://ecos.sourceware.org/ml/systemtap/2010-q3/msg00145.html
* [1] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
* [2] https://gcc.gnu.org/onlinedocs/gcc/Constraints.html
* [3] https://sourceware.org/git/?p=systemtap.git;a=blob;f=includes/sys/sdt.h
*/
#ifndef __USDT_H
#define __USDT_H
/*
* Changelog:
*
* 0.1.0
* -----
* - Initial release
*/
#define USDT_MAJOR_VERSION 0
#define USDT_MINOR_VERSION 1
#define USDT_PATCH_VERSION 0
/* C++20 and C23 added __VA_OPT__ as a standard replacement for non-standard `##__VA_ARGS__` extension */
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || (defined(__cplusplus) && __cplusplus > 201703L)
#define __usdt_va_opt 1
#define __usdt_va_args(...) __VA_OPT__(,) __VA_ARGS__
#else
#define __usdt_va_args(...) , ##__VA_ARGS__
#endif
/*
* Trigger USDT with `group`:`name` identifier and pass through `args` as its
* arguments. Zero arguments are acceptable as well. No USDT semaphore is
* associated with this USDT.
*
* Such "semaphoreless" USDTs are commonly used when there is no extra data
* collection or processing needed to collect and prepare USDT arguments and
* they are just available in the surrounding code. USDT() macro will just
* record their locations in CPU registers or in memory for tracing tooling to
* be able to access them, if necessary.
*/
#ifdef __usdt_va_opt
#define USDT(group, name, ...) \
__usdt_probe(group, name, __usdt_sema_none, 0 __VA_OPT__(,) __VA_ARGS__)
#else
#define USDT(group, name, ...) \
__usdt_probe(group, name, __usdt_sema_none, 0, ##__VA_ARGS__)
#endif
/*
* Trigger USDT with `group`:`name` identifier and pass through `args` as its
* arguments. Zero arguments are acceptable as well. USDT also get an
* implicitly-defined associated USDT semaphore, which will be "activated" by
* tracing tooling and can be used to check whether USDT is being actively
* observed.
*
* USDTs with semaphore are commonly used when there is a need to perform
* additional data collection and processing to prepare USDT arguments, which
* otherwise might not be necessary for the rest of application logic. In such
* case, USDT semaphore can be used to avoid unnecessary extra work. If USDT
* is not traced (which is presumed to be a common situation), the associated
* USDT semaphore is "inactive", and so there is no need to waste resources to
* prepare USDT arguments. Use USDT_IS_ACTIVE(group, name) to check whether
* USDT is "active".
*
* N.B. There is an inherent (albeit short) gap between checking whether USDT
* is active and triggering corresponding USDT, in which external tracer can
* be attached to an USDT and activate USDT semaphore after the activity check.
* If such a race occurs, tracers might miss one USDT execution. Tracers are
* expected to accommodate such possibility and this is expected to not be
* a problem for applications and tracers.
*
* N.B. Implicit USDT semaphore defined by USDT_WITH_SEMA() is contained
* within a single executable or shared library and is not shared outside
* them. I.e., if you use USDT_WITH_SEMA() with the same USDT group and name
* identifier across executable and shared library, it will work and won't
* conflict, per se, but will define independent USDT semaphores, one for each
* shared library/executable in which USDT_WITH_SEMA(group, name) is used.
* That is, if you attach to this USDT in one shared library (or executable),
* then only USDT semaphore within that shared library (or executable) will be
* updated by the kernel, while other libraries (or executable) will not see
* activated USDT semaphore. In short, it's best to use unique USDT group:name
* identifiers across different shared libraries (and, equivalently, between
* executable and shared library). This is advanced consideration and is
* rarely (if ever) seen in practice, but just to avoid surprises this is
* called out here. (Static libraries become a part of final executable, once
* linked by linker, so the above considerations don't apply to them.)
*/
#ifdef __usdt_va_opt
#define USDT_WITH_SEMA(group, name, ...) \
__usdt_probe(group, name, \
__usdt_sema_implicit, __usdt_sema_name(group, name) \
__VA_OPT__(,) __VA_ARGS__)
#else
#define USDT_WITH_SEMA(group, name, ...) \
__usdt_probe(group, name, \
__usdt_sema_implicit, __usdt_sema_name(group, name), \
##__VA_ARGS__)
#endif
struct usdt_sema { volatile unsigned short active; };
/*
* Check if USDT with `group`:`name` identifier is "active" (i.e., whether it
* is attached to by external tracing tooling and is actively observed).
*
* This macro can be used to decide whether any additional and potentially
* expensive data collection or processing should be done to pass extra
* information into the given USDT. It is assumed that USDT is triggered with
* USDT_WITH_SEMA() macro which will implicitly define associated USDT
* semaphore. (If one needs more control over USDT semaphore, see
* USDT_DEFINE_SEMA() and USDT_WITH_EXPLICIT_SEMA() macros below.)
*
* N.B. Such checks are necessarily racy and speculative. Between checking
* whether USDT is active and triggering the USDT itself, tracer can be
* detached with no notification. This race should be extremely rare and worst
* case should result in one-time wasted extra data collection and processing.
*/
#define USDT_IS_ACTIVE(group, name) ({ \
extern struct usdt_sema __usdt_sema_name(group, name) \
__usdt_asm_name(__usdt_sema_name(group, name)); \
__usdt_sema_implicit(__usdt_sema_name(group, name)); \
__usdt_sema_name(group, name).active > 0; \
})
/*
* APIs for working with user-defined explicit USDT semaphores.
*
* This is a less commonly used advanced API for use cases in which user needs
* an explicit control over (potentially shared across multiple USDTs) USDT
* semaphore instance. This can be used when there is a group of logically
* related USDTs that all need extra data collection and processing whenever
* any of a family of related USDTs are "activated" (i.e., traced). In such
* a case, all such related USDTs will be associated with the same shared USDT
* semaphore defined with USDT_DEFINE_SEMA() and the USDTs themselves will be
* triggered with USDT_WITH_EXPLICIT_SEMA() macros, taking an explicit extra
* USDT semaphore identifier as an extra parameter.
*/
/**
* Underlying C global variable name for user-defined USDT semaphore with
* `sema` identifier. Could be useful for debugging, but normally shouldn't be
* used explicitly.
*/
#define USDT_SEMA(sema) __usdt_sema_##sema
/*
* Define storage for user-defined USDT semaphore `sema`.
*
* Should be used only once in non-header source file to let compiler allocate
* space for the semaphore variable. Just like with any other global variable.
*
* This macro can be used anywhere where global variable declaration is
* allowed. Just like with global variable definitions, there should be only
* one definition of user-defined USDT semaphore with given `sema` identifier,
* otherwise compiler or linker will complain about duplicate variable
* definition.
*
* For C++, it is allowed to use USDT_DEFINE_SEMA() both in global namespace
* and inside namespaces (including nested namespaces). Just make sure that
* USDT_DECLARE_SEMA() is placed within the namespace where this semaphore is
* referenced, or any of its parent namespaces, so the C++ language-level
* identifier is visible to the code that needs to reference the semaphore.
* At the lowest layer, USDT semaphores have global naming and visibility
* (they have a corresponding `__usdt_sema_<name>` symbol, which can be linked
* against from C or C++ code, if necessary). To keep it simple, putting
* USDT_DECLARE_SEMA() declarations into global namespaces is the simplest
* no-brainer solution. All these aspects are irrelevant for plain C, because
* C doesn't have namespaces and everything is always in the global namespace.
*
* N.B. Due to USDT metadata being recorded in non-allocatable ELF note
* section, it has limitations when it comes to relocations, which, in
* practice, means that it's not possible to correctly share USDT semaphores
* between main executable and shared libraries, or even between multiple
* shared libraries. USDT semaphore has to be contained to individual shared
* library or executable to avoid unpleasant surprises with half-working USDT
* semaphores. We enforce this by marking semaphore ELF symbols as having
* a hidden visibility. This is quite an advanced use case and consideration
* and for most users this should have no consequences whatsoever.
*/
#define USDT_DEFINE_SEMA(sema) \
struct usdt_sema __usdt_sema_sec USDT_SEMA(sema) \
__usdt_asm_name(USDT_SEMA(sema)) \
__attribute__((visibility("hidden"))) = { 0 }
/*
* Declare extern reference to user-defined USDT semaphore `sema`.
*
* Refers to a variable defined in another compilation unit by
* USDT_DEFINE_SEMA() and allows to use the same USDT semaphore across
* multiple compilation units (i.e., .c and .cpp files).
*
* See USDT_DEFINE_SEMA() notes above for C++ language usage peculiarities.
*/
#define USDT_DECLARE_SEMA(sema) \
extern struct usdt_sema USDT_SEMA(sema) __usdt_asm_name(USDT_SEMA(sema))
/*
* Check if user-defined USDT semaphore `sema` is "active" (i.e., whether it
* is attached to by external tracing tooling and is actively observed).
*
* This macro can be used to decide whether any additional and potentially
* expensive data collection or processing should be done to pass extra
* information into USDT(s) associated with USDT semaphore `sema`.
*
* N.B. Such checks are necessarily racy. Between checking the state of USDT
* semaphore and triggering associated USDT(s), the active tracer might attach
* or detach. This race should be extremely rare and worst case should result
* in one-time missed USDT event or wasted extra data collection and
* processing. USDT-using tracers should be written with this in mind and is
* not a concern of the application defining USDTs with associated semaphore.
*/
#define USDT_SEMA_IS_ACTIVE(sema) (USDT_SEMA(sema).active > 0)
/*
* Invoke USDT specified by `group` and `name` identifiers and associate
* explicitly user-defined semaphore `sema` with it. Pass through `args` as
* USDT arguments. `args` are optional and zero arguments are acceptable.
*
* Semaphore is defined with the help of USDT_DEFINE_SEMA() macro and can be
* checked whether active with USDT_SEMA_IS_ACTIVE().
*/
#ifdef __usdt_va_opt
#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
__usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema), ##__VA_ARGS__)
#else
#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
__usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema) __VA_OPT__(,) __VA_ARGS__)
#endif
/*
* Adjustable implementation aspects
*/
#ifndef USDT_ARG_CONSTRAINT
#if defined __powerpc__
#define USDT_ARG_CONSTRAINT nZr
#elif defined __arm__
#define USDT_ARG_CONSTRAINT g
#elif defined __loongarch__
#define USDT_ARG_CONSTRAINT nmr
#else
#define USDT_ARG_CONSTRAINT nor
#endif
#endif /* USDT_ARG_CONSTRAINT */
#ifndef USDT_NOP
#if defined(__ia64__) || defined(__s390__) || defined(__s390x__)
#define USDT_NOP nop 0
#else
#define USDT_NOP nop
#endif
#endif /* USDT_NOP */
/*
* Implementation details
*/
/* USDT name for implicitly-defined USDT semaphore, derived from group:name */
#define __usdt_sema_name(group, name) __usdt_sema_##group##__##name
/* ELF section into which USDT semaphores are put */
#define __usdt_sema_sec __attribute__((section(".probes")))
#define __usdt_concat(a, b) a ## b
#define __usdt_apply(fn, n) __usdt_concat(fn, n)
#ifndef __usdt_nth
#define __usdt_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N
#endif
#ifndef __usdt_narg
#ifdef __usdt_va_opt
#define __usdt_narg(...) __usdt_nth(_ __VA_OPT__(,) __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#else
#define __usdt_narg(...) __usdt_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#endif
#endif /* __usdt_narg */
#define __usdt_hash #
#define __usdt_str_(x) #x
#define __usdt_str(x) __usdt_str_(x)
#ifndef __usdt_asm_name
#define __usdt_asm_name(name) __asm__(__usdt_str(name))
#endif
#define __usdt_asm0() "\n"
#define __usdt_asm1(x) __usdt_str(x) "\n"
#define __usdt_asm2(x, ...) __usdt_str(x) "," __usdt_asm1(__VA_ARGS__)
#define __usdt_asm3(x, ...) __usdt_str(x) "," __usdt_asm2(__VA_ARGS__)
#define __usdt_asm4(x, ...) __usdt_str(x) "," __usdt_asm3(__VA_ARGS__)
#define __usdt_asm5(x, ...) __usdt_str(x) "," __usdt_asm4(__VA_ARGS__)
#define __usdt_asm6(x, ...) __usdt_str(x) "," __usdt_asm5(__VA_ARGS__)
#define __usdt_asm7(x, ...) __usdt_str(x) "," __usdt_asm6(__VA_ARGS__)
#define __usdt_asm8(x, ...) __usdt_str(x) "," __usdt_asm7(__VA_ARGS__)
#define __usdt_asm9(x, ...) __usdt_str(x) "," __usdt_asm8(__VA_ARGS__)
#define __usdt_asm10(x, ...) __usdt_str(x) "," __usdt_asm9(__VA_ARGS__)
#define __usdt_asm11(x, ...) __usdt_str(x) "," __usdt_asm10(__VA_ARGS__)
#define __usdt_asm12(x, ...) __usdt_str(x) "," __usdt_asm11(__VA_ARGS__)
#define __usdt_asm(...) __usdt_apply(__usdt_asm, __usdt_narg(__VA_ARGS__))(__VA_ARGS__)
#ifdef __LP64__
#define __usdt_asm_addr .8byte
#else
#define __usdt_asm_addr .4byte
#endif
#define __usdt_asm_strz_(x) __usdt_asm1(.asciz #x)
#define __usdt_asm_strz(x) __usdt_asm_strz_(x)
#define __usdt_asm_str_(x) __usdt_asm1(.ascii #x)
#define __usdt_asm_str(x) __usdt_asm_str_(x)
/* "semaphoreless" USDT case */
#ifndef __usdt_sema_none
#define __usdt_sema_none(sema)
#endif
/* implicitly defined __usdt_sema__group__name semaphore (using weak symbols) */
#ifndef __usdt_sema_implicit
#define __usdt_sema_implicit(sema) \
__asm__ __volatile__ ( \
__usdt_asm1(.ifndef sema) \
__usdt_asm3( .pushsection .probes, "aw", "progbits") \
__usdt_asm1( .weak sema) \
__usdt_asm1( .hidden sema) \
__usdt_asm1( .align 2) \
__usdt_asm1(sema:) \
__usdt_asm1( .zero 2) \
__usdt_asm2( .type sema, @object) \
__usdt_asm2( .size sema, 2) \
__usdt_asm1( .popsection) \
__usdt_asm1(.endif) \
);
#endif
/* externally defined semaphore using USDT_DEFINE_SEMA() and passed explicitly by user */
#ifndef __usdt_sema_explicit
#define __usdt_sema_explicit(sema) \
__asm__ __volatile__ ("" :: "m" (sema));
#endif
/* main USDT definition (nop and .note.stapsdt metadata) */
#define __usdt_probe(group, name, sema_def, sema, ...) do { \
sema_def(sema) \
__asm__ __volatile__ ( \
__usdt_asm( 990: USDT_NOP) \
__usdt_asm3( .pushsection .note.stapsdt, "", "note") \
__usdt_asm1( .balign 4) \
__usdt_asm3( .4byte 992f-991f,994f-993f,3) \
__usdt_asm1(991: .asciz "stapsdt") \
__usdt_asm1(992: .balign 4) \
__usdt_asm1(993: __usdt_asm_addr 990b) \
__usdt_asm1( __usdt_asm_addr _.stapsdt.base) \
__usdt_asm1( __usdt_asm_addr sema) \
__usdt_asm_strz(group) \
__usdt_asm_strz(name) \
__usdt_asm_args(__VA_ARGS__) \
__usdt_asm1( .ascii "\0") \
__usdt_asm1(994: .balign 4) \
__usdt_asm1( .popsection) \
__usdt_asm1(.ifndef _.stapsdt.base) \
__usdt_asm5( .pushsection .stapsdt.base,"aG","progbits",.stapsdt.base,comdat)\
__usdt_asm1( .weak _.stapsdt.base) \
__usdt_asm1( .hidden _.stapsdt.base) \
__usdt_asm1(_.stapsdt.base:) \
__usdt_asm1( .space 1) \
__usdt_asm2( .size _.stapsdt.base, 1) \
__usdt_asm1( .popsection) \
__usdt_asm1(.endif) \
:: __usdt_asm_ops(__VA_ARGS__) \
); \
} while (0)
/*
* NB: gdb PR24541 highlighted an unspecified corner of the sdt.h
* operand note format.
*
* The named register may be a longer or shorter (!) alias for the
* storage where the value in question is found. For example, on
* i386, 64-bit value may be put in register pairs, and a register
* name stored would identify just one of them. Previously, gcc was
* asked to emit the %w[id] (16-bit alias of some registers holding
* operands), even when a wider 32-bit value was used.
*
* Bottom line: the byte-width given before the @ sign governs. If
* there is a mismatch between that width and that of the named
* register, then a sys/sdt.h note consumer may need to employ
* architecture-specific heuristics to figure out where the compiler
* has actually put the complete value.
*/
#if defined(__powerpc__) || defined(__powerpc64__)
#define __usdt_argref(id) %I[id]%[id]
#elif defined(__i386__)
#define __usdt_argref(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */
#else
#define __usdt_argref(id) %[id]
#endif
#define __usdt_asm_arg(n) __usdt_asm_str(%c[__usdt_asz##n]) \
__usdt_asm1(.ascii "@") \
__usdt_asm_str(__usdt_argref(__usdt_aval##n))
#define __usdt_asm_args0 /* no arguments */
#define __usdt_asm_args1 __usdt_asm_arg(1)
#define __usdt_asm_args2 __usdt_asm_args1 __usdt_asm1(.ascii " ") __usdt_asm_arg(2)
#define __usdt_asm_args3 __usdt_asm_args2 __usdt_asm1(.ascii " ") __usdt_asm_arg(3)
#define __usdt_asm_args4 __usdt_asm_args3 __usdt_asm1(.ascii " ") __usdt_asm_arg(4)
#define __usdt_asm_args5 __usdt_asm_args4 __usdt_asm1(.ascii " ") __usdt_asm_arg(5)
#define __usdt_asm_args6 __usdt_asm_args5 __usdt_asm1(.ascii " ") __usdt_asm_arg(6)
#define __usdt_asm_args7 __usdt_asm_args6 __usdt_asm1(.ascii " ") __usdt_asm_arg(7)
#define __usdt_asm_args8 __usdt_asm_args7 __usdt_asm1(.ascii " ") __usdt_asm_arg(8)
#define __usdt_asm_args9 __usdt_asm_args8 __usdt_asm1(.ascii " ") __usdt_asm_arg(9)
#define __usdt_asm_args10 __usdt_asm_args9 __usdt_asm1(.ascii " ") __usdt_asm_arg(10)
#define __usdt_asm_args11 __usdt_asm_args10 __usdt_asm1(.ascii " ") __usdt_asm_arg(11)
#define __usdt_asm_args12 __usdt_asm_args11 __usdt_asm1(.ascii " ") __usdt_asm_arg(12)
#define __usdt_asm_args(...) __usdt_apply(__usdt_asm_args, __usdt_narg(__VA_ARGS__))
#define __usdt_is_arr(x) (__builtin_classify_type(x) == 14 || __builtin_classify_type(x) == 5)
#define __usdt_arg_size(x) (__usdt_is_arr(x) ? sizeof(void *) : sizeof(x))
/*
* We can't use __builtin_choose_expr() in C++, so fall back to table-based
* signedness determination for known types, utilizing templates magic.
*/
#ifdef __cplusplus
#define __usdt_is_signed(x) (!__usdt_is_arr(x) && __usdt_t<__typeof(x)>::is_signed)
#include <cstddef>
template<typename T> struct __usdt_t { static const bool is_signed = false; };
template<typename A> struct __usdt_t<A[]> : public __usdt_t<A *> {};
template<typename A, size_t N> struct __usdt_t<A[N]> : public __usdt_t<A *> {};
#define __usdt_def_signed(T) \
template<> struct __usdt_t<T> { static const bool is_signed = true; }; \
template<> struct __usdt_t<const T> { static const bool is_signed = true; }; \
template<> struct __usdt_t<volatile T> { static const bool is_signed = true; }; \
template<> struct __usdt_t<const volatile T> { static const bool is_signed = true; }
#define __usdt_maybe_signed(T) \
template<> struct __usdt_t<T> { static const bool is_signed = (T)-1 < (T)1; }; \
template<> struct __usdt_t<const T> { static const bool is_signed = (T)-1 < (T)1; }; \
template<> struct __usdt_t<volatile T> { static const bool is_signed = (T)-1 < (T)1; }; \
template<> struct __usdt_t<const volatile T> { static const bool is_signed = (T)-1 < (T)1; }
__usdt_def_signed(signed char);
__usdt_def_signed(short);
__usdt_def_signed(int);
__usdt_def_signed(long);
__usdt_def_signed(long long);
__usdt_maybe_signed(char);
__usdt_maybe_signed(wchar_t);
#else /* !__cplusplus */
#define __usdt_is_inttype(x) (__builtin_classify_type(x) >= 1 && __builtin_classify_type(x) <= 4)
#define __usdt_inttype(x) __typeof(__builtin_choose_expr(__usdt_is_inttype(x), (x), 0U))
#define __usdt_is_signed(x) ((__usdt_inttype(x))-1 < (__usdt_inttype(x))1)
#endif /* __cplusplus */
#define __usdt_asm_op(n, x) \
[__usdt_asz##n] "n" ((__usdt_is_signed(x) ? (int)-1 : 1) * (int)__usdt_arg_size(x)), \
[__usdt_aval##n] __usdt_str(USDT_ARG_CONSTRAINT)(x)
#define __usdt_asm_ops0() [__usdt_dummy] "g" (0)
#define __usdt_asm_ops1(x) __usdt_asm_op(1, x)
#define __usdt_asm_ops2(a,x) __usdt_asm_ops1(a), __usdt_asm_op(2, x)
#define __usdt_asm_ops3(a,b,x) __usdt_asm_ops2(a,b), __usdt_asm_op(3, x)
#define __usdt_asm_ops4(a,b,c,x) __usdt_asm_ops3(a,b,c), __usdt_asm_op(4, x)
#define __usdt_asm_ops5(a,b,c,d,x) __usdt_asm_ops4(a,b,c,d), __usdt_asm_op(5, x)
#define __usdt_asm_ops6(a,b,c,d,e,x) __usdt_asm_ops5(a,b,c,d,e), __usdt_asm_op(6, x)
#define __usdt_asm_ops7(a,b,c,d,e,f,x) __usdt_asm_ops6(a,b,c,d,e,f), __usdt_asm_op(7, x)
#define __usdt_asm_ops8(a,b,c,d,e,f,g,x) __usdt_asm_ops7(a,b,c,d,e,f,g), __usdt_asm_op(8, x)
#define __usdt_asm_ops9(a,b,c,d,e,f,g,h,x) __usdt_asm_ops8(a,b,c,d,e,f,g,h), __usdt_asm_op(9, x)
#define __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,x) __usdt_asm_ops9(a,b,c,d,e,f,g,h,i), __usdt_asm_op(10, x)
#define __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,x) __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,j), __usdt_asm_op(11, x)
#define __usdt_asm_ops12(a,b,c,d,e,f,g,h,i,j,k,x) __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,k), __usdt_asm_op(12, x)
#define __usdt_asm_ops(...) __usdt_apply(__usdt_asm_ops, __usdt_narg(__VA_ARGS__))(__VA_ARGS__)
#endif /* __USDT_H */
+88 -23
View File
@@ -74,6 +74,14 @@
#define noinline __attribute__((noinline))
#endif
#ifndef __nocf_check
#define __nocf_check __attribute__((nocf_check))
#endif
#ifndef __naked
#define __naked __attribute__((__naked__))
#endif
#ifndef PR_SET_NO_NEW_PRIVS
#define PR_SET_NO_NEW_PRIVS 38
#define PR_GET_NO_NEW_PRIVS 39
@@ -5027,7 +5035,36 @@ TEST(tsync_vs_dead_thread_leader)
EXPECT_EQ(0, status);
}
noinline int probed(void)
#ifdef __x86_64__
/*
* We need naked probed_uprobe function. Using __nocf_check
* check to skip possible endbr64 instruction and ignoring
* -Wattributes, otherwise the compilation might fail.
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"
__naked __nocf_check noinline int probed_uprobe(void)
{
/*
* Optimized uprobe is possible only on top of nop5 instruction.
*/
asm volatile (" \n"
".byte 0x0f, 0x1f, 0x44, 0x00, 0x00 \n"
"ret \n"
);
}
#pragma GCC diagnostic pop
#else
noinline int probed_uprobe(void)
{
return 1;
}
#endif
noinline int probed_uretprobe(void)
{
return 1;
}
@@ -5080,35 +5117,46 @@ static ssize_t get_uprobe_offset(const void *addr)
return found ? (uintptr_t)addr - start + base : -1;
}
FIXTURE(URETPROBE) {
FIXTURE(UPROBE) {
int fd;
};
FIXTURE_VARIANT(URETPROBE) {
FIXTURE_VARIANT(UPROBE) {
/*
* All of the URETPROBE behaviors can be tested with either
* uretprobe attached or not
* All of the U(RET)PROBE behaviors can be tested with either
* u(ret)probe attached or not
*/
bool attach;
/*
* Test both uprobe and uretprobe.
*/
bool uretprobe;
};
FIXTURE_VARIANT_ADD(URETPROBE, attached) {
.attach = true,
};
FIXTURE_VARIANT_ADD(URETPROBE, not_attached) {
FIXTURE_VARIANT_ADD(UPROBE, not_attached) {
.attach = false,
.uretprobe = false,
};
FIXTURE_SETUP(URETPROBE)
FIXTURE_VARIANT_ADD(UPROBE, uprobe_attached) {
.attach = true,
.uretprobe = false,
};
FIXTURE_VARIANT_ADD(UPROBE, uretprobe_attached) {
.attach = true,
.uretprobe = true,
};
FIXTURE_SETUP(UPROBE)
{
const size_t attr_sz = sizeof(struct perf_event_attr);
struct perf_event_attr attr;
ssize_t offset;
int type, bit;
#ifndef __NR_uretprobe
SKIP(return, "__NR_uretprobe syscall not defined");
#if !defined(__NR_uprobe) || !defined(__NR_uretprobe)
SKIP(return, "__NR_uprobe ot __NR_uretprobe syscalls not defined");
#endif
if (!variant->attach)
@@ -5118,12 +5166,17 @@ FIXTURE_SETUP(URETPROBE)
type = determine_uprobe_perf_type();
ASSERT_GE(type, 0);
bit = determine_uprobe_retprobe_bit();
ASSERT_GE(bit, 0);
offset = get_uprobe_offset(probed);
if (variant->uretprobe) {
bit = determine_uprobe_retprobe_bit();
ASSERT_GE(bit, 0);
}
offset = get_uprobe_offset(variant->uretprobe ? probed_uretprobe : probed_uprobe);
ASSERT_GE(offset, 0);
attr.config |= 1 << bit;
if (variant->uretprobe)
attr.config |= 1 << bit;
attr.size = attr_sz;
attr.type = type;
attr.config1 = ptr_to_u64("/proc/self/exe");
@@ -5134,7 +5187,7 @@ FIXTURE_SETUP(URETPROBE)
PERF_FLAG_FD_CLOEXEC);
}
FIXTURE_TEARDOWN(URETPROBE)
FIXTURE_TEARDOWN(UPROBE)
{
/* we could call close(self->fd), but we'd need extra filter for
* that and since we are calling _exit right away..
@@ -5148,11 +5201,17 @@ static int run_probed_with_filter(struct sock_fprog *prog)
return -1;
}
probed();
/*
* Uprobe is optimized after first hit, so let's hit twice.
*/
probed_uprobe();
probed_uprobe();
probed_uretprobe();
return 0;
}
TEST_F(URETPROBE, uretprobe_default_allow)
TEST_F(UPROBE, uprobe_default_allow)
{
struct sock_filter filter[] = {
BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
@@ -5165,7 +5224,7 @@ TEST_F(URETPROBE, uretprobe_default_allow)
ASSERT_EQ(0, run_probed_with_filter(&prog));
}
TEST_F(URETPROBE, uretprobe_default_block)
TEST_F(UPROBE, uprobe_default_block)
{
struct sock_filter filter[] = {
BPF_STMT(BPF_LD|BPF_W|BPF_ABS,
@@ -5182,11 +5241,14 @@ TEST_F(URETPROBE, uretprobe_default_block)
ASSERT_EQ(0, run_probed_with_filter(&prog));
}
TEST_F(URETPROBE, uretprobe_block_uretprobe_syscall)
TEST_F(UPROBE, uprobe_block_syscall)
{
struct sock_filter filter[] = {
BPF_STMT(BPF_LD|BPF_W|BPF_ABS,
offsetof(struct seccomp_data, nr)),
#ifdef __NR_uprobe
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_uprobe, 1, 2),
#endif
#ifdef __NR_uretprobe
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_uretprobe, 0, 1),
#endif
@@ -5201,11 +5263,14 @@ TEST_F(URETPROBE, uretprobe_block_uretprobe_syscall)
ASSERT_EQ(0, run_probed_with_filter(&prog));
}
TEST_F(URETPROBE, uretprobe_default_block_with_uretprobe_syscall)
TEST_F(UPROBE, uprobe_default_block_with_syscall)
{
struct sock_filter filter[] = {
BPF_STMT(BPF_LD|BPF_W|BPF_ABS,
offsetof(struct seccomp_data, nr)),
#ifdef __NR_uprobe
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_uprobe, 3, 0),
#endif
#ifdef __NR_uretprobe
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_uretprobe, 2, 0),
#endif