Linux的perf工具如何理解堆栈跟踪?

21

Linux的性能分析工具perf被广泛用于生成c/c++、JVM代码、Node.js代码等的火焰图。

Linux内核本质上能否理解堆栈跟踪信息?我在哪里可以阅读有关工具如何审查进程的堆栈跟踪信息的更多信息,即使这些进程是以完全不同的语言编写的?


这并不是源代码所使用的编程语言的问题,而是二进制文件的运行方式。最终,编程语言的代码会被转换成二进制(可执行文件),然后在处理器上运行。很可能Linux的可执行文件采用ELF格式。堆栈跟踪是为此二进制代码生成的。如果该二进制文件具有描述其过程的符号表,则跟踪中将显示一些常规名称。(Brendan Gregg描述了Java缺少符号的问题,对应于火焰图上的空白部分。) - xealits
@xealits,你有 Brendan 关于 Java 缺失符号的笔记链接吗?那可能会为我填补缺失的部分。 - Shahbaz
xealits,ELF没有堆栈跟踪;有时对于Linux内核来说找到完整的调用堆栈可能并不容易 - 由于编译器优化,一些帧指针可能被省略。一些Java虚拟机在堆栈帧格式上有魔法:http://www.brendangregg.com/perf.html "4.4 堆栈跟踪。始终使用帧指针进行编译。省略帧指针是一种邪恶的编译器优化,会破坏调试器,..自大约3.9内核以来,perf_events已经支持了一个解决缺少用户级堆栈中帧指针的方法:libunwind,它使用dwarf。...由于x86上的hotspot,Java可能无法显示完整的堆栈" - osgx
“完全不同的语言?”- 哪些语言?你的CPU架构是什么? - osgx
一些链接:"Java in flames""JIT symbols"。对于Linux Java来说,它是虚拟处理器(Java虚拟机,JVM),Java程序在JVM中运行,它们不是Linux进程,因此本质上不会被视为进程。他以某种方式增加了Linux中JVM内部的可见性,并使用户的Java程序及其堆栈变得可见。 @osgx,我对ELF和所有这些问题并不很熟悉,因此我离题了。 - xealits
1个回答

38

这里有一篇关于perf中堆栈跟踪的简短介绍,作者是Gregg: http://www.brendangregg.com/perf.html

4.4 堆栈跟踪

始终使用帧指针进行编译。省略帧指针是一种邪恶的编译器优化,会破坏调试器,并且往往是默认设置。如果没有帧指针,则可能会在perf_events中看到不完整的堆栈... 有两种解决方法:使用dwarf数据展开堆栈或返回帧指针。

Dwarf

自3.9内核以来,perf_events已支持用户级堆栈中缺少帧指针的解决方法:libunwind,它使用dwarf。可以使用"-g dwarf"启用此功能。 ... 编译器优化(-O2),在这种情况下省略了帧指针。... 使用-fno-omit-frame-pointer重新编译:

非C语言可能具有不同的帧格式,或者也可能省略帧指针:

4.3. JIT 符号 (Java, Node.js)

拥有虚拟机 (VMs) 的程序,如 Java 的 JVM 和 node 的 v8,在执行函数和管理堆栈时都有自己的虚拟处理器。如果使用 perf_events 进行分析,你会看到 VM 引擎的符号...perf_events 有 JIT 支持来解决这个问题,需要 VM 维护一个 /tmp/perf-PID.map 文件进行符号转换。

请注意,由于 x86 上的热点省略了帧指针(就像 gcc 一样),因此 Java 可能一开始不会显示完整的堆栈。在较新版本中(JDK 8u60+),您可以使用 -XX:+PreserveFramePointer 选项来修复此行为...

Gregg 关于 Java 和堆栈跟踪的博客文章: http://techblog.netflix.com/2015/07/java-in-flames.html ("Fixing Frame Pointers" - 在某些 JDK8 版本中已经修复,并在启动程序时添加了 JDK9 选项)

现在,你的问题:

linux 的 perf 工具是如何理解堆栈跟踪的?

perf 实用工具 基本上(在早期版本中)只是解析从Linux内核子系统 "perf_events"(或有时是 "events")返回的数据,通过系统调用 perf_event_open 进行访问。对于调用堆栈跟踪,可以使用选项 PERF_SAMPLE_CALLCHAIN / PERF_SAMPLE_STACK_USER

sample_type PERF_SAMPLE_CALLCHAIN 记录函数调用链(堆栈回溯)。

          PERF_SAMPLE_STACK_USER (since Linux 3.7)
                 Records the user level stack, allowing stack unwinding.

“Linux内核是否本地化理解堆栈跟踪?” “这取决于您的CPU架构,它可能会(如果已实现)也可能不会。从活动进程中获取/读取调用栈的采样函数(callchain)在内核中的独立于架构的部分中被定义为具有空体的__weak:”

http://lxr.free-electrons.com/source/kernel/events/callchain.c?v=4.4#L26

 27 __weak void perf_callchain_kernel(struct perf_callchain_entry *entry,
 28                                   struct pt_regs *regs)
 29 {
 30 }
 31 
 32 __weak void perf_callchain_user(struct perf_callchain_entry *entry,
 33                                 struct pt_regs *regs)
 34 {
 35 }

在4.4内核中,x86 / x86_64、ARC、SPARC、ARM / ARM64、Xtensa、Tilera TILE、PowerPC和Imagination Meta的体系结构相关部分重新定义了用户空间调用链采样器。

http://lxr.free-electrons.com/ident?v=4.4;i=perf_callchain_user

arch/x86/kernel/cpu/perf_event.c, line 2279
arch/arc/kernel/perf_event.c, line 72
arch/sparc/kernel/perf_event.c, line 1829
arch/arm/kernel/perf_callchain.c, line 62
arch/xtensa/kernel/perf_event.c, line 339
arch/tile/kernel/perf_event.c, line 995
arch/arm64/kernel/perf_callchain.c, line 109
arch/powerpc/perf/callchain.c, line 490
arch/metag/kernel/perf_callchain.c, line 59

对于某些架构和/或模式,从用户栈中读取调用链可能并不容易。

您使用的CPU架构是什么?使用了哪些语言和VM?

我在哪里可以阅读有关工具如何检测进程的堆栈跟踪的更多信息,即使这些进程完全使用不同的语言编写?

您可以尝试使用gdb和/或针对该语言的调试器以及libc的backtrace函数或libunwind中只读展开的支持(其中包含一个本地回溯示例在libunwind中show_backtrace())。

它们可能会更好地支持帧解析/与语言的虚拟机或展开信息的集成。如果gdb(使用backtrace命令)或其他调试器无法从运行程序中获取堆栈跟踪,则可能没有办法获取堆栈跟踪。

如果他们可以获取调用跟踪信息,但是perf不能(即使使用-fno-omit-frame-pointer重新编译C / C ++后),可能可以将架构和帧格式的此类组合添加到perf_eventsperf中进行支持。
有几个博客提供了一些通用回溯问题和解决方案的信息:

perf_events/perf的Dwarf支持:


1
有关perf程序如何进行分析中断的信息:https://dev59.com/qojca4cB1Zd3GeqPrxt4 - osgx
基于Dwarf的堆栈跟踪默认是有限的,通过在perf record命令中增加--call-graph dwarf,81920参数可以获取更详细的调用堆栈。 - osgx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接