5

在零售构建中分析核心转储通常需要将objdump任何特定模块与源相关联。如果功能非常复杂,通常将程序集转储与源相关联会变得很痛苦。今天,我尝试创建assembly listing一个特定模块(使用 compile 选项-S),期望我会看到一个带有汇编或某些相关性的交错源。不幸的是,列表不够友好,无法关联,所以我想知道

  • 给定一个核心转储,我可以从中确定崩溃位置
  • objdump通过重新编译失败的模块组装清单
  • -S选项的模块。

是否可以与来源进行一一对应?

例如,我将程序集列表视为

.LBE7923:
        .loc 2 4863 0
        movq    %rdi, %r14
        movl    %esi, %r12d
        movl    696(%rsp), %r15d
        movq    704(%rsp), %rbp
.LBB7924:
        .loc 2 4880 0
        testq   %rdx, %rdx
        je      .L2680
.LVL2123:
        testl   %ecx, %ecx
        jle     .L2680
        movslq  %ecx,%rax
        .loc 2 4882 0
        testl   %r15d, %r15d
        .loc 2 4880 0
        leaq    (%rax,%rax,4), %rax
        leaq    -40(%rdx,%rax,8), %rdx
        movq    %rdx, 64(%rsp)

但不明白如何解释标签.LVL2123和指令.loc 2 4863 0

注意 正如所描述的答案,阅读汇编源代码并根据符号(如函数调用、分支、返回语句)直观地确定模式是我通常所做的。我不否认它不起作用,但是当一个函数非常复杂时,阅读汇编列表页面是一种痛苦,并且通常你最终会得到很少匹配的列表,因为函数被内联或优化器只是简单地扔了代码随心所欲。我有一种感觉,看看效率如何Valgrind处理优化的二进制文件以及在 Windows WinDBG 中如何处理优化的二进制文件,我缺少一些东西。所以我虽然我会从编译器输出开始并使用它来关联。如果我的编译器负责修改二进制文件,那将是说明如何与源代码关联的最佳人选,但不幸的是,这几乎没有帮助,而且.loc确实具有误导性。不幸的是,我经常不得不通读各种平台上无法重现的转储,而我花最少的时间是通过 WinDBG 调试 Windows Mini-dumps,而在调试 Linux Coredumps 上花费了相当多的时间。我虽然这可能是我做事不正确,所以我想出了这个问题。

4

3 回答 3

4

.loc指令是您正在寻找的。这些表示第 #4863、4880 行等。源代码和优化的汇编程序之间没有完美的映射(这就是为什么您多次看到 4880 的原因)。但是.loc,您如何知道它在文件中的位置。语法是:

.loc <file> <line> <column>
于 2012-05-02T12:56:23.283 回答
4

是否可以与来源进行一一对应?

A:不,除非所有优化都被禁用。编译器最初可能会在每行中发出一组指令(或类似指令的东西),但优化器随后会重新排序、拆分、融合并通常完全改变它们。


如果我要反汇编发布代码,我会查看应该与代码有明确逻辑关系的说明。例如,

.LBB7924:
        .loc 2 4880 0
        testq   %rdx, %rdx
        je      .L2680

如果为零,它看起来像一个分支%rdx,它来自第 4880 行。找到该行,识别正在测试的变量,记下它当前分配给%rdx.

.LVL2123:
        testl   %ecx, %ecx
        jle     .L2680

好的,所以这个测试和分支具有相同的目标,所以接下来发生的任何事情都知道%rdx并且%ecx都是非零的。原始代码的结构可能如下:

if (a && b) {

或者也许是:

if (!a || !b) {

优化器重新排序了两个分支......

现在您已经有了一些结构,希望可以与原始代码匹配,您还可以找出寄存器分配。例如,如果您知道要测试的东西是某个结构的数据成员,则向后阅读以查看%rdx从内存中加载的位置:它是从固定偏移量加载到其他寄存器的吗?如果是这样,该寄存器可能是对象地址。

祝你好运!

于 2012-05-02T14:20:38.617 回答
1

除非您静态链接到系统库,否则即使没有调试符号,二进制文件中也会有符号名称 - 链接到的系统库函数的名称。

这些通常可以帮助缩小您在代码中的位置。例如,如果您在函数 foo() 中看到它调用 open(),然后调用 ioctl(),然后它在调用 read() 之前崩溃,您可能很容易在 foo 的源代码中找到该点。(就此而言,您甚至可能不需要转储 - 在 linux 上,您可以使用 ltrace 或 strace 获取与库和系统函数相关的崩溃发生记录)

请注意,尽管在某些二进制格式中,可能会通过二进制文件中其他地方的微小包装器间接访问库函数。通常,转储在程序流中的调用地址处仍会具有相关的符号名称信息。但是即使没有,您也可以通过它们在二进制文件中的地址范围来识别这些外部链接包装器,当您看到它们时,您可以找到它的代码并找出它链接到的外部函数。

但正如其他人所提到的,如果您有源代码和系统经常崩溃以提供帮助,那么您最快的选择通常是使用调试符号进行重建,或者插入日志输出并获得更有用的崩溃记录。

于 2012-05-02T14:56:47.040 回答