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:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(®s, 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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user