《eBPF 与可观测性:bpftrace、追踪与动态插桩》

系统讲解 eBPF:内核虚拟机与 verifier 安全机制、各类 hook 点(kprobe/uprobe/tracepoint/XDP/tc)、程序加载与 maps 通信、bpftrace 单行命令与脚本实战(CPU/内存/网络/IO 追踪)、BCC 工具集与 CO-RE,以及生产环境的安全权限与落地实践。

引言

传统的 strace 太重、perf 太底层,而eBPF 让「安全地在内核里运行沙箱程序」成为可能——不修改内核、不改应用,就能在任意 hook 点观测系统。本文从 eBPF 的原理(虚拟机、verifier、maps)讲起,介绍 kprobe/uprobe/tracepoint 三类动态插桩,用 bpftrace 单行命令与脚本追踪 CPU/内存/网络/IO,再介绍 BCC 工具集与 CO-RE 移植,最后讨论生产环境的安全权限与落地经验。

前置:/linux-kernel-tuning/(内核机制基础)。性能分析思路见 /linux-performance-tuning/。


目录


1. eBPF 是什么:虚拟机、verifier 与 hook 点

eBPF 是内核里的「沙箱虚拟机」:把受限程序挂到内核事件上,只读系统状态、不冒内核崩溃风险。

编写(eBPF C/bpftrace) → 编译成字节码 → verifier 静态检查
→ JIT 编译成原生指令 → 挂到 hook 点 → 事件触发即执行

verifier 是安全的关键:它逐指令检查「无越界、无死循环、无任意指针访问」,保证 eBPF 程序不会搞崩内核。可挂载的 hook 点类型:

hook 类型捕获什么示例事件
tracepoint内核稳定事件点syscalls、sched、block 事件
kprobe任意内核函数kprobe:do_sys_open
uprobe用户态函数uprobe:/bin/bash:readline
tc / XDP网络收发路径包过滤、早丢弃
perf event硬件/软件计数器CPU 周期、cache miss
# 检查内核是否支持 eBPF
bpftool feature probe
# 查看已加载的程序
bpftool prog list

心智:tracepoint 稳定但覆盖有限,kprobe 全覆盖但随内核版本变。生产首选 tracepoint/稳定 hook,kprobe 用于深入分析但要对齐内核版本。


2. 程序加载与运行机制

一条 eBPF 程序从 C 源码到运行要经历:编译 → 校验 → JIT → 装载 → 挂接,再通过 maps 与用户态通信。

bpftrace/BCC 前端
  ↓ 生成 eBPF 字节码
verifier(拒绝危险程序)
  ↓ JIT 成原生指令
加载进内核 → attach 到 hook
  ↓
与用户态交换数据:maps(哈希/数组/环形缓冲)+ perf event
# 查看当前加载的程序与 maps
bpftool prog list
bpftool map list
# 查看某程序字节码(反汇编)
bpftool prog dump jited id 42

maps 是 eBPF 的数据交换层:内核侧程序把计数、直方图写进 maps,用户态程序读取展示。常见类型有 hash(按 key 计数)、array、perf_event_array(事件流)、ringbuf(高性能事件传递)。

记忆:eBPF 程序本身不做持久存储,一切数据都经 maps 交换。「计数类」用 hash map、「事件流类」用 ringbuf/perf buffer,选错 map 类型会显著影响开销。


3. kprobe、uprobe、tracepoint 动态插桩

动态插桩的三把刀:kprobe 切内核函数、uprobe 切用户态函数、tracepoint 用稳定的内核事件点:

# kprobe:内核函数入口/返回
bpftrace -e 'kprobe:do_sys_open { printf("open: %s\n", comm); }'
# uprobe:用户态函数
bpftrace -e 'uprobe:/bin/bash:readline { printf("readline: %s\n", comm); }'
# tracepoint:稳定事件
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'
类型稳定性开销用途
tracepoint高(内核保证接口)低生产首选,覆盖常见路径
kprobe低(函数名随版本变)中深挖任意内核函数
kprobe 返回低中看函数返回值/延迟
uprobe中(符号需存在)中用户态应用函数级观测

心法:写插桩前先确认符号存在。kprobe 用 bpftrace -l 'kprobe:*' 列可用点,uprobe 用 nm 确认用户态符号,写错名字会 attach 失败而不是静默跳过。


4. bpftrace 入门与单行命令

