在反汇编器生成的汇编代码上识别出奇怪的指令

逆向工程 拆卸 部件 x86 转储
2021-06-24 12:54:12

测试在 x86 32 位 Linux、Ubuntu 12.04、GCC 4.6.3 objdump 2.22

基本上当我gcc用来生成foo这样的函数的汇编代码时

gcc -S foo.c -O2

在 function 的末尾foo,我可以得到这样的指令序列(我修改了它并附上了每条指令及其机器代码以使其清晰):

             ......
1977                                                 .cfi_restore_state
1978  8B150000 0000                                  movl    nodes, %edx
1979  89442410                                       movl    %eax, 16(%esp)
1980  A1000000 00                                    movl    i_depth, %eax
1981  8974240C                                       movl    %esi, 12(%esp)
1982  C7442404 FC000000                              movl    $.LC55, 4(%esp)
1983  89542414                                       movl    %edx, 20(%esp)
1984  89442408                                       movl    %eax, 8(%esp)
1985  C7042401 000000                                movl    $1, (%esp)
1986  E8FCFFFF FF                                    call    __printf_chk
1987  E937FFFF FF                                    jmp     .L181
1988                                         .L186:
1989  E8FCFFFF FF                                    call    __stack_chk_fail

foo1:

这看起来很正常。

但是,当我编译+链接以创建ELF可执行文件,然后objdump像这样反汇编它时

gcc foo.c -O2
objdump -Dr -j .text foo

反汇编器产生的指令是这样的(为了更容易理解,我做了一点修改):

11856 89442410                                mov %eax,0x10(%esp)
11857 A1000000 00                             mov 0x80851AC,%eax
11858 8974240C                                mov %esi,0xC(%esp)
11859 C7442404 00000000                       movl $S_0x8064658,0x4(%esp)
11860 89542414                                mov %edx,0x14(%esp)
11861 89442408                                mov %eax,0x8(%esp)
11862 C7042401 000000                         movl $0x1,(%esp)
11863 E8FCFFFF FF                             call __printf_chk
11864 E933FFFF FF                             jmp 0x80547EB
11865
11866 E8FCFFFF FF                             S_0x80548BC : call __stack_chk_fail
11867 EB0D                                    jmp foo1
11868 90                                      nop
11869 90                                      nop
11870 90                                      nop
11871 90                                      nop
11872 90                                      nop
11873 90                                      nop
11874 90                                      nop
11875 90                                      nop
11876 90                                      nop
11877 90                                      nop
11878 90                                      nop
11879 90                                      nop
11880 90                                      nop
11881                                         foo1:

查看函数的结尾foo,我发现了在原始汇编代码中找不到的指令序列。

这似乎是一个填充问题,但我不确定。

所以我的问题是:

  1. 这些指令序列是做什么用的?
  2. 无论如何要告诉(汇编程序?链接器?)不要生成这些指令序列..?因为基本上我正在使用汇编代码分析工具,而这些指令序列很烦编码。
1个回答

这些指令序列是做什么用的?

它们用于代码优化

CPU缓存

为了优化内存访问,CPU 使用自己的(小)内部内存,称为cache它通常由名为L1L2的几个级别组成。较低的后缀数字意味着内存位于更靠近CPU 内核的位置,因此访问速度更快,但也更小下面给出了从链接中获取的这个概念的说明

CPU 缓存级别

访问 CPU 缓存比读取 RAM 内存要快得多有关更多信息,请参阅此问题),这就是为什么最好将数据保存在缓存中而不是每次从 RAM 中读取数据(甚至更糟 - 硬盘)。

但是 CPU 不仅仅缓存数据——它还缓存指令为了有效缓存指令,它们必须正确对齐以下引用来自这里

大多数微处理器以对齐的 16 字节或 32 字节块获取代码。如果一个重要的子程序入口或跳转标签恰好位于 16 字节块的末尾,那么微处理器在获取该代码块时只会得到几个有用的代码字节。在解码标签之后的第一条指令之前,它可能还必须获取接下来的 16 个字节。

这可以通过将重要的子例程条目和循环条目对齐 16 来避免。 [...] 如果子例程是关键热点的一部分并且前面的代码是不太可能在相同的上下文中执行。

因此,可能是在foo1:有一些短循环的情况下,编译器决定对齐此块以将其放入 CPU 缓存中,以便更快地执行。

正如@user45891 在评论中已经指出的那样,gcc 中的这种优化是通过 option 开启的-O2,所以当你不想要这样的优化时不要使用它。

但是为什么两个输出之间有差异呢?

因为第一个结果来自 gcc ( link )执行的两个第一个编译状态

编译最多可涉及四个阶段:预处理、正确编译、组装和链接,始终按此顺序进行。

-S

在适当的编译阶段后停止;不要组装。

而第二个是“完全编译”并链接的。