找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1072

积分

0

好友

153

主题
发表于 4 天前 | 查看: 6| 回复: 0

LWN 上有大量关于动态插桩内核代码的文章。借助一种称为用户静态定义跟踪(USDT)探针的特殊机制,这些强大的跟踪能力同样可以应用于用户空间应用程序。USDT探针为用户空间代码插桩提供了一种低开销的方案,是调试生产环境运行中应用的利器。在关于 BPF 和 BCC 的系列文章中,我们将探讨 USDT 探针的由来,以及如何使用它们来洞察您自身应用程序的行为。

USDT 探针的概念最早源于 Sun 公司的 DTrace 工具。虽然 DTrace 并非静态跟踪点的首创者(其原始论文的“相关工作”部分提及了多种早期实现),但它无疑极大地推广了这一技术。随着 DTrace 的普及,许多应用程序开始在关键函数中嵌入 USDT 探针,以辅助运行时行为的跟踪与诊断。因此,这些探针通常需要通过 --enable-dtrace 这类构建配置开关来启用,也就不足为奇了。

例如,MySQL 提供了众多探针,帮助数据库管理员了解连接来源、正在执行的 SQL 命令,以及客户端与服务器间数据传输的底层细节。其他流行的软件,如 Java、PostgreSQL、Node.js,甚至 GNU C 库,也都提供了启用探针的选项,覆盖了从内存分配到垃圾回收等广泛的活动。

Linux 系统上,有多个工具可以处理 USDT 探针。SystemTap 是一个受欢迎的选择,也是 DTrace 的替代方案(因为 DTrace 直到近期才在 Linux 上获得支持)。perf 工具对 USDT 探针(内核中称为“静态定义跟踪”)的支持于 v4.8-rc1 版本合并。甚至 LTTng 自 2012 年起就能发出与 USDT 兼容的探针。而开发者工具链中最新、或许也是最用户友好的工具和脚本,则来自 BPF 编译器集合(BCC)。

用于处理 USDT 探针的 BCC 工具

自 2016 年 3 月起,BCC 就开始支持 USDT 探针。tplist 工具允许您查看内核、应用程序或库中可用的探针,并能帮助发现可供 trace 工具启用的探针名称。在一个支持 SDT 的 C 库版本上运行该工具,结果示例如下:

# tplist.py -l /lib64/libc-2.27.so
    /lib64/libc-2.27.so libc:setjmp
    /lib64/libc-2.27.so libc:longjmp
    /lib64/libc-2.27.so libc:longjmp_target
    /lib64/libc-2.27.so libc:memory_mallopt_arena_max
    /lib64/libc-2.27.so libc:memory_mallopt_arena_test
...

-l 参数指示 tplist 文件参数是库或可执行文件。省略 -l 则会让 tplist 打印内核跟踪点列表。

可以对列表应用过滤器来缩短输出。例如,使用过滤器 sbrk 只会打印名称包含“sbrk”的探针。使用 -vv 参数则会打印探针位置及可用参数:

./tplist.py -vv -l /lib64/libc-2.27.so sbrk
    /lib64/libc-2.27.so libc:memory_sbrk_less [sema 0x0]
    location #1 0x816dd
    argument #1 8 unsigned bytes @ ax
    argument #2 8 signed   bytes @ bp
    /lib64/libc-2.27.so libc:memory_sbrk_more [sema 0x0]
    location #1 0x826af
    argument #1 8 unsigned bytes @ ax
    argument #2 8 signed   bytes @ r12

了解参数细节对于知晓哪些寄存器存放了函数参数至关重要。知道参数位置后,我们就可以使用 BCC 的 trace 工具通过类似以下命令打印其内容:

# trace.py 'u:/lib64/libc-2.27.so:memory_sbrk_more "%u", arg1' -T
    TIME     PID     TID     COMM            FUNC             -
    21:46:51 12781   12781   ls              memory_sbrk_more 114974720

上述输出表明,运行 ls 命令的进程触发了 memory_sbrk_more 探针并记录了一次命中。

BCC 中的其他工具为 Java、Python、Ruby 和 PHP 等高级语言启用了 USDT 探针。例如,lib/ustat.py 是一个监控工具,它整合了方法调用、垃圾回收、对象分配等多种事件信息,并通过类似 top 的界面展示:

# ustat.py
    Tracing... Output every 10 secs. Hit Ctrl-C to end
    12:17:17 loadavg: 0.33 0.08 0.02 5/211 26284
    PID    CMDLINE      METHOD/s   GC/s   OBJNEW/s   CLOAD/s  EXC/s  THR/s
    3018   node/node    0          3      0          0        0      0

上述输出显示,进程 ID 为 3018 的 node 进程在十秒内触发了三次垃圾回收事件。与大多数脚本一样,ustat.py 会持续运行直至被用户中断。

BCC 还包含针对特定应用的专用脚本。例如,bashreadline.py 可打印所有运行中 bash shell 的命令:

# bashreadline.py
    TIME      PID    COMMAND
    05:28:25  21176  ls -l
    05:28:28  21176  date
    05:28:35  21176  echo hello world

dbslower.py 则用于打印延迟超过指定阈值的数据库(MySQL 或 PostgreSQL)操作,是进行 运维 性能分析的实用工具:

# dbslower.py mysql -m 1000
    Tracing database queries for pids 25776 slower than 1000 ms...
    TIME(s)        PID          MS QUERY
    1.421264       25776  2002.183 call getproduct(97)
    3.572617       25776  2001.381 call getproduct(97)

为您的应用程序添加 USDT 探针

SystemTap 提供了一个用于向应用程序添加静态探针的 API。要创建这些探针,您需要安装 systemtap-sdt-devel 包,该包提供 sys/sdt.h 头文件。以下是一个简单的 C 程序示例,展示了如何添加并使用 BCC 工具来列出和启用探针:

#include <sys/sdt.h>
#include <sys/time.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    struct timeval tv;
    while(1) {
        gettimeofday(&tv, NULL);
        DTRACE_PROBE1(test-app, test-probe, tv.tv_sec);
        sleep(1);
    }
    return 0;
}

这个程序会循环运行,每秒触发一次名为 test-probe 的探针,并将当前秒数作为参数传递。DTRACE_PROBEn() 系列宏用于在代码中创建探针点,宏名末尾的数字 n 代表参数个数。

这些宏的实现方式是在探针位置放置一条无操作(no-op)的汇编指令,并在应用程序的 ELF 文件中写入包含探针地址和名称等信息的注释。由于未激活的探针运行时开销仅是一条无操作指令,且 ELF 注释不加载到内存,因此对性能影响极小。

使用 tplist 工具,我们可以查看编译后程序中的探针及其参数:

# tplist.py -vv -l ./simple-c
    simple-c test-app:test-probe [sema 0x0]
    location #1 0x40057b
    argument #1 8 signed   bytes @ ax

假设上述 simple-c 程序正在运行,我们可以构造探针说明符,使用 trace 工具来打印其第一个参数:

# trace.py 'u:./simple-c:test-probe "%u", arg1' -T -p $(pidof simple-c)
    TIME     PID     TID     COMM            FUNC             -
    21:55:44 13450   13450   simple-c        test-probe       1524430544
    21:55:45 13450   13450   simple-c        test-probe       1524430545

输出的最后一列显示了探针触发时的秒数,即我们在探针中传递的参数。需要注意探针参数的数据类型,因为它会对应到探针说明符中使用的 printf 风格格式字符串。

结论

USDT 探针将内核跟踪点的灵活性延伸到了用户空间应用程序。得益于 DTrace 的推动,许多流行应用和高级编程语言都已支持 USDT 探针。BCC 提供了简洁的工具来操作这些探针,使开发者能够轻松列出可用探针并跟踪打印诊断数据。通过使用 SystemTap 的 API 和 DTRACE_PROBE() 宏系列,您可以在自己的代码中方便地添加探针。USDT 探针能以极小的运行时开销,帮助您在生产环境中有效地对应用程序进行故障排除和深度剖析。

原文来源: https://lwn.net/Articles/753601/




上一篇:Windows交叉编译Go应用:打包部署至Linux服务器的完整指南
下一篇:零宽字符盲水印实战:用JavaScript为内部文档添加隐形追踪标记
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 19:00 , Processed in 0.142141 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表