bpftrace 是 eBPF 的 awk——探针 /过滤条件/ { 动作 } 三段式,一条命令完成观测:

# 统计各进程发起系统调用的次数
bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[comm] = count(); }'
# 追踪文件打开(谁打开了哪个文件)
bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
  @[comm, str(args->filename)] = count(); }'
# 内核函数执行频率
bpftrace -e 'kprobe:* { @[probe] = count(); }'

常用内建变量:pid(进程号)、comm(进程名)、uid、kstack/ustack(内核/用户栈)、arg0...(参数)、str()(字符串转换)、@(map 变量)、@[](聚合)。

# 只看特定进程:过滤条件
bpftrace -e 'tracepoint:syscalls:sys_enter_* /pid == 1234/ { @[probe] = count(); }'
# 5 秒后自动退出
bpftrace -e 'kprobe:do_sys_open { @[comm] = count(); }' -d 5

记忆:单行命令四件套:@[key] = count() 计数、@[key] = sum() 求和、@[key] = hist() 直方图、printf() 打事件。90% 的快速排查用这四招就够。


5. bpftrace 脚本实战:CPU 与内存

把单行命令组合成脚本,就能回答「CPU 被谁吃了」「内存为什么涨」:

# 采样 CPU:谁在跑 + 内核栈
bpftrace -e 'profile:hz:99 { @[comm, kstack] = count(); }'
# 任务在 CPU 上的排队延迟(runqlat 直方图)
bpftrace -e 'tracepoint:sched:sched_wakeup {
  @usecs = hist((nsecs - args->target_sleep_delta) / 1000); }'

内存侧用 kprobe 追踪分配热点:

# 追踪 kmalloc 调用者栈(内核内存分配热点)
bpftrace -e 'kprobe:kmalloc { @[kstack] = count(); }'
# 用户态 malloc 频率(需符号)
bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc {
  @[comm, ustack] = count(); }'
输出示例(runqlat 直方图):
@usecs:
[16, 32)             120 |@@@@
[32, 64)             980 |@@@@@@@@@@@@@
[64, 128)            2100|@@@@@@@@@@@@@@@@@@@@@@
[128, 256)            700 |@@@@@@@

心法:profile 采样是最安全的 CPU 分析——采样 99Hz 持续几十秒,开销小、结论直观。直方图比平均值更能暴露「长尾延迟」:中位数低但 99 分位飙高,说明偶发排队。


6. 网络追踪:XDP 与 tc 与 kprobe

网络是 eBPF 应用最广的领域:XDP 在驱动层早丢包、tc 在流量控制层处理、kprobe 深挖协议栈:

# XDP:驱动层丢包(CPU 开销极低,适合 DDoS 防护)
bpftrace -e 'kprobe:ndo_start_xmit { @[comm] = count(); }'
# tc:进入/离开网络栈的数据
# kprobe 追踪 TCP 收发
bpftrace -e 'kprobe:tcp_sendmsg { @bytes = sum(arg2); }'
bpftrace -e 'kprobe:tcp_recvmsg { @bytes = sum(arg2); }'
层hook特点
驱动层XDP最早看到包、性能最高、可丢可转发
流量控制tc出入方向都能改包/标记
协议栈kprobe/tracepoint观测 TCP 状态、重传、窗口
# TCP 重传观测(网络质量问题定位)
bpftrace -e 'tracepoint:tcp:tcp_retransmit_skb {
  @[n2s(args->saddr), n2s(args->daddr)] = count(); }'

记忆:「性能优先丢包用 XDP,灵活改包用 tc,诊断分析用 kprobe/tracepoint」。XDP 程序运行在驱动的 NAPI 上下文,绕过整个协议栈,是 DDoS 缓解的标准武器。


7. IO 与文件系统追踪

从 VFS 调用到块设备下发,eBPF 可以点亮整条 IO 路径的每一站:

# VFS 层:谁在读写哪些文件
bpftrace -e 'tracepoint:syscalls:sys_enter_read {
  @[comm, str(args->buf), args->count] = count(); }'
# 块层:设备请求延迟直方图
bpftrace -e 'kprobe:blk_start_request { @start[arg1] = nsecs; }
  kprobe:blk_complete_request /@start[arg1]/ {
    @usecs = hist((nsecs - @start[arg1]) / 1000);
    delete(@start[arg1]); }'
