程序编译过程
程序编译
程序的编译过程如下图所示,分为预处理、编译、汇编、链接等几个阶段。预处理:预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。
编译: 将得到的i文件翻译成汇编代码。s文件。 汇编: 将汇编文件翻译成机器指令,并打包成可重定位目标程序的O文件。该文件是二进制文件,字节编码是机器指令。
链接: 将引用的其他O文件并入到我们程序所在的o文件中,处理得到最终的可执行文件。编译程序 把用高级程序设计语言书写的源程序,翻译成等价的计算机汇编语言或机器语言书写的目标程序的翻译程序。
汇编程序汇编代码:汇编语言编写的程序,机器不能直接识别,要由一种程序将汇编语言翻译成机器语言,这种起翻译作用的程序叫汇编程序,汇编程序是系统软件中语言处理系统软件。目标程序又称“目的程序”。
由编译程序将源程序编译成与之等价的由机器码构成的,计算机能直接运行的程序,该程序叫目标程序。链接器 (linker) 将一个个的目标文件 ( 或许还会有若干程序库 ) 链接在一
起生成一个完整的可执行文件。在符号解析 (symbol resolution) 阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合 :
(1) 集合 E 是将被合并到一起组成可执行文件的所有目标文件集合;
(2) 集合 U 是未解析符号 (unresolved symbols ,比如已经被引用但是还未被定义的符号 ) 的集合;
(3) 集合 D 是所有之前已被加入到 E 的目标文件定义的符号集合。一开始, E 、 U 、 D 都是空的。
链接器的工作过程:
(1): 对命令行中的每一个输入文件 f ,链接器确定它是目标文件还是库文件,如果它是目标文件,就把 f 加入到 E ,并把 f 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中,然后处理下一个输入文件。
(2): 如果 f 是一个库文件,链接器会尝试把 U 中的所有未解析符号与 f 中各目标模块定义的符号进行匹配。如果某个目标模块 m 定义了一个 U 中的未解析符号,那么就把 m 加入到 E 中,并把 m 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中。不断地对 f 中的所有目标模块重复这个过程直至到达一个不动点 (fixed point) ,此时 U 和
D 不再变化。而那些未加入到 E 中的 f 里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。 (3): 如果处理过程中往 D 加入一个已存在的符号 ,或者当扫描完所有输入文件时 U 非空,链接器报错并停止动作。否则,它把 E 中的所有目标文件合并在一起生成可执行文件。这种\翻译\通常有两种方式,即编译方式和解释方式。编译方式是指利用事先编好的一个称为编译程序的机器语言程序,
作为系统软件存放在计算机内,当用户将高级语言编写的源程序输入计算机后,编译程序便把源程序整个地翻译成用 机器语言表示的与之等价的目标程序,然后计算机再执行该目标程序,以完成源程序要处理的运算并取得结果。 解释方式是指源程序进入计算机后,解释程序边扫描边解释,逐句输入逐句翻译,计算机一句句执行,并不产生目标程序。可执行文件格式
Microsoft引进了PE文件格式,更经常被称为PE格式,作为最初的Win32规范的一部分。
然而PE文件源自VAX/VMS上早期的通用目标文件格式(Common Object File Format,COFF)。
Microsoft编译器生成的OBJ文件也使用COFF格式。
PE文件一个非常好的地方就是它的数据结构在磁盘上与在内存(逻辑地址空间)中一样。加载一个可执行文件到内存(例
如通过调用LoadLibrary函数)主要就是把PE文件中的某个部分映射到地址空间中。如果你知道如何在一个 PE文件中找到某些内容,你几乎可以确定当文件被加载进内存时可以找到同样的信息。
注意到PE文件并不是作为单一的内存映射文件被映射进内存的这一点非常重要。相反,Windows加载器查看PE文件并确定文件中的哪些部分需要被映射。当映射进内存时,文件中的高偏移相对于内存中的高地址。某项内容在磁盘文件中的偏移可能与它被加载进内存之后的偏移不同,但是将磁盘文件中的偏移转换成内存偏移需要的所有信息都存在(见下图)。
当PE文件由Windows加载器加载进内存时,它在内存中被称为模块(module)。文件被映射到的内存的起始地址被称为HMODULE。这是需要记住的一点:给你一个HMODULE,你就知道在那个地址处到底有什么样的数据结构,并且你可以根据PE文件的知识找到内存中所有其它的数据结构。这个强大的功能可以被用作其它用途,例如拦截API。
内存中的模块代表一个进程所需的可执行文件中的所有代码、数据和资源。PE文件中的其它部分可能会被读取,但并不被映射进内存(例如重定位节)。一些部分可能根本就不被映射,例如放在文件末尾的调试信息。PE文件头中的一个
域告诉系统将这个可执行文件映射进内存时需要占用多少内存。不被映射的数据放在文件末尾,位于所有需要被映射的部分之后。
描述PE文件(和COFF文件)的关键位置是WINNT.H文件。在这个头文件中,你能找到几乎所有结构的定义、枚举类型以及使用PE文件或它在内存中的等价结构所需的定义。有许多工具可以用来查看PE文件。Visual Studio附带的Dumpbin和Platform SDK附带的Depends就是其中的两个。
PE文件的节(section)
PE文件的节代表代码或某些类型的数据。虽然代码只能是代码,但数据却有许多种不同类型。除了可读/可写的程序数据(例如全局变量)外,节中其它类型的数据包括函数导入表和导出表、资源以及重定位信息等。每个节都有它自己的一组内存属性,其中包括节中是否包含代码,是只读的还是可读/可写的以及节中的数据是否在所有使用这个可执行文件的进程中是共享的等等。
一般说来,一个节中的所有代码和数据在逻辑上是相关的。通常一个PE文件中至少有两个节,一个是代码节,另一个是数据节。一般在PE文件中至少还有一种其它类型的数据节。第二部分中具体描述各种节。
每个节都有一个惟一的名字。它通常用来表示节的用途。例