1、预处理
#define
#ifdef
……
#endif
#include <>
宏定义、文件包含、条件编译等命令进行处理
代码优化,编译可执行文件变小,执行效率降低。
2、编译(翻译)
编译之前,C 语言编译器会进行词法分析、语法分析,接着会把源代码翻译成中间语言,即汇编语言。如果想看到这个中间结果,可以用 gcc -S
。需要提到的是,诸如 Shell 等解释语言也会经历一个词法分析和语法分析的阶段,不过之后并不会进行“翻译”,而是“解释”,边解释边执行。
把源代码翻译成汇编语言,实际上是编译的整个过程中的第一个阶段,之后的阶段和汇编语言的开发过程没有什么区别。这个阶段涉及到对源代码的词法分析、语法检查(通过 -std
指定遵循哪个标准),并根据优化(-O
)要求进行翻译成汇编语言的动作。
语法检查
如果仅仅希望进行语法检查,可以用 gcc
的 -fsyntax-only
选项;如果为了使代码有比较好的可移植性,避免使用 gcc
的一些扩展特性,可以结合 -std
和 -pedantic
(或者 -pedantic-erros
)选项让源代码遵循某个 C 语言标准的语法。这里演示一个简单的例子:
$ cat hello.c
#include <stdio.h>
int main()
{
printf("hello, world\n")
return 0;
}
$ gcc -fsyntax-only hello.c
hello.c: In function ‘main’:
hello.c:5: error: expected ‘;’ before ‘return’
$ vim hello.c
$ cat hello.c
#include <stdio.h>
int main()
{
printf("hello, world\n");
int i;
return 0;
}
$ gcc -std=c89 -pedantic-errors hello.c #默认情况下,gcc是允许在程序中间声明变量的,但是turboc就不支持
hello.c: In function ‘main’:
hello.c:5: error: ISO C90 forbids mixed declarations and code
语法错误是程序开发过程中难以避免的错误(人的大脑在很多情况下都容易开小差),不过编译器往往能够通过语法检查快速发现这些错误,并准确地告知语法错误的大概位置。因此,作为开发人员,要做的事情不是“恐慌”(不知所措),而是认真阅读编译器的提示,根据平时积累的经验(最好总结一份常见语法错误索引,很多资料都提供了常见语法错误列表,如《C Traps & Pitfalls》和编辑器提供的语法检查功能(语法加亮、括号匹配提示等)快速定位语法出错的位置并进行修改。
汇编
简述
汇编实际上还是翻译过程,只不过把作为中间结果的汇编代码翻译成了机器代码,即目标代码,不过它还不可以运行。如果要产生这一中间结果,可用 gcc -c
,当然,也可通过 as
命令处理汇编语言源文件来产生。
汇编是把汇编语言翻译成目标代码的过程,如果有在 Windows 下学习过汇编语言开发,大家应该比较熟悉 nasm
汇编工具(支持 Intel 格式的汇编语言),不过这里主要用 as
汇编工具来汇编 AT&T
格式的汇编语言,因为 gcc
产生的中间代码就是 AT&T
格式的。
生成目标代码
下面来演示分别通过 gcc -c
选项和 as
来产生目标代码。
$ file hello.s
hello.s: ASCII assembler program text
$ gcc -c hello.s #用gcc把汇编语言编译成目标代码
$ file hello.o #file命令用来查看文件类型,目标代码可重定位的(relocatable),
#需要通过ld进行进一步链接成可执行程序(executable)和共享库(shared)
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
$ as -o hello.o hello.s #用as把汇编语言编译成目标代码
$ file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
gcc
和 as
默认产生的目标代码都是 ELF 格式的,因此这里主要讨论ELF格式的目标代码(如果有时间再回顾一下 a.out
和 coff
格式,当然也可以先了解一下,并结合 objcopy
来转换它们,比较异同)。
ELF 文件初次接触
目标代码不再是普通的文本格式,无法直接通过文本编辑器浏览,需要一些专门的工具。如果想了解更多目标代码的细节,区分 relocatable
(可重定位)、executable
(可执行)、shared libarary
(共享库)的不同,我们得设法了解目标代码的组织方式和相关的阅读和分析工具。下面主要介绍这部分内容。
BFD is a package which allows applications to use the same routines to operate on object files whatever the object file format. A new object file format can be supported simply by creating a new BFD back end and adding it to the library.
binutils(GNU Binary Utilities)的很多工具都采用这个库来操作目标文件,这类工具有 objdump
,objcopy
,nm
,strip
等(当然,我们也可以利用它。如果深入了解ELF格式,那么通过它来分析和编写 Virus 程序将会更加方便),不过另外一款非常优秀的分析工具 readelf
并不是基于这个库,所以也应该可以直接用 elf.h
头文件中定义的相关结构来操作 ELF 文件。
下面将通过这些辅助工具(主要是 readelf
和 objdump
),结合 ELF 手册来分析它们。将依次介绍 ELF 文件的结构和三种不同类型 ELF 文件的区别。
ELF 文件的结构
ELF Header(ELF文件头)
Program Headers Table(程序头表,实际上叫段表好一些,用于描述可执行文件和可共享库)
Section 1
Section 2
Section 3
...
Section Headers Table(节区头部表,用于链接可重定位文件成可执行文件或共享库)
对于可重定位文件,程序头是可选的,而对于可执行文件和共享库文件(动态链接库),节区表则是可选的。可以分别通过 readelf
文件的 -h
,-l
和 -S
参数查看 ELF 文件头(ELF Header)、程序头部表(Program Headers Table,段表)和节区表(Section Headers Table)。
文件头说明了文件的类型,大小,运行平台,节区数目等。