本文中描述了默认链接脚本中使用的一些链接脚本关键词的功能。
PROVIDE 相关命令与 . 标号
PROVIDE 命令
首先我们需要关注 PROVIDE 这个命令,其含义如下:
在一些情况中,链接脚本需要定义一个只被引用且没有在链接时使用的任何对象
文件中被定义的符号。例如,传统的链接器定义了 etext 符号。然而,ANSI C
需要用户能够不遇到任何错误就能将 etext 作为一个函数的名称。PROVIDE 关
键字可以被用来定义一个符号,例如 etext,相当于当它被引用且没有定义的时候提供一个默认值。
使用格式如下:
PROVIDE(symbol = expression )
下面是一个使用 PROVIDE 定义 etext 的示例:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
这里的 . 也是链接脚本的关键字,它表示的是当前行的地址。
在这个例子中,如果程序定义了 _etext,链接器将会打印一个多重定义的错误。与此相反的是,当程序定义了 etext 时,链接器将会默认使用程序中的定义。如果程序中只引用了 etext 却没有定义它,链接器将会使用链接脚本中的定义。
默认链接脚本中与这个例子相关的代码如下:
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
可以看到这里 __etext 与 _etext 以及 etext 标号都使用 PROVIDE 命令来设定为当前行的地址(. 表示当前行的地址),当用户程序中也定义了相同的符号名时,将会直接使用用户程序中定义的符号的地址。
PROVIDE HIDDEN 命令
与 PROVIDE 类似。对于 ELF 格式的目标来说,这个符号将被隐藏不会被导出。
链接脚本中的 section 通配符
在上面对 .interp section 的描述中,我们可以看到它对应的内容为 {*(.interp)}。 这里实际上使用了类似 shell 中通配符匹配的功能来匹配任意输入文件名中存在的 .interp section。
链接脚本中的通配符可以用来匹配文件名、section 名,元字符与功能描述见下表。
元字符 | 功能描述 |
---|---|
* | 匹配任意数量的字符 |
? | 匹配任意一个字符 |
[chars] | 匹配多个字符的单个实例, - 字符可以被用来指定字符的范围,[a-c] 表示匹配任意一个小写字母 |
\ | 括起来后面的字符 |
注意文件名匹配只会在命令行参数中显式指定的文件与 INPUT 命令中指定的文件中匹配,链接器不会向 shell 一样搜索目录来进行通配符扩展!
当文件名匹配到多个模式时,链接器将会使用匹配到的第一个模式。
链接器的 --gc-sections 选项
–gc-sections 全称为 Garbage Collection,主要用于回收”垃圾“ section,这里“垃圾” section 指的是代码中没有引用到的 sections,开启了这个选项后像构造函数与析构函数等等没有被程序引用的函数、数据所在的 section 将不会放到输出文件中。
由于这里的回收针对的是未引用的函数、数据,在编译时需要指定 -ffunction-sections,-fdata-sections 来为每一个函数、数据元素创建一个单独的 section。
对于 –ffunction-sections 与 -fdata-sections 的使用,可以参考 kpatch 项目中 kpatch-build 过程比对打补丁前后代码区别的原理,它就使用了这两个参数编译内核,为每一个函数单独创建一个 section,这样才能根据打补丁前后每个 section 的内容对比出变化的函数。
KEEP 命令
那么有没有方法来阻止这样的行为呢?KEEP 命令就是用来干这个活的!
由于链接器无法预期用户在链接是传入的参数,因此对一些程序没有引用却需要放到输出文件中的 section,如构造函数与析构函数等等,链接脚本中通过使用 KEEP 命令来阻止链接器回收这些 section,我们可以在默认链接脚本中看到很多 KEEP 命令的使用实例。