将源与C++程序的程序集列表相关联

Correlate Source with Assembly Listing of a C++ Program

本文关键字:程序集 列表 关联 程序 C++      更新时间:2023-10-16

在零售构建中分析核心转储通常需要关联任何特定模块的objdump和源。通常,如果函数非常复杂,将程序集转储与源相关联会变得很痛苦。今天我尝试创建一个特定模块的assembly listing(使用编译选项-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这样的指令

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

是否可以与消息来源进行一对一的通信?

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


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

.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从内存加载的位置:它是否从固定偏移量加载到其他寄存器?如果是这样,则该寄存器可能是目标地址。

祝你好运!

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

.loc <file> <line> <column>

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

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

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

但正如其他人所提到的,如果你的源代码和系统崩溃的频率足够高,所以你最快的选择通常是使用调试符号重建,或者插入日志记录输出并获得更有用的崩溃记录。