设为首页 加入收藏

TOP

3.5.1 目标文件格式
2013-10-12 06:48:52 来源: 作者: 【 】 浏览:88
Tags:3.5.1 目标 文件 格式

3.5  Linux 0.12目标文件格式

为了生成内核代码文件,Linux 0.12使用了两种编译器。第一种是汇编编译器as86和相应的链接程序(或称为链接器)ld86。它们专门用于编译和链接运行在实地址模式下的16位内核引导扇区程序bootsect.S和设置程序setup.s。第二种是GNU 的汇编器as(gas)和C语言编译器gcc以及相应的链接程序gld。编译器用于为源程序文件产生对应的二进制代码和数据目标文件。链接程序用于对相关的所有目标文件进行组合处理,形成一个可被内核加载执行的目标文件,即可执行文件。

本节首先简单说明编译器产生的目标文件结构,然后描述链接器如何把需要链接在一起的目标文件模块组合在一起,以生成二进制可执行映像文件或一个大的模块文件。最后说明Linux 0.12内核二进制代码文件Image的生成原理和过程。这里给出了Linux 0.12内核支持的a.out目标文件格式的信息。as86和ld86生成的是MINIX专门的目标文件格式,将在涉及这种格式的16章中给出。因为MINIX目标文件结构与a.out目标文件格式类似,所以这里不对其进行说明。有关目标文件和链接程序的基本工作原理可参见John R. Levine著的"Linkers & Loaders"一书。

为便于描述,这里把编译器生成的目标文件称为目标模块文件(简称模块文件),而把链接程序输出产生的可执行目标文件称为可执行文件。并且把它们统称为目标文件。

3.5.1  目标文件格式

在Linux 0.12系统中,GNU gcc或gas编译输出的目标模块文件和链接程序所生成的可执行文件都使用了UNIX传统的a.out格式。这是一种被称为汇编与链接输出(Assembly & linker editor output)的目标文件格式。对于具有内存分页机制的系统来说,这是一种简单有效的目标文件格式。a.out格式文件由一个文件头和随后的代码区(text section,也称为正文段)、已初始化数据区(data section,也称为数据段)、重定位信息区、符号表以及符号名字符串构成,如图3-7所示。其中代码区和数据区通常也被分别称为正文段(代码段)和数据段。

 
图3-7  a.out格式的目标文件

a.out格式7个区的基本定义和用途是:

执行头部分(exec header)。执行文件头部分。该部分中含有一些参数(exec结构),是有关目标文件的整体结构信息。例如代码和数据区的长度、未初始化数据区的长度、对应源程序文件名以及目标文件创建时间等。内核使用这些参数把执行文件加载到内存中并执行,而链接程序(ld)使用这些参数将一些模块文件组合成一个可执行文件。这是目标文件唯一必要的组成部分。

代码区(text segment)。由编译器或汇编器生成的二进制指令代码和数据信息,含有程序执行时被加载到内存中的指令代码和相关数据。能以只读形式加载。

数据区(data segment)。由编译器或汇编器生成的二进制指令代码和数据信息,这部分含有已经初始化过的数据,总是被加载到可读写的内存中。

代码重定位部分(text relocation)。这部分含有供链接程序使用的记录数据。在组合目标模块文件时用于定位代码段中的指针或地址。当链接程序需要改变目标代码的地址时就需要修正和维护这些地方。

数据重定位部分(data relocation)。类似于代码重定位部分的作用,但是用于数据段中指针的重定位。

符号表部分(symbol table)。这部分同样含有供链接程序使用的记录数据。这些记录数据保存着模块文件中定义的全局符号以及需要从其他模块文件中输入的符号,或者是由链接器定义的符号,用于在模块文件之间对命名的变量和函数(符号)进行交叉引用。

字符串表部分(string table)。该部分含有与符号名相对应的字符串,供调试程序调试目标代码,与链接过程无关。这些信息可包含源程序代码和行号、局部符号以及数据结构描述信息等。

对于一个指定的目标文件并非一定会包含所有以上信息。由于Linux 0.12系统使用了Intel CPU的内存管理功能,因此它会为每个执行程序单独分配一个64MB的地址空间(逻辑地址空间)使用。在这种情况下,因为链接器已经把执行文件处理成从一个固定地址开始运行,所以相关的可执行文件中就不再需要重定位信息。下面对其中几个重要区或部分进行说明。

1.执行头部分

目标文件的文件头中含有一个长度为32B的exec数据结构,通常称为文件头结构或执行头结构。其定义如下所示。有关a.out结构的详细信息请参见include/a.out.h文件后的介绍。

struct exec {
unsigned long a_magic  // 执行文件魔数。使用N_MAGIC等宏访问。
unsigned a_text   // 代码长度,字节数。
unsigned a_data   // 数据长度,字节数。
unsigned a_bss // 文件中的未初始化数据区长度,字节数。
unsigned a_syms   // 文件中的符号表长度,字节数。
unsigned a_entry  // 执行开始地址。
unsigned a_trsize // 代码重定位信息长度,字节数。
unsigned a_drsize // 数据重定位信息长度,字节数。
}

根据a.out文件中头结构魔数字段的值,可以把a.out格式的文件分成几种类型。Linux 0.12系统使用了其中两种类型:模块目标文件使用了OMAGIC(Old Magic)类型的a.out格式,它指明文件是目标文件或者是不纯的可执行文件。其魔数是0x107(八进制0407)。而执行文件则使用了ZMAGIC类型的a.out格式,它指明文件为需求分页处理(demand-paging,即需求加载,load on demand)的可执行文件。其魔数是0x10b(八进制0413)。这两种格式的主要区别在于它们对各个部分的存储分配方式上。虽然该结构的总长度只有32B,但是对于一个ZMAGIC类型的执行文件来说,其文件开始部分却需要专门留出1KB的空间给头结构使用。除被头结构占用的32B以外,其余部分均为0。从1024字节之后才开始放置程序的正文段和数据段等信息。而对于一个OMAGIC类型的.o模块文件来说,文件开始部分的32字节头结构后面紧接着就是代码区和数据区。

执行头结构中的a_text和a_data字段分别指明后面只读的代码段和可读写数据段的字节长度。a_bss字段指明内核在加载目标文件时数据段后面未初始化数据区域(bss段)的长度。由于Linux在分配内存时会自动对内存清零,因此bss段不需要被包括在模块文件或执行文件中。为了形象地表示目标文件逻辑地具有一个bss段,在后面图示中将使用虚线框来表示目标文件中的bss段。

a_entry字段指定了程序代码开始执行的地址,而a_syms、a_trsize和a_drsize字段则分别说明了数据段后符号表、代码和数据段重定位信息的大小。对于可执行文件来说并不需要符号表和重定位信息,因此除非链接程序为了调试目的而包括了符号信息,执行文件中的这几个字段的值通常为0。

2.重定位信息部分

Linux 0.12系统的模块文件和执行文件都是a.out格式的目标文件,但是只有编译器生成的模块文件中包含用于链接程序的重定位信息。代码段和数据段的重定位信息均有重定位记录(项)构成,每个记录的长度为8B,其结构如下所示:

struct relocation_info
{
int r_address;  // 段内需要重定位的地址。
unsigned int r_symbolnum:24;   // 含义与r_extern有关。指定符号表中一个符号或者一个段。
unsigned int r_pcrel:1;  // 1位。PC相关标志。
unsigned int r_length:2;  // 2位。指定要被重定位字段长度(2的次方)。
unsigned int r_extern:1; // 外部标志位。1 - 以符号的值重定位。0 - 以段的地址重定位。
unsigned int r_pad:4;  // 没有使用的4个位,但最好将它们复位掉。
};

重定位项的功能有两个。一是当代码段被重定位到一个不同的基地址处时,重定位项则用于指出需要修改的地方。二是在模块文件中存在对未定义符号引用时,当此未定义符号最终被定义时链接程序就可以使用相应重定位项对符号的值进行修正。由上面重定位记录项的结构可以看出,每个记录项含有模块文件代码区(代码段)和数据区(数据段)中需要重定位处长度为4B的地址以及规定如何具体进行重定位操作的信息。地址字段r_address是指可重定位项从代码段或数据段开始算起的偏移值。2bit的长度字段r_length指出被重定位项的长度,0到3分别表示被重定位项的宽度是1B、2B、4B或8B。标志位r_pcrel指出被重定位项是一个"PC相关的"项,即它作为一个相对地址被用于指令当中。外部标志位r_extern控制着r_symbolnum的含义,指明重定位项参考的是段还是一个符号。如果该标志值是0,那么该重定位项是一个普通的重定位项,此时r_symbolnum字段指定在哪个段中寻址定位。如果该标志是1,那么该重定位项是对一个外部符号的引用,此时r_symbolnum指定目标文件中符号表中的一个符号,需要使用符号的值进行重定位。

3.符号表和字符串部分

目标文件的最后部分是符号表和相关的字符串表。符号表记录项的结构如下所示:

struct nlist {
union {
char*n_name;   // 字符串指针。
struct nlist *n_next;   // 或者是指向另一个符号项结构的指针。
long n_strx;   // 或者是符号名称在字符串表中的字节偏移值。
} n_un;
unsigned char n_type;  // 该字节分成3个字段,参见a.out.h文件146-154行。
char n_other; // 通常不用。
shortn_desc;  //
unsigned long n_value;   // 符号的值。
};

由于GNU gcc编译器允许任意长度的标识符,因此标识符字符串都位于符号表后的字符串表中。每个符号表记录项长度为12字节,其中第一个字段给出了符号名字符串(以null结尾)在字符串表中的偏移位置。类型字段n_type指明了符号的类型。该字段的最后一个位用于指明符号是否为外部的(全局的)。如果该位为1的话,那么说明该符号是一个全局符号。链接程序并不需要局部符号信息,但可供调试程序使用。n_type字段的其余位用来指明符号类型。a.out.h头文件中定义了这些类型值常量符号。符号的主要类型包括:

text、data或bbs 指明是本模块文件中定义的符号。此时符号的值是模块中该符号的可重定位地址。

abs 指明符号是一个绝对的(固定的)不可重定位的符号。符号的值就是该固定值。

undef 指明是一个本模块文件中未定义的符号。此时符号的值通常是0。

但作为一种特殊情况,编译器能够使用一个未定义的符号来要求链接程序为指定的符号名保留一块存储空间。如果一个未定义的外部(全局)符号具有非零值,那么对链接程序而言该值就是程序希望指定符号寻址的存储空间的大小值。在链接操作期间,如果该符号确实没有定义,那么链接程序就会在bss段中为该符号名建立一块存储空间,空间的大小是所有被链接模块中该符号值最大的一个。这就是bss段中所谓的公共块(common block)定义,主要用于支持未初始化的外部(全局)数据。例如程序中定义的未初始化的数组。如果该符号在任意一个模块中已经被定义了,那么链接程序就会使用该定义而忽略该值。


】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇3.5.3 链接程序输出 下一篇3.5.2 Linux 0.12中的目标文件格式

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: