链接文件一般以“.lds”、“.link”为后缀的文件在嵌入式软件工程中出现。其作用是规定把输入文件内的section(部分)放入输出文件内, 以及控制输出文件内各部分在程序地址空间内的布局。
GNU ld支持通过使用链接器脚本来配置链接过程。链接器脚本是用特定于GNU ld应用程序的专用脚本语言编写的。虽然ld并不严格要求使用链接器脚本文件,但它提供了一种更易于维护的替代方法,可以在命令行上将复杂的配置选项指定为参数。链接器脚本的主要用途是指定最终可执行二进制文件的格式和布局。这与操作系统开发尤其相关,在操作系统开发中,可执行二进制文件通常需要特定的文件布局,才能被某些引导加载程序识别。GNU GRUB就是这样一个引导程序。
链接器脚本通常通过使用-T脚本来调用。调用ld应用程序时的ld命令行参数。
下面列出了链接器脚本中使用的一些重要关键字:
ENTRY(进入)
ENTRY(main)
ENTRY(MultibootEntry)
ENTRY关键字用于定义应用程序的入口点,即输出文件中的第一条可执行指令。该关键字接受链接程序/内核入口点的符号名作为单个参数。所提供的符号名指向的代码将是。ELF和PE二进制文件中的文本部分。
OUTPUT_FORMAT(输出格式)
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_FORMAT("pe-i386")
OUTPUT_FORMAT指令只接受一个参数。它指定可执行文件的输出格式。要了解系统binutils和GCC支持哪些输出格式,可以使用objdump-i命令。
STARTUP (启动)
STARTUP(Boot.o)
STARTUP(crt0.o)
启动需要一个参数。它是要链接到可执行文件开头的文件。对于userland项目,这通常是crt0。o或crtbegin。o、 对于内核,通常是包含程序集样板的文件启动堆栈,在某些情况下是GDT之类的,然后调用kmain()。
SEARCH_DIR(搜索目录)
SEARCH_DIR(Directory)
这将为您的库搜索目录添加路径。-nostlib标志将导致在该路径中找到的任何库被有效忽略。我不知道为什么,这似乎就是ld的工作原理。它将链接器脚本指定的搜索目录视为标准目录,因此会忽略它们,而不使用默认的libs和此类标志
INPUT(输入)
INPUT(File1.o File2.o File3.o ...)
INPUT
(
File1.o
File2.o
File3.o
...
)
INPUT是一个“链接器脚本中”替换项,用于将对象文件添加到命令行。您通常会指定类似于ld File1的内容。o文件2。o、 可以使用输入部分在链接器脚本中执行此操作。
OUTPUT (输出)
OUTPUT(Kernel.bin)
OUTPUT命令指定要生成的文件作为链接过程的输出。这是最终创建的二进制文件的名称。此命令的效果与-o filename命令行标志的效果相同,后者会覆盖它。
MEMORY (记忆存储)
MEMORY
{
ROM (rx) : ORIGIN = 0, LENGTH = 256k
RAM (wx) : org = 0x00100000, len = 1M
}
MEMORY声明一个或多个内存区域,其属性指定该区域是否可以写入、读取或执行。这主要用于不同地址空间区域可能包含不同访问权限的嵌入式系统。
上面的示例脚本告诉链接器有两个内存区域:
a) “ROM”从地址0x00000000开始,长度为256kB,可以读取和执行。
b) “RAM”从地址0x00100000开始,长度为1MB,可以写入、读取和执行。
SECTIONS (部分)
SECTIONS
{
.text.start (_KERNEL_BASE_) : {
startup.o( .text )
}
.text : ALIGN(0x1000) {
_TEXT_START_ = .;
*(.text)
_TEXT_END_ = .;
}
.data : ALIGN(0x1000) {
_DATA_START_ = .;
*(.data)
_DATA_END_ = .;
}
.bss : ALIGN(0x1000) {
_BSS_START_ = .;
*(.bss)
_BSS_END_ = .;
}
}
该脚本告诉链接器将“.text”部分的代码放入启动。o在开头,从逻辑地址_内核_基开始。然后是所有其他输入文件的所有“.text”、“.data”和“.bss”部分的页面对齐部分。
链接器符号定义为保存每个节的开始和结束地址。这些符号在应用程序中具有外部链接,可以作为代码中的指针访问。有关链接器文件符号的其他信息,请参见下文。
KEEP (保持)
链接器脚本中的KEEP语句将指示链接器保留指定的节,即使其中没有引用任何符号。此语句在链接器脚本的节中使用。当在链接时执行垃圾收集时,这一点就变得很重要,通过将–gc节开关传递给链接器来启用。KEEP语句指示链接器在创建依赖关系图时使用指定的节作为根节点,以查找未使用的节。本质上是强制将该部分标记为已使用。
该语句常见于针对ARM体系结构的链接器脚本中,用于将中断向量表放置在偏移量0x00000000处。如果没有这个指令,代码中可能不会显式引用的表将被删除。
SECTIONS
{
.text :
{
KEEP(*(.text.ivt))
*(.text.boot)
*(.text*)
} > ROM
/** ... **/
}
Symbols (象征)
可以在链接器脚本中定义任意符号。这些符号被添加到程序的符号表中。表中的每个符号都有一个名称和一个关联的地址。链接器脚本中已赋值的符号将被赋予外部链接,并可在程序代码中作为指针访问。
floating_point = 0;
SECTIONS
{
.text :
{
*(.text)
_etext = .;
}
_bdata = (. + 3) & ~ 3;
.data : { *(.data) }
}
在上面的示例中,符号浮点被定义为零。符号_etext被定义为最后一个字符后面的地址。文本输入部分。符号_bdata被定义为以下地址:。文本输出部分向上对齐到4字节边界。
PROVIDE
为在任何链接目标中没有定义但是被引用的一个符号,而在链接脚本定义一个符号。 PROVIDE(symbol = expression)。提供定义‘_exfun’的例子:
SECTIONS
{
.text :
{
*(.text)
_exfun = .;
PROVIDE(_exfun = .);
}
}
如果程序定义了’ _exfun ‘(带有前导下划线),链接器将给出重复定义错误。另一方面,如果程序定义了’ exfun‘(没有前导下划线),链接器会默认使用程序中的定义。如果程序引用了’ exfun '但没有定义它,链接器将使用链接器脚本中的定义。
PROVIDE指令将考虑定义一个普通符号,即使这样的符号可以与PROVIDE将创建的符号组合在一起。当考虑构造函数和析构函数列表符号时,这一点尤其重要,因为它们通常被定义为普通符号。
ASSERT(断言)
ASSERT(exp, message)
在最终链接阶段之前进行检查。表示在段内使用PROVIDE的定义,如果用户没有为其设置值,此表达式将无法通过检测。唯一的例外是PROVIDE的符号刚刚引用了’.’。
我们以Telink SIG Mesh中boot.link文件为例:
/* to tell the linker the program begin from __start label in cstartup.s, thus do not treat it as a unused symbol */
ENTRY(__start)
SECTIONS
{
. = 0x0;
.vectors :
{
*(.vectors)
*(.vectors.*) /* MUST as follows, when compile with -ffunction-sections -fdata-sections, session name may changed */
}
. = (((. + 15) / 16)*16); /* must 16 byte align if use "AT" for ".text" */
PROVIDE(_vector_end_ = . );
. = (. * (1 - __BOOT_LOADER_EN)) + (__FW_RAMCODE_SIZE_MAX * __BOOT_LOADER_EN);
PROVIDE(_ram_code_start_ = . );
.ram_code :
AT(_vector_end_)
{
*main.o(.ram_code)
*main.o(.ram_code.*)
*ota_fw_ow.o(.ram_code)
*ota_fw_ow.o(.ram_code.*)
*flash.o(.ram_code)
*flash.o(.ram_code.*)
*(.ram_code)
*(.ram_code.*)
}
. = (((. + 15) / 16)*16); /* must 16 byte align if use "AT" for ".text" */
PROVIDE(_rstored_ = . );
. = 0x4000;
PROVIDE(_ramcode_size_ = . );
PROVIDE(_ramcode_size_div_16_ = (. + 15 ) / 16);
PROVIDE(_ramcode_size_div_256_compile_ = (. + 255) / 256);
PROVIDE(_ramcode_size_div_16_align_256_ = ( (. + 255) / 256) * 16);
PROVIDE(_ramcode_size_align_256_ = ( _ramcode_size_div_16_align_256_)* 16);
. = (_rstored_ + __FW_OFFSET);
.text :
AT((_rstored_ * (1 - __BOOT_LOADER_EN)) + ((_vector_end_ + (_rstored_ - _ram_code_start_)) * __BOOT_LOADER_EN))
{
*(.text)
*(.text.*)
}
.rodata :
{
*(.rodata)
*(.rodata.*)
}
. = (((. + 3) / 4)*4);
PROVIDE(_dstored_ = .); /*location in flash*/
PROVIDE(_code_size_div_256_ = (. + 255) / 256);
PROVIDE(_dstored_bin_ = (((. - __FW_OFFSET) * (1 - __BOOT_LOADER_EN)) + ((_vector_end_ + (_dstored_ - _ram_code_start_)) * __BOOT_LOADER_EN))); /*location in bin*/
PROVIDE(_code_size_ = _dstored_bin_);
PROVIDE(_ramcode_size_div_256_ = (_ramcode_size_div_256_compile_ * (1 - __MCU_RUN_SRAM_EN)) + (_code_size_div_256_ * __MCU_RUN_SRAM_EN));
. = __RAM_START_ADDR + 0x900 + (_ramcode_size_div_256_ * 0x100); /* 0x100 alighned, must greater than or equal to:0x808000 + ram_code_size + irq_vector(0x100) + IC_tag(0x100) + IC_cache(0x800) == 0x808a00 + ram_code_size */
.data : /*is retention for 825x. is 'data' for others. must use name 'data'.*/
AT ( _dstored_bin_ )
{
. = (((. + 3) / 4)*4);
PROVIDE(_start_data_ = . );
*(.retention_data)
*(.retention_data.*)
*(.data);
*(.data.*);
. = (((. + 3) / 4)*4);
PROVIDE(_end_data_ = . );
}
.bss(NOLOAD) : /*is retention for 825x. is 'bss' for others. must use name 'bss'.*/
{
. = (((. + 3) / 4)*4); /* new section, default 16 byte aligned*/
PROVIDE(_start_bss_ = .);
*(.retention_bss)
*(.retention_bss.*)
*(.sbss)
*(.sbss.*)
*(.bss)
*(.bss.*)
}
. = (((. + 3) / 4)*4);
PROVIDE(_end_bss_ = .); /*_end_bss_ must outside of {}, if not it will not correct*/
.retention_data : /*no retention data. only for 825x, use 'retention_data', because tdebug can't know private name*/
AT (_dstored_bin_ + (_end_data_ - _start_data_))
{
. = (((. + 3) / 4)*4);
PROVIDE(_no_retention_data_start_ = . );
*(.no_ret_data)
*(.no_ret_data.*)
. = (((. + 3) / 4)*4);
PROVIDE(_no_retention_data_end_ = . );
}
.retention_bss(NOLOAD) : /*no retention data. only for 825x, use 'retention_bss', because tdebug can't know private name*/
AT (_dstored_bin_ + (_end_data_ - _start_data_) + (_no_retention_data_end_ - _no_retention_data_start_))
{
. = (((. + 3) / 4)*4);
PROVIDE(_no_retention_bss_start_ = . );
*(.no_ret_bss)
*(.no_ret_bss.*)
. = (((. + 3) / 4)*4);
PROVIDE(_no_retention_bss_end_ = . );
}
PROVIDE(_bin_size_ = _code_size_ + (_end_data_ - _start_data_)+(_no_retention_data_end_ - _no_retention_data_start_));
PROVIDE(_bin_size_div_16_ = (_bin_size_ + 15 ) / 16);
PROVIDE(_ictag_start_ = __RAM_START_ADDR + (_ramcode_size_div_256_) * 0x100); /*not for retention*/
PROVIDE(_ictag_end_ = __RAM_START_ADDR + (_ramcode_size_div_256_ + 1) * 0x100); /*not for retention*/
PROVIDE(_bootloader_ram_code_end_ = (_ram_code_start_ + _code_size_ - _vector_end_));
/*PROVIDE(_ram_use_size_div_16_ = (_retention_data_end_ - 0x840000 + 15 ) / 16); only for 825x*/
ASSERT((_end_bss_ - 0x840000) * __PM_DEEPSLEEP_RETENTION_ENABLE <= 0x8000, "**Retention Area Overflow, Check your data/bss usages**")
ASSERT((_bin_size_ + 16) * __FLASH_512K_ENABLE <= 0x30000, "**Error:firmware size Overflow!!!**")
ASSERT((_ramcode_size_div_16_align_256_ * 16 * __FW_START_BY_BOOTLOADER_EN) <= __FW_RAMCODE_SIZE_MAX, "**Error:ramcode size Overflow!!!**")
}