4.1.3 生成机器代码的过程

4.1.3 生成机器代码的过程

将一个高级语言(如C 语言)编写的源程序转换为可执行目标代码的过程分为以下4个步骤:①预处理。例如,在C语言源程序中有一些以“#”开头的语句,可以在预处理阶段对这些语句进行处理,在源程序中插入所有用“#include”命令指定的文件和用“#define”声明指定的宏;②编译。将预处理后的源程序文件编译生成相应的汇编语言程序;③汇编。由汇编程序将汇编语言源程序文件转换为可重定位的机器语言目标代码文件;④链接。由链接器将多个可重定位的机器语言目标文件以及库函数[如printf()函数]链接起来,生成最终的可执行文件。

下面以C编译器gcc为例来说明一个C语言程序被转换为可执行代码的过程。

以下是hello.c的C语言源程序代码:

将上述hello.c源程序文件转换为可执行文件为hello,则可用以下命令一步到位生成最终的可执行文件:

该命令中的选项-o指出输出文件名。编译选项-o1表示采用最基本的第一级优化。通常,提高优化级别会得到更好的性能,但会使编译时间增长,而且使目标代码与源程序对应关系变得复杂,从程序执行的性能来说,通常认为对应选项-o2的第二级优化是最好的选择,也可以采用默认的优化选项-oo,即无任何编译优化。

也可以将上述完整的预处理、汇编、编译和链接过程,通过以下多个不同的编译选项命令分步骤进行:①使用命令“gcc-E hello.c-o hello.i”对hello.c进行预处理,生成预处理结果文件hello.i;②使用命令“gcc-S hello.i-o hello.s”或“gcc-S hello.c-o hello.s”对hello.i或hello.c进行编译,生成汇编代码文件hello.s;③使用命令“gcc-c hello.s-o hello.o”对hello.s进行汇编,生成可重定位目标文件hello.o;④使用命令“gcc hello.o-o hello”将可重定位目标文件hello.o进行链接,生成可执行文件hello。

其中汇编代码文件hello.s是可显示文本文件,其输出的部分结果如下:

对于不可显示的可重定位目标文件hello.o,可使用带-d选项的objdump命令来对目标代码进行反汇编。使用命令“objdump-d hello.o”可以得到以下结果:

将上述用objdump反汇编出来的汇编代码与直接由gcc汇编得到的汇编代码(hello.s输出结果)进行比较后可以发现,它们几乎完全相同,只是在数值形式和指令助记符的后缀等方面稍有不同。gcc生成的汇编指令中用十进制形式表示数值,而objdump反汇编出来的汇编指令中则用十六进制形式表示数值。两者都以“$”开头表示一个立即数。gcc生成的很多汇编指令助记符结尾中带有“L”或“W”等长度后缀,它是操作数长度指示符,这里“L”表示指令中处理的操作数为双字,即32位,“W”表示指令中处理的操作数为单字,即16位,上述这种汇编格式称为AT&T 格式,它是objdump和gcc使用的默认格式。本书均采用AT&T 格式。

AT&T格式与Intel格式

GCC采用的是AT&T 的汇编格式,也称GAS格式(Gnu ASembler,GNU 汇编器),而在一些汇编语言的书籍上会采用Intel的汇编格式,两者主要在语法上有以下几个方面的异同。

1.操作码的后缀

在AT&T 的操作码后面有一个后缀,其含义就是指出操作码的大小。“l”表示长整数(32位),“w”表示字(16位),“b”表示字节(8位),如表4.1所示。

表4.1 后缀

2.前缀

在AT&T 中,寄存器前冠以“%”,而立即数前冠以“$”。在Intel的语法中,十六进制和二进制立即数后缀分别冠以“h”和“b”,而在AT&T 中,十六进制立即数前冠以“0x”,如表4.2所示。

表4.2 前缀

3.操作数的方向

Intel与AT&T 操作数的方向正好相反,AT&T 中,第一个数是源操作数,第二个数是目的操作数。

在Intel中:

mov eax,[ecx]

在AT&T 中:

movl (%ecx),%eax

4.内存单元操作数

在Intel的语法中,基寄存器用“[]”括起来,而在AT&T 中,用“()”括起来。

在Intel中:

mov  eax,[ebx+5]

在AT&T 中:

movl  5(%ebx),%eax

5.间接寻址方式

间接寻址方式如表4.3所示。

表4.3 间接寻址方式