The ORCs are coming
The ORCs are coming相关重点内容翻译,难免有误,见谅
实现这一目标涉及实现编译时堆栈验证机制,以确保所有内核代码始终保持堆栈处于合理状态。
最后一步是一个适当的展开器,它使用这个现在可靠的堆栈信息。
在编译时执行堆栈验证的 objtool 在构建的内核中的每个点构建堆栈状态模型
,也许 objtool 可以发出调试记录,以比 DWARF 更简单的格式使展开器可以使用该信息。 结果可能是使用更有效的数据格式的更可靠的展开器
这个名字表面上代表“oops rewind capability”,尽管它显然是对 DWARF 的一种游戏(反过来,它是对 ELF 可执行格式的一种游戏)。 新的 ORC 格式的核心很简单; 它基于这种结构:
struct orc_entry {
s16 sp_offset;
s16 bp_offset;
unsigned sp_reg:4;
unsigned bp_reg:4;
unsigned type:2;
};
orc_entry 结构的目的是告诉 unwinder 代码如何在堆栈上定位自己。
这些结构中的一个与内核中的每个可执行地址相关联,还有一个简单的数据结构,允许展开器在给定程序计数器地址的情况下找到正确的条目。
结构的解释取决于类型字段。 如果是 ORC_TYPE_CALL,则代码在正常的 C 风格调用帧内运行,并且可以通过将 sp_offset 值添加到由 sp_reg 指示的寄存器中找到的值来找到该帧的开始。
相反,如果类型是 ORC_TYPE_REGS,之和总是指向描述系统调用之前的处理器(和堆栈)状态的 pt_regs 结构。
最后,ORC_TYPE_REGS_IRET 表示 sp_reg 和 sp_offset 可用于查找硬件中断的返回帧。这三种可能性似乎足以描述将遇到的任何情况,至少在 x86 架构上是这样。
(bp_reg 和 bp_offset 字段在当前实现中似乎没有多大用处)。
由此产生的机制比 DWARF 机制简单得多。 除此之外,这意味着它的速度要快得多——据称至少是 20 倍。
在响应内核 oops 时,展开性能可能并不重要,但对于函数跟踪和分析来说却很重要。
ORC 方法也声称比告诉编译器使用帧指针更可靠,并且它不会受到帧指针带来的显着性能影响。
而且,如上所述,ORC 格式完全在内核社区的控制之下,因此它不应该与新的编译器版本中断,如果是这样,内核开发人员可以修复它。
当然,很难预测未来的编译器开发人员在破坏调用堆栈信息方面会有多大的创造力。 Poimboeuf 在补丁发布中承认存在风险,但指出:
如果较新版本的 GCC 提出了一些破坏 objtool 的优化,我们可能需要重新审视当前的实现。 一些可能的解决方案是要求 GCC 使优化更容易接受,或者让 objtool 使用 DWARF 作为附加输入,或者创建一个 GCC 插件来帮助 objtool 进行分析。
另一个缺点是ORC格式比DWARF占用更多的空间,占用1MB左右的额外内存。 Poimboeuf 建议,如果事实证明这是一个真正的问题,可以减少内存使用。 “但是,它可能需要牺牲一些速度和简单性的结合”。
问答;
我还没有尝试了解路径集... ORC 会跟踪文件/lineno 还是仅跟踪符号名称?
ORC 不会跟踪这些事情; 它只是提供理解内核堆栈所需的信息。 kallsyms 机制可以像往常一样将符号与地址相关联。
一个相关的问题; 这最终会渗透到用户空间,以便我们可以在没有帧指针的情况下获得可靠的性能回溯吗?
(是的,有 --call-graph=dwarf,但它需要将整个堆栈转储到 perf 跟踪,因为 DWARF 太慢而无法实时跟踪。所以它会产生缓慢而巨大的跟踪。)
ORC重点原理汇总理解:
堆栈验证工具用于分析目标文件中的所有代码路径,确定有关文件中每个指令地址处的堆栈状态的信息,并构建展开表以描述每个指令地址处的堆栈状态。 该数据被写入两个并行数组,
.orc_unwind 和 .orc_unwind_ip。 使用两个部分可以更快地查找给定指令地址的 ORC 数据,因为数据的可搜索部分 (.orc_unwind_ip) 更紧凑。
这些表由 struct orc_entry 元素组成,这些元素描述了如何定位前一个函数的堆栈和帧指针。 每个元素对应一个或多个代码位置。
新的 ORC 格式的核心很简单; 它基于这种结构:
struct orc_entry {
s16 sp_offset;
s16 bp_offset;
unsigned sp_reg:4;
unsigned bp_reg:4;
unsigned type:2;
};
orc_entry 结构的目的是告诉 unwinder 代码如何在堆栈上定位自己。
这些结构中的一个与内核中的每个可执行地址相关联,还有一个简单的数据结构,允许展开器在给定程序计数器地址的情况下找到正确的条目。
结构的解释取决于类型字段。 如果是 ORC_TYPE_CALL,则代码在正常的 C 风格调用帧内运行,并且可以通过将 sp_offset 值添加到由 sp_reg 指示的寄存器中找到的值来找到该帧的开始。
相反,如果类型是 ORC_TYPE_REGS,之和总是指向描述系统调用之前的处理器(和堆栈)状态的 pt_regs 结构。
最后,ORC_TYPE_REGS_IRET 表示 sp_reg 和 sp_offset 可用于查找硬件中断的返回帧。这三种可能性似乎足以描述将遇到的任何情况,至少在 x86 架构上是这样。
(bp_reg 和 bp_offset 字段在当前实现中似乎没有多大用处)。