引言
传统的 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 点
- 2. 程序加载与运行机制
- 3. kprobe、uprobe、tracepoint 动态插桩
- 4. bpftrace 入门与单行命令
- 5. bpftrace 脚本实战:CPU 与内存
- 6. 网络追踪:XDP 与 tc 与 kprobe
- 7. IO 与文件系统追踪
- 8. BCC 工具集与 CO-RE
- 9. 安全权限与生产实践
- 10. 速查表
- 延伸阅读
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 |
| 列出可用 kprobe | bpftrace -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 |
| 禁止非特权 eBPF | sysctl 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 权限
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。