【嵌入式】如何写一个链接文件

链接文件一般以“.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!!!**")
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值