编译器编译源代码后生成的文件叫做目标文件,从结构上讲它是已经编译后的可执行文件格式,只是还没有经过链接,其中可能有些符号或有些地址还没有调整。它本身就是按照可执行文件格式存储的,跟真正的可执行文件在结构上稍有不同。
目标文件的格式:
现在 PC 平台流行的可执行文件格式主要是 Windows 下的 PE( Portable Executable,可移植可执行 ) 和 Linux 的 ELF( Executable and Linkable Format,可执行链接格式 ),它们都是 COFF (Common Object Fifle Format,一般目标文件格式 )格式的变种。从广义上看,目标文件和可执行文件的格式其实几乎是一样的,在 Linux 下,我们可以将他们统称为 ELF 文件;在 Windows 下可以统称为 PE-COFF 文件格式。
在 Linux 下使用 file 命令查看相应的文件格式:
目标文件是什么样的
目标文件是什么样的呢? 下面我们通过一个具体的例子来看一下。如不加说明,以下分析都是基于 32 位 x86 平台,如何在 64 位系统下编译 32 位可执行程序,需要安装 32 位版本的 glibc库,然后在编译时加上选项 “-m32”,如下所示:
源代码如下:
下面我们用 GCC 编译这个文件(只编译不链接),然后用 objdump 查看 ELF 文件的基本信息,如下:
我们可以看到目标文件是由很多的“节”(Section)组成的,有 .text节、.data节、.bss节、.rodata节、.comment—注释信息节和 .note.GNU-stack—堆栈提示节等。目标文件将所有信息按不同的属性以“节”(Section)的形式存储,有时候也叫“段”(Segment),很难在中文的翻译上加以区分,其实它们两个还是有区别的。
Section 和 Segment 的区别
在汇编源码中,通常用语法关键字 section 或 segment 来表示一段区域,在程序中“逻辑地”划分一段区域,这个区域就是节。此时所说的 section 和 segment 都是汇编语法中的关键字,它们在语法中都表示“节”,不是段,只是不同编译器的关键字不同而已。经过汇编生成目标文件之后,由这些 section 和 segment 修饰的程序区域便成为了“节”(section)。
但是操作系统加载程序时,不关心节的数量和大小,只关心节的属性,因为程序是要加载到内存中才能运行,而内存的访问会涉及到全局描述符表中段描述符的访问权限等属性,操作系统通过设置 GDT 全局描述符表来构建段描述符,在段描述符中指定段的位置、大小和属性。操作系统加载程序时不需要对逐个节进行加载,只要给出相同权限的节的集合就行了,比如把所有只读可执行的节归并到一块,所有可读可写的节归并到一块,这样操作系统就能为它们分配不同的段选择子,从而指向不同段描述符,实现不同的访问权限。
汇编器只生成了目标文件,尚未链接,因此将“节”合并的工作是由链接器来完成的,链接器将目标文件中属性相同的节合并成一个大的 section 集合,这个集合称为 segment,也就是段,就是平时说的可执行程序内存空间中的代码段和数据段。
总结下就是“节”出现于目标文件中,段诞生于可执行文件中,某个节(section)属于某个段