本页目录

ftrace 与 bpftrace 用户态追踪

覆盖: uprobe → USDT (DTrace probes) → bpftrace 用户态脚本 → perf probe on userspace → 用户态动态追踪全景 适用: Linux + bpftrace/bcc/perf

uprobe: 用户态函数探测

# uprobe: 在任意用户态函数入口/返回放置探测点
# 语法: uprobe:<binary>:<function>

# 例: 追踪 bash 的 readline:
echo 'p:bash_readline /bin/bash:readline' \
     > /sys/kernel/debug/tracing/uprobe_events
echo 1 > /sys/kernel/debug/tracing/events/uprobes/bash_readline/enable
cat /sys/kernel/debug/tracing/trace_pipe
# → 每次 bash 调 readline → 记录

# 带参数:
echo 'p:bash_readline /bin/bash:readline cmd=+0(%si):string' \
     > uprobe_events
# cmd: 函数返回后的第一个参数 (%si=x0 on ARM, %rdi on x86)

# uretprobe: 函数返回时
echo 'r:bash_readline_ret /bin/bash:readline ret=$retval' \
     > uprobe_events

USDT: 预埋的静态探测点

// USDT (User Statically Defined Tracing): 类似 DTrace probes
// 编译时预埋, 运行时零开销 (NOP) → 直到被激活

#include <sys/sdt.h>  // systemtap-sdt-dev (Debian) or dev-util/systemtap (Gentoo)

DTRACE_PROBE2(myapp, request_start, request_id, url);
// → 编译为 NOP sled → 运行时被 uprobe 替换为 INT3 breakpoint

// 查看二进制中的 USDT 埋点:
bpftrace -l 'usdt:/path/to/binary:*'
readelf -n /path/to/binary | grep NT_STAPSDT

bpftrace: 用户态追踪

# 一行追踪:
bpftrace -e 'uprobe:/bin/bash:readline { printf("readline: %s\n", str(reg("di"))); }'

# 直方图:
bpftrace -e 'uprobe:/lib64/libc.so.6:malloc { @size = hist(arg0); }'

# 统计:
bpftrace -e 'uretprobe:/lib64/libc.so.6:malloc /
             retval != 0/ { @bytes[comm] = sum(arg0); }'

# 按进程:
bpftrace -e 'uprobe:/usr/bin/nginx:ngx_http_process_request {
             @requests[pid] = count(); }'

# 追踪函数耗时:
bpftrace -e 'uprobe:/lib64/libc.so.6:malloc { @start[tid] = nsecs; }
             uretprobe:/lib64/libc.so.6:malloc /@start[tid]/ {
               @lat_us = hist((nsecs - @start[tid]) / 1000);
               delete(@start[tid]);
             }'

# 追踪 open 调用 (带文件名):
bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
             printf("%s %s\n", comm, str(args->filename)); }'

bcc 用户态工具

# bcc: Python + BPF 的高级封装
# 已编译的工具 (在 /usr/share/bcc/tools/):

execsnoop        # exec() 全家: 谁 fork 了谁
opensnoop        # open() 追踪: 文件名, 返回值, 耗时
bitesize         # 按进程统计 IO 大小 (直方图)
filelife         # 临时文件的生命周期
statsnoop        # stat() 追踪

# 用途: 不需要写 BPF 程序 → 直接用预编译工具

与内核追踪的组合

# 从用户态到内核态的完整链路:
bpftrace -e '
uprobe:/lib64/libpthread.so.0:pthread_mutex_lock {
    @lock_start[tid] = nsecs;
}
uretprobe:/lib64/libpthread.so.0:pthread_mutex_lock {
    $duration = (nsecs - @lock_start[tid]) / 1000;
    if ($duration > 10000) {  // > 10μs → 可能发生了 futex_wait
        printf("slow lock: %s %d μs\n", comm, $duration);
    }
    delete(@lock_start[tid]);
}
tracepoint:syscalls:sys_enter_futex {
    // 这里的 tid 可以和上面的关联 → 确认是 futex 导致的慢
}'

调试技巧

# 找二进制中可探测的函数:
nm -D /usr/lib64/libc.so.6 | grep ' T ' | head -20   # 导出符号
objdump -T /usr/lib64/libc.so.6 | grep -E '\.text'    # 代码段符号

# 查看已安装的 uprobe:
cat /sys/kernel/debug/tracing/uprobe_events
cat /sys/kernel/debug/tracing/kprobe_events

# 查看 perf 探测点:
perf probe -l

# 清理:
echo > /sys/kernel/debug/tracing/uprobe_events

参考

  • 工具⁠: bpftrace (github.com/bpftrace), bcc (github.com/iovisor/bcc)
  • 文档⁠: man perf-probe, Brendan Gregg's blog
  • LWN: "Uprobes and USDT", "Dynamic tracing with bpftrace"

关键词: uprobe, uretprobe, USDT, DTRACE_PROBE, bpftrace, bcc, user-space tracing, SDT