☰
eBPF命令行工具实战:bpftool、bcc-tools与bpftrace排障指南
2026/10/8 8:51:45 网站建设 项目流程

前几天帮一个朋友处理线上问题,服务每到凌晨就出现一波明显的磁盘IO毛刺,htop里看进程状态一切正常,strace挂上去又担心影响生产。我干脆打开终端,用 eBPF 命令行工具从内核视角查了一层,几分钟就定位到是某个定时任务在反复打开同一个配置目录下的大量小文件。类似这种场景我遇到不是一次两次了。很多人一听到 eBPF,就以为是内核大神才能碰的东西,其实现在围绕它已经长出了一套非常成熟的命令行工具生态:bpftool 负责管理内核里的 BPF 对象,bcc-tools 提供大量开箱即用的运维脚本,bpftrace 则让你像用 awk 一样写动态追踪脚本。这篇文章不打算讲枯燥的内核理论知识,直接按我平时排障的习惯,把这几件好东西逐个拆开,给你讲清楚它们各自能干什么、怎么用、以及有哪些坑。

1. 先在脑子里装一张工具链地图:从探针点到三个命令行工具

想用好这些命令行工具,第一步不是背命令,而是搞清楚数据是怎么从内核流到你屏幕上的。eBPF 本身不是单个命令,而是一个运行在内核里的安全虚拟机。它允许你把一小段字节码加载进操作系统,挂到某个事件点去执行,事件触发时内核会采集数据并回传给用户态。这里面的关键概念有三个:探针点、事件数据和加载器。

1.1 事件驱动模型到底在驱动什么

传统排查工具大多靠"轮询"或者"被抓包",比如 top 每隔几秒刷一次数据,strace 则通过 ptrace 系统调用接管目标进程。eBPF 走的是完全不同的路线:事件驱动。你定义了 kprobe 或者 tracepoint 这类探针点,当内核执行到对应位置时,与探针绑定的 BPF 程序会同步被触发,采集当时的参数、进程号、函数调用栈等信息。它不轮询,也不暂停进程,所以在大部分场景下开销远小于 strace,这也是它适合在生产环境做排查的根本原因。

我得强调一个容易误解的点:eBPF 命令行工具并不是 eBPF 本身,它们只是"前端"。真正干活的,是内核里的 BPF 虚拟机、BPF 辅助函数和 map 数据结构。命令行工具负责把人类可读的请求(比如"统计每秒新创建的进程")编译成字节码,加载进内核,再把内核送回来的数据翻译成表格或直方图。

1.2 四件套的分工:bpftool、bcc、bpftrace、libbpf

这四样东西常被混为一谈,实际职责差异很大,我习惯用下面这张对照表来理解它们。

工具定位典型命令适合谁
bpftoolBPF 对象管理器和内核诊断器bpftool prog show、bpftool map dump想看内核里到底加载了哪些 BPF 程序的人
bcc-tools多功能的运维脚本全家桶execsnoop、opensnoop、biolatency不想写代码、想直接出结果的人
bpftrace一门专为动态追踪设计的解释型脚本语言bpftrace -e 'kprobe:vfs_read { @[comm] = count(); }'习惯写一行式 awk、需要自定义探针的人
libbpfC 库,用于构建原生 eBPF 应用配合 clang 编译.bpf.o文件做平台开发、二次集成的人

排障现场用前三者的频率最高。libbpf 更多属于开发者的范畴,本文只做简单介绍,重点还是放在命令行工具上面。我见过很多同事一上来就用 bpftrace 写复杂脚本,结果卡在语法上调半天。其实很多时候,bcc 里现成的工具已经覆盖了 80% 的场景,而 bpftool 则可以帮你验证"工具跑的到底是不是你想的那样"。真正高效的顺序是:先看看有没有现成工具,再用 bpftrace 定制,最后才考虑自己写 libbpf 程序。

2. 环境检查清单:跑第一个命令之前先确认四件事

我见过太多人卡在 "permission denied" 或者 "failed to create kprobe" 这类报错上,浪费大量时间。其实只要在上机前花两分钟过一遍下面的检查,大部分问题都能绕开。

2.1 内核版本与 BTF 符号表

绝大多数现代 eBPF 工具对内核版本是有要求的。我的经验是:内核低于 4.9 的基本可以放弃大部分 bcc 工具;低于 5.4 的建议只用老牌 bcc 工具,避免碰那些依赖 CO-RE(Compile Once - Run Everywhere)的 libbpf-tools。想看内核是否支持关键特性,两条命令就够了:

uname -r cat /sys/kernel/btf/vmlinux | wc -c

第二条命令的输出如果是一堆字节数(通常几十 MB),说明内核开启了 BTF 信息,新版 bpftrace 和 bcc 里的 libbpf-tools 可以直接跨内核版本跑。如果文件不存在,说明内核编译时没有开CONFIG_DEBUG_INFO_BTF,这时要么换内核,要么降低工具版本,使用传统方式加载探针。BTF 缺失是新手最容易遇到的隐性坑,因为 bcc 老工具不依赖它,bpftrace 还能用,但部分 libbpf 工具就会莫名其妙地报 "failed to load program"。

2.2 权限、挂载点和用户态依赖

eBPF 程序属于高权限操作,非特权用户默认加载不了 BPF 程序。内核 5.8 之后引入了专门的CAP_BPF和CAP_PERFMONcapability,很多发行版默认允许特权用户操作。我建议排查问题时统一用sudo,否则会看到大量含糊其辞的权限报错。另外,如果你是在容器里跑 eBPF 工具,情况会更麻烦一些:容器默认缺内核 capability,常见的做法是起容器时带上--privileged并挂载宿主机的/sys/fs/bpf目录,否则你看到的 BPF 文件系统是空的,工具无法正常工作。

BPF 文件系统挂载点也是检查项之一,执行下面命令确认:

sudo mount -t bpf bpf /sys/fs/bpf ls /sys/fs/bpf

还有一类依赖容易被忽略:bpftrace 和 bcc 依赖 LLVM 来做即时编译,如果你用的发行版 LLVM 版本过旧,部分新语法会编译失败。检查方式很简单:

llvm-config --version bpftrace --info | grep -i version

我的个人建议是:如果你准备系统性地把 eBPF 命令行工具当排障武器用,优先选内核 5.15 以上的较新发行版,能省掉大量环境兼容性的烦恼。

3. bpftool 实战:从对象管理到网络路径诊断

bpftool 是内核自带 BPF 子系统最直接的管理入口。它不像 bcc 那样面向业务场景,而是面向 BPF 对象本身。很多时候你在系统里跑到一半想确认"有几个探针在干活、有没有残留程序、map 里到底存了什么数据",bpftool 是唯一的答案来源。

3.1 用 prog show 看清系统里跑着什么 BPF 程序

执行sudo bpftool prog show,你会看到类似这样的输出:

46: kprobe name do_sys_open tag b6ec59bdf146afe8 gpl loaded_at 2025-06-10T10:23:45+0800 uid 0 xlated 112B jited 89B memlock 4096B btf_id 221 pids execsnoop(5896)

这里最值得关注的是type(程序类型)、tag(程序指纹)和pids(当前被哪个进程使用)。pids字段很有用,它能直接告诉你这个 eBPF 程序是被哪个用户态进程拉起来的。如果你发现某个 BPF 程序没有对应的存活进程,说明拉它的程序可能已经崩溃,留下了悬挂的探针。此时可以用sudo bpftool prog detach或者直接重启相关服务来清理。

另一个高频用法是配合--json输出,格式化成结构化数据后方便脚本解析:

sudo bpftool prog show --json | jq '.[] | {id, name, type, tag}'

3.2 用 net 和 map 打通网络路径与数据流

bpftool 的net子命令查看的是 XDP、tc 这类网络挂钩点。有一次我在排查一个网络延迟问题,一上来就执行了:

sudo bpftool net show

发现某个网络接口的 tc ingress 上挂了一个老版本的 BPF 程序,而这个程序来自一个早已废弃的服务。它在每个数据包上都做了一次额外匹配,延迟自然高。这种问题用传统 netstat、ss 完全看不出来,因为数据已经进了内核协议栈,只是被 BPF 程序截流了。

bpftool map系列命令用来操作 BPF map。BPF 程序通过 map 在用户态和内核态之间交换数据,很多时候 map 里存的就是程序的关键状态,比如流量统计、过滤规则。查看 map 列表:

sudo bpftool map show sudo bpftool map dump id 7

map 的 dump 输出往往是哈希值或二进制格式,直接看可能不太友好。一个实操技巧是先通过bpftool map lookup id 7 key 0x...精确查某个 key,再配合bpftool map update修改数据。这里要特别提醒:不要在非测试环境随便 update map 数据,你改的可能就是内核里正在用的过滤规则,一次误操作可能直接中断线上流量。

3.3 用 btf 和 feature probe 快速给内核做体检

遇到 "这个工具为什么跑不起来" 的问题时,我最先执行的其实是bpftool feature probe,它会扫描当前内核支持的 BPF 特性,包括辅助函数、程序类型、map 类型是否开启。输出示例:

$ sudo bpftool feature probe Scanning system configuration... - JIT compiler is enabled - JIT hardening is enabled - JIT kallsyms are enabled - kallsyms export is enabled for kernel ...

如果你发现工具依赖的某个辅助函数没有出现在列表中,问题就定位到了:不是命令写错了,是内核编译选项没开。bpftool btf dump则用于导出内核的 BTF 格式类型信息,开发阶段排查类型问题非常有用:

sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c | head -n 50

4. bcc-tools:开箱即用的运维工具箱

bcc(BPF Compiler Collection)是 eBPF 工具链里历史最久、覆盖面最广的一套。它的特点是:你不需要自己写 BPF 程序,只需要调用现成的 Python 脚本,背后会自动完成字节码生成、加载、数据收集和可视化。所以它的定位很明确:拿来就能用。

4.1 安装与工具目录

不同的发行版包名不一样,Debian/Ubuntu 上是bpfcc-tools,RHEL 系是bcc-tools。装完后工具通常位于/usr/share/bcc/tools,命令名末尾可能会带bpfcc后缀(比如opensnoop-bpfcc)。如果找不到,先用which确认一下再进脚本,避免手滑执行了系统里其他同名命令。

装完后,我通常是这么挑工具的:

ls /usr/share/bcc/tools | sort | less

这个目录列表本身就是一本按场景组织的排障手册。文件系统相关的有opensnoop、filetop、syncsnoop;网络相关的有tcplife、tcpconnect、tcpretrans;调度性能相关的有runqlat、offcputime;进程相关的有execsnoop、exitsnoop。

4.2 六个高频工具及真实场景

举几个我实际用过的例子。

execsnoop跟踪系统中所有新进程的创建,输出进程名、PID、父进程和参数。排查"为什么突然冒出这么多进程"时,它比其他工具都直观:

sudo execsnoop

opensnoop跟踪 open 系统调用,能看到哪个进程访问了哪个文件。前面提到的凌晨磁盘 IO 问题,我就是靠它确认了某个守护进程疯狂打开配置目录里的小文件:

sudo opensnoop -n nginx -d 30

biolatency输出块设备 IO 延迟的直方图,在定位数据库磁盘慢查询时很直观,输出直接就是分桶分布的 ASCII 图。

tcplife则是网络排查的利器,能够显示 TCP 连接从建立到关闭的完整生命周期,包括连接时长、收发字节数。某次业务反馈"连接偶尔特别慢",我用它筛出了大量短连接,再去应用层定位到连接池配置问题:

sudo tcplife -L --csv

funccount可以统计某个内核函数在一段时间内被调用的次数,最适合回答"这个函数是不是太热"。比如:

sudo funccount vfs_read

trace是 bcc 里最接近手写探针的工具,支持一行式 Python 语法。比如查看所有对/tmp目录的 open 操作,并打印调用者:

sudo trace 'do_sys_openat2 "%s" arg2' 'comm == "nginx"'

这里面我踩过一个小坑:老版本的open探针名和新的 openat2 探针名不一样,如果脚本报找不到符号,先用perf probe -l或者直接查一下内核符号表,再换探针名称。

4.3 bcc 与 libbpf-tools 的迭代问题

新版本 bcc 里有一部分工具已经被重写为 libbpf-tools,它们依赖 BTF,启动更快,输出也更稳定。如果某些旧工具在新内核上跑不稳,不妨尝试同名的*-bpfcc替代路径。但如果你是嵌入式或者云上老内核环境,那套 libbpf-tools 可能因为 BTB 缺失直接起不来,此时兜底方案还是老 bcc 工具。

5. bpftrace:一行脚本完成动态追踪

如果说 bcc-tools 是"点外卖",bpftrace 就是"拿着菜谱自己炒"。它是一门专门为动态追踪设计的语言,语法风格和 awk 很像,但追踪对象不是文本行,而是内核事件。它特别适合那种"现成工具没有覆盖到、但你就想知道一件事"的场景。

5.1 一句话理解 bpftrace 语法

bpftrace 程序的基本单元是"探针 + 动作",类似 awk 的"匹配 + 动作"。比如下面这行代码统计各进程执行的open系统调用次数:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'

拆开看:tracepoint:syscalls:sys_enter_openat是探针点,{} 里面是动作,@开头的变量就是 BPF map 的封装,count()是内置计数函数。comm是内置变量,表示当前进程名。

bpftrace 还支持条件过滤。用/加条件过滤,可以只关注特定进程:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat / comm == "nginx" / { printf("%s\n", str(args.filename)); }'

这里args.filename是 tracepoint 的参数,str()负责把内核指针转换成字符串。带条件过滤的写法在处理大规模高频事件时特别有用,能把采集噪声降到最低。

5.2 三个高频模板:计数、直方图、采样

模板一:延迟直方图。追踪某个内核函数的耗时分布,比单看平均值可靠得多:

sudo bpftrace -e 'kprobe:vfs_read { @start[pid] = nsecs; } kretprobe:vfs_read / @start[pid] / { @us = hist((nsecs - @start[pid]) / 1000); delete(@start[pid]); }'

这段代码的思路是:入口探针记录每个进程的开始时间,出口探针计算时间差并放入hist()直方图,然后删除临时记录。hist()输出的是 2 为底的分桶分布,能立刻看出延迟是否呈现双峰形态。双峰往往是缓存命中与未命中的混合场景,这是平均数完全掩盖的信息。

模板二:CPU 调用栈采样。理想情况下你应该看到运行热点出现在业务代码里,但有时候你看到的是大量内核路径,这本身就是异常信号:

sudo bpftrace -e 'profile:hz:99 { @[kstack] = count(); }'

这里profile探针是一个定时探针,以 99Hz 的频率对所有 CPU 采样,每次记录当前内核调用栈。跑 30 秒后 Ctrl+C,bpftrace 会打印调用栈出现频次排序。

模板三:进程退出路径观测。想知道进程为什么退出,可以追踪sys_enter_exit或者sys_enter_exit_group:

sudo bpftrace -e 'tracepoint:sched:sched_process_exit { printf("%s %d\n", comm, pid); }'

5.3 调试参数、输出控制与常见报错

bpftrace 有成体系的-d调试参数,排查脚本问题很有帮助:

sudo bpftrace -d -e '...' sudo bpftrace -dd -e '...'

-d显示生成的 LLVM IR,-dd则进一步显示最终生成的 BPF 字节码。我遇到"探针明明写对了但没输出"的情况时,会先看字节码是否生成了,判断是编译阶段挂掉还是加载阶段挂掉。另一个很实用的参数是-c,指定跟随某个命令运行,只在那个进程生命周期内追踪:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args.filename)); }' -c 'nginx -t'

常见报错有几个:一是kprobe not found,通常是内核函数名写错或者被__nokprobe屏蔽了,可以先用bpftrace -l 'kprobe:*vfs_read*'列一下;二是wrong number of args,这是探针参数个数不匹配,需要对照 tracepoint 定义查;三是段错误,多半是 bpftrace 自身版本 bug,升级到最新版大概率解决。

6. 实战复盘:一次 CPU 毛刺的定位路径

工具讲完了,最怕的还是不知道怎么串起来用。下面用一个我实际处理过的 CPU 毛刺问题,演示一下工具组合拳的打法。这个案例不涉及具体业务细节,但排查路径很典型。

6.1 第一轮:profile 采样锁定调用栈

业务反馈某组容器每天下午会出现 CPU 毛刺,持续几分钟后恢复。我先用htop确认毛刺时段确实有 CPU 占满的进程,然后又确认了这不是 ksoftirqd 或者内核线程的问题。接下来我没有直接抓进程线程栈,而是用 bpftrace 对全系统做调用栈采样:

sudo bpftrace -e 'profile:hz:99 { @[comm, kstack] = count(); }' > stack_20250610.txt

大约采集了 120 秒,等毛刺结束,把输出按计数排序。结果没有立刻指向业务代码,而是出现了大量do_sys_openat2和__x64_sys_openat的内核路径,且频次最高的进程是日志采集器。换句话说,这次毛刺不是计算密集,而是一个进程在短时间内发起了海量文件打开操作。

6.2 第二轮:funccount 确认热点函数

调用栈毕竟是采样,存在一定随机性。为了确认,我直接用 funccount 对vfs_open、do_sys_openat2等函数做精确计数,对比毛刺时段和正常时段的数量级差异。结果显示毛刺时段do_sys_openat2的调用次数是正常时段的 20 倍以上,基本锁定问题出在文件打开路径上。

6.3 第三轮:opensnoop 与 tcplife 交叉验证

再回到事件层,用opensnoop跟踪到底是谁在打开什么文件。输出显示,日志采集器在同时轮询多个目录,每个目录下有大量累积未清理的小文件,导致每次扫描都要发起成百上千次 open。与此同时,我用tcplife查看了对应时段的外发连接,发现日志上传队列出现拥塞,进一步加剧了文件读写的频率。至此根因清晰:文件清理策略失效导致存量文件过多,采集器扫描开销被放大。解决方案是调整清理策略、手动压缩归档旧日志,问题就不再出现了。

这套流程的关键在于:先用 profile 做粗筛,再用 funccount 做量化,最后用 bcc 工具回到业务语义层确认。单靠某一个工具很难一步到位。

7. 管理、清理与边界感:把 eBPF 工具用稳

工具再好,如果跑起来没纪律,也会给自己和团队挖坑。最后这部分聊聊生产环境中必须养成的几个习惯。

7.1 生命周期与残留处理

BPF 程序的生命周期通常和拉起的用户态进程绑定:bpftrace 退出时,对应探针和 map 会被自动销毁。但如果进程是异常 kill 掉的,或者你把 BPF 程序 pin 到了/sys/fs/bpf目录,程序就可能在内核里残留。检查残留的看家本领还是 bpftool:

sudo bpftool prog show | grep -E 'pids|name'

如果发现某个程序没有对应 pids、且不是系统服务挂载的,果断 detach 或清理 pin 文件:

sudo rm -f /sys/fs/bpf/xxx

这里要注意先看bpftool prog show确认 pin 路径,不要乱删系统级 BPF 对象。

7.2 性能开销与生产使用纪律

eBPF 探针本身开销很小,但不代表可以滥用。一个高频探针挂在繁忙路径上,比如对每个网络包计数,在万兆流量下会让吞吐量明显下降。我的经验是两条:能用采样不用全量计数;能加过滤条件就加过滤条件。比如 bpftrace 的 profile 采样默认是 99Hz,全量事件则必须配合/条件过滤,只在特定进程或者特定参数命中时动作。长时间挂探针还会大量占用系统内存,因为 BPF map 的内存是常驻的,跑完记得退出工具并确认是否有残留。

另外,如果生产环境开了很多探针,建议设置一个观测脚本,定期记录bpftool prog show和bpftool map show的摘要,避免哪天排查问题时发现环境里不知道飘着多少历史遗留探针。

7.3 权限边界与安全提醒

eBPF 是有能力修改内核行为的,不只是观测。虽然普通 CLI 工具的绝大多数命令都是只读观测,但 map 的 update、prog 的 attach 操作都会直接影响内核运行状态。所以在生产环境要遵循两条原则:权限最小化,别让普通开发账号拥有 CAP_BPF;操作可回滚,任何涉及 detach、update 的命令先确认当前对象归属和业务影响。内核版本较新的系统里非特权用户默认被禁止加载 BPF 程序,这是安全机制的一部分,不建议为了图方便去调整kernel.unprivileged_bpf_disabled。

我自己的习惯是,平时排障优先用 bpftrace 的只读统计类和 bcc 的观测脚本,动 map 数据的命令一律走变更评审流程。工具本身只是放大器,能不能安全地用,取决于使用者的边界感。

回到最初的场景,eBPF 命令行工具给我的最大感受是:它把内核从一个糊着的黑盒子变成了一块可以随时掀开盖板看的仪表盘。你不需要成为内核源码专家,只要理解探针、事件、map 这套基本模型,再把 bpftool、bcc-tools、bpftrace 按职责排好队,很多困扰很久的疑难问题都能快速找到下手点。如果有机会,我建议你也从bpftrace -l开始,一个探针一个探针地玩,那感觉比看任何文档都直观。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询