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

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

1、链接脚本的作用

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

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

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

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

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

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

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