源码下载地址:百度网盘,提取码:cat1 。
之前的文章已经讲解了如何在 clion
里面调试 ld
链接器,本文不打算讲 链接器的源码逻辑,因为我也没看,我也不会,本文主要讲 链接器 主要做了什么事情。
第一件事,修正地址。我们知道 gcc
把 main.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 个符号,array
,main
,sum
,前面两个符号,都能根据 st_shndx
,st_value
,st_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个字段,上图中的 Sym
跟 Type
字段是 xelfviewer.exe 自动算出来,方便你查看的。
这两行数据,实际上是 array
跟 sum
符号的重定位信息。array
先不管他,本文重点讲解 sum
符号的重定位。
先讲解 r_info
字段,因为这个字段特别重要,后面的 Sym
跟 Type
字段就是从 r_info 拆出来的,前32位是 Sym
,后32位是 Type
。Sym
是 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。