【问题标题】:Locate the machine instructions that access the memory in executable在可执行文件中找到访问内存的机器指令
【发布时间】:2017-10-22 06:54:41
【问题描述】:

编辑:我想通过插入断点并比较断点前后的内存来测试系统。
我使用静态分析来获取 C 源代码位置列表和调试信息(即 dwarf)提供了 C 源代码和可执行文件中的机器指令之间的映射。
但问题是有许多机器指令映射到一行 C 源代码,我需要对它们全部进行测试。
要测试的机器指令是修改内存状态。 所以我想通过去掉不修改内存的指令来减少指令的数量。

例如,我有以下源代码test.c,我有line number 5。

2   int var1 = 10;
3   void foo() {
4       int *var2 = (int*)malloc(sizeof(int));
5       for(*var2=var1;;) {
6       /* ... */
7       }
8   }

需要明确的是,line number 5 访问全局内存 var1 和堆内存 *var2。

我用命令gcc -g test.c编译了上面的程序,结果是

(a.out)
00000000004004d6 <foo>:
  4004d6:   55                      push   %rbp
  4004d7:   48 89 e5                mov    %rsp,%rbp
  4004da:   48 83 ec 10             sub    $0x10,%rsp
  4004de:   bf 04 00 00 00          mov    $0x4,%edi
  4004e3:   e8 d8 fe ff ff          callq  4003c0 <malloc@plt>
  4004e8:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  4004ec:   8b 15 1e 04 20 00       mov    0x20041e(%rip),%edx        # 600910 <var2>
  4004f2:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  4004f6:   89 10                   mov    %edx,(%rax)
  4004f8:   eb fe                   jmp    4004f8 <foo+0x22>

和dwarfdump -l a.out 给我以下结果。

0x004004d6  [   3, 0] NS uri: "/home/workspace/test.c"
0x004004de  [   4, 0] NS
0x004004ec  [   5, 0] NS
0x004004f8  [   5, 0] DI=0x1

现在我知道,在 a.out 中,位置 0x4004ec、0x4004f2、0x4004f6 和 0xf004f8 映射到 C 源代码中的 line number 5。
但我想排除不访问(堆、全局或本地)内存的0x4004f8 (jmp)。

有谁知道如何只获取访问内存的指令?

【问题讨论】:

  • 您想包含隐式内存操作数,例如用于 push/pop 和 call/ret 的堆栈吗? (还有rep movs 或rep stos,gcc 有时会内联。)英特尔语法反汇编可能很方便,因为所有显式内存操作数都包含ptr,如mov rax, qword ptr [rbp - 0x8],因此您可以进行文本搜索。
  • 在 asm 源代码中,&lt;size&gt; ptr 语法不是必需的,但像 objdump -drwC -Mintel 这样的反汇编程序是明确的。在 AT&T 语法中,您也可以只查找 () 或裸符号名称作为操作数。哦,别忘了过滤掉lea 指令。 lea 类似于 C 中的 &amp; 运算符。它是使用内存操作数语法和机器编码的移位加法指令。
  • 为什么你问?请编辑您的问题以激发它并改进它!
  • 闻起来很像XY problem。如果你激发了你的问题并解释了你的总体目标,你会得到更好的答案。
  • 我正在攻读博士学位,但我正面临一个困难的调试问题。

标签: c gcc assembly disassembly instructions


【解决方案1】:

这只是回答有关使用显式内存操作数查找 asm 指令的问题。关于将它们与 C 语句关联的部分在-O0 编译器输出之外是非常虚假的(其中每个语句被编译为单独的指令块以支持 GDB 的 jump 到同一函数中的另一行,或者在内存中修改变量同时在断点处停止)。请参阅 Basile 的回答,它试图对问题中的 C 语句内容有所了解。


Intel 语法反汇编可能很方便,因为所有显式内存操作数都包含ptr,如mov rax, qword ptr [rbp - 0x8],因此您可以进行文本搜索。

在 asm 源代码中,当寄存器操作数暗示操作数大小时,不需要 &lt;size&gt; ptr 语法,但像 objdump -drwC -Mintel 这样的反汇编程序总是将其放入。

在 AT&T 语法中,您也可以只查找 () 或裸符号名称作为操作数。

不要忘记过滤掉lea 指令。 lea 类似于 C 中的 &amp; 运算符。它是使用内存操作数语法和机器编码的移位加法指令。

也不要忘记过滤掉各种 long-nop 指令,这些指令使用寻址模式在一条指令中获得适量的填充。例如:

66 2e 0f 1f 84 00 00 00 00 00   nop    WORD PTR cs:[rax+rax*1+0x0]

所以如果助记符是lea 或nop,则忽略该指令。 (32位代码有时使用其他指令作为NOP,但通常它实际上是一个lea,它在由gas/ld从编译器.p2align指令生成的机器代码中设置一个寄存器。)


objdump 反汇编带有显式操作数的rep stos,例如rep stos QWORD PTR es:[rdi],rax。所以你实际上会得到rep movs 和rep stos 操作数。 (请注意,rep movs 和 rep cmps 有两个内存操作数,这与普通指令不同。它们隐含在机器代码中,但 objdump 使它们显式。)这也将丢失隐式内存操作数,例如 @987654347 的堆栈@/pop 和call/ret。

【讨论】:

  • 过滤lea的部分是个好主意。但是“长眠”呢?我认为您应该指定一个示例,因为将它们过滤掉对于此答案的正确性至关重要。
  • @anatolyg:好主意。 objdump 对长 NOP 指令使用 nop 助记符,所以应该很容易。我还有什么遗漏吗?
【解决方案2】:

给定的 C 语句被编译成几条机器指令,其中几条可以访问内存。想想ptr-&gt;fld = arr[i++] * arr[j]--; .... 顺便说一句,在某些 的情况下,arr[j] 可能已被更早使用,可能已经位于某些register 中,因此可能不需要另一个内存负载(但只有一家商店,以后可以延期)。

我想知道访问由给定代码生成的(堆、全局或本地)内存的机器指令在可执行文件中的位置

所以您的问题一般来说可能没有意义。一些机器指令(或没有它们)可能会访问内存(与源代码中的单个 C 语句相关)。并且register allocation 和寄存器溢出可能会发生,因此给定的机器指令可能与远离“当前”C 指令(没有意义)的 C 变量相关。

optimizing compiler 允许混合多个 C 语句,并可能输出混合的机器代码。另请阅读sequence points。机器代码指令和 C 语句之间没有明显的映射关系(特别是启用了优化),这就是为什么您经常在启用较少优化的情况下进行调试(所以gcc -g 更喜欢与-O0 或-Og 一起使用,而不是更多) .

使用GCC 编译您的src.c 源文件

gcc -O -S -Wall -fverbose-asm src.c

你会得到一个更易读的src.s 汇编文件。您可以使用一些编辑器或寻呼机来查看生成的文件。

有谁知道如何只获取访问内存的指令?

这没有多大意义。优化编译器有时会共享一些与多个不同 C 语句相关的通用机器代码。

顺便说一句,您可能还要求 GCC 转储各种内部表示,例如使用 gcc -O -fdump-tree-all ;然后你会得到数百个(文本)内部转储文件(部分转储各种内部表示)。请记住,GCC 有数百个optimization passes。

请注意,通过添加自己的 GCC plugin(或 GCC MELT 扩展),您可能更有兴趣处理 GCC 内部表示(例如 GENERIC 或 GIMPLE 甚至 RTL)。这可能需要数月的工作(尤其是了解 GCC 内部架构和表示的细节)。

如果不了解您的高层次目标和动机,我们将无法为您提供更多帮助。

您应该阅读更多关于semantics 和undefined behavior 的信息,这与您的问题(间接)相关,而不是您所相信的。

请注意,C 语句不对应(一对多)机器指令。 optimizing compiler 不逐个编译 C 语句,它在一次编译整个 translation unit(例如inline expansions、loop unrolling、stack unwinding、constant folding、register allocation 和溢出,interprocedural optimizations 和 dead code 消除)。这就是为什么 C 编译器是 so complex 数百万行源代码行的野兽。顺便说一句,大多数 C 编译器(例如 GCC 或 Clang)都是 free software,所以你可以花几个月或几年的时间研究它们的源代码。

还可以阅读一些关于编译器的好书(例如最新的Dragon Book)、一些关于语义的书籍以及programming languages pragmatics。

如果您对 GCC 内部特别感兴趣,我的 GCC MELT 的 documentation 页面(也可以使用 here)包含 很多 幻灯片和参考资料。

如果你只关心机器指令,你可能会完全忘记C并在libopcode等反汇编程序库的帮助下工作(参见this ),仅在目标文件中的机器代码上。

还可以查看其他静态源代码分析器,包括 Coccinelle & Frama-C 和 libclang。

如果您只对 GCC 发出的代码感兴趣并且有能力重新编译您的 C 源代码,您可以改为在 GCC 编译器中工作(通过您的 GCC 插件或 GCC MELT 扩展)GIMPLE 级别并检测(也许transform) 那些访问内存的 GIMPLE 指令。检测(也可能是转换)修改内存的 GIMPLE 语句可能更简单,而且可能就足够了。

我想通过插入断点并比较断点前后的内存来测试系统。

这有点类似于例如address sanitizers 和其他 instrumentation features 的 GCC。你可能会花几年时间做类似的事情(并转换一些 GIMPLE),然后你可能想在 GCC 中添加几个额外的通道(并且你可能需要一些额外的运行时支持)。

但是请注意,最近的 GDB 是可编写脚本的(在 Guile 或 Python 中)并且具有观察点。如果您只想调试一个特定的程序,那可能就足够了(您可能不需要深入研究编译器内部,这需要几个月或几年的工作)。您还应该使用 valgrind 和 address sanitizers。

【讨论】:

  • 我对优化知之甚少。我知道当寄存器分配发生时,编译器可能会决定不使用内存。我也知道机器指令的数量是无限的。例如 a=b; c=d; d=e; ... 在一行中。没关系。我只想知道所有访问内存的指令。不管有多少。
  • 但是为什么这些访问内存的指令对你来说很重要? PREFETCH 机器指令呢?它们算作“内存访问”吗?编译器有时会通过发出一些用于几个不同 C 语句的机器指令来进行优化。因此,如果没有额外的解释,您的问题毫无意义。并且寄存器溢出指令与某些精确的 C 语句不对应!
  • 该问题只询问说明。所有关于与 C 语句相关联的绒毛都是题外话(尽管这绝对是可能存在 X-Y 问题的迹象,而且这个问题的答案实际上可能不是 OP 所需要的,因此因花时间编写而受到支持。)
  • 请让我想想PREFETCH。但正如您所提到的,在多个 C 语句之间共享机器指令是个问题。它们(共享机器指令)如何以 dwarf 格式表示? GCC 会将 C 语句的所有位置映射到机器指令吗?
  • 请编辑您的问题,添加几段说明您的总体目标和动机。没有它们,您将得不到任何有价值的帮助。我们只能猜测(而且通常是错误的)你想做什么。或者您对编译器的角色和程序的semantics 非常困惑和幼稚。
猜你喜欢
  • 2017-04-26
  • 1970-01-01
  • 2016-06-30
  • 1970-01-01
  • 2015-02-16
  • 2017-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多