fentry 挂不上?内核近 4 成函数被内联,BTF 正在补上这块盲区
本文地址:https://www.ebpf.top/post/inline-btf-tracing
写 BPF 追踪程序时,最让人泄气的一刻是:明明在源码里看到了那个函数,fentry 却挂不上,kprobe 也报找不到符号。Oracle 的 Alan Maguire(pahole 共同维护者)在博客 Adding inlined functions to the BPF Type Format 里,先把「函数为什么追不到」拆成几类,再给出一个让被内联函数也能被描述、进而被追踪的 BTF 扩展方案。
先给结论:在作者统计的一份 Fedora 43 内核里,可直接用 fentry/fexit 的函数约 50,852 个(50.0%),完全被内联、没有独立副本的约 40,026 个(39.4%),对应约 40 万处内联点(inline site)。内联点大约是可追踪函数的 10 倍。这部分过去基本无解,也是新方案瞄准的对象。方案涉及 libbpf、pahole 和 kbuild 三处的补丁系列,目前仍在推进,消费侧(如 kprobe multi)也在跟进,还不是发行版的默认能力。
本号《BTF 实践指南》讲过 BTF 如何描述类型和函数,这篇补上「被编译器内联掉的函数」这一块。
1. 问题:一个函数追不到,通常是四种原因之一
挂载失败时,先判断属于哪一类,再决定换挂点还是换方法。作者给出的四类如下:
图中的分类用于初步排查:ftrace 可用列表、BTF 签名和 kprobe 黑名单是不同的检查项,不能仅凭其中一项断定所有追踪方式都不可用。
第一类:函数受 notrace、noinstr 等插桩限制。 这类限制用于避免追踪递归或保护不能插桩的关键路径。函数在 /proc/kallsyms 里有、却不在 /sys/kernel/tracing/available_filter_functions 里,只能说明它不在 ftrace 的可用列表中,不能据此断定一定标记了 notrace 或 noinstr。available_filter_functions_addrs 还给出 ftrace 补丁点地址、函数名和可选的模块名,地址可能与 kallsyms 不同。能否改用 kprobe,要另外核对 kprobe 黑名单及目标路径的限制;不能把 notrace 一概理解为所有追踪方式都不可用。受禁止插桩约束的目标应换上下游挂点。
第二类:函数存在,但 BTF 里没有它的签名。 fentry/fexit 依赖 BTF 中的 BTF_KIND_FUNC/FUNC_PROTO,verifier 要靠签名才能安全地读参数。编译器优化有时会去掉参数,或把参数放进 ABI 约定之外的寄存器,这时 pahole 生成 BTF 会跳过该函数。可以用 bpftool btf dump file /sys/kernel/btf/vmlinux 检查内核本体的签名;模块函数还应检查 /sys/kernel/btf/<模块名>。未找到签名只能说明所查 BTF 中缺少它,不能单凭这一点认定是优化导致;GCC 编译的内核里,这类函数常带 .isra 或 .constprop 后缀。对策是改用 kprobe,但要注意参数可能不在你预期的寄存器里。
第三类:函数被完全内联。 编译器把函数体直接展开进调用者,省掉调用开销。内核大量使用 inline 和 __always_inline,尤其是头文件里的小函数。这类函数在 /proc/kallsyms 里根本没有符号,过去除了改代码(比如去掉 static,但开启 LTO 时也会失效)几乎没有办法。
第四类:具备 fentry/fexit 的基本条件。 函数出现在 available_filter_functions_addrs 中,且对应内核或模块 BTF 中有可用的 BTF_KIND_FUNC/FUNC_PROTO 签名时,可优先尝试 fentry/fexit;最终仍以当前内核的挂载检查结果为准。
作者在 Fedora 43 内核上的分布统计(合计 101,604 个函数):
| 类别 | 函数数 | 占比 |
|---|---|---|
| 可用 fentry/fexit | 50,852 | 50.0% |
| 完全内联 | 40,026 | 39.4% |
| notrace / noinstr | 9,071 | 8.9% |
| 优化导致无 BTF | 1,655 | 1.6% |
可以看到,追不到的函数里,内联占了大头,远多于另外两类之和。
2. 原理:内联点的信息原本只在 DWARF 里
好消息是 kprobe 能挂在几乎任意指令上,理论上可以在内联点下探针。难点在于:哪条指令属于哪个被内联的函数、参数此刻在哪个寄存器或栈位置,这些信息只存在于 DWARF 调试信息里。DWARF 动辄几百 MB,通常要单独装 debuginfo 包,生产机上往往没有。
BTF 则不同,它体积小、直接编进内核镜像,在 /sys/kernel/btf 下随时可读。所以思路很自然:如果能把约 40 万个内联点紧凑地写进 BTF,生产机就能在没有 DWARF 的情况下追踪内联函数。
难点也在「40 万」这个量级上,表示方式必须足够省。BTF 保持小体积靠的是去重,例如整个内核只保留一份 struct sk_buff,而不是每个目标文件各一份。新表示也必须能被去重。
3. 做法:三个新 BTF kind,把「是谁」和「参数在哪」拆开存
每个内联点至少要记三件事:内联点地址、被内联的是哪个函数、参数从哪里取。作者为此新增了三个 kind:
BTF_KIND_LOC_PARAM:描述一个参数怎么取,可以是寄存器、寄存器加偏移、对寄存器加偏移解引用,或者是一个常量。BTF_KIND_LOC_PROTO:由一组 LOC_PARAM 组成,描述某个内联点全部参数的位置。BTF_KIND_LOCSEC:一个 ELF section(如.text)里的内联点列表,每项包含被内联函数的 FUNC id、对应的 LOC_PROTO,以及内联点相对 section 起点的 32 位偏移。
关键设计在于分离:函数身份只放在 LOCSEC 的条目里,这部分天然无法去重;而参数位置描述 LOC_PARAM/LOC_PROTO 在大量内联点之间是重复的,可以被充分去重。同一套表示既能描述在多个内联点被内联、每处参数可用情况不同的函数,也能描述「部分内联」的函数,即一部分调用点被内联,其余仍是普通函数。
为了让这套表示落地,作者先做了三项前置改造:
- kind layout 信息:过去旧工具遇到不认识的 BTF kind 会整个解析失败。现在 BTF 可携带一段「kind 布局」描述,旧工具即使不认识新 kind,也能跳过它继续解析。
- multi-split BTF:inline 信息体积大,放在单独的 split BTF 里。模块的 BTF 本身已经叠在 vmlinux 之上,模块的 inline 信息还要再叠一层,例如
xfs.inline叠在xfs上,xfs再叠在vmlinux上。 - 扩宽 vlen 和 kind 字段:
btf_type的info字段原来只用 16 位存 vlen,LOCSEC 最多只能装 65,535 项,装不下.text里的 40 万个内联点。改造后 vlen 用 24 位(上限约 1,670 万),kind 从 5 位扩到 7 位(最多 128 种)。
4. 进展:生成侧有了雏形,消费侧还在跟进
工具链各环节的状态,按作者博客的描述整理如下:
| 环节 | 状态 |
|---|---|
| libbpf | 补丁系列加入三个新 kind |
| pahole | --btf_features=inline 可生成 |
| kbuild | 生成 .BTF.inline,Eduard Zingerman 在 resolve_btfids 中拆分 |
| 消费侧 | Jiri Olsa 在看 kprobe multi 如何使用 |
kbuild 的设计是:开启 CONFIG_DEBUG_INFO_BTF=y 时,vmlinux 和模块会多出一个 .BTF.inline section,并通过 /sys/kernel/btf/ 下的 .inline 文件暴露。vmlinux 的 inline 信息未压缩约 10MB,所以也支持 CONFIG_DEBUG_INFO_BTF=m,把它放进按需加载的 btf_vmlinux_inline.ko,访问时才加载。
需要强调的是,这些都以作者博客的描述为准,属于仍在推进的补丁系列。你手头的内核、libbpf 和 pahole 是否已经包含,需要对照版本自行确认,不要默认升级就能用。
5. 限制与建议:内联追踪就绪前,先把诊断做对
在消费侧真正可用之前,被内联的函数仍然很难追。眼下能做的是:
- 分项初步排查:结合
available_filter_functions_addrs、对应内核或模块的 BTF,以及kallsyms判断可能的原因;这些检查不能单独证明函数被内联或禁止所有追踪。考虑 kprobe 时还需核对其黑名单和目标路径限制。 - 无 BTF 的优化函数改用 kprobe:同时核对参数的实际寄存器,不要假设符合 ABI。
- 内联函数换挂点:改挂外层未被内联的调用者,或在测试环境里通过改代码避免内联,作者也承认这种办法很脆弱。
- 跟进工具链:关注 pahole、libbpf 的 inline 特性和内核的
.BTF.inline进展,等 kprobe multi 接上这些 kind 再评估。
从「描述可见的函数」到「描述被编译器抹掉的调用点」,BTF 正在补上它和 DWARF 之间最大的一块差距。这和本号《BTFGen》关注的是同一个方向:让只有 BTF 的生产机,也能拿到原本只有 DWARF 才能提供的信息。
延伸阅读
- 原文作者:DavidDi
- 原文链接:https://www.ebpf.top/post/inline-btf-tracing/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 最后更新时间:2026-10-21 23:06:23.243175585 +0800 CST