Skip to content

Commit 3c217a1

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
selftests/bpf: add build ID tests
Add a new set of tests validating behavior of capturing stack traces with build ID. We extend uprobe_multi target binary with ability to trigger uprobe (so that we can capture stack traces from it), but also we allow to force build ID data to be either resident or non-resident in memory (see also a comment about quirks of MADV_PAGEOUT). That way we can validate that in non-sleepable context we won't get build ID (as expected), but with sleepable uprobes we will get that build ID regardless of it being physically present in memory. Also, we add a small add-on linker script which reorders .note.gnu.build-id section and puts it after (big) .text section, putting build ID data outside of the very first page of ELF file. This will test all the relaxations we did in build ID parsing logic in kernel thanks to freader abstraction. Reviewed-by: Eduard Zingerman <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent d4dd977 commit 3c217a1

File tree

5 files changed

+204
-2
lines changed

5 files changed

+204
-2
lines changed

tools/testing/selftests/bpf/Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -845,9 +845,10 @@ $(OUTPUT)/veristat: $(OUTPUT)/veristat.o
845845

846846
# Linking uprobe_multi can fail due to relocation overflows on mips.
847847
$(OUTPUT)/uprobe_multi: CFLAGS += $(if $(filter mips, $(ARCH)),-mxgot)
848-
$(OUTPUT)/uprobe_multi: uprobe_multi.c
848+
$(OUTPUT)/uprobe_multi: uprobe_multi.c uprobe_multi.ld
849849
$(call msg,BINARY,,$@)
850-
$(Q)$(CC) $(CFLAGS) -O0 $(LDFLAGS) $^ $(LDLIBS) -o $@
850+
$(Q)$(CC) $(CFLAGS) -Wl,-T,uprobe_multi.ld -O0 $(LDFLAGS) \
851+
$(filter-out %.ld,$^) $(LDLIBS) -o $@
851852

852853
EXTRA_CLEAN := $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) \
853854
prog_tests/tests.h map_tests/tests.h verifier/tests.h \
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
3+
#include <test_progs.h>
4+
5+
#include "test_build_id.skel.h"
6+
7+
static char build_id[BPF_BUILD_ID_SIZE];
8+
static int build_id_sz;
9+
10+
static void print_stack(struct bpf_stack_build_id *stack, int frame_cnt)
11+
{
12+
int i, j;
13+
14+
for (i = 0; i < frame_cnt; i++) {
15+
printf("FRAME #%02d: ", i);
16+
switch (stack[i].status) {
17+
case BPF_STACK_BUILD_ID_EMPTY:
18+
printf("<EMPTY>\n");
19+
break;
20+
case BPF_STACK_BUILD_ID_VALID:
21+
printf("BUILD ID = ");
22+
for (j = 0; j < BPF_BUILD_ID_SIZE; j++)
23+
printf("%02hhx", (unsigned)stack[i].build_id[j]);
24+
printf(" OFFSET = %llx", (unsigned long long)stack[i].offset);
25+
break;
26+
case BPF_STACK_BUILD_ID_IP:
27+
printf("IP = %llx", (unsigned long long)stack[i].ip);
28+
break;
29+
default:
30+
printf("UNEXPECTED STATUS %d ", stack[i].status);
31+
break;
32+
}
33+
printf("\n");
34+
}
35+
}
36+
37+
static void subtest_nofault(bool build_id_resident)
38+
{
39+
struct test_build_id *skel;
40+
struct bpf_stack_build_id *stack;
41+
int frame_cnt;
42+
43+
skel = test_build_id__open_and_load();
44+
if (!ASSERT_OK_PTR(skel, "skel_open"))
45+
return;
46+
47+
skel->links.uprobe_nofault = bpf_program__attach(skel->progs.uprobe_nofault);
48+
if (!ASSERT_OK_PTR(skel->links.uprobe_nofault, "link"))
49+
goto cleanup;
50+
51+
if (build_id_resident)
52+
ASSERT_OK(system("./uprobe_multi uprobe-paged-in"), "trigger_uprobe");
53+
else
54+
ASSERT_OK(system("./uprobe_multi uprobe-paged-out"), "trigger_uprobe");
55+
56+
if (!ASSERT_GT(skel->bss->res_nofault, 0, "res"))
57+
goto cleanup;
58+
59+
stack = skel->bss->stack_nofault;
60+
frame_cnt = skel->bss->res_nofault / sizeof(struct bpf_stack_build_id);
61+
if (env.verbosity >= VERBOSE_NORMAL)
62+
print_stack(stack, frame_cnt);
63+
64+
if (build_id_resident) {
65+
ASSERT_EQ(stack[0].status, BPF_STACK_BUILD_ID_VALID, "build_id_status");
66+
ASSERT_EQ(memcmp(stack[0].build_id, build_id, build_id_sz), 0, "build_id_match");
67+
} else {
68+
ASSERT_EQ(stack[0].status, BPF_STACK_BUILD_ID_IP, "build_id_status");
69+
}
70+
71+
cleanup:
72+
test_build_id__destroy(skel);
73+
}
74+
75+
static void subtest_sleepable(void)
76+
{
77+
struct test_build_id *skel;
78+
struct bpf_stack_build_id *stack;
79+
int frame_cnt;
80+
81+
skel = test_build_id__open_and_load();
82+
if (!ASSERT_OK_PTR(skel, "skel_open"))
83+
return;
84+
85+
skel->links.uprobe_sleepable = bpf_program__attach(skel->progs.uprobe_sleepable);
86+
if (!ASSERT_OK_PTR(skel->links.uprobe_sleepable, "link"))
87+
goto cleanup;
88+
89+
/* force build ID to not be paged in */
90+
ASSERT_OK(system("./uprobe_multi uprobe-paged-out"), "trigger_uprobe");
91+
92+
if (!ASSERT_GT(skel->bss->res_sleepable, 0, "res"))
93+
goto cleanup;
94+
95+
stack = skel->bss->stack_sleepable;
96+
frame_cnt = skel->bss->res_sleepable / sizeof(struct bpf_stack_build_id);
97+
if (env.verbosity >= VERBOSE_NORMAL)
98+
print_stack(stack, frame_cnt);
99+
100+
ASSERT_EQ(stack[0].status, BPF_STACK_BUILD_ID_VALID, "build_id_status");
101+
ASSERT_EQ(memcmp(stack[0].build_id, build_id, build_id_sz), 0, "build_id_match");
102+
103+
cleanup:
104+
test_build_id__destroy(skel);
105+
}
106+
107+
void serial_test_build_id(void)
108+
{
109+
build_id_sz = read_build_id("uprobe_multi", build_id, sizeof(build_id));
110+
ASSERT_EQ(build_id_sz, BPF_BUILD_ID_SIZE, "parse_build_id");
111+
112+
if (test__start_subtest("nofault-paged-out"))
113+
subtest_nofault(false /* not resident */);
114+
if (test__start_subtest("nofault-paged-in"))
115+
subtest_nofault(true /* resident */);
116+
if (test__start_subtest("sleepable"))
117+
subtest_sleepable();
118+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
3+
4+
#include "vmlinux.h"
5+
#include <bpf/bpf_helpers.h>
6+
7+
struct bpf_stack_build_id stack_sleepable[128];
8+
int res_sleepable;
9+
10+
struct bpf_stack_build_id stack_nofault[128];
11+
int res_nofault;
12+
13+
SEC("uprobe.multi/./uprobe_multi:uprobe")
14+
int uprobe_nofault(struct pt_regs *ctx)
15+
{
16+
res_nofault = bpf_get_stack(ctx, stack_nofault, sizeof(stack_nofault),
17+
BPF_F_USER_STACK | BPF_F_USER_BUILD_ID);
18+
19+
return 0;
20+
}
21+
22+
SEC("uprobe.multi.s/./uprobe_multi:uprobe")
23+
int uprobe_sleepable(struct pt_regs *ctx)
24+
{
25+
res_sleepable = bpf_get_stack(ctx, stack_sleepable, sizeof(stack_sleepable),
26+
BPF_F_USER_STACK | BPF_F_USER_BUILD_ID);
27+
28+
return 0;
29+
}
30+
31+
char _license[] SEC("license") = "GPL";

tools/testing/selftests/bpf/uprobe_multi.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,21 @@
22

33
#include <stdio.h>
44
#include <string.h>
5+
#include <stdbool.h>
6+
#include <stdint.h>
7+
#include <sys/mman.h>
8+
#include <unistd.h>
59
#include <sdt.h>
610

11+
#ifndef MADV_POPULATE_READ
12+
#define MADV_POPULATE_READ 22
13+
#endif
14+
15+
int __attribute__((weak)) uprobe(void)
16+
{
17+
return 0;
18+
}
19+
720
#define __PASTE(a, b) a##b
821
#define PASTE(a, b) __PASTE(a, b)
922

@@ -75,6 +88,30 @@ static int usdt(void)
7588
return 0;
7689
}
7790

