From 823264bea78cc0184ffa0ab968a1f7601e622d09 Mon Sep 17 00:00:00 2001 From: Yafang Shao Date: Sun, 12 Jan 2025 14:45:12 +0800 Subject: [PATCH 1/2] libbpf: Add support for dynamic tracepoint Dynamic tracepoints can be created using debugfs, perf or similar tools. For example: $ perf probe -a 'tcp_listendrop sk' This command creates a new tracepoint under debugfs: $ ls /sys/kernel/debug/tracing/events/probe/tcp_listendrop/ enable filter format hist id trigger Notably, the probed function tcp_listendrop() is an inlined kernel function. Although this dynamic tracepoint appears as a tracepoint, it is internally implemented as a kprobe. Therefore, if we want to attach a bpf prog to it, the bpf prog must be loaded as a kprobe prog. The primary motivation for adding support for dynamic tracepoints is to simplify tracing of inlined kernel functions using BPF tools, such as bpftrace. By leveraging tools like perf, users can create a dynamic tracepoint for an inlined kernel function and then attach a BPF program to it. To achieve this, a new section, SEC("kprobe/SUBSYSTEM/PROBE"), has been introduced. Suggested-by: Jiri Olsa Signed-off-by: Yafang Shao Cc: Daniel Xu --- tools/lib/bpf/libbpf.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 6c262d0152f81..96a9ebfe5f82d 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -11623,11 +11623,34 @@ bpf_program__attach_kprobe_multi_opts(const struct bpf_program *prog, return libbpf_err_ptr(err); } +/* A dynamic tracepoint: "kprobe/SUBSYSTEM/PROBE" */ +static int attach_dynamic_tracepoint(const struct bpf_program *prog, const char *func_name, + struct bpf_link **link) +{ + char *tp_subsys, *tp_name; + + tp_subsys = strdup(func_name); + if (!tp_subsys) + return -ENOMEM; + + tp_name = strchr(tp_subsys, '/'); + if (!tp_name) { + free(tp_subsys); + return -EINVAL; + } + + *tp_name = '\0'; + tp_name++; + *link = bpf_program__attach_tracepoint(prog, tp_subsys, tp_name); + free(tp_subsys); + return libbpf_get_error(*link); +} + static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link) { DECLARE_LIBBPF_OPTS(bpf_kprobe_opts, opts); + const char *func_name, *dynamic_tp; unsigned long offset = 0; - const char *func_name; char *func; int n; @@ -11643,6 +11666,10 @@ static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf else func_name = prog->sec_name + sizeof("kprobe/") - 1; + dynamic_tp = strchr(func_name, '/'); + if (dynamic_tp) + return attach_dynamic_tracepoint(prog, func_name, link); + n = sscanf(func_name, "%m[a-zA-Z0-9_.]+%li", &func, &offset); if (n < 1) { pr_warn("kprobe name is invalid: %s\n", func_name); From 77c1890e2080105a0ae2dec6868da1b33599fe19 Mon Sep 17 00:00:00 2001 From: Yafang Shao Date: Sun, 12 Jan 2025 14:45:13 +0800 Subject: [PATCH 2/2] selftests/bpf: Add selftest for dynamic tracepoint The result is as follows, $ tools/testing/selftests/bpf/test_progs --name=dynamic_tp #85 dynamic_tp:OK Summary: 1/0 PASSED, 0 SKIPPED, 0 FAILED Signed-off-by: Yafang Shao --- .../bpf/prog_tests/test_dynamic_tp.c | 64 +++++++++++++++++++ .../testing/selftests/bpf/progs/dynamic_tp.c | 27 ++++++++ 2 files changed, 91 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/test_dynamic_tp.c create mode 100644 tools/testing/selftests/bpf/progs/dynamic_tp.c diff --git a/tools/testing/selftests/bpf/prog_tests/test_dynamic_tp.c b/tools/testing/selftests/bpf/prog_tests/test_dynamic_tp.c new file mode 100644 index 0000000000000..c205e0c8e3e31 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/test_dynamic_tp.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include + +#include "dynamic_tp.skel.h" + +int dynamic_tp(const char *cmd) +{ + const char *kprobe_file = "/sys/kernel/debug/tracing/kprobe_events"; + ssize_t bytes_written; + int fd, err; + + fd = open(kprobe_file, O_WRONLY | O_APPEND); + if (!ASSERT_GE(fd, 0, "open kprobe_events")) + return -1; + + bytes_written = write(fd, cmd, strlen(cmd)); + if (!ASSERT_GT(bytes_written, 0, "write kprobe_events")) { + close(fd); + return -1; + } + + err = close(fd); + if (!ASSERT_OK(err, "close kprobe_events")) + return -1; + return 0; +} + +void test_dynamic_tp(void) +{ + struct dynamic_tp *skel; + pid_t child_pid; + int status, err; + + /* create a dynamic tracepoint */ + err = dynamic_tp("p:my_dynamic_tp kernel_clone"); + if (!ASSERT_OK(err, "create dynamic tp")) + return; + + skel = dynamic_tp__open_and_load(); + if (!ASSERT_OK_PTR(skel, "load progs")) + goto remove_tp; + skel->bss->pid = getpid(); + err = dynamic_tp__attach(skel); + if (!ASSERT_OK(err, "attach progs")) + goto cleanup; + + /* trigger the dynamic tracepoint */ + child_pid = fork(); + if (!ASSERT_GT(child_pid, -1, "child_pid")) + goto cleanup; + if (child_pid == 0) + _exit(0); + waitpid(child_pid, &status, 0); + + ASSERT_EQ(skel->bss->result, 1, "result"); + +cleanup: + dynamic_tp__destroy(skel); +remove_tp: + dynamic_tp("-:my_dynamic_tp kernel_clone"); +} diff --git a/tools/testing/selftests/bpf/progs/dynamic_tp.c b/tools/testing/selftests/bpf/progs/dynamic_tp.c new file mode 100644 index 0000000000000..d3be37c220f3a --- /dev/null +++ b/tools/testing/selftests/bpf/progs/dynamic_tp.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +char _license[] SEC("license") = "GPL"; + +#define MAX_STACK_TRACE_DEPTH 32 +unsigned long entries[MAX_STACK_TRACE_DEPTH] = {}; +#define SIZE_OF_ULONG (sizeof(unsigned long)) + +int result, pid; + +SEC("kprobe/kprobes/my_dynamic_tp") +int dynamic_tp(struct pt_regs *ctx) +{ + int ret; + + ret = bpf_get_stack(ctx, entries, MAX_STACK_TRACE_DEPTH * SIZE_OF_ULONG, 0); + if (ret < 0) { + result = -1; + return ret; + } + if (bpf_get_current_pid_tgid() >> 32 == pid) + result = 1; + return 0; +}