2. 目标文件【《链接、装载与库》学习笔记】

目标文件的格式

现在PC平台流行的可执行文件格式主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),他们都是COFF(Common File Format)格式的变种。目标文件就是源代码编译后但未进行链接的中间文件(Windows的.obj和Linux下的.o

动态链接库(DLL)和静态链接库的文件都按照可执行文件的格式存储。静态链接库可以理解为一个包含有很多目标文件的文件包。ELF文件标准里面把系统中常用ELF格式的文件归为4类:

ELF文件类型 说明 实例
可重定位文件 包含代码和数据,可以被用来链接成可执行文件/共享目标文件(静态链接库属于这一类 .o
可执行文件 包含可以直接执行的程序,一般没有扩展名
共享目标文件 包含代码和数据。可以生成新的目标文件或者使用动态链接器结合 .so
核心转储文件 当进程意外终止,系统将该进程地址空间的内容及种植信息转存 core dump
可以使用file命令查看相应文件格式。

目标文件的结构

目标i文件把信息按照不同的属性,以“”(Section)的形式存储,有时候也称“”(Segment)。在一般情况下他们都表示一个一定长度的区域。

代码段存放程序源代码编译后的机器指令。常见的名字有.code.text数据段存放全局变量、局部静态变量数据,常见的名字是.data。未初始化的局部变量和全局变量放在.bss段里。.bss段只是为了未初始化的全局变量和局部静态变量预留位置。

ELF文件的开头是一个“文件头”描述整个文件的文件属性,其中包含段表描述文件各个段在文件中的偏移位置及段的属性。文件头后面是各个段的内容。

总体来说,程序源代码被编译之后分成两种段:程序指令和程序数据。代码段属于程序指令,数据段和.bss段属于程序数据。

使用

1
objdump -h SimpleSection.o

可以查看各个目标文件的结构和内容

data段保存已经初始化了的全局静态变量和局部静态变量rodata段存放的是只读数据,一般是程序里的只读变量和字符串常量。(有时字符串常量在.data段,有时在.rodata

.bss段存放的是未初始化的全局变量和局部静态变量。(为他们预留空间)

.为前缀开头,说明这些表的名字是系统保留的,应用程序也可以使用非系统保留的名字作为段名。

使用GCC时,我们在全局变量或函数之前加上__attribute__((section("name")))属性就可以把相应的变量或函数放在以name作为段名的段中。

ELF文件结构描述

段表(Section Header Table)描述了ELF文件包含的所有段的信息,比如每个段的段名、段的长度、在文件中偏移、读写权限等。

文件头

ELF目标文件格式的最前部是ELF文件头,包含描述整个文件的基本属性,比如文件版本,目标机器型号,入口地址。紧接着是各个段。

使用readelf命令来详细查看ELF文件。

1
readelf -h SimpleSection.o

elf文件头结构及相关常数定义在/usr/include/elf.h里。

所有ELF文件开始的4个字节都必须相同(第一个字节对应DEL控制符,后面3个字节对应“ELF”的ASCII),这4个字节被称为魔数,用于确定文件的类型。接下来1个字节用于标识ELF文件类。

e_type成员表示ELF文件类型,系统通过这个常量来判断ELF的真正文件类型,相关常量以ET_开头。e_machine成员表示平台属性,相关常量以EM_开头。

段表

编译器、链接器、装载器依靠段表来定位和访问各个段的属性。段表在ELF文件的位置由文件头的e_shoff成员决定。

真正的段表结构需要通过readelf工具得到:

1
readelf -S SimpleSection.o

段表的结构是一个以Elf32_Shdr结构体为元素的数组。这个结构体被称为段描述符

段的名字只是在链接和编译过程中有意义,但是不能真正表示段的类型,我们也可以将一个数据段命名为.text。对于编译器和链接器来说,主要决定段属性的是段的类型sh_type和段的标志位sh_flags段的类型相关常量以SHT__开头。段的标志位相关常量以SHF__开头。

重定位表

链接器在处理目标文件时要对目标文件中的某些部位进行重定位,即代码段和数据段中哪些绝对地址的引用的位置。这些信息记录在重定位表中。

字符串表

把字符串集中起来集中放在一个表,然后使用字符串在表中的偏移来引用字符串。字符串表常见的段名为.strtab或则.shstrtab

符号

在链接中我们把函数和变量统称为符号,函数名或者变量名都是符号名。每一个目标表文件都有一个相应的符号表,符号有对应的符号值。同时,段名、行号信息也都是符号。

使用nm来查看ELF文件的符号表:

1
nm SimpleSection.o

ELF符号表结构

符号表段名一般叫.symtab。是一个Elf32_Sym结构的数组。

符号类型和绑定信息st_info)低4位表示符号的类型,高28位表示符号绑定信息(局部符号、全局符号、弱引用)。

符号所在段st_shndx)如果符号定义在本目标文件中,那么符号所在段表示段表中的下标。

符号值st_value)如果是一个函数或者变量,那么符号的值就是函数或变量的地址。还有很多特殊情况。

1
readelf -s SimpleSection.o

查看符号。

符号修饰

为了支持C++的特性,使用符号修饰符号改编的机制。C++使用某种名称修饰,使得每个函数签名对应一个修饰后名称

c++filt工具可以用来解析被修饰过的名称:

1
2
c++filt _ZN1N1C4funcEi
N::C::func(int)

强符号和弱符号

不能重复定义的被称为强符号,可以通过GCC的__attribute__((weak)) 定义任何一个强符号为弱符号。

链接器按如下规则处理全局符号:

  • 不允许强符号被多次定义。
  • 如果一个符号在某个目标是强符号,在其他文件时弱符号,选择强符号。
  • 如果全是弱符号,选择占用空间最大的一个。

强引用:如果没找到符号的定义,链接器报错;弱引用:如果没找到符号定义默认为0.在GCC中可以通过__attribute((weakref))声明为弱引用。

调试信息

在GCC编译时加上-g参数,编译器会在产生目标文件中加上调试信息。ELF采用DWARF的标准调试信息格式。


2. 目标文件【《链接、装载与库》学习笔记】
http://blog.bluspace.ren/2025/12/01/2. 目标文件【《链接、装载与库》学习笔记】/
作者
Blauter
发布于
2025年12月1日
许可协议