# 更简单的现成工具:BCC 的 biolatency
biolatency 10
# 文件系统同步等待(ext4 日志提交)
bpftrace -e 'kprobe:ext4_sync_file { @[comm] = count(); }'

心法:IO 排障先分「哪一层」:VFS 慢可能是元数据、块层慢可能是排队、设备慢才是盘本身。biolatency 给出延迟直方图,配合 iostat 就能判断是设备饱和还是偶发抖动。


8. BCC 工具集与 CO-RE

BCC 提供几十个开箱即用的 eBPF 工具,覆盖 CPU/内存/网络/IO 全维度,是生产排查的「瑞士军刀」:

sudo apt install bpftrace bpfcc-tools
# 常用工具一览
execsnoop      # 追踪新进程执行
opensnoop     # 追踪文件打开
runqlat       # CPU 排队延迟
biolatency     # 块设备 IO 延迟
tcpconnect     # 记录 TCP 建连
tcplife        # TCP 连接生命周期
profile        # CPU 采样火焰图数据
oomkill        # 追踪 OOM killer 事件
# 生产最高频的三个
opensnoop -p 1234          # 这个进程在打开什么文件
execsnoop                   # 谁在启动进程(排查恶意行为)
oomkill                     # OOM 时到底杀了谁

CO-RE(Compile Once - Run Everywhere):利用内核 BTF 信息生成与内核版本无关的 eBPF 程序,解决「换内核就要重编工具」的痛点。

记忆:BCC 工具按命名就能记:*slnoop 是事件流、*lat 是延迟直方图、*stat 是统计、profile 是采样。先跑 BCC 现成工具定位方向,再写 bpftrace 精调,效率最高。


9. 安全权限与生产实践

eBPF 能看内核也能改网络路径,权限与上线规范必须收紧:

# 禁止非特权用户加载 eBPF(默认内核参数)
sysctl kernel.unprivileged_bpf_disabled
# root 也需要 CAP_BPF/CAP_PERFMON/CAP_SYS_ADMIN 之一
capsh --print | grep bpf
风险点缓解
内核崩溃/死锁verifier 已静态保证安全性,但仍避免超高频率 hook
数据外泄maps 中敏感数据及时清理,权限收紧
开销失控profile 采样率、事件过滤 /pid==/ 条件
容器逃逸面容器默认禁止 eBPF(--privileged 才放行)

生产实践要点:

# 采样率与时长克制:99Hz 采样 30 秒够定位绝大多数问题
bpftrace -e 'profile:hz:99 { @[comm] = count(); }' -d 30
# 先 bpftrace -l 确认 hook 存在,避免 attach 失败
bpftrace -l 'kprobe:do_sys_open*'

铁律:生产环境三条红线 = 高频 kprobe 先压测再上、事件日志不落敏感明文、容器内 eBPF 默认关。观测本身也有成本,目标是「用最小开销拿到可行动的结论」。


10. 速查表

需求命令
查看已加载程序bpftool prog list
列出可用 kprobebpftrace -l 'kprobe:*'
系统调用计数bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[comm] = count(); }'
文件打开追踪opensnoop
进程启动追踪execsnoop
CPU 采样bpftrace -e 'profile:hz:99 { @[comm] = count(); }'
CPU 排队延迟runqlat
块设备 IO 延迟biolatency
TCP 建连tcpconnect
OOM 追踪oomkill
禁止非特权 eBPFsysctl kernel.unprivileged_bpf_disabled=1
用户态函数插桩uprobe:/bin/bash:readline { ... }

一句话记忆:eBPF = 内核里的安全沙箱;观测用 tracepoint(稳)优先、kprobe(全)兜底、uprobe 看用户态;bpftrace 三句式「探针/过滤/动作」,BCC 开箱即用,生产切记控制采样率与权限。


延伸阅读

  • /linux-kernel-tuning/ — 内核机制与 sysctl 调优
  • /linux-performance-tuning/ — 性能排查方法论与工具链
  • /linux-process-management/ — 进程状态与监控基础
  • /linux-network-commands/ — 网络排错与抓包
  • /linux-containers-isolation/ — 容器隔离与 eBPF 权限

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「linux」更多文章

  1. 《进程调度与 CPU:CFS、优先级与 cgroup CPU 控制》
  2. 《Linux 网络虚拟化:namespace、veth、网桥与虚拟交换机》
  3. 《ELF 二进制与动态链接:从符号到加载》