博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【软件开发底层知识修炼】十一 链接器-链接脚本
阅读量:1997 次
发布时间:2019-04-28

本文共 2649 字,大约阅读时间需要 8 分钟。

上一篇文章学习了链接器之-main函数不是第一个执行的函数:

今天继续学习链接器,学习链接是如何动作的,从而引入链接脚本的概念。本文就学习链接脚本的概念。

1、链接脚本的作用

我们都知道可重定位文件经过链接器链接后最终形成可执行文件。这个链接的过程大概就是分为符号解析和重定位。

那么链接器到底是如何工作的呢?这取决于链接脚本。我们可以看下图:

在这里插入图片描述

几个可重定位文件与相应的库文件进行链接,链接器经过链接器的指导,最终形成可执行程序。

在学习链接链接脚本的大致格式之前,先练总结一下链接脚本的几个作用(实际上这些作用我们都知道,只不过今天才知道链接脚本的存在而已):

  • 链接脚本用于描述链接器处理目标文件(可重定位文件)与库文件的方式

    1. 合并各个目标文件中的段
    2. 重定位各个段的起始地址
    3. 重定位各个符号的最终地址(这个地址实际上是段内偏移地址)

2、链接脚本的格式

链接脚本也就是一个脚本文件,语法比较简单,下面我们直接看一个例子,来说明一个链接脚本大致有哪些内容:

在这里插入图片描述

上述的的描述还是很全面很仔细的。我们主要注意一下几点:

  • 各个段的链接地址必须符合具体平台的规则,比如在Intel处理器上与在Amd处理器上,或许各个段在整个地址空间的位置就会有一些差别
  • 链接脚本中能够直接定义标识符并制定存储地址
  • 链接脚本能够直接定义源代码(需要编译的.c .c++程序)中的标识符的地址
  • 我们主要学习Linux系统,在Linux中代码段(.text段的地址范围为[0x08048000,0x08049000])

我们现在可能还不理解上面的几条注意事项,但是经过下面的例子,就一定可以理解了:

我们写了如下的C程序与链接脚本:

8-1.c

#include 
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 
#include
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

转载地址:http://mbytf.baihongyu.com/

你可能感兴趣的文章
网络编程之 Socket函数 (一)
查看>>
网络编程之 Socket函数 (二)
查看>>
网络编程之 Socket的模式(一) --- “阻塞/非阻塞” 与 “同步/异步”
查看>>
网络编程之 Socket的模式(二) --- “Linux网络I/O模型”
查看>>
网络编程之 Socket的模式(三) --- “Window网络I/O模型”
查看>>
网络编程之 Socket的模式(四) --- “Window网络I/O模型” 续
查看>>
ffmpeg & mplayer & vlc 手册
查看>>
视频编解码学习之二:编解码框架
查看>>
Redis拓展篇----过期策略
查看>>
Redis学习拓展篇---保护Redis
查看>>
Golang源码学习----string包
查看>>
Go语言并发组件
查看>>
Go语言的并发模式
查看>>
Linux中如何优雅的删除被打开的文件
查看>>
从零开始学Linux内核-----从Unix到Linux
查看>>
Linux内核学习----进程管理
查看>>
linux内核学习-----进程调度
查看>>
算法实现----二分查找go语言实现
查看>>
Redis中面试常见的问题整理
查看>>
Linux学习---中断和中断处理
查看>>