LWN:利用SFrame提供更可靠的用户空间调用栈记录!

SFrame是一种新的机制,允许内核创建可靠的用户空间调用栈信息,无需framepointer,减少了内存和运行时开销。此技术基于ORC堆栈解析器,由SteveRostedt和InduBhagat在Linux峰会中介绍,可用于调试、优化和profiling。虽然存在如二进制重定位和动态加载库的问题,但SFrame有望成为一种有效的工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

Reliable user-space stack traces with SFrame

By Jonathan Corbet
May 22, 2023
LSFMM+BPF
DeepL assisted translation
https://lwn.net/Articles/932209/

许多调试和优化任务中都需要拿到完整的调用栈信息(stack trace),但是,要想能可靠地获得这些信息可能是有令人惊讶的难度的。在 2023 Linux Storage, Filesystem, Memory-Management and BPF Summit 峰会上,Steve Rostedt 和 Indu Bhagat 介绍了一种叫做 SFrame 的机制,能够在内核中创建出可靠的用户空间调用栈信息,并且也没有其他一些解决方案带来的内存和运行时间上的开销。

4cb9e823cdb1ead15f695f5f16254d14.png

Rostedt 首先说,获得一个用户空间进程的完整 stack trace 对许多工作来说都是有价值的。准确 profiling 就需要它,所以 perf 和 ftrace 都使用了 stack trace。BPF 程序也可以从调用栈状态的信息中获取信息。

要想可靠地获得 stack frame 的传统方案是在编译相关程序的时候打开 frame pointer。frame pointer 仅仅是一个 CPU 寄存器,专门用来包含当前 stack frame 的基地址。该 frame 中会保存前一个 frame pointer 的副本,记录了前一个 frame 的开始位置。因此,内核(或任何其他程序)就可以根据这一串 frame pointer 来定位 stack 中的每个 frame。如果编译时没有打开 frame 指针,内核的 perf 子系统就必须在每个 event 发生时都把大量的 stack 内容复制下来,以便以后使用 DWARF unwinder 工具来进行后处理。这是件开销很大的事情。

但是 frame pointer 也不是毫无代价的。管理 frame pointer 就需要在每个函数的入口处运行一些准备代码。为 frame pointer 专门使用了一个寄存器,就使得 CPU 中非常稀缺的寄存器中要牺牲一个,不能用于其他用途,从而减慢了程序的执行速度。正如本文(https://lwn.net/Articles/919940/ )所描述的那样,用 frame pointer 构建用户空间会导致可以测量得出的性能损失,因此这可能会导致使用上引发争议。

Rostedt 继续说,内核有一个叫做 ORC 的堆栈解析器(stack unwinder),比 DWARF 简单得多。它是在 4.14 版本中加入的,用来支持 live patching 功能(这是另一个需要依靠 stack trace 的应用场景)。内核的 objtool 工具在构建时创建了 ORC 数据,并在内核可执行文件的一个 section 中添加了两个 table,其中 orc_unwind 是用来保存 stack frame 信息的,而 orc_unwind_ip 用来将指令指针(instruction pointer)值映射到正确的 unwind entry 上。

SFrame 是基于 ORC 的,提供了相同的机制,但是用于用户空间而不是内核空间。如果一个可执行文件编译时使用了 SFrame 数据,那么 kernel 可以创建完整的 stack trace,而不需要 frame pointer。当然,总是有代价的;在这种情况下,开发者为了速度而牺牲了一点磁盘空间(用来存放 ORC table)。如果需要使用的话,就可以在内核的 ptrace()路径中读取这些数据,所以在不需要的时候是不会影响到执行时使用的资源的。还需要一些额外工作来处理一些用户空间的复杂问题;例如,由于二进制文件是可重定位的(relocatable),因此必须有一种机制来对 SFrame 数据加上准确的偏移量。

Rostedt 概述了 SFrame 相关支持在内核中的工作方式。一个 perf 事件,始于一个不可屏蔽的中断(NMI),它最终走到了 perf 代码中。如果需要 stack trace,那么内核将尝试读取 call stack;如果遇到了 page fault 的话,那么这个 event 就没有 stack trace。他想把这段代码改成寻找 SFrame 数据。NMI handler 这个处理程序将设置一个 flag,表明在返回用户空间之前还有工作要做;ptrace() 这部分代码会看到这个 flag,并在用户上下文中重建 stack trace。这样做的效果之一就是使我们有可能在哪怕使在读取堆栈时发生 page fault 的时候也能恢复出 stack。

这种方法也需要对用户空间的 perf 工具做一些改动。在 NMI 时产生的初始 perf event 将不包括调用栈(要在后面才会取得),所以它会有一个 bit 设置为 "stack trace 即将发生"。在 stack trace 最终出现在 ring buffer 之前,可能会有几个中间事件(intervening event)先产生。Joel Fernandes 问道,内核是否可以在 NMI 时在 ring buffer 中保留空间,然后再将其填入。Rostedt 回答说,因为 ring buffer 可能最终会出现多个具有相同 stack trace 的事件;为每个 event 都保留空间,最终会导致浪费空间。

Rostedt 最后说,stack 不太可能被换出(swap out)到磁盘上,所以生成 trace 通常不会产生 I/O 并把 page 通过 page fault 来换入。也就是说,生成 trace 的时候会需要引入一些其他的数据,因为 SFrame 表被存储在磁盘上的可执行文件中。SFrame 数据只有在实际使用时才会被 map 上来,所以在一个进程中的第一次使用时会导致短暂卡住,这是会生成 mapping。

52fec13c2e40af2bdef041f6d5541f70.png

Bhagat(她做了这个功能中的很多具体实现工作)说,在内核中用汇编编写的部分代码可能会有问题。这些代码中的非标准的堆栈用法很可能会让 unwinder 工具看糊涂了。她说,通过内核的这些部分来进行 unwind 是否重要,还有待观察。

另一个潜在的问题是,SFrame 数据在磁盘上的存储方式并不是对齐的;这可能导致内核中出现不对齐的内存访问。为了避免这种情况,需要对数据进行少量的复制,一些"奇怪的转换(weird casts)",以及诸如此类的一些动作。另一种方法是强迫数据被对齐,但会使存储空间占用更多。大家似乎一致认为,存储数据不对齐是最好的解决方案,而且也没有必要改变它。

其他悬而未决的问题包括需要处理 dlopen(),它会将可执行 text 段从另一个文件映射到调用进程的内存范围中。这个问题也许可以通过增加一个系统调用来解决,用这个系统调用来告诉内核在哪里可以针对这个特定可执行文件的 SFrame 数据的映射位置。just-in-time 编译代码也是一个问题,对于这种没有真正对应的文件的代码段,也就没有 SFrame 数据。

会议结束时,与会者认为 SFrame 将是一个很好的工具,这项工作应该继续下去。

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

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

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

format,png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值