为什么为我的C程序生成的机器代码与书中给出的不同?
我在Richard Blum的《专业汇编语言》一书中找到了这段文字 编译步骤转换文本编程语言语句 输入执行应用程序所需的说明代码 作用每个HLL代码行都与一个或多个 更多与安装在其上的特定处理器相关的指令代码 应用程序将运行。例如,简单的HLL代码为什么为我的C程序生成的机器代码与书中给出的不同?,c,gcc,assembly,x86,C,Gcc,Assembly,X86,我在Richard Blum的《专业汇编语言》一书中找到了这段文字 编译步骤转换文本编程语言语句 输入执行应用程序所需的说明代码 作用每个HLL代码行都与一个或多个 更多与安装在其上的特定处理器相关的指令代码 应用程序将运行。例如,简单的HLL代码 int main() { int i = 1; exit(0); } 编译成以下IA-32指令代码: 55 89 E5 83 EC 08 C7 45 FC 01 00 00 00 83 EC 0C 6A 00 E8 D1 FE FF
int main()
{
int i = 1;
exit(0);
}
编译成以下IA-32指令代码:
55
89 E5
83 EC 08
C7 45 FC 01 00 00 00
83 EC 0C
6A 00
E8 D1 FE FF FF
但当我自己尝试这个程序时,我无法重现这些结果
首先是关于我的系统和编译器的一些细节
$ cat /etc/debian_version
8.3
$ uname -a
Linux debian1 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u2 (2016-01-02) x86_64 GNU/Linux
$ gcc --version
gcc (Debian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ dpkg -l gcc-multilib
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name Version Architecture Description
+++-=========================================-=========================-=========================-========================================================================================
ii gcc-multilib 4:4.9.2-2 amd64 GNU C compiler (multilib files)
这是我写的程序
$ cat foo.c
#include <stdlib.h>
int main()
{
int i = 1;
exit(0);
}
以下是我编译后得到的结果
$ gcc -m32 -c foo.c
$ objdump -d foo.o
foo.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 14 sub $0x14,%esp
11: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp)
18: 83 ec 0c sub $0xc,%esp
1b: 6a 00 push $0x0
1d: e8 fc ff ff ff call 1e <main+0x1e>
以下是我编译和链接后得到的结果
$ gcc -c foo.c
$ objdump -d a.out | grep -A15 "<main>"
080483fb <main>:
80483fb: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483ff: 83 e4 f0 and $0xfffffff0,%esp
8048402: ff 71 fc pushl -0x4(%ecx)
8048405: 55 push %ebp
8048406: 89 e5 mov %esp,%ebp
8048408: 51 push %ecx
8048409: 83 ec 14 sub $0x14,%esp
804840c: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp)
8048413: 83 ec 0c sub $0xc,%esp
8048416: 6a 00 push $0x0
8048418: e8 c3 fe ff ff call 80482e0 <exit@plt>
804841d: 66 90 xchg %ax,%ax
804841f: 90 nop
08048420 <__libc_csu_init>:
如何复制书中作者提供的结果?书中没有的额外说明包括:
80483fb: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483ff: 83 e4 f0 and $0xfffffff0,%esp
8048402: ff 71 fc pushl -0x4(%ecx)
8048408: 51 push %ecx
8048409: 83 ec 14 sub $0x14,%esp
...
804841d: 66 90 xchg ax,ax
804841f: 90 nop
前几行将堆栈与16字节边界对齐。这提高了性能参数不能跨越缓存线边界,并允许使用仅在16位对齐地址上运行的SIMD指令
末尾的xchg%ax,%ax是一个2字节的NOP。nop的3个字节并不重要,因为它们无论如何都是不可访问的。它们用于将_libc_csu_init函数填充到合适的对齐位置
至于为什么汇编不同,汇编是一种编程语言,通常有不止一种方法。您不能期望一个C程序在不同的编译器、同一编译器的版本或同一版本的配置之间提供相同的输出
在您的特定情况下,16位堆栈对齐是由于,3字节nop是由于
当您调用gcc时,这些参数被配置为默认参数。可以直接使用,也可以根据-O2或类似要求使用。使用具有完全相同标志的完全相同的编译器版本。因为有很多方法可以从C生成asm代码,所以通常不能期望所有编译器都有相同的输出。你的书真的没有列出该机器代码的反汇编吗?这有点奇怪。请注意,-mprefered stack boundary=4或更高现在是。另外,gcc和clang确实可以从main中省略这些内容,因为CRT启动代码在Linux启动进程并对齐堆栈16B之后保持堆栈对齐。但是是的,NOP的3个字节只是在下一个函数之前填充,而不是当前函数的一部分。它在调用noreturn函数出口处结束。我想知道为什么它费心调用noreturn函数,而不是无条件地跳转到它。“有什么明显的我遗漏了吗,@peter?@CodyGray:IDK为什么尾部调用优化没有发生。”。我也在想同样的事情。我不认为我们都遗漏了什么明显的东西。我的意思是,如果函数返回,它就会崩溃,exit3保证函数不会失败。我猜gcc没有对noreturn函数使用jmp的窥视孔优化,因为这意味着异常的堆栈帧。哦,对了,call会推送一个返回地址,所以exit会在上面查找它的arg。gcc可能已发出mov$0,4%esp/jmpexit@plt,但这是一个更奇怪的窥视孔和更糟糕的代码大小