文章目录
链接脚本
1. 链接脚本概念
链接脚本(Linker Script,LS),任何一个可执行文件(不论是ELF还是EXE)都是由代码段(.text)、数据段(.data)、未初始化的数据段(.bss)等段(section)组成的。在程序的链接过程中,就需要链接脚本将大量编译好的二进制文件(.o文件)合并成一个二进制可执行文件,也就是把每一个二进制文件整合到一个大文件中。
2. 链接脚本格式
一个简单的链接脚本如下
SECTIONS
{
. = 0×10000;
.text : { *(.text) } // 代码段
. = 0×8000000;
.data : { *(.data) } // 数据段
.bss : { *(.bss) } // 未初始化的数据段
}
. = 0×10000 : 链接地址,把定位器符号置为0×10000 (若不指定, 则该符号的初始值为0).
.text : { *(.text) } : 将所有(*符号代表任意输入文件)输入文件的.text 段合并成一个.text 段, 该段的地址由定位器符号的值指定, 即0×10000.
. = 0×8000000 :把定位器符号置为0×8000000
.data : { *(.data) } : 将所有输入文件的.data 段合并成一个.data 段, 该段的地址被置为0×8000000.
.bss : { *(.bss) } : 将所有输入文件的.bss 段合并成一个.bss 段,该段的地址被置为0×8000000+.data 段的大小.
链接器每读完一个段描述后, 将定位器符号的值增加该段的大小. 注意: 此处没有考虑对齐约束.
2.1 加载地址链接地址、运行地址概念
1、链接地址、运行地址、加载地址、存储地址关系
-
加载地址:程序保存在介质(如Flash)中的地址。
-
链接地址:编译器编译时候,指定的a.out中第一条指令的地址
-
运行地址:a.out在内存中存储的第一条指令地址
3. SECTIONS的格式
SECTIONS是链接脚本(Linker Script,LS)语法中最重要的,最基本的,也是最主要的命令,它用于描述输出文件的内存布局。SECTIONS命令告诉链接脚本文件如何把输入文件的段映射到输出文件的各个段,如何将输入段整合为输出段,如何把输出段放入程序地址空间和进程地址空间。
SECTIONS
{
sections-command
sections-command
sections-command
}
sections-command有如下四种格式:
-
ENTRY命令;
-
符号赋值语句;
-
一个输出段的叠加描述(output section description)
-
一个段的叠加描述(overlay description)
3.1 输出段描述和叠加描述
输出段描述具有如下格式:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
…
} [>REGION] [AT>LMA_REGION] [:PHDR HDR...] [=FILLEXP]
例子:
// arch/arm64/kernel/vmlinux.lds.S
.text : { /* Real text segment */
_stext = .; /* Text and read-only data */
__exception_text_start = .;
*(.exception.text)
__exception_text_end = .;
IRQENTRY_TEXT
SOFTIRQENTRY_TEXT
ENTRY_TEXT
TEXT_TEXT
SCHED_TEXT
CPUIDLE_TEXT
LOCK_TEXT
KPROBES_TEXT
HYPERVISOR_TEXT
IDMAP_TEXT
HIBERNATE_TEXT
TRAMP_TEXT
*(.fixup)
*(.gnu.warning)
. = ALIGN(16);
*(.got) /* Global offset table */
}
. = ALIGN(SEGMENT_ALIGN);
_etext = .; /* End of text section */
注意:这里SECTION和SECTIONS命令不一样。SECTION是SECTIONS命令内的一个输出段描述符
[ ]内的内容为可选选项, 一般不需要.
SECTION:段名字
SECTION左右的空白、圆括号、冒号是必须的,换行符和其他空格是可选的。
每个OUTPUT-SECTION-COMMAND为以下四种之一,
- 符号赋值语句
- 一个输入段描述
- 直接包含的数据值
- 一个特殊的输出段关键字
3.1.1 输出段名字(SECTION)
输出段名字必须符合输出文件格式要求,比如:a.out格式的文件只允许存在.text、.data和.bss 段名。// 需要修改
输出段地址(ADDRESS):
ADDRESS是一个表达式,它的值用于设置VMA。
3.1.2 输入段描述
输入段描述是最基本的连接脚本描述。输入段描述如下
一个输入段描述,由一个文件名后跟有可选的括号中的段名列表组成。文件名和段名可以通配符形式出现。
例如:*(.text) 包含所有文件的.text段
*(.text) : 表示所有输入文件的.text 段
(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的所有输入文件的.ctors 段。
data.o(.data) : 表示data.o文件的.data 段
data.o : 表示data.o文件的所有段
*(.text .data) : 表示所有文件的.text 段和.data 段,顺序是:第一个文件的.text 段,第一个文件的.data 段,第二个文件的.text 段,第二个文件的.data 段,...
*(.text) *(.data) : 表示所有文件的.text 段和.data 段,顺序是:第一个文件的.text 段,第二个文件的.text 段,...,最后一个文件的.text 段,第一个文件的.data 段,第二个文件的.data 段,...,最后一个文件的.data 段
4. 链接脚本常见的关键字
常见关键字:
-
ENTRY(SYMBOL);将SYMBOL的值设置成入口地址。一般设置为_start。入口地址(entry point): 进程执行的第一条用户空间的指令在进程地址空间的地址)
-
OUTPUT(FILENAME); 定义输出文件的名字。可以用它来指定默认的输出文件名称。当然我们一般都用手动gcc -o进行指定,如果我们没有进行手动指定的话,输出文件名称就以这个FILENAME为输出文件名。
-
STARTUP(filename);指定filename为第一个输入文件。
-
OUTPUT_FORMAT(default, big, little);定义3种输出文件的格式。若有命令行选项-EB(大端),则使用第二个输出格式,有命令行指定-EL(小端),则使用第三个格式。否则使用默认的default输出格式。
-
OUT_ARCH(arch);设置输出文件的体系架构。
-
INCLUDE filename : 包含其他名为filename的链接脚本
相当于c程序内的的#include指令, 用以包含另一个链接脚本.
脚本搜索路径由-L选项指定. INCLUDE指令可以嵌套使用, 最大深度为10. 即: 文件1内INCLUDE文件2, 文件2内INCLUDE文件3… , 文件10内INCLUDE文件11. 那么文件11内不能再出现 INCLUDE指令了.
-
INPUT(files): 将括号内的文件做为链接过程的输入文件
ld首先在当前目录下寻找该文件,如果没找到, 则在由-L指定的搜索路径下搜索。 file可以为 -lfile形式,就象命令行的-l选项一样, 如果该命令出现在暗含的脚本内,则该命令内的file在链接过程中的顺序由该暗含的脚本在命令行内的顺序决定.
-
GROUP(files) : 指定需要重复搜索符号定义的多个输入文件
除了file必须是库文件以外,该命令与INPUT相似, 且file文件作为一组被ld重复扫描,直到不在有新的未定义的引用出现。
-
OUTPUT(FILENAME) : 定义输出文件的名字
同ld的-o选项, 不过-o选项的优先级更高. 所以它可以用来定义默认的输出文件名. 如a.out
-
SEARCH_DIR(PATH) :定义搜索路径,
同ld的-L选项, 不过由-L指定的路径要比它定义的优先被搜索。
-
STARTUP(filename) : 指定filename为第一个输入文件
在链接过程中, 每个输入文件是有顺序的. 此命令设置文件filename为第一个输入文件。就象这个文件是在命令行上第一个被指定的文件一样, 如果在一个系统中,,入口点总是存在于第一个文件中,那这个就很有用。
-
OUTPUT_FORMAT(BFDNAME) : 设置输出文件使用的BFD格式
同ld选项-o format BFDNAME, 不过ld选项优先级更高.
-
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) : 定义三种输出文件的格式(大小端)
对于此命令,要在命令行中使用-EB或-EL选项来指定不同的输出文件格式
如果’-EB’和’-EL’都没有使用, 那输出格式会是第一个参数 DEFAULT,
如果使用了’-EB’,输出格式会是第二个参数 BIG,
如果使用了’-EL’, 输出格式会是第三个参数, LITTLE.
比如:缺省的基于 MIPS ELF 平台连接脚本使用如下命令:
OUTPUT_formAT(elf32-bigmips, elf32-bigmips, elf32-littlemips)
这表示缺省的输出文件格式是’elf32-bigmips’, 但是当用户使用’-EL’命令行选项的时候, 输出文件就会被以`elf32-littlemips’格式创建.
-
TARGET(BFDNAME):设置输入文件的BFD格式
同ld选项-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 则最用一个TARGET命令设置的BFD格式将被作为输出文件的BFD格式.
其他链接脚本命令:
ASSERT(EXP, MESSAGE): 如果EXP不为真,终止连接过程EXTERN(SYMBOL SYMBOL …):在输出文件中增加未定义的符号,如同连接器选项-u
FORCE_COMMON_ALLOCATION:为common symbol(通用符号)分配空间,即使用了-r连接选项也为其分配
NOCROSSREFS(SECTION SECTION …):检查列出的输出段,如果发现他们之间有相互引用,则报错。对于某些系统,特别是内存较紧张的嵌入式系统,某些段是不能同时存在内存中的,所以他们之间不能相互引用。
OUTPUT_ARCH(BFDARCH):设置输出文件的machine architecture(体系结构),BFDARCH为被BFD库使用的名字之一。可以用命令objdump -f查看。
可通过 man -S 1 ld查看ld的联机帮助, 里面也包括了对这些命令的介绍.
-
对符号的赋值
在目标文件内定义的符号可以在链接脚本内被赋值. (注意和C语言中赋值的不同!) 此时该符号被定义为全局的, 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.
注意:在链接脚本中给符号赋值,赋的是符号对应的地址值,而不是普通的值。
5. arm64链接脚本实例
OUTPUT_ARCH(aarch64) // 输出系统的架构
ENTRY(_text) // 程序的入口
jiffies = jiffies_64; // 用于记录系统自启动到当前时刻系统时钟所产生的滴答数
SECTIONS
{
/*
* XXX: The linker does not define how output sections are
* assigned to input sections when there are multiple statements
* matching the same input section name. There is no documented
* order of matching.
*/
/DISCARD/ : {
ARM_EXIT_DISCARD(EXIT_TEXT)
ARM_EXIT_DISCARD(EXIT_DATA)
EXIT_CALL
*(.discard)
*(.discard.*)
*(.interp .dynamic)
*(.dynsym .dynstr .hash .gnu.hash)
*(.eh_frame)
}
. = KIMAGE_VADDR + TEXT_OFFSET;
.head.text : {
_text = .;
HEAD_TEXT
}
.text : { /* Real text segment */
_stext = .; /* Text and read-only data */
__exception_text_start = .;
*(.exception.text)
__exception_text_end = .;
IRQENTRY_TEXT
SOFTIRQENTRY_TEXT
ENTRY_TEXT
TEXT_TEXT
SCHED_TEXT
CPUIDLE_TEXT
LOCK_TEXT
KPROBES_TEXT
HYPERVISOR_TEXT
IDMAP_TEXT
HIBERNATE_TEXT
TRAMP_TEXT
*(.fixup)
*(.gnu.warning)
. = ALIGN(16);
*(.got) /* Global offset table */
}
. = ALIGN(SEGMENT_ALIGN);
_etext = .; /* End of text section */
RO_DATA(PAGE_SIZE) /* everything from this point to */
EXCEPTION_TABLE(8) /* __init_begin will be marked RO NX */
NOTES
. = ALIGN(SEGMENT_ALIGN);
__init_begin = .;
__inittext_begin = .;
INIT_TEXT_SECTION(8)
.exit. : {
ARM_EXIT_KEEP(EXIT_TEXT)
}
. = ALIGN(4);
.altinstructions : {
__alt_instructions = .;
*(.altinstructions)
__alt_instructions_end = .;
}
.altinstr_replacement : {
*(.altinstr_replacement)
}
. = ALIGN(PAGE_SIZE);
__inittext_end = .;
__initdata_begin = .;
.init.data : {
INIT_DATA
INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
SECURITY_INITCALL
INIT_RAM_FS
*(.init.rodata.* .init.bss) /* from the EFI stub */
}
.exit.data : {
ARM_EXIT_KEEP(EXIT_DATA)
}
PERCPU_SECTION(L1_CACHE_BYTES)
.rela.dyn : ALIGN(8) {
*(.rela .rela*)
}
__rela_offset = ABSOLUTE(ADDR(.rela.dyn) - KIMAGE_VADDR);
__rela_size = SIZEOF(.rela.dyn);
#ifdef CONFIG_RELR
.relr.dyn : ALIGN(8) {
*(.relr.dyn)
}
__relr_offset = ABSOLUTE(ADDR(.relr.dyn) - KIMAGE_VADDR);
__relr_size = SIZEOF(.relr.dyn);
#endif
. = ALIGN(SEGMENT_ALIGN);
__initdata_end = .;
__init_end = .;
_data = .;
_sdata = .;
RW_DATA_SECTION(L1_CACHE_BYTES, PAGE_SIZE, THREAD_ALIGN)
/*
* Data written with the MMU off but read with the MMU on requires
* cache lines to be invalidated, discarding up to a Cache Writeback
* Granule (CWG) of data from the cache. Keep the section that
* requires this type of maintenance to be in its own Cache Writeback
* Granule (CWG) area so the cache maintenance operations don't
* interfere with adjacent data.
*/
.mmuoff.data.write : ALIGN(SZ_2K) {
__mmuoff_data_start = .;
*(.mmuoff.data.write)
}
. = ALIGN(SZ_2K);
.mmuoff.data.read : {
*(.mmuoff.data.read)
__mmuoff_data_end = .;
}
PECOFF_EDATA_PADDING
__pecoff_data_rawsize = ABSOLUTE(. - __initdata_begin);
_edata = .;
BSS_SECTION(0, 0, 0)
. = ALIGN(PAGE_SIZE);
idmap_pg_dir = .;
. += IDMAP_DIR_SIZE;
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
tramp_pg_dir = .;
. += PAGE_SIZE;
#endif
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
reserved_ttbr0 = .;
. += RESERVED_TTBR0_SIZE;
#endif
swapper_pg_dir = .;
. += SWAPPER_DIR_SIZE;
swapper_pg_end = .;
__pecoff_data_size = ABSOLUTE(. - __initdata_begin);
_end = .;
STABS_DEBUG
HEAD_SYMBOLS
}