每一个链接都是由链接脚本控制的,链接脚本是用链接命令语言编写的脚本。链接都会用到一个链接脚本,如果你没有指定自己的脚本,就会使用默认的链接脚本。可以用 "--verbose" 命令行选项显示默认的连接脚本。指定命令行参数,比如 '-r'、'-N'都会影响默认的链接脚本。也可以用 '-T' 来指定自己的链接脚本,也可以隐式地把自己的连接脚本当成链接输入文件,就像普通的链接文件一
样,参见链接文件说明。
如上图,链接器是将多个对象文件链接成可执行程序。
链接器输入文件:目标文件或链接脚本文件;
链接器输出文件:可执行文件;
目标文件(包括可执行文件) 具有固定的格式,在 Unix 或GNU/linux 平台下,一般为 ELF 格式。
链接器是由链接器脚本控制,该链接器脚本控制输入文件的链接方式。该脚本以连接器命令语言编写,控制一下链接属性:
链接器有一个内置脚本,它用作将代码和数据分配到内存的默认链接器脚本。用户不能修改默认脚本,但是,它可以通过两种方式进行更改:
指定为普通链接器输入文件的脚本称为隐式脚本。因为它们扩充了默认链接描述文件,所以隐式脚本通常只包含 symbol assignments(符号分配) 或INPUT、GROUP、VERSION 命令。
在连接器脚本中,分号通常仅出于美观用作分隔符,否则将被忽略。但一下地方是必需的:
可以使用标准 C 分隔符将注释包含在链接描述文件中:
/* ... */
文件或格式名称等字符串通常可以直接输入,无需分隔符。
如果文件名包含逗号等字符,否则该字符将用于分隔文件名,则文件名可以用双引号引起来。 文件名中不能使用双引号字符。
许多命令参数接受算术表达式。 表达式的语法与 C 中的表达式语法相同,具有以下特点:
链接器中有个特殊变量:点号 ' . '
点号始终包含当前输出位置计数器,由于 点号 始终引入输出节中的位置,它必须始终出现在 SECTIONS 命令中的表达式中。
点号 可以出现在表达式中允许使用普通符号的任何位置,但它的赋值有副作用。
程序运行的第一条指令就是调用入口地址,可以使用 ENTRY 链接命令来指定程序的入口地址。
入口地址的指定方式有:
使用 INCLUDE 将其他链接脚本包含到当前脚本,链接器会在当前目录和用 -L 参数指定过的目录下查找被包含的文件。
链接脚本可以嵌套包含,最多层数为 10 层。
INCLUDE 既可以放在链接脚本的开始,也可以放在 MEMROY 或 SECTIONS 命令里面,或放在输出节的描述里面。
格式:INCLUDE filename
功能:包含其他脚本文件。
搜索路径:当前目录、-L添加的目录。
放置位置:链接脚本开始、MEMORY或SECTIONS中、输出节描述中。
INPUT 命令直接链接指定的文件名,就像从命令行输入一样。
例如如果要包含subr.o,
但是又不想在每一条链接命令中都写上的话,就可以在链接脚本中使用 ‘INPUT(subr.o)’。实际上,还可以把所有输入文件 (*.o) 都写在链接脚本里面,然后只要用 ' -T ' 指定一下链接脚本就好了。
为了防止设置了根目录,文件名要用'/'开始,这样连接脚本就会从根目录开始检索文件,否则,链接器就会在当前目录下查找文件,如果找不到文件,链接器就会在所有归档库里面检索。根目录也可以在文件名一开始的时候用 ‘=’ 来强制指定,或者在文件名前面加上 $SYSROOT。
如果使用 ‘INPUT (-lfile)’,链接器会自动翻译成libfile.a,就好像使用命令行参数 "-l" 一样。如果使用INPUT命令在链接脚本中包含文件的话,文件会从链接脚本所在的目录开始检索。这会影响到归档文件的检索。
格式:
INPUT(file, file, …)
INPUT(file file …)
功能:指定要链接的输入文件(.o,.a)。 搜索路径:$SYSROOT、当前目录。
subr.o包含:INPUT(subr.o)
libfile.a包含:INPUT(-lfile)
格式:
GROUP(file, file, …)
GROUP(file file …)
GROUP命令语法跟 INPUT 差不多,但是专门用来指定归档文件 (*.a),这个会不断的检索直到发现一个新的未定义的符号引用。参见命令行参数里面关于 '-(' 的描述。
OUTPUT用来设置输出文件的名称,等价的命令行参数为-o filename。默认输出文件名称为a.out。
格式:OUTPUT(filename)
功能:设置输出文件名称。 等价命令行参数:-o filename
SEARCH_DIR命令用来添加链接器的搜索路径,等价的命令行参数是 -L path。
如果即用了 -L 也用了SEARCH_DIR,那么链接器会优先使用 -L 设置的路径。
格式:SEARCH_DIR(path)
功能:添加链接器的搜索路径。
等价命令行参数:-L path
STARTUP命令用来指定第一个被链接的输入文件,等价于命令行中第一个输入文件,当目标操作系统的要求程序入口地址必须位于第一个输入文件的时候使用。
格式:STARTUP(filename)
功能:指定第一个被链接的输入文件。
使用场景:目标操作系统的要求程序入口地址必须位于第一个输入文件的时候。
OUTPUT_FORMAT命令用来设置输出文件使用的BFD格式。等价的命令行参数为 --oformat bfdname。命令行参数优先。
OUTPUT_FORMAT可以设置三个格式,当命令行没有 -EB 和 -EL的时候,使用第一个格式,当有-EB的时候使用第二个参数,当有 -EL 的时候,使用第三个参数。
格式:
OUTPUT_FORMAT(bfdname)
OUTPUT_FORMAT(default, big, little)
功能:设置输出文件使用的BFD格式。 等价命令行:--oformat bfdname
TARGET命令用来设置链接器读取输入文件的时候使用的BFD格式。等价命令行参数 -b bfdname。
格式:
TARGET(bfdname)
symbol = expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;
symbol <<= expression ;
symbol >>= expression ;
symbol &= expression ;
symbol |= expression ;
符号被定义为在脚本中具有全局范围;
符号赋值语句在两方面与链接器脚本表达式中使用运算符不同:
赋值语句可以出现在下面位置:
格式:
HIDDEN(symbol = expression)
定义成 HIDDEN 的符号不会被输出到目标文件,如:
HIDDEN(floating_point = 0);
SECTIONS
{
.text :
{
*(.text)
HIDDEN(_etext = .);
}
HIDDEN(_bdata = (. + 3) & ~ 3);
.data : { *(.data) }
}
格式:
PROVIDE(symbol = expression)
定义一个输入文件里面引用但未定义的符号,如:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
_etext 可以在输入文件中引用,如果输入文件中也定义了 _etext,那么优先使用输入文件中的,但是如果输入文件中定义了_etext,链接的时候就会报多重定义的错误。
跟 PROVIDE 功能类似,但不会输出到目标文件中。
SECTIONS
{
sections-command
sections-command
…
}
在一个脚本文件中只能声明一条 SECTIONS 命令,但是该命令可以包含任意数量的语句来指定必要的映射和放置信息。
每一个SECTIONS 的组成都有:
如果链接文件中没有定义SECTIONS,那么输入文件中的节就会原封不动的输出到目标文件中。
SECTIONS命令中最常用的语句是输出部分描述,它指定了输出部分的属性:它的位置、对齐方式、内容、填充模式和目标内存区域。
格式:
section [address] [(type)] :
[AT(lma)]
[ALIGN(section_align) | ALIGN_WITH_INPUT]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
…
} [>region] [AT>lma_region] [:phdr :phdr …] [=fillexp] [,]
输入section 的描述指定了被映射到输出section 的输入部分。
*(.init.rodata .init.rodata.*)
这里使用了通配符,表示输入部分为:
这句话意思是:将所有目标文件中的 .init.rodata 段和 .init.rodata.* 段都包含到输出 section 中。
* 号:任意数量的字符;
? 号:任何单个字符;
[CHARS] 号:匹配任意一个CHARS内的单个字符,可用 ' - ' 号表示范围。例如,[ A-Z ],表示匹配 A~Z之间的单个字符;
KEEP(*(.initcallearly.init))
当链接命令行使用选项 --gc-secionts 后,链接器可能将某些它认为没用的 section 过滤掉。
KEEP 用来强制链接器保留一些特定的 section。
例如上面代码,其实可以看成:
*(.initcallearly.init)
加上KEEP,则要求链接器不能优化掉这个section,哪怕是没用。
参考:
https://blog.csdn.net/shenjin_s/article/details/88712249
https://zhuanlan.zhihu.com/p/516338675