可执行文件与目标文件的粘合剂:链接

实验目的

<<程序员的自我修养>>在第四章静态链接中,提到ld会自动寻找函数如printf的位置和所依赖的目标文件,最终形成可执行文件。本文将探究动态链接下形成可执行文件所依赖的目标文件是什么,这些目标文件又是怎样链接成为可执行文件。

实验环境

Linux 5.10.16.3-microsoft-standard-WSL2
gcc version: 11.3.0

源代码

首先来看一个最简单的c程序

#file: simple.c

int global_val = 2;
int main() {
    int local_val;
    local_val = global_val + 1;
        return local_val;
   }

该程序仅仅将一个全局变量加1赋值给一个局部变量,然后将其返回。

编译运行

使用默认参数,将该程序使用gcc编译,得到一个可执行文件

gcc simple.c -o simple

然后使用readelf -S simple查看simple可执行文件的section headers,可以看到simple中包含了.text, .data, .init, .fini等section, 使用objdump -ds simple还可以查看这些section中的具体内容。

可能你学过csapp,了解编译、静态链接、动态链接的概念,但是你考虑过这些sections是从哪里来的吗?

从汇编到链接

gcc不使用任何参数,那么就默认进行了预处理、编译、汇编、链接四个过程,使用-c参数,我们可以使gcc停在汇编阶段,不进行链接。

gcc -c -o simple.o simple.c

使用file命令,可以看出simple.o是可重定位文件。使用objdump命令,我们可以查看simple.o的二进制代码。

simple.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:	f3 0f 1e fa          	endbr64 
   4:	55                   	push   %rbp
   5:	48 89 e5             	mov    %rsp,%rbp
   8:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # e <main+0xe>
   e:	83 c0 01             	add    $0x1,%eax
  11:	89 45 fc             	mov    %eax,-0x4(%rbp)
  14:	8b 45 fc             	mov    -0x4(%rbp),%eax
  17:	5d                   	pop    %rbp
  18:	c3                   	ret    

可以看到simple.o只包含.text, .data, .comment, .note.gnu.property, .eh_frame段,与simple可执行文件相比,缺失了.init, .init_array, .rela.dyn, .dynamic等节,那么很明显,这些缺失的段来自于gcc的最后一个过程:链接。

另外,还可以看到simple.o中的main函数和simple中的main函数是完全一样的,且main函数都位于.text段中,不同的是simple的.text段中还包含其他函数,所以simple的.text段大小大于simple.o。

链接

使用gcc的–verbose参数,我们可以把编译链接的中间步骤打印出来

gcc --verbose -o simple simple.c

	...
 /usr/lib/gcc/x86_64-linux-gnu/11/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/11/liblto_plugin.so
 -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper -plugin-opt=-fresolution=/tmp/ccZX4ABo.res 
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc 
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 
--hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o simple 
/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o 
/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crti.o 
/usr/lib/gcc/x86_64-linux-gnu/11/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/11 
-L/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu 
-L/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib 
-L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/11/../../.. 
/tmp/ccsvVfED.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state 
--as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/11/crtendS.o 
/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-v' '-o' 'simple' '-mtune=generic' '-march=x86-64' '-dumpdir' 'simple.'

这里,我们只保留了gcc链接过程的命令,我们可以看到,这里的链接器实际上是collect2, 这里的collect2函数可以视为ld链接器, 本质上是一个静态链接过程。我们可以看到,collect2的参数包括以下文件:

liblto_plugin.so
ld-linux-x86-64.so.2
Scrt1.o
crti.o
crtbeginS.o
ccsvVfED.o
crtendS.o
crtn.o

那么我们现在可以回答上面提到的一个问题,simple可执行文件中的section一部分来自于simple.o,另外一部分来自于以上文件。

比方说,simple中有一个叫做.init的节,主要是完成main函数调用之前的初始化工作。simple, crti.o, crtn.o都有.init节,并且simple中的.init开始部分来自于crti.o, 结束部分来自于crtn.o。其实不仅仅是.init, 对于.fnit节,simple, crti.o, crtn.o也有着相同的关系。

.text段中的_start函数来自于Scrt1.o;
crtbeginS.o提供了.text段中的__do_global_dtors_aux、deregister_tm_clones、register_tm_clones、frame_dummy函数。crtbeginS.o和crtendS.o主要是用于C++全局对象的构造和析构。 详见 <<程序员的自我修养>> p345。
.interp段来自于ld-linux-x86-64.so.2, 它是一个字符串,保存了动态链接器的路径。

.dynamic段包含了动态链接器所需要的全部信息,这个段我猜测是在其他段的基础之上完成的,因为这个段中包含了符号表、重定位表、初始化代码、结束代码等位置。

总结

本文,使用gcc的–verbose参数,列举出了在链接过程中所需要的目标文件, 然后对这些目标文件与可执行文件之间的关系进行了分析。 可以看出,可执行文件中包含了这些目标文件,这些目标文件就像静态库,在链接的过程和源代码编译出的可重定位文件(本例中的simple.o)链接在一起形成了一个可执行文件。

后续

仔细查看simple的section, 我们发现其中既有.init,又有.init_array, 那么.init_array的作用是什么,simple中的_start段和glibc中的_start段的关系又是什么?这些我们留在以后的文章中进行探究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值