上几篇文章学习了Binutils辅助工具里面的几个实用的工具,那些工具对于以后的学习都是非常有帮助的,尤其是C语、C++语言的学习以及调试是非常有帮助的。点击链接查看上一篇文章:点击查看
本篇文章开始一个新的知识的学习,链接器的学习。学习完链接器的系列文章,我们将全面了解链接器的工作原理。
注意:本文讲解的并不是很详细,有些关键词例如符号、重定位、段等都没有具体说。这些比较原始的知识最好先去阅读相关书籍,进行补充。本文只是通过实验来理解链接器的作用!!!
1、可重定位文件与可执行文件
我们都知道,源程序在经过gcc编译器编译后,实际上是经过四个步骤—预处理,编译,汇编,链接。最终得到一个可执行程序。这个可执行程序最终将会***作系统的加载器加载带内存中去执行。
在经过汇编之后,生成的文件是可重定位文件,然后可重定位文件经过链接器的链接,最终生成可执行文件。 今天我们就是来学习这个链接器的。
那么可重定位文件是一个什么样的文件?为什么它不能执行?
经过汇编后的文件是可重定位文件。它的文件格式与可执行文件很像(对于Linux,都是elf文件格式)。对于可重定位文件,它里面的代码与数据,都是各个文件独立的代码与数据,在一个工程中,会存在多个C文件,每个C文件都会被首先编译生成一个可重定位文件,然后经过链接器将这些可重定位文件进行链接,从而生成最终的可执行文件。
对于可重定位文件:
- 各个段没有具体的起始地址,只要段大小信息
- 各个标识符没有实际地址,只有在段中的偏移地址(相对地址)
- 段和标识符的实际地址都需要链接器具体制定,这也是链接器的主要作用
对于可执行文件:
- 各个段有自己的起始地址,这些地址就是将来要被加载到内存中的地址(虚拟内存),有了起始地址,才能说加载到内存,不然都不知道加载到哪里,何来的执行呢?这就是可执行文件与可重定位文件一个区别
- 可执行文件中的各个符号,都有了正确的地址,以及符号被引用的地方也正确填上了符号的地址
以上内容,说的很简单,如果不懂,参考《程序员的自我修养》与《深入理解计算机系统》第7章
2、通过代码分析,具体了解链接器的作用
链接器的作用简单的说就是:
- 符号解析
- 重定位
下面我们以具体的程序例子来说明:
test.c
#include <stdio.h>
int g_global = 0;
int g_test = 1;
extern int* g_pointer;
extern void func();
int main(int argc, char *argv[])
{
printf("&g_global = %p\n", &g_global);
printf("&g_test = %p\n", &g_test);
printf("&g_pointer = %p\n", &g_pointer);
printf("g_pointer = %p\n", g_pointer);
printf("&func = %p\n", &func);
printf("&main = %p\n", &main);
func();
return 0;
}
func.c
#include <stdio.h>
int* g_pointer;
void func()
{
g_pointer = (int*)"D.T.Software";
return;
}
对上述两个源程序进行编译生成两个可重定位文件:
- gcc -c func.c -o func.o
- gcc -c test.c -o test.o
生成了可可重定位文件func.o与test.o
- 我们使用上几篇文章的学习的Binutils辅助工具来查看这两个可重定位文件的符号信息:
-
nm func.o
-
nm test.o
可以看到,在test.o与func.o中,各个符号的地址都是0,而且有的符号还是未定义的。地址为0是因为,在没有链接之前,各个可重定位文件是独立的,他们无法加载到内存中去执行,各个符号还没有进行重定位。而又的符号未定义是因为该文件中引用了外部文件的代码或者数据。比如上述代码test.c程序中引用了func.c程序中的g_pointer变量与func()函数,那么在test.c程序中他们就是未定义的,需要将test.o与func.o链接,才能使整个程序是完整的。
- 还可以查看他们的段信息
-
objdump -h test.o
-
objdump -h func.o
可以看到,各个段的地址都是0(VMA与LMA),所以这种可重定位文件是不可执行的,它连加载地址都没有怎么执行???
最后我们将上述两个可重定位文件进行链接生成可执行文件,看看可执行文件里面是什么样子的?
- gcc func.o test.o -o lyy
生成了可执行文件lyy
运行可执行文件:
- ./lyy
- 现在查看可执行文件lyy的符号信息
- nm lyy
看到画红框的地方,是我们程序中有的,左边的地址都是各个符号的地址,此时不为0了,每个符号都有自己的加载地址。其他多余不认识的符号,我们再后面的文章会进行讲解。
- 查看可执行文件lyy的段信息,看看它与可重定位文件有什么区别
- objdump -h lyy
以上图片显示的不全!
可以看出,可执行文件的各个段,也都有了加载地址。那么他就可以加载到内存中进行执行了。
上面我们没有分析符号的引用。由于分析符号的引用需要查看反汇编代码,这里反汇编代码太长了,就不贴了。直接说原理。
实际上经过链接后,可执行文件中,对于符号的引用,已经可以将正确的符号地址填写到符号引用处(因为符号经过重定位已经有了运行时的地址,将这个地址填写到它被引用的地方即可)。当符号引用处是正确的符号地址,在运行时,引用才能够正确得到结果。
3、链接器的意义
从上面的实验,大致可以理解链接器的意义:
链接器的主要作用是将各个可重定位目标模块之间的引用部分处理好,使得各个模块之间可以正确衔接。类似于下图:
链接器的工作内容:
-
将目标文件(可重定位文件)与库文件整合成最终的可执行文件
- 合并各个目标文件的段(.text .data .bss等)以及使得符号与付哈引用之间进行一个关联—符号解析
- 确定各个段以及各个段中符号的最终地址—重定位
4、总结
上述内容,不够系统,也不够细致,只是从实验的角度来具体看可执行文件与可重定位文件,然后理解链接器到底做了什么。前提是你已经理解了一些基础的知识。如果不理解,还需要回头去看看编译链接过程中的基础知识。
本文参考狄泰软件学院相关课程
想学习的可以加狄泰软件学院群,
群聊号码:199546072
学习探讨加个人(可以免费帮忙下载CSDN资源):
qq:1126137994
微信:liu1126137994