链接脚本文件(.ld .lds)详解

 链接脚本实例:(STM32F407VG,RT-Thread Studio生成的工程所含)

 * linker script for STM32F407ZG with GNU ld
 */

/* Program Entry, set to mark it as "used" and avoid gc */
MEMORY
{
    ROM (rx) : ORIGIN = 0x08000000, LENGTH =  1024k /* 1024K flash */
    RAM (rw) : ORIGIN = 0x20000000, LENGTH =  128k /* 128K sram */
}
ENTRY(Reset_Handler)
_system_stack_size = 0x400;

SECTIONS
{
    .text :
    {
        . = ALIGN(4);
        _stext = .;
        KEEP(*(.isr_vector))            /* Startup code */

      . = ALIGN(4);
      *(.text) /* remaining code */
      *(.text.*) /* remaining code */
      *(.rodata) /* read-only data (constants) */


        /* section information for utest */
        . = ALIGN(4);
        __rt_utest_tc_tab_start = .;
        KEEP(*(UtestTcTab))
        __rt_utest_tc_tab_end = .;
        . = ALIGN(4);

        PROVIDE(__ctors_start__ = .);
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array))
        PROVIDE(__ctors_end__ = .);

        . = ALIGN(4);

        _etext = .;
    } > ROM = 0
    /* .data section which is used for initialized data */

    .stack : 
    {
        . = ALIGN(4);
        _sstack = .;
        . = . + _system_stack_size;
        . = ALIGN(4);
        _estack = .;
    } >RAM

    __bss_start = .;
    .bss :
    {
        . = ALIGN(4);
        /* This is used by the startup in order to initialize the .bss secion */
        _sbss = .;

        *(.bss)
        *(.bss.*)
        *(COMMON)

        . = ALIGN(4);
        /* This is used by the startup in order to initialize the .bss secion */
        _ebss = . ;
        
        *(.bss.init)
    } > RAM
    __bss_end = .;

    _end = .;

    /* Stabs debugging sections.  */
    .stab          0 : { *(.stab) }
    .stabstr       0 : { *(.stabstr) }
    .stab.excl     0 : { *(.stab.excl) }
    .stab.exclstr  0 : { *(.stab.exclstr) }
}

 特别注意:

1     .text  section :{}   .stack :{} 表示输出文件包含的 section

2     {}里面的 section,是输入文件的 section,比如 *(.isr_vector)    *(.text)    *(.rodata) 这些 .isr_vector section   .text section   .rodata section,都有指定输入文件,*表示所有的输入文件;所以 *(.isr_vector) 表示从所有的输入文件中获取所有 .isr_vector section 放在一块连续的地址空间;main.o(.data) 表示从 main.o文件中获取所有的 .data section 放在一块连续的地址空间

3     链接脚本从上往下,如果输入文件 A 已经被取出 .text section,此后输入文件 A 就没有 .text section,不能再被获取

4     关于 section 的命名,名字前可以包含 . ,也可以不包含,大多取名会包含 .

概述:

链接器:把一个或多个输入文件合并成一个输出文件,输入文件是目标文件或者链接脚本文件,输出文件是目标文件(库文件)或者可执行文件,链接器从链接脚本读完一个 section 后,将定位器符号的值增加该 section 的大小

链接脚本:控制输出文件内各部分在程序地址空间内的布局,地址空间包括 ROM 和 RAM

-T 选项用于指定自己的链接脚本,否则使用默认的链接脚本

语法:

定位符 . 

. 是定位器符号,可以对定位器符号赋值指定接下来内容的存储位置,如“ .= 0x10000”,也可以通过定位器符获取此位置的地址,比如 ”_stext = .;”,此后就可以用变量 _stext 表示此位置地址

入口地址

