静态链接浅析

阅前声明:本文是一篇“复现、验证和笔记”,更详细的内容还请查看文末链接中的博客,作者写得非常好 : )


1.为什么要静态链接

假设我们有如下C程序:

1 // a.c
2 extern int shared;
3 int main()
4 {
5     int a = 100;
6     swap(&a, &shared);
7 }
1 // b.c
2 int shared = 1;
3 
4 void swap(int* a, int* b){
5     int tmp = *a;
6     *a =  *b;
7     *b = tmp;
8 }

a.c文件引入了外部变量shared和外部函数swap(),如果直接对a.c gcc a.c -o a 进行编译,则会报错:

我们可以 gcc -c a.c 得到目标对象文件,再 readelf -s a.o 查看其符号表:

Value的作用是告知该符号的地址(更准确的说是在段中的偏移),Ndx的作用是告知该符号是在段表中的第几个段。

符号shared和swap的Ndx均为UND(UNDefined,未定义),即找不到这两个符号。

对b.c gcc -c b.c,查看其符号表:

发现shared和swap存在于b.o中。

因此可以想到,只要将a.o和b.o链接在一起,成为一个目标对象文件即可。

链接命令:ld a.o b.o -e main -o ab

其中ld表示使用ld链接器,-e main表示程序的入口地址为main函数,-o ab表示生成"ab"可执行文件。

补充一点,我们通常使用gcc命令能够直接生成可执行文件,是因为gcc命令会综合调用预处理器cpp ---> 编译器ccl ---> 汇编器as ---> 链接器ld,从而将源程序生成为可执行程序。

readelf -s ab,看看ab中的符号:

这样,swap、shared和main三个符号就都有了。

readelf -S ab 查看段表印证一下,swap和main是函数符号,所以在.text中,shared是变量符号,所以在.data中,没问题。


2.静态链接规则

链接器链接a.o和b.o是将二者的相似段进行了合并,以text段为例:

objdump -h a.o(当然也可以用readelf命令):

大小为0x27。

objdump -h b.o

大小为0x2c。

27 + 2c = 0x53

由此可以猜测,ab的.text段的大小为0x53。

objdump -h ab

事实确实如此。


3.VMA和File off

二者都可以表示段的地址。

VMA表示该段加载到内存中的虚拟地址,File off表示该段在文件中的偏移量。

还是这张图片,看以看出VMA和File off的不同之处,推算得知文件在加载到内存后的虚拟地址应为0x400000。

链接时,不仅计算得到各个段的File off,同时也计算出了各个段在加载到内存时的VMA,为执行该文件做好了准备。


4.符号的虚拟地址

链接前,各符号在文件中的偏移地址是固定的,在链接后,可以进一步确定符号的虚拟地址。

以swap为例,readelf -s b.o

swap所在的.text段在b.o的起始位置。

a.o与b.o链接合并为ab后,b.o的.text段会被置于a.o的.text段下面。

已知a.o的.text段的大小为0x27,ab的.text段的VMA为0x401000。

0x27 + 0x401000 = 0x401027

那么b.o的.text段的VMA应为0x401027。

readelf -s ab

符合预期。


5.重定位

链接后,我们就可以得知每个符号的虚拟地址,在引用该符号时,就可以根据符号的虚拟地址进行调整。

具体如下:

objdump -d a.o

call指令对应e8,而后面的四字节是要调用的函数指令相对于下一条指令的偏移地址,为00 00 00 00,显然是无意义的,因为在链接前我们还不知道swap的虚拟地址。

在链接后生成的ab中,objdump -d ab

因为是大端序,所以02 00 00 00其实是0x02,下一条指令的地址为0x401025。

0x401025 + 0x02 = 0x401027

发现swap的地址确实是401027。

这就是重定位——对指令中涉及的符号引用,在链接后,根据符号的虚拟地址进行调整。


6.重定位段

那么链接器是如何知道e8这个call指令中的地址是需要重定位的呢?

答案就是有一个重定位段,专门记录这些信息。

objdump -r a.o: 

VALUE列中的swap表示swap这个符号在.text段中被引用了,需要重定位。具体引用位置在OFFSET列,即.text段的偏移量为0x21处。

在刚才的图:

可以看到swap的位置正是0x21(20 + 1),这个位置被称为重定位入口。


7.总结

总的来说,静态链接就做了两件事:

①将各目标对象文件的相似段合并,为各个新段分配虚拟内存地址、记录新段在新文件中的偏移量,即VMA和File off。同时,计算并记录所有符号的虚拟内存地址。

②根据各目标文件中的重定位段的信息,对合并后的.text段中涉及符号引用的位置进行重定位。


参考博客:

posted @ 2023-06-06 15:09  Hell0er  阅读(166)  评论(0)    收藏  举报