#include <stdio.h>
int time;
int foo(int a) {
int b = a + 1;
return b;
}
int main(int argc, char *argv[])
{
printf("%d\n", foo(5));
return 0;
}
运行结果:
编译之后我们通过 readelf -S 查看可执行文件的段信息
-s或者--full-contents
显示目标文件每个节区的二进制完整内容,对应的会显示该内容在目标文件中的偏移位置,还有将内容转换成ASCII码的形式
-S或者--source
混合显示源码和汇编代码,如果编译目标文件的时候指定了-g参数的话,效果会非常明显。否则,和-d参数没有什么区别,事实上-S参数默认包含了-d参数。
进入正题:
重定位,可执行目标文件
重定位
为了决定段的大小、符号定义、符号引用,并指出包含那些库模块、将这些段放置在输出地址空间的什么地方,链接器会将所有的输入文件进行扫描。扫描完成后的下一步就是链接过程的核心,重定位。由于重定位过程的两个步骤,判断程序地址计算最初的非空段,和解析外部符号的引用,是依次、共同处理的,所以我们讲重定位即同时涉及这两个过程。
链接器的第一次扫描会列出各个段的位置,并收集程序中全局符号与段相关的值。一旦链接器确定了每一个段的位置,它需要修改所有的相关存储地址以反映这个段的新位置。在大多数体系结构中,数据中的地址是绝对的,那些嵌入到指令中的地址可能是绝对或者相对的。链接器因此需要对它们进行修改,我们稍后会讨论这个问题。第一遍扫描也会建立第五章中所讲的全局符号表。链接器还会将符号表中的地址解析为引用全局符号时所存储的地址。
原文链接:https://blog.youkuaiyun.com/A1342772/article/details/77675897
可执行目标文件
我们已经知道连接器是如何将多个目标模块合并成一个可执行目标文件的。
此时,我们的 C 程序已经从一组 ASCII 文件文本,转化为一个二进制文件,而且这个二进制文件中包含加载程序到存储器并运行它所需的所有信息。来源:https://blog.youkuaiyun.com/hudazhe/article/details/79350352?utm_source=app
ELF 可执行目标文件格式
我们可以发现,可执行目标文件类似于可重定位目标文件的格式。
在 ELF 头部描述文件的总体格式,包括程序的入口点(程序运行时要执行的第一条指令的地址)。
init 段定义了一个 _init 函数,程序的初始化代码会调用它。
ELF 可执行文件被设计的很容易加载到存储器,可执行文件的连续的片被映射到连续的存储器段。段头部表描述了这种映射关系。
符号
将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。
不同的符号是有强弱之分的:
强符号:函数和初始化的全局变量。存放在.data
弱符号:未初始化的全局变量。存放在.bss
链接器在处理强弱符号同名情况的时候遵守以下规则:
- 不能出现多个同名的强符号,不然就会出现链接错误
- 如果有同名的强符号和弱符号,选择强符号
- 如果有多个弱符号,随机选取