目录
代码分析
BPF程序分析
功能说明
usdt_auto_attach & usdt_manual_attach
SEC("usdt/libc.so.6:libc:setjmp")
用户态程序分析
功能说明
skel->bss
skel->links
skel->progs
bpf_program__attach_usdt
执行效果
UTSD与uprobe 的性能比较
UTSD (Userland Statically Defined Tracing) 是一种在用户空间应用程序中插入静态跟踪点的技术。这种技术可以让你在程序中插入一些预定义的跟踪点,然后在运行时通过跟踪工具来动态地启用或禁用这些跟踪点。
UTSD 使用了 Linux 内核的 uprobes 功能来实现用户空间的静态跟踪点。uprobes 可以让你在用户空间程序的任意位置设置断点,当程序执行到这个位置时,就会触发 uprobes 并运行你注册的处理函数。这样,你就可以在这个处理函数中插入你的跟踪代码,用于收集你想要的信息。
UTSD 的一个主要应用场景是性能分析和故障诊断。通过 UTSD,你可以在应用程序中插入一些跟踪点,用于记录程序的关键事件,例如函数的调用和返回,或者某个关键变量的修改等。然后,你可以在程序运行时动态地启用或禁用这些跟踪点,并收集这些事件的信息,以帮助你理解程序的行为,找出性能瓶颈或者故障原因。
需要注意的是,UTSD 是一种较为底层的跟踪技术,使用它需要一定的系统编程知识,以及对 Linux 内核的理解。而且,并非所有的 Linux 发行版都支持 uprobes,因此在某些系统上,你可能无法使用 UTSD。
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2022 Hengqi Chen */
#include
#include
#include
#include
pid_t my_pid;
SEC("usdt/libc.so.6:libc:setjmp")
int BPF_USDT(usdt_auto_attach, void *arg1, int arg2, void *arg3)
{
pid_t pid = bpf_get_current_pid_tgid() >> 32;
if (pid != my_pid)
return 0;
bpf_printk("USDT auto attach to libc:setjmp: arg1 = %lx, arg2 = %d, arg3 = %lx", arg1, arg2,
arg3);
return 0;
}
SEC("usdt")
int BPF_USDT(usdt_manual_attach, void *arg1, int arg2, void *arg3)
{
bpf_printk("USDT manual attach to libc:setjmp: arg1 = %lx, arg2 = %d, arg3 = %lx", arg1,
arg2, arg3);
return 0;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";
程序中定义了两个探针处理函数 usdt_auto_attach 和 usdt_manual_attach,它们都被设计为处理来自 libc:setjmp 函数的探针触发。它们的区别在于,usdt_auto_attach 函数只处理当前进程的探针触发,而 usdt_manual_attach 函数处理所有进程的探针触发。
libc:setjmp 函数的探针,一种UTSD,我们也可以在自己的代码中设置UTSD,性能会比直接uprobe 一个用户函数要好一些
下面是一个使用 SystemTap 的 DTRACE_PROBE 宏在 C 代码中定义 USDT 探针的例子:
#include
void my_function() {
int arg1 = 10;
int arg2 = 20;
DTRACE_PROBE2(my_provider, my_probe, arg1, arg2);
// ...其他代码...
}
USDT (User Statically Defined Tracing) 探针最初是由 DTrace 工具引入的,所以 DTRACE_PROBE 宏通常用于定义这些探针。然而,因为 USDT 探针的概念已经被多个跟踪工具所接受,其他工具也提供了自己的方式来定义这些探针。
例如,Linux 中的 SystemTap 也提供了类似的 STAP_PROBE 宏来定义探针:
#include
void my_function() {
int arg1 = 10;
int arg2 = 20;
STAP_PROBE2(my_provider, my_probe, arg1, arg2);
// ...其他代码...
}
这段代码中的 STAP_PROBE2 宏的工作方式和 DTRACE_PROBE2 宏类似。同样,你也可以使用 BPF 或 SystemTap 工具来挂钩这个探针。
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2022 Hengqi Chen */
#include
#include
#include
#include
#include "usdt.skel.h"
static volatile sig_atomic_t exiting;
static jmp_buf env;
static void sig_int(int signo)
{
exiting = 1;
}
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
static void usdt_trigger()
{
setjmp(env);
}
int main(int argc, char **argv)
{
struct usdt_bpf *skel;
int err;
libbpf_set_print(libbpf_print_fn);
skel = usdt_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}
skel->bss->my_pid = getpid();
err = usdt_bpf__load(skel);
if (!skel) {
fprintf(stderr, "Failed to load BPF skeleton\n");
return 1;
}
/*
* Manually attach to libc.so we find.
* We specify pid here, so we don't have to do pid filtering in BPF program.
*/
skel->links.usdt_manual_attach = bpf_program__attach_usdt(
skel->progs.usdt_manual_attach, getpid(), "libc.so.6", "libc", "setjmp", NULL);
if (!skel->links.usdt_manual_attach) {
err = errno;
fprintf(stderr, "Failed to attach BPF program `usdt_manual_attach`\n");
goto cleanup;
}
/*
* Auto attach by libbpf, libbpf should be able to find libc.so in your system.
* By default, auto attach does NOT specify pid, so we do pid filtering in BPF program
*/
err = usdt_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
if (signal(SIGINT, sig_int) == SIG_ERR) {
err = errno;
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
goto cleanup;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");
while (!exiting) {
/* trigger our BPF programs */
usdt_trigger();
fprintf(stderr, ".");
sleep(1);
}
cleanup:
usdt_bpf__destroy(skel);
return -err;
}
它在每秒触发一个 setjmp 调用,并使用 BPF 来跟踪这些调用。
这一行代码是将当前进程的 PID(由 getpid() 函数获取)赋值给 BPF 骨架(skel)的 BSS 段中名为 my_pid 的变量。具体来说,skel->bss->my_pid 是一个指向 BPF 骨架的 BSS 段中 my_pid 变量的指针。
BPF 骨架中的各个段对应于生成的 BPF 程序中的各个段。在 BPF 中,通常有以下几种段:
在 BPF 骨架(skeleton)中,skel->links 是一个存储 struct bpf_link 指针的结构。每一个 bpf_link 对象代表一个已经加载并附加(attach)到某个事件(例如一个函数调用,或者一个网络事件)的 BPF 程序。
在你给出的代码中,skel->links.usdt_manual_attach 是一个 struct bpf_link 指针,它指向一个 BPF 程序,该程序已经附加到了 USDT(User Statically Defined Tracing)探针。
如果你在代码中看到 skel->links.some_name,你可以理解为 some_name 是一个已经加载并附加的 BPF 程序。
bpf_link 对象提供了一个对已加载并附加的 BPF 程序的引用,这样你就可以在稍后的代码中使用它,例如在你需要卸载(detach)或者销毁(destroy)这个 BPF 程序的时候。
在 BPF 骨架(skeleton)中,skel->progs 是一个结构体,其中包含了所有的 BPF 程序。每个 BPF 程序都是一个 struct bpf_program 类型的指针,这个结构体包含了 BPF 程序的字节码,以及其他与该程序相关的信息。
在你给出的代码中,skel->progs.usdt_manual_attach 是一个 struct bpf_program 指针,它指向一个名为 usdt_manual_attach 的 BPF 程序。
如果你在代码中看到 skel->progs.some_name,你可以理解为 some_name 是一个 BPF 程序。
bpf_program__attach_usdt 是 libbpf 库提供的一个函数,用于将 BPF 程序附加到一个 USDT(User Statically Defined Tracing)探针。
struct bpf_link *bpf_program__attach_usdt(struct bpf_program *prog,
pid_t pid,
const char *binary_path,
const char *provider,
const char *probe_name,
const char *fn_name);
这个函数的参数是:
这个函数的返回值是一个 struct bpf_link 指针,代表了 BPF 程序和 USDT 探针之间的链接。如果链接成功,这个指针应该非 NULL。如果链接失败,这个指针将为 NULL,并且可以通过 errno 获取错误信息。
让我们更详细地解析这段代码:
skel->links.usdt_manual_attach = bpf_program__attach_usdt(
skel->progs.usdt_manual_attach, getpid(), "libc.so.6", "libc", "setjmp", NULL);
if (!skel->links.usdt_manual_attach) {
err = errno;
fprintf(stderr, "Failed to attach BPF program `usdt_manual_attach`\n");
goto cleanup;
}
bpf_program__attach_usdt 函数将返回一个 struct bpf_link 指针,代表了 BPF 程序和 USDT 探针之间的链接。如果链接成功,这个指针被赋值给 skel->links.usdt_manual_attach,以便后续的使用和管理。如果链接失败,函数将返回 NULL。
usdt-56794 [004] d..21 74634.623846: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74634.623848: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74635.623982: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74635.623983: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74636.624120: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74636.624121: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74637.624394: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74637.624396: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74638.624702: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74638.624704: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74639.624842: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74639.624846: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74640.624982: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74640.624985: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74641.625110: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74641.625113: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74642.625246: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74642.625248: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74643.625385: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74643.625387: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74644.625513: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
usdt-56794 [004] d..21 74644.625516: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d5e764b240, arg2 = 0, arg3 = 55d5e760836d
USDT (User Statically Defined Tracing) 和 uprobes 是两种用于在用户空间进行跟踪的机制,它们各自有其优点和适用场景。
因此,从性能的角度来看,USDT 通常比 uprobes 有优势,因为 USDT 的开销更小。然而,这并不意味着 USDT 总是比 uprobes 更好。它们各自适应不同的场景:如果你需要跟踪的程序已经定义了 USDT 跟踪点,那么使用 USDT 是最好的选择;如果你需要跟踪的程序没有定义 USDT 跟踪点,或者你需要在运行时动态地插入跟踪点,那么使用 uprobes 是更好的选择。