购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2 用脚本链接目标文件

什么是链接脚本?链接脚本就是程序链接时的参考文件,其目的是描述输入文件中各段应该怎样被映射到输出文件,以及程序运行时的内存布局,等等。绝大多数情况下,链接程序在链接的过程中都使用到了链接脚本。

有的读者可能会疑惑,似乎我们在利用GCC编译程序的时候并没有使用到什么链接脚本,程序依然会被成功链接并且正常执行。其实,链接工具arm-elf-ld在未被指定使用哪一个链接脚本的时候,会使用内嵌到链接工具内部的默认脚本,我们可以使用-verbose参数来查看该链接脚本。

当应用程序处在操作系统之上时,往往不需要显式指定链接脚本,因为自己编写的链接脚本可能和操作系统默认环境不符,导致运行出错,所以通常使用链接命令内置的脚本,这样可以保证程序运行环境和操作系统默认环境相一致,保证了程序的正常运行。但是,如果我们的程序是运行于操作系统之下或程序就是操作系统本身,那么链接脚本就显得十分重要了。要根据硬件平台的实际环境去编写合适的链接脚本,代码或数据才有可能出现在正确的位置上并正常运行。

本小节我们将讨论链接脚本的具体细节,使用的方式并不是简单地罗列参考手册的内容,而会针对可能用到的知识做深入的研究,将那些根本不可能用到的知识扔到垃圾桶里。

让我们首先来看一下一个典型的链接脚本是什么样子的。

代码2-2

代码2-2就是一个最简单的链接脚本,其中使用了一个非常重要的命令—SECTIONS,这个命令是用来描述输出文件的内存布局的。SECTIONS命令的标准格式如下。

代码2-3

一个SECTIONS命令由若干个sections-command组成。通过该命令,我们可以设计出程序运行时各段在内存中的分布情况。例如,在代码2-2中,代码段被安排到内存的0x1000处,数据段则分布在内存的0x8000000位置,而bss段从形式上紧挨着数据段,整个脚本看起来很容易理解。

SECTIONS命令的具体用法如下。

(1)点号(.):点号在SECTIONS命令里被称为位置计数器。通俗地讲,它代表了当前位置,你可以对位置计数器赋值,例如,第三行中我们对点号赋予了0x1000这个值,其结果是代码段的起始位置从0x1000开始。我们也可以将位置计数器的值赋给某个变量,而这个变量可以在源程序中使用。明确指定某个段的起始地址并不是必需的,就如上例中,在bss段前面就没有对位置计数器赋值,在这种情况下,位置计数器会采用一个默认值,而这个默认值也会随着各段的大小动态地增加。例如,代码2-2中,在数据之前,位置计数器的值为0x8000000,而在数据段之后,位置计数器并没有被显性赋值,其默认值是0x8000000加上数据段的长度。所以,可以说在内存中bss段形式上是紧挨着数据段分布的。当然,实际情况可能并非如此,我们稍后会做讨论。如果SECTIONS命令一开始就没有对位置计数器赋值,则其默认值为零。

(2)输出段定义:代码2-2中,各关键字代表了输出段的段名。.text关键字定义了该输出位置为代码段,花括号内部定义了代码段的具体内容,其中,星号(*)代表所有文件,*(.text)的意思是所有目标文件的代码段都将被链接到这一区域,我们也可以特别地指定某个目标文件出现在代码段的最前面。.data关键字定义了该输出位置为数据段,其格式和用法与.text关键字相同。.bss关键字定义了输出位置是bss段,格式和用法与上面相同。这种写法很可能给我们造成一种错觉,即对输出段的定义必须以text、data或bss关键字命名。其实输出段完全可以任意定义,因为输出段的实际内容与输出段的命名无关,而只与花括号内的具体内容有关。

(3)ALIGN(N):在代码2-2中并没有ALIGN关键字。但是在实际应用中,我们经常需要使用ALIGN产生对齐的代码或数据。很多体系结构对对齐的代码或数据有严格的要求,还有一些体系结构在处理对齐的数据或代码时效率更高,这都体现了ALIGN关键字的重要性。我们可以用某一数值取代ALIGN()括号中的N,同时对位置计数器赋值,如.=ALIGN(4)就表示位置计数器会向高地址方向取最近的4字节的整数倍。

链接脚本除了SECTIONS命令之外,还有一个比较常用的命令是ENTRY命令,该命令等同于arm-elf-ld命令的参数-e,即显式地指定哪一个函数为程序的入口。

现在我们可以应用上面提到的知识,针对前面的helloworld程序写一个链接脚本,其内容如下。

代码2-4

结合前面的描述,上面这段链接脚本的作用是将程序的代码段链接到地址0x0处,保证了系统上电时就能从该地址中获取第一条指令并运行。程序的数据段在代码段之后,而bss段则被安排到数据段之后。这两个段的起始地址都是经过对齐的。最后,链接脚本还明确规定将名为helloworld的函数作为整个程序的入口。这也就是说,helloworld函数就是出现在内存0x0位置上的那个函数。

好了,链接脚本写完了,那我们应该如何使用该脚本呢?这就要在运行链接命令时借助于-T参数。使用-T参数可以让链接器从脚本中读取并解析相应命令,然后按照命令的要求链接生成可执行程序。

如果针对前面的例子,它的用法会像下面这样。

命令2-4

这样我们就得到与前一小节同样的结果,但使用的方法却更加专业。 4eN51v7M0ufts12hIjJwGstJSr/ekfXeNFPoSnQljEQPclafC4UKCSVcGHxIZ1Uu

点击中间区域
呼出菜单
上一章
目录
下一章
×