为什么我的二进制代码中有很多填充/前导 nop 指令?

逆向工程 拆卸 部件 小精灵
2021-06-14 01:15:29

测试平台在 Linux 32 位 x86 上。所以基本上我写了一个简单的 C 程序,如下所示:

void main()
{
        double a = 10.0;
        printf("hello world %f\n", a);

}

我用gcc编译成ELF二进制文件,然后用objdump反汇编。我解决了对 .rodata 部分的引用,并在此改进了 asm 代码:

extern  printf
section .rodata

S_80484d0   db 0x68
db 0x65
  db 0x6c
db 0x6c
db 0x6f
db 0x20
db 0x77
db 0x6f
db 0x72
db 0x6c
db 0x64
db 0x20
db 0x25
db 0x66
db 0x0a


S_80484f0 db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40

section .text
global main
main:
push   ebp
mov    ebp,esp
and    esp,0xfffffff0
sub    esp,0x20
fld    qword [S_80484f0]
fstp   QWORD [esp+0x18]
fld    QWORD [esp+0x18]
fstp   QWORD [esp+0x4] 
mov    DWORD [esp],S_80484d0
call   printf
leave
ret

然后我重新编译这个 asm 代码以获得一个新的 ELF 二进制文件,并比较这两个二进制文件的 .text 部分。

这是令人困惑的事情:我能找到的唯一不同是在主函数前面有更多的领先 nop 像这样:

新的 ELF 二进制前导 nop:

新的 ELF 二进制文件

新的 ELF 二进制结尾 nop:

新精灵二进制 2

旧的 ELF 二进制文件:

旧的 ELF 二进制文件

基本上我认为这不是“对齐”问题,因为 nop 太多了。

更何况,当我把原来的代码改成简单的helloworld代码(没有双号a),那么这两个ELF二进制文件基本上没有区别

任何人都可以帮我解释为什么会生成这么多 nop 吗?

谢谢

1个回答

结盟。

请注意,所有 NOP 都在 ...C0, ...F0 处结束(并且下一个函数开始)。编译器和/或链接器插入了填充字节,以便函数从 0x10 对齐的地址开始。

不同的编译器/链接器将为这些字节使用不同的值。我见过 90 (nop)、CC (int3) 以及恰好填充函数之间空间的多字节 NOP。

您应该在Stack Overflow上查看关于同一问题的这个很好的答案

简而言之,这是出于性能原因,因为处理器通常以 16 或 32 字节的步长获取指令,因此让函数从这些边界之一开始是有意义的。