coredumpctl
创建核心转储后,该实用程序会显示程序的堆栈跟踪。
例如,在 Firefox 核心转储上:
Stack trace of thread 14469:
#0 0x00007f0ac652d3bd pthread_cond_wait@@GLIBC_2.3.2 (libpthread.so.0)
#1 0x0000560f2ab95488 _ZN7mozilla6detail21ConditionVariableImpl4waitERNS0_9MutexImplE (firefox)
#2 0x0000560f2ab95646 _ZN7mozilla6detail21ConditionVariableImpl8wait_forERNS0_9MutexImplERKNS_16BaseTimeDurationINS_27TimeDurati>
#3 0x00007f0aba9799f9 n/a (libxul.so)
#4 0x00007f0aba96eb9a n/a (libxul.so)
#5 0x00007f0ac652708c start_thread (libpthread.so.0)
#6 0x00007f0ac5abce7f __clone (libc.so.6)
考虑到这是 C 代码,因此它被编译,符号并没有直接嵌入到二进制文件中:那么这怎么可能呢?
另外,readelf 在实践中是如何做到这一点的?
(我的猜测,这与ELF文件中嵌入的符号表有关)
答案1
正如您猜对的那样,这些符号来自 ELF 文件中嵌入的符号信息。即使不存在完整的符号表,也需要一些符号信息才能进行动态链接。
就实际的堆栈跟踪而言,当调用函数时,会保存 cpu 返回的位置。对于像 x86 这样的 cpu,它被压入堆栈。对于 RISC 机器,它通常被放入寄存器中。如果该函数想要调用任何其他函数(即它不是leaf
函数),则该寄存器被压入堆栈。堆栈跟踪代码在堆栈上找到这些地址,在其前面的符号中查找最近的地址并报告它。一些堆栈跟踪代码会打印符号名称及其距离,这可以让您对它的准确性更有信心。例如,如果符号在返回地址之前仅 40 个字节,那么与之前的 40,000 个字节相比,人们更有信心它在该代码中。在后一种情况下,人们可能会怀疑返回地址指向不同的函数,但该函数在符号表中没有条目。
很多事情都会导致这个结果不准确。如果编译器将函数 a 内联到函数 b 中,那么您可能在函数 a 中,但堆栈跟踪会报告您在 b 中。
如果编译器执行“尾部调用优化”,其中函数 a 以类似于函数 c 的方式结尾return b();
,并且函数 c 调用函数 a,则您可能期望跟踪显示 c->a->b,但您只会看到 c->b。如果您查看 c 的源代码并发现它从不直接调用 b,这可能会令人困惑。