我已经和C一起工作了一段时间,最近才开始进入ASM。当我编译程序时:
int main(void)
{
int a = 0;
a += 1;
return 0;
}
objdump反汇编有代码,但是在ret之后nops:
...
08048394 <main>:
8048394: 55 push %ebp
8048395: 89 e5 mov %esp,%ebp
8048397: 83 ec 10 sub $0x10,%esp
804839a: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%ebp)
80483a1: 83 45 fc 01 addl $0x1,-0x4(%ebp)
80483a5: b8 00 00 00 00 mov $0x0,%eax
80483aa: c9 leave
80483ab: c3 ret
80483ac: 90 nop
80483ad: 90 nop
80483ae: 90 nop
80483af: 90 nop
...
从我学到的东西中,nops什么都不做,因为ret之后甚至都不会被执行。
我的问题是:为什么要这么麻烦? ELF(linux-x86)无法使用任何大小的.text段(+ main)吗?
我很感激任何帮助,只是想学习。
答案 0 :(得分:86)
首先,gcc
并不总是如此。填充由-falign-functions
控制,由-O2
和-O3
自动启用:
-falign-functions
-falign-functions=n
将函数的开头与大于
n
的下一个2次幂对齐,跳过n
个字节。例如,-falign-functions=32
将函数与下一个32字节边界对齐,但-falign-functions=24
将仅与下一个32字节边界对齐 如果这可以通过跳过23个字节或更少来完成。
-fno-align-functions
和-falign-functions=1
是等效的,意味着函数不会对齐。当n是2的幂时,有些汇编器只支持这个标志;在 这种情况,它被四舍五入。
如果未指定n或为零,请使用与机器相关的默认值。
在-O2,-O3等级启用。
执行此操作可能有多种原因,但x86上的主要原因可能是:
大多数处理器在对齐的16字节或32字节块中获取指令。有可能 有利的是将关键循环条目和子程序条目对齐16,以便最小化 代码中16字节边界的数量。或者,确保在关键循环条目或子例程条目之后的前几条指令中没有16字节边界。
(引自“优化装配中的子程序 语言“由Agner Fog。”
编辑:以下是演示填充的示例:
// align.c
int f(void) { return 0; }
int g(void) { return 0; }
使用gcc 4.4.5使用默认设置编译时,我得到:
align.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <f>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 00 00 00 00 mov $0x0,%eax
9: c9 leaveq
a: c3 retq
000000000000000b <g>:
b: 55 push %rbp
c: 48 89 e5 mov %rsp,%rbp
f: b8 00 00 00 00 mov $0x0,%eax
14: c9 leaveq
15: c3 retq
指定-falign-functions
会:
align.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <f>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 00 00 00 00 mov $0x0,%eax
9: c9 leaveq
a: c3 retq
b: eb 03 jmp 10 <g>
d: 90 nop
e: 90 nop
f: 90 nop
0000000000000010 <g>:
10: 55 push %rbp
11: 48 89 e5 mov %rsp,%rbp
14: b8 00 00 00 00 mov $0x0,%eax
19: c9 leaveq
1a: c3 retq
答案 1 :(得分:13)
这样做是为了将下一个函数与8,16或32字节边界对齐。
来自A.Fog的“使用汇编语言优化子程序”:
11.5代码对齐
大多数微处理器在对齐的16字节或32字节块中获取代码。如果一个重要的子例程入口或跳转标签碰巧接近一个16字节块的末尾,则在获取该代码块时,微处理器将只获得一些有用的代码字节。在它可以解码标签之后的第一条指令之前,它也可能需要获取接下来的16个字节。通过将重要的子例程条目和循环条目对齐16,可以避免这种情况。
[...]
对齐子例程条目就像放入多个子例程一样简单 NOP 根据需要在子程序之前根据需要使地址可以被8,16,32或64整除。
答案 2 :(得分:6)
据我所知,指令在cpu中流水线化,不同的cpu块(加载器,解码器等)处理后续指令。当执行RET
指令时,很少有下一条指令已加载到cpu管道中。这是一个猜测,但你可以开始挖掘这里,如果你发现(可能是NOP
的特定数量是安全的,请分享你的发现。