上一篇文章学习了链接器之-main函数不是第一个执行的函数:main函数不是第一个执行的函数
今天继续学习链接器,学习链接是如何动作的,从而引入链接脚本的概念。本文就学习链接脚本的概念。
1、链接脚本的作用
我们都知道可重定位文件经过链接器链接后最终形成可执行文件。这个链接的过程大概就是分为符号解析和重定位。
那么链接器到底是如何工作的呢?这取决于链接脚本。我们可以看下图:
几个可重定位文件与相应的库文件进行链接,链接器经过链接器的指导,最终形成可执行程序。
在学习链接链接脚本的大致格式之前,先练总结一下链接脚本的几个作用(实际上这些作用我们都知道,只不过今天才知道链接脚本的存在而已):
-
链接脚本用于描述链接器处理目标文件(可重定位文件)与库文件的方式
- 合并各个目标文件中的段
- 重定位各个段的起始地址
- 重定位各个符号的最终地址(这个地址实际上是段内偏移地址)
2、链接脚本的格式
链接脚本也就是一个脚本文件,语法比较简单,下面我们直接看一个例子,来说明一个链接脚本大致有哪些内容:
上述的的描述还是很全面很仔细的。我们主要注意一下几点:
- 各个段的链接地址必须符合具体平台的规则,比如在Intel处理器上与在Amd处理器上,或许各个段在整个地址空间的位置就会有一些差别
- 链接脚本中能够直接定义标识符并制定存储地址
- 链接脚本能够直接定义源代码(需要编译的.c .c++程序)中的标识符的地址
- 我们主要学习Linux系统,在Linux中代码段(.text段的地址范围为[0x08048000,0x08049000])
我们现在可能还不理解上面的几条注意事项,但是经过下面的例子,就一定可以理解了:
我们写了如下的C程序与链接脚本:
8-1.c
#include <stdio.h>
int s1;
extern int s2;
int main()
{
printf("&s1 = %p\n", &s1);
printf("&s2 = %p\n", &s2);
return 0;
}
8-1.lds
SECTIONS
{
.text 0x08048400:
{
*(.text)
}
. = 0x01000000;
s1 = .;
. += 4;
s2 = .;
.data 0x0804a800:
{
*(.data)
}
.bss :
{
*(.bss)
}
}
我们看上面的程序,在C程序中有一个extern int s2;
这个s2不是这个C文件的,是外部文件的,很明显,我们只有两个文件,另一个就是我们指定的链接脚本文件。
使用下述命令进行编译:
- gcc 8-1.c 8-1.lds -o lyy
生成可执行程序lyy
(注意如果编译的时候不加上我们自己定义的链接脚本,编译就会出错)
运行:
- ./lyy
执行结果为:
很明显,s1的地址由于我们再链接脚本中指定了:
. = 0x01000000;
s1 = .;
所以s1的地址是:0x01000000;
而由于在链接脚本中有如下的两句话:
. += 4;
s2 = .;
所以s2的地址为:0x01000004;
如果我们不使用链接脚本指定这两个变量的地址,那么他们的地址就是随机的,这也符合我们平时的结果。
3、借助链接脚本修改程序的入口函数
不知道是否还记得在上一篇文章中:点击查看。我们学习了程序的执行流程,知道了main函数并不是真正的第一个开始执行的函数。而且我们有办法在编译程序的时候改变第一个执行的程序。那个时候使用的是在编译的时候指定入口函数的地址,就像下面这样:
- gcc -e program -nostartfiles program.c -o program
今天我们来学习另一种方法,来修改程序的入口函数。
那就是在链接脚本中指定,大概格式如下:
下面给出一个例子,这个例子中没有main函数,我们自己指定一个函数,然后将它设为入口函数:
8-2.c
#include <stdio.h>
#include <stdlib.h>
int program()
{
printf("D.T.Software\n");
exit(0);
}
8-2.lds
ENTRY(program)
SECTIONS
{
.text 0x08048400:
{
*(.text)
}
}
使用下面命令进行编译(也可以先编译输出目标文件,然后进行链接,下面的命令直接一步完成而已):
- gcc -nostartfiles 8-2.c 8-2.lds -o lyy2
没有报错,生成了可执行文件lyy2
运行程序结果为:
很明显,我们利用这个方法成功修改了这个C程序的入口函数。
为了更加深入,我们可以看看该可执行程序的符号信息:
使用以下命令查看可执行程序lyy2
- nm lyy2
由于上面的8-2.lds链接脚本指定代码段其实地址为: 0x08048400
,而我们的可执行程序的入口函数(program函数)的地址其实就是代码段(.text)地址,所以如上图,T代表代码段,地址为0x08048400。很完美的解释。
4 、 默认的链接脚本
上面我们学习了链接脚本的各种知识,但是我们平时并没有使用它或者看到它。但是这并不意味着学习它就没有用处。它对于我们理解整个系统原理有很大帮助。
可以使用下面的命令查看默认的链接脚本:
- ld --verbose > defaults.lds
上述命令将默认的链接脚本输出到文件defaults.lds文件中,我们可以打开defaults.lds文件来查看默认链接脚本文件。
5、总结
记住,我们在学习的内容是可以让你走的更远,走的更高的铺垫。或许对你产生不了直接的影响,但是绝对会对你将来的学习之路产生深远的影响。不要一口吃一个胖子,慢慢来,从应用软件做起,深入学习底层原理!你的未来一定更加美好!!!
本文参考狄泰软件学院相关课程
想学习的可以加狄泰软件学院群,
群聊号码:199546072
学习探讨加个人(可以免费帮忙下载CSDN资源):
qq:1126137994
微信:liu1126137994