《编译系统-自底向上研究方法》链接器分析 - 弦外之音

/ 0评 / 0

源码下载地址:百度网盘,提取码:cat1 。

之前的文章已经讲解了如何在 clion 里面调试 ld 链接器,本文不打算讲 链接器的源码逻辑,因为我也没看,我也不会,本文主要讲 链接器 主要做了什么事情。

第一件事,修正地址。我们知道 gccmain.c 编译成 main.o 的时候,是没有引用其他文件,命令如下:

gcc -c -o main.o main.c

我们还知道 ,main.c 里面是调用了 sum 函数的,翻译成 call 指令,肯定需要一个参数,这个参数决定指令跳到哪里。我们用 objdump 打印一下 main.o 的内容。如下:

可以看到 call 指令的参数全是 0,这个严格来说,不是参数,而是操作数,一些汇编书籍讲的操作数,但是在本文里我叫做参数,方便理解。

这里 gcc 编译的时候,他找不到 sum 函数的定义的位置,就会 用 0 填充,先占个位置,后面让链接器来把这个地址修正。只要 main.c 里面声明了 sum 函数,编译的时候就不会报错,这里如果我把 sum 的声明去掉,如下,编译就会报错。

上图没有报错,只是报了个 warning,算了,不管他,只是演示一下。、



回来主题,链接器会对这些 00 00 00 00 的地址进行修正,我们 用 objdump -d main 来看看修正之后的效果,如下图:

从上图可以看到,链接器生成的 main 进行了地址修正,变成了 08 ,当前指令 偏移 8 字节 正好就是 sum 函数的入口。


下面讲一下 ld 链接器是根据什么规则来修正地址,以及我们经常遇到的 undefined reference to xxx 是什么意思。

上文说到,call 之前的参数是 00 00 00 00 ,4个字节的 00,肯定有个地方,或者表,标明了 sum 这个符号,对应着这4个字节的位置。这个信息 肯定在 main.o 里面,因为只有这样,链接器才能找到这个位置进行替换。

用 xelfviewer.exe 打开 main.o 进行分析。为了方便读者对着,main.o 下载地址 百度网盘 ,提取码:q2sy 。

从上图可以看到,.data 这个段有 12个字节,就是 array 数组的内容,不过这个不是本文重点,继续看下图:

上图中我圈出来 3 个符号,arraymainsum,前面两个符号,都能根据 st_shndxst_valuest_size 找到在 main.o 文件的偏移位。唯独这个 sum 符号,全都是 0 。

修改地址,需要用到一个 重定位段,如下:

这个段名称 是 .rela.text ,全称,Relocation text,重定位表,作用于前面的 .text 段,.text 段哪些符号要重定位,都会放到 .rela.text 段。从 sh_info 字段也能看出,这个段是作用于 .text 段的, sh_info 是要作用到的段的下标。.rela.text 段的 sh_info 是 1,表明作用于下标为 1的段。.text 段的下标就是 1。

重定位表 里面有很多个 元素,结构体是 Elf64_Rel/usr/include/elf.h

typedef struct
{
  Elf64_Addr    r_offset;       /* Address */
  Elf64_Xword   r_info;         /* Relocation type and symbol index */
  Elf64_Sxword  r_addend;       /* Addend */
} Elf64_Rela;

这个结构体是 24 个字节。如下图:

从上图可以看到,一共两个元素,也就是 48字节,跟 段表里面的 sh_size 字段的值吻合, sh_size 是 0x30 。提醒一下 Elf64_Rela 结构体只有 3个字段,上图中的 SymType 字段是 xelfviewer.exe 自动算出来,方便你查看的。

这两行数据,实际上是 arraysum 符号的重定位信息。array 先不管他,本文重点讲解 sum 符号的重定位。

先讲解 r_info 字段,因为这个字段特别重要,后面的 SymType 字段就是从 r_info 拆出来的,前32位是 Sym ,后32位是 TypeSym 是 symbol 符号的缩写。所以 sym 字段可以关联到符号,这个字段其实就是符号表的下标,我们看一下 第 8 个符号是什么,如下图:

第 8个 符号就是 array。再来看看第二行的 sym 是 0xb (第11个符号),也就是 sum 符号,所以重定位表 第二行的数据才是 sum 符号的。

现在来看 r_offset 字段的含义,看名字就知道是偏移位,那是谁的偏移位,是 .text 字段的偏移位, .text 字段入口 加上 r_offset 就是要修正的内容的位置,我们知道 .text 的 sh_offset 是 0x40,现在加上 0x15,也就是 0x55,用 notepad++ 看 0x50 的位置内容,如下图:

注意看上图的 e8 , e8 就是call 指令的机器码,这个位置,正好就是 sum 函数的位置,4个 00。

这样,重定位表 就确定了要修改 符号要修改的地址。链接器后面确定了 sum 函数的位置,就能利用重定位表修改 相应的地方。


链接器的分析暂时就到此为止了,我暂时不打算花太多时间研究 ld 的各个参数命令以及源码实现,这里推荐一些相关书籍,有兴趣的同学可以自行阅读。

1,《Linker and Loader》

2,《程序员的自我修养》第3,4章。

3,《深入理解计算机系统》第7章。

可以结合之前的 clion 源码调试 来理解 链接器的逻辑实现。


由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1,QQ:2338195090。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注