91+
extern char build_id_start[];
92+
extern char build_id_end[];
93+
94+
int __attribute__((weak)) trigger_uprobe(bool build_id_resident)
95+
{
96+
int page_sz = sysconf(_SC_PAGESIZE);
97+
void *addr;
98+
99+
/* page-align build ID start */
100+
addr = (void *)((uintptr_t)&build_id_start & ~(page_sz - 1));
101+
102+
/* to guarantee MADV_PAGEOUT work reliably, we need to ensure that
103+
* memory range is mapped into current process, so we unconditionally
104+
* do MADV_POPULATE_READ, and then MADV_PAGEOUT, if necessary
105+
*/
106+
madvise(addr, page_sz, MADV_POPULATE_READ);
107+
if (!build_id_resident)
108+
madvise(addr, page_sz, MADV_PAGEOUT);
109+
110+
(void)uprobe();
111+
112+
return 0;
113+
}
114+
78115
int main(int argc, char **argv)
79116
{
80117
if (argc != 2)
@@ -84,6 +121,10 @@ int main(int argc, char **argv)
84121
return bench();
85122
if (!strcmp("usdt", argv[1]))
86123
return usdt();
124+
if (!strcmp("uprobe-paged-out", argv[1]))
125+
return trigger_uprobe(false /* page-out build ID */);
126+
if (!strcmp("uprobe-paged-in", argv[1]))
127+
return trigger_uprobe(true /* page-in build ID */);
87128

88129
error:
89130
fprintf(stderr, "usage: %s <bench|usdt>\n", argv[0]);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
SECTIONS
2+
{
3+
. = ALIGN(4096);
4+
.note.gnu.build-id : { *(.note.gnu.build-id) }
5+
. = ALIGN(4096);
6+
}
7+
INSERT AFTER .text;
8+
9+
build_id_start = ADDR(.note.gnu.build-id);
10+
build_id_end = ADDR(.note.gnu.build-id) + SIZEOF(.note.gnu.build-id);
11+

0 commit comments

Comments
 (0)