ENTRY(SYMBOL):将符号 SYMBOL 的值设置为入口地址,入口地址是进程执行的第一条指令在进程地址空间的地址(比如 ENTRY(Reset_Handler) 表示进程最开始从复位中断服务函数处执行

 有多种方法设置进程入口地址,以下编号越小,优先级越高

1、ld 命令行的 -e 选项

2、链接脚本的 ENTRY(SYMBOL) 命令

3、在汇编程序中定义了 start 符号,使用 start 符号值

4、如果存在 .text section,使用 .text section 首地址的值

5、使用地址 0 的值

内存布局

脚本中以MEMORY命令定义了存储空间,其中以ORIGIN定义地址空间的起始地址,LENGTH定义地址空间的长度。

结构:

MEMORY {
NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2
NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
…
}

NAME :存储区域的名字。(自己可以随意命名)

ATTR :定义该存储区域的属性。ATTR属性内可以出现以下7 个字符:

R 只读section
W 读/写section
X 可执行section
A 可分配的section
I 初始化了的section
L 同 I
! 不满足该字符之后的任何一个属性的section
ORIGIN :关键字,区域的开始地址,可简写成 org 或 o

LENGTH :关键字,区域的大小,可简写成 len 或 l

例子:

MEMORY
{
    ROM (rx) : ORIGIN = 0x08000000, LENGTH =  1024k /* 1024K flash */
    RAM (rw) : ORIGIN = 0x20000000, LENGTH =  128k /* 128K sram */
}

在链接文件中定义的变量(符号)可以在目标文件中使用

在链接文件中定义变量 

  _init_start = .;
  .application_init  : { *(.application_init) }
  _init_end = .;

 在源文件中使用变量 _init_start

#include <stdio.h>
#include <string.h>

struct _s_application_init {
    int(*function)(void);
};

extern struct _s_application_init _init_start;//段".application_init"的起始地址,在*.lds文件中定义
extern struct _s_application_init _init_end;//段".application_init"的末尾地址,在*.lds文件中定义

#define __app_init_section __attribute__((section(".application_init")))
#define __application_init(function) 
    struct _s_application_init _s_a_init_##function  __app_init_section = {function}

static int application_init_a(void)
{
    printf("execute funtion : %s
", __FUNCTION__);
    return 0;
}
__application_init(application_init_a);int main(int argc, char **argv)
{
    /*
     * 从段的起始地址开始获取数据,直到末尾地址
     */

    struct _s_application_init *pf_init = &_init_start;
    do {
        printf("Load init function from address %p
", pf_init);
        pf_init->function();
        ++pf_init;
    } while (pf_init < &_init_end);

    return 0;
}

PROVIDE 关键字(感觉有无 PROVIDE 修饰都可以被输入文件引用,看上面的例子)

该关键字定义一个(输入文件内被引用但没定义)符号。相当于定义一个全局变量的符号表,其他C文件可以通过该符号来操作对应的存储内存。

SECTIONS
{
    .text :
    {
        *(.text)        PROVIDE(_etext = .);
    }
}

如上,在链接脚本中声明了_etext 符号。特别注意的是_etext 只是一个符号,没有存储内存,并不是一个变量,该符号对应(映射)的是一个地址,其地址为 .text section 之后的第一个地址。C文件中引用用法如下:

int main()
{
    //引用该变量
    extern char  _etext;
    char *p = &_etext;
    //...
}

若在链接脚本中 " _etext = 0x100; ",即表示符号_etext对应的地址为0x100, 此时 & _etext的值为 0x100, char a= *p;表示为 从0x100地址取存储的值赋值给变量a

在目标文件内定义的符号可以在链接脚本内被赋值

此时该符号被定义为全局的. 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.

e.g. 通过下面的程序查看变量a的地址:

/* a.c */  
#include <stdio.h>
#include <stdlib.h>

int a = 100;  
int main(void)  
{  
    printf( “&a=0x%p“, &a );  
    return 0;  
}

文件a.lds

/* a.lds :注意格式有要求*/  
a = 3;  

编译命令:$gcc -Wall -o a-without-lds a.c,执行./a-without-lds输出&a = 0×8049598.

编译命令:$gcc -Wall -o a-with-lds a.c a.lds,执行a-with-lds,输出&a = 0×3

对符号的赋值只对全局变量起作用! 一些简单的赋值语句,能使用任何c语言内的赋值

除了可以在 C源文件中指定函数属于某个 section,汇编文件也可以,比如 startup.s

  .section  .text.Reset_Handler
  .weak  Reset_Handler
  .type  Reset_Handler, %function
Reset_Handler:  
  ldr   sp, =_estack     /* set stack pointer */
  bl  entry
  bx  lr    
.size  Reset_Handler, .-Reset_Handler
  .section  .isr_vector,"a",%progbits
  .type  g_pfnVectors, %object
  .size  g_pfnVectors, .-g_pfnVectors
    
g_pfnVectors:
  .word  _estack
  .word  Reset_Handler

 section 结构

SECTIONS
{
       ...
      secname [start_ADDR] [(TYPE)] : [AT (LMA_ADDR)]
      { 
        contents 
      } [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]
      ...
}

[ ]内的内容是可选选项

secname: 表示输出文件的 section 名,即输出文件中有哪些 section。而contents就是描述输出文件的这个 section 内容从哪些输入文件的哪些 section 里抽取而来。

输出section名字必须符合输出文件格式要求,比如:a.out格式的文件只允许存在.text、.data和.bss section名。而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于 section名字内,此时如果名字内包含特殊字符(比如空格、逗号等),那么需要

用引号将其组合在一起。

如下,将输入文件的数据段存放在输出文件的数据段(section 名自己定义,section 名前后必须要有空格)

SECTIONS
{
       ...
      .data :
      { 
        main.o(.data)
        *(.data)
      } 
      ...
}

其中 *(.data) 表示将所有的输入文件的 .data section 链接到输出文件 .data section 中, 特别注意的是,之前链接的就不会再链接,这样做的目的是可以将某些特殊的输入文件链接到地址前面。

start_addr :表示将某个段强制链接到的地址( VMA ),start_addr 改变定位符的值。

SECTIONS
{
   .bss :
   {
      . = ALIGN(4);        /* Align the start of the section */
      _sbss = .;           /* Provide the name for the start of this section */
      
      *(.bss)
      *(.bss.*)
      . = ALIGN(512);
      USB_RAM_START = .;
    . += USB_RAM_GAP;
      
      . = ALIGN(4);        /* Align the end of the section */
   } > RAM
   _ebss = .;              /* Provide the name for the end of this section */

   m_usb_bdt USB_RAM_START (NOLOAD) :
   {
     *(m_usb_bdt)
     USB_RAM_BDT_END = .;
   }

   m_usb_global USB_RAM_BDT_END (NOLOAD) :
   {
     *(m_usb_global)
   }
}

TYPE:每个输出section都有一个类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section的类型。它可以为以下五种值

  • NOLOAD 该section在程序运行时,不被载入内存。
  • DSECT,COPY,INFO,OVERLAY :这些类型很少被使用,为了向后兼容才被保留下来。这种类型的section必须被标记为“不可加载的”,以便在程序运行不为它们分配内存。

AT( LAM_ADDR ):输出 section 的 LMA,默认情况下 LMA 等于 VMA,但可以通过关键字 AT() 指定 LMA。

用关键字 AT()指定,括号内包含表达式,表达式的值用于设置LMA。如果不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围。这个属性主要用于构件ROM境象。

REGION:这个region就是前面说的MEMORY命令定义的位置信息。

VMA 和 LMA

section包含两个地址:VMA(virtual memory address虚拟内存地址或程序地址空间地址)和LMA(load memory address加载内存地址或进程地址空间地址)。通常VMA和LMA是相同的。

  • VMA是执行输出文件时section所在的地址
  • LMA是加载输出文件时section所在的地址

一般而言, 某section的VMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash中(由LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定)。

KEEP 关键字

在链接命令行内使用了选项 -gc-sections 后,链接器可能将某些它认为没用的 section 过滤掉,此时就有必要强制让链接器保留一些特定的 section,可用 KEEP() 关键字达此目的。如 KEEP(* (.text)) 或 KEEP(SORT(*)(.text))。说的通俗易懂就是:防止被优化。

ALIGN 关键字

表示字节对齐, 如 “ . = ALIGN(4);”表示从该地址开始后面的存储进行4字节对齐。

实例详解:

 SECTIONS
 {
   .= 0x10000;
   .text : { *(.text) }
   .= 0×8000000;
   .data : { *(.data) }
   .bss : { *(.bss) }
 }

解释一下上诉的例子:

.= 0x10000:把定位器符号置为 0x10000(若不指定,则该符号的初始值为0)

.text : { *(.text) }:*符号代表所有的输入文件,此句表示获取所有输入文件的 .text section放在一块连续的地址空间,首地址由上一句的定位器符号确定,即 0x10000

.= 0x8000000:把定位器符号置为 0x8000000

.data : { *(.data) }:获取所有输入文件的 .data section 放在一块连续的地址空间,该 section 的首地址为 0x8000000

.bss : { *(.bss) }:获取所有输入文件的 .bss section 放在一块连续的地址空间,该 section 的首地址为 0x8000000 + .data section 的大小

 输出文件包含 .text section     .data section     .bss section

原文地址:https://www.cnblogs.com/god-of-death/p/14879078.html