月度归档:2015年04月

可执行目标文件—链接

摘自 深入理解计算机系统
可执行目标文件:
可执行目标文件是由链接器链接生成的最终的目标文件。C语言产生的可执行目标文件,通常是一个二进制文件包含加载程序到存储器运行所需的全部信息。下图为 一个典型的 ELF可执行文件所包含的信息。
可执行目标文件结构

可执行目标文件的格式类似于可重定位目标文件的格式。ELF头部描述文件的总体格式。它还包含程序的入口点(程序运行时需要执行的第一条指令的地址)。.text .rodata .data和可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行时存储器地址以外。 .init节定义了一个小函数,_init 程序初始化代码会调用它。因为可执行文件是完全链接的,所以不需要加载 .rel节。
ELF可执行文件被设计为很容易加载到存储器,连续的可执行文件的组块被映射到连续的存储器段。段头表 描述了这种映射关系。
段头表,会根据可执行目标文件的内容初始化两个存储器段。

加载可执行目标文件:
linux下运行可执行目标文件,需要在命令提示符下输入
[root@localhost ~]# ./m
因为程序m不是一个shell程序,所以shell会将程序m作为一个可执行目标文件,所以运行程序m,系统会调用操作系统中的加载器来运行程序m。
加载器:将可执行文件中的代码和数据从磁盘copy到存储器中运行,然后通过跳转程序入口点 来运行程序。通常把程序copy到存储器并运行的过程叫加载。
每个linux程序都有一个运行时的存储器映像,如下所示:
运行时存储器映像

运行 :
ox080480c0 _start //程序入口点
call _libc_init_first // 开始 .text中的代码
call _init //开始 .init中的代码
call atexit //开始.text中的代码 附加exit需要联动调用的函数信息。
call main //运行应用程序
call _exit //返回控制给操作系统

链接器之---重定位

摘自 深入理解计算机系统
链接器完成符号解析后,就将每个符号的引用和明确了每个符号的定义,链接器就知道了代码节和数据节确切的大小,接下来需要进行重定位操作。
一 重定位
1 重定位 节和符号的定义:
链接器将所有相同类型的节合并为同一类型的新的聚合节。例 来自输入模块的.data节被全部合并到一个节,这个节称为输出的可执行文件的.data节,然后链接器将运行存储器地址赋值给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,程序中的每个指令和全局变量都有唯一的运行时的存储地址。
2 重定位 节中的符号引用:
链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行时的地址。为了执行这一步,链接器以来重定位表目的可重定位目标模块中的数据结构。
二 重定位表目
通过汇编器生成目标模块后,因为还未形成最后的数据和代码,所有此时还无法得知存储器中的位置,所以汇编器生成文件时,都会生成一个重定位的表目,供链接器生成可执行文件时,修改目标文件中的引用,代码的重定位表目放在 .rel.text中,已初始化数据的重定位表目放在.rel.data中。
ELF重定位表目的格式:
typedef struct{
int offset;//修改引用的字偏移
int symbol:24, //修改引用应该指向的符号
type:8; //告诉链接器如何修改新的引用
} Elf32_Rel
ELF 中定义了11种不同的重定位类型,以下列举两种重定位类型
1 R_386_PC32:重定义一个使用32位PC相关的地址引用。当CPU执行使用PC相关寻址的指令时,它将在指令中编码的32位值加上 PC当前运行的值,得到有效地址。
2 R_386_32:重定位一个使用32位绝对地址的引用。通过绝对寻址,CPU直接使用在指令中的编码的32位作为有效地址,无须进行任何修改。

三 重定位符号引用
例:重定位算法
foreach section s{
foreach relocation entry r{
refptr =s +r.offset; //默认修改偏移值
if(r.type == R_386_PC32){
refaddr =ADDR(s);
*refptr =(unsigned) (ADDR(r.symbol) + *refptr – refaddr); //加上32位值 PC当前运行的值,得到有效地址
}

if(r.type == R_386_32){
*refptr =(unsigned) (ADDR(r.symbol) + *refptr); //32位作为有效地址
}
}
}

重定位算法

链接器ld 解析静态库的原理

摘自 深入理解计算机系统
链接器在程序开发的时候起着非常重要的作用,以下的文章将阐述链接器解析静态库的原理:
在符号解析阶段,链接器从左至右按照它们在编译器上出现的相同顺序来扫描可重定位目标文件和存档文件,扫描时,链接器会生成一个可重定位文件的集合E,这个集合文件合并输出就可以形成一个可执行文件和一个未解析符号集合U,以及输入的已定义集合D,初始化时 E U D 都是空的。
对于命令行上的每个输入文件f
链接器会判断f是一个目标文件还是一个存档文件(静态库),如果f是目标文件,那么链接器会将f添加到E,修改U D来反映f中的符号的定义和引用,并继续下一个输入文件。
如果判断f为一个存档文件,那么链接器就尝试匹配U中未解析的符号和由静态库成本定义的符号,如果某个存档文件成员m,定义了一个符号来解析U中的一个引用,那么就将m加到E中,并且修改 U D 来反映m符号的定义和引用。
链接器会 依次进行上述两个操作,直到 U D 全部修改完毕,此时不包含E中 的成员目标文件被丢弃,链接器继续下一个输入文件。

如果链接器遍历完所有的输入文件,u是非空的,那么此时就会报编译错误,无法找到**静态库异常,否则合并U和E生成可执行文件。

注意事项[总结]:
静态库 一般放在命令行的结尾,如果需要引入多个静态库 按照依赖关系依次从左向右依次排列,也可以重复多引用几次静态库。