亲宝软件园·资讯

展开

C语言目标文件的详细讲解

叫我小秦就好了 人气:0

前言

一个 C 语言程序经编译器和汇编器生成可重定位目标文件,再经链接器生成可执行目标文件。那么目标文件中存放的是什么?我们的源代码在经编译以后又是怎么存储的?

文章为 《深入理解计算机系统》的读书笔记,更为详细的内容可以阅读原书。

目标文件分类

目标文件有三种形式:

可重定位目标文件

下图为一个典型的 ELF 可重定位目标文件的格式。

ELF object file

ELF 头以一个 16 字节的序列开始,这个序列包含了:生成该文件的系统的字的大小和字节顺序、目标文件的类型、机器类型、节头部表(也称段表)的文件偏移,以及节头部表中条目的大小和数量等。

节头部表是由描述文件中各个节的条目(entry)组成的数组。节头部表描述了文件中各个节在文件中的偏移位置及节的属性等,从节头部表里面可以得到每个节的所有信息。

分段的优点

为什么要这么麻烦,把程序的指令和数据分开存放?

符号和符号表

每个可重定位目标模块 m 都有一个符号表,它包含 m 定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:

符号表是由汇编器用编译器输出到汇编语言 .s 文件中的符号构造的。.symtab 节中包含 ELF 符号表,这张符号表包含一个条目的数组。下面是 ELF 符号表条目格式:

typedef struct {
    int	  name;		// String table offset
    char  type:4,	// Function or data (4 bits)
    	  binding:4;// Local or global (4 bits)
    char  reserved;	// Unused
    short section;	// Section header index
    long  value;	// Section offset or absolute address
    long  size;		// Object size in bytes
} Elf64_Symbol;

name 是字符串表中的字节偏移,指向符号的字符串名字。value 是符号的位置。对于可重定位目标文件,value 是距定义目标的起始位置的偏移。对于可执行目标文件,该值是一个绝对运行时地址。size 是目标的大小(以字节为单位)。

每个符号都被分配到目标文件的某个节,由 section 字段表示,该字段是一个到节头部表的索引。有三个特殊的的伪节,它们在节头部表中是没有条目的:

只有可重定位目标文件中才有伪节,可执行目标文件中是没有的。

GCC 将可重定位目标文件中的符号分配到 COMMON 和 .bss 的规则:

符号解析

对于局部符号的解析是非常简单的,因为编译器只允许每个模块中每个局部符号有一个定义。不过,对全局符号的引用解析就麻烦的多。当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理。

如果多个模块定义同名的全局符号,会发生什么呢?下面是 Linux 编译系统采用的方法。

在编译时,编译器向汇编器输出每个全局符号,或者是强或者是弱,汇编器把这个信息编码在可重定位目标文件的符号表里。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。

根据强弱符号的定义,Linux 链接器使用如下规则来处理多重定义的符号名:

重定位

当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以,无论何时汇编器遇到对最终位置位置的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在 .rel.text 中,已初始化数据的重定位条目放在 .rel.data 中。

下图为 ELF 重定位条目的格式:

elf object

符号解析完成后,代码中的每个符号引用和正好一个符号定义关联起来。此时,就可以开始重定位了。在重定位中,将合并输入模块,并为每个符号分配运行时地址。重定位由两步组成:

merge secssion

可执行目标文件

下图为一个典型的 ELF 可执行文件:

elf can

可执行文件加载到内存:

elf load

总结 

加载全部内容

相关教程
猜你喜欢
用户评论