目标文件主要以下几个部分
段表是ELF的重要结构,用于获取各个段的信息。可通过文件头提供的偏移进行访问 段表是一段连续内存地址,可以看作一个数组,数组的0位为NULL,从1开始储存每个位置存储一个段描述符,描述每个段的信息。
段描述符结构如下
符号表为ELF中的一个段.symtab,结构为一个Elf32_Sym数组。数组序号0处为未定义符号
Elf32_Sym结构如下
某些特殊的符号被声明在ld的链接脚本里,若使用ld链接脚本则可以使用以下特殊符号 这些符号是由链接器解析,装载时定义的,链接后才有意义
由于C与C++对符号的管理和修饰方式不同,在C++中,为了与C兼容,提供了extern "C",用于声明或定义一个C的符号
extern "C" {
int func(int);
int var;
}
extern "C" int func(int);
extern "C" int val
当C++需要使用C语言库时,由于符号管理与修饰方式不同,会无法正确链接到C语言库中的符号,需要使用extern "C";但C中不存在extern "C",为了同时兼容C和C++,C++在编译C++程序时会定义__cplusplus这个宏,可以根据这个宏为C和C++分别定义路径,或者选择是否使用extern "C"
#ifdef __cplusplus
extern "C" {
#endif
void *memset(void *, int , size_t);
#ifdef __cplusplus
}
#endif
强符号弱符号是定义全局符号时设定的一种性质;C/C++一般判断已初始化的全局变量为强符号,未初始化的全局变量为弱符号;强弱符号有以下性质
强引用弱引用是使用一个引用时设定的一种性质 链接时,链接器会尝试需找该引用的定义
弱引用可以用于库和模块的拓展和裁剪;若某个库可以添加或被裁剪,则可以把对该库的引用设为弱引用,然后根据该弱引用的值判断该库是否被链接;若存在,则调用该库,否则使用另一种方法
可执行文件的段是由目标文件中的段合并而成,合成策略为相似段合并,即将相同类型 按序合并
符号地址的确定是在段的虚拟地址确定后执行的操作。由于上面提到的段合并策略,每个符号在链接后对于段首的相对位置是确定的,所以只需要确定段的虚拟地址即可确定符号地址
重定位表实际上为一个段,若某个段需要重定位,则会存在一个对应的重定位表(.text段的重定位表为.rel.text)
所有需要被重定位的位置被称为重定位入口(Relocation entry),而重定位表则保存了对应段的重定位入口
重定位表为一个数组,其保存的结构体的结构如下
在链接时,链接器会将所有输入的目标文件的符号表合并为一个全局符号表用于查询
某些符号由于各种情况处于一个未定义的状态(定义在其他目标文件中的全局引用等),此时对于该符号存在一个相对应的重定位入口,通过该入口进行重定位时,则会去全局符号表中寻找该符号的目标地址,进行重定位
common块的存在主要用于实现弱符号的功能;由于某个弱符号可能存在多个不同文件中的定义,在链接前无法确定使用哪种定义,所以使用COMMON块用于占位,仅存储定义的位长信息
在多符号定义时,存在以下三种情况
第一种情况会直接报错,重复定义
第三种情况会选择多个弱符号中所需空间最大的定义
第二种情况则会选择强符号的定义;若某个弱符号的所需空间大于该强符号,则会报warning,因为该情况可能导致弱符号引用处的定义所需空间不足
实际上,在链接前,未定义的全局变量并不存在于BSS段,而是以COMMON块的形式存在,因为链接前该变量的定义无法确定,不能确定其所占用空间;在链接后其所需空间确定,则放入BSS段,为其分配空间
由于C++语言特性,在编译过程中不可避免的会产生一定重复代码,比如
一般的解决方案为,此类重复代码存储于一个独立的段,比如某一模板函数foo<T>()
,可分别为其不同类型设置一个段,比如.temp.foo<int>
与.temp.foo<float>
,在链接合并相似段时则可以消除重复代码
由于在对某个库的某一功能进行调用时,需要将整个函数库都链接进来,这样会将大量用不到的函数与功能一并链接,造成冗余
VISUAL C++ 存在一种称为函数级别链接的编译选项,将所有的函数保存在一个单独的段中,这样在链接时就可以仅将需要的函数链接,其他的函数的段则抛弃;但这样也会降低编译和链接的效率,目标文件也会随着段的增多而膨胀
GCC 同样提供了类似的编译选项,分别为-ffunction-sections
和-fdata-sections
,分别将函数和变量保存至独立段
C++的全局构造先于main函数,全局析构在main函数之后;这些操作在Linux下一般由Glibc实现,在链接时设置程序入口为_start
,并将初始化部分和结束后的处理进行链接,这些代码一般置于ELF文件中的.init
和.fini
段中
静态库可以看成一组目标文件的集合,比如/usr/lib/libc.a
,libc.a为一个压缩文件,其中保存了一系列.o目标文件,ld链接器会根据引用解压需要的目标文件进行链接
在静态链接库中,常常出现一个目标文件中只有一个或几个函数,这样组织的原因是为了减少冗余代码,避免用户链接不需要用到的函数或模块,减小可执行文件的体积
链接控制脚本用于控制链接的过程,在linux下进行链接就会调用默认的链接脚本;默认的链接脚本存储于/usr/lib/ldscripts
中,ld会根据命令行参数选择合适的链接脚本,可以通过ld -T link.script
来调用自定义链接控制脚本
链接脚本分为两种语句,命令语句和赋值语句;有以下规定
=, +, -, *, /, +=, >>=
等/* */
作为注释常用命令语句
-e
选项ENTRY
_start
符号,则使用.text
段,则使用该段的第一字节地址-Lpath
/* SECTIONS命令基本格式 */
SECTIONS
{
...
secname : {contents}
...
}
.text .data .bss
这三个段名,若使用该格式,则不能使用其他段名/DISCARD/
,即抛弃,用其作为输出名则代表输出被抛弃