LWN:优化kernel内联函数调试信息!

关注了就能看到更多这么棒的文章哦~

Better debugging information for inlined kernel functions

By Daroc Alden
April 30, 2025
LSFMM+BPF
Gemini-1.5-flash translation
https://lwn.net/Articles/1018475/

现代编译器会进行大量优化,这会使调试复杂化。Song Liu 和 Thierry Treyer 在 2025 年 Linux Storage, Filesystem, Memory-Management, and BPF Summit 会议上谈到了 BPF 类型格式 (BPF Type Format, BTF) 调试信息的一项潜在改进,这可以部分解决该问题。他们希望将选择性内联函数 (selectively inlined functions) 的信息添加到 BTF 中,以便更好地支持跟踪工具 (tracing tools)。Treyer 远程参与了会议。

最常见的编译器优化之一是内联 (inlining),它将一个函数的代码直接嵌入到其调用者中,从而避免函数调用的开销,并可能暴露其他隐藏的优化机会。然而,现代编译器并非总是内联对特定函数的每个调用,即使这些调用能从内联中受益。编译器使用启发式算法 (heuristics) 来决定哪些调用点 (call sites) 会从内联中受益。这意味着程序员很容易遇到这样的情况:函数仍然出现在二进制文件 (binary) 的符号表 (symbol table) 中(因为有些调用没有被内联),但跟踪该函数却不会显示对它的调用(因为热点调用 (hot calls) 被内联了,因此该函数的符号不再指向它们)。

Liu 说,这是一个问题。这不仅因为它使调试更加困难,还因为它促使开发者将重要的内核函数标记为不可内联 (not inlinable),以便他们能够依赖跟踪这些函数。从技术上讲,可以通过使用 DWARF 调试信息 (DWARF debugging information) 找到内联位置来跟踪选择性内联函数,一些跟踪工具会自动完成这一点。然而,DWARF 是一种庞大、复杂的格式,这使得将它用于此目的时速度较慢。其他方法,例如 tracepoint 和 Linux 安全模块 hook (Linux security module hook),也适用于选择性内联函数。然而,Liu 认为这些方法并不能完全替代常规跟踪,因为在调试内核问题时,在实际处理问题之前,通常不清楚需要跟踪哪些函数。

Liu 概述了两种改善这种情况的不同方案:一种是在内核的 BTF 中仅标记选择性内联函数,另一种是包含它们被内联位置的信息。第一种方案的好处在于其简洁性;可以轻松添加一个额外的函数属性,并将 pahole 等工具 转换为处理它。Liu 说,选择性内联函数之所以成为问题,主要是因为它们造成的困惑。仅仅能够警告开发者正在发生什么情况就有所帮助。

第二个想法将使其他工具更容易实现与 perf probe 类似的功能:在函数被内联的每个位置以及其非内联位置设置断点 (breakpoint)。=perf= 通过解析 DWARF 来实现这一点;而 Liu 的改动将允许其他工具使用 BTF 实现同样的目的。

然而,跟踪一个内联函数并非像在正确位置放置断点那么简单。当函数被内联时,参数 (argument) 和返回值 (return value) 可能由于其他优化而消失。BTF 需要一种方法来指示如何转换调用点 (callsite) 的机器状态 (machine state) 以恢复函数的参数。

Liu 和 Treyer 分析了内核现有的调试信息,以确定这是否可行。在 150,000 个不同的调用点上,内联函数的 228,000 个参数中,大约 83% 的位置可以表示为寄存器 (register) 的偏移量 (offset),通常是因为该参数存在于调用函数的栈 (stack) 上,或者作为指针 (pointer) 传递。Liu 警告说,这项分析不包括少数最常被内联的函数,因为它们会不成比例地扭曲数据。

一位听众询问这个数字是 per-argument 计算的还是 per-function 计算的。Liu 澄清说,这是 per-argument 计算的。该听众接着询问,有多少比例的函数的参数只能以这种方式表示。Liu 展示了一张幻灯片,表明他查看的大约一半的函数都可以使用这种方案获取所有参数。

知道这是可行的后,Liu 提出了一个关于如何在 BTF 中编码内联函数参数信息的方案:每个参数将由一个高度受限的虚拟机 (virtual machine) 中的一系列操作符 (operator) 描述。支持的操作包括加载常量 (constant)、解引用寄存器 (dereferencing registers)、应用偏移量等。因此,例如,一个存储在由寄存器指向的结构体 (structure) 成员中的参数,将表示为类似 "加载寄存器,添加偏移量,解引用,结束" 的序列。使用 Liu 和 Treyer 的原型实现 (prototype implementation),这将为内核的 BTF 增加约 10MB 的大小,不过这个数字是在进行去重 (deduplication) 之前计算的,去重可能会大幅降低大小。

最后一个问题是,BTF 只存在于未被完全内联的函数。在每个调用点都被内联的函数,比选择性内联函数造成的困惑要少,但支持它们会相当简单。添加这些函数将为内核的 BTF 增加额外的 32,000 个函数。

听众对编码参数信息的设计提出了许多问题,主要集中在如何简化它并移除冗余信息 (redundant information)。Alexei Starovoitov 说,他认为这种方法似乎 ""受到了 DWARF 状态机 (DWARF state machine) 的启发"",而且他们有机会采用更简单的方法。Liu 同意该方案还有些粗糙,并欢迎改进建议。

Starovoitov 还指出,Liu 提出的编码对于 16 字节的值无法正常工作,而内核中使用了相当多的此类值。Andrii Nakryiko 询问问题是否在于这些值有时存储在一对寄存器中,而不是内存 (memory) 中。Starovoitov 同意有时是这样,但概述了小型结构体可能遇到的其他一些边缘情况 (edge-cases)。Liu 同意研究 16 字节值的编码问题。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值