「转载」简单的ld链接脚本学习

本文转载自:https://blog.csdn.net/w_virgil/article/details/83036358

一、 链接脚本的整体认识

什么是链接文件呢?作用是什么呢?
当编写了多个C文件时,我们将他们编译链接成一个可执行的文件,此时就需要用到链接脚本文件(ld)。ld脚本主要功能就是:将多个目标文件(.o)和库文件(.a)链接成一个可执行的文件。

链接脚本文件主要有什么内容呢? 为了规范,我们分为三个部分:

1.链接配置(可有可无)
如一些符号变量的定义、入口地址、输出格式等

STACK_SIZE = 0X200;
OUTPUT_FORMAT(elf32-littlearm)
OUTPUT_ARCH(arm)
ENTRY(_start)

 2.内存布局定义

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

MEMORY
{
FLASH (rx) : ORIGIN = 0, LENGTH = 64K
}

 3.段链接定义

脚本中以SECTIONS命令定义一些段(text、data、bss等段)链接分布。

SECTIONS
{
    .text :
    {
      *(.text*)
    } > FLASH
}

 .text段即代码段,* (.text*)指示将工程中所有目标文件的.text段链接到FLASH中。

二、常用关键字、命令

1.MEMORY命令

使用MEMORY来定义内存如下:

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 = 0, LENGTH = 256K
ram (!rx) : org = 0×40000000, l = 4M
}

2.定位符号‘.’的使用

‘.’表示当前地址,它可以被赋值也可以赋值给某个变量。
如下为将当前地址赋值给某个变量(链接器链接是按照SECTIONS里的段顺序排列的,前面的排列完之后就能计算出当前地址

RAM_START = .;

 如下为将段存放在特定的地址中:

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

 “. = 0×10000;”该语句表示将当前地址设置为0x10000。如上代码中,意思是将所有目标文件的text段从0x10000地址开始存放。

3.SECTIONS 命令

SECTIONS基本的命令语法如下:

 
SECTIONS
{
       ...
      secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
      { 
        contents 
      } >region :phdr =fill
      ...
}
 

 这么多参数中,只有secname 和 contents 是必须的,即可简写成:

SECTIONS
{
       ...
      secname :
      { 
        contents 
      } 
      ...
}

 链接脚本本质就是描述输入和输出。secname表示输出文件的段,即输出文件中有哪些段。而contents就是描述输出文件的这个段从哪些文件里抽取而来,即输入文件,一般就是目标文件之类的。
如下,将目标文件的数据段存放在输出文件的数据段(段名自己定义,段名前后必须要有空格)

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

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

我们继续来讲一下其他参数。

    start :表示将某个段强制链接到的地址。
    AT(addr):实现存放地址和加载地址不一致的功能,AT表示在文件中存放的位置,而在内存里呢,按照普通方式存储。
    region:这个region就是前面说的MEMORY命令定义的位置信息。

4.PROVIDE关键字:

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

 
SECTIONS
{
    .text :
    {
        *(.text)
        _etext = .;
        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。

    KEEP 关键字

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

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

 

三、简单示例

 

下面以KL26芯片的链接脚本作为一个简单的示例,代码如下:

/*
 * In this linker script there is no heap available.
 * The stack start at the end of the ram segment.
 */
STACK_SIZE = 0x2000;             /*  stack size config   8k        */

/*
 * Take a look in the "The GNU linker" manual, here you get
 * the following information about the "MEMORY":
 *
 * "The MEMORY command describes the location and size of 
 * blocks of memory in the target."
 */
MEMORY
{
   FLASH_INT     (rx)  : ORIGIN = 0x00000000, LENGTH = 0x00000100
   FLASH_CONFIG  (rx)  : ORIGIN = 0x00000400, LENGTH = 0x00000010
   FLASH_TEXT    (rx)  : ORIGIN = 0x00000410, LENGTH = 0x0001F7F0
   RAM           (rwx) : ORIGIN = 0x1FFFF000, LENGTH = 16K
}

/*
 * And the "SECTION" is used for:
 *
 * "The SECTIONS command tells the linker how to map input
 * sections into output sections, and how to place the output
 * sections in memory.
 */
SECTIONS
{
   /* The startup code goes first into internal flash */
    .interrupts :
    {
      __VECTOR_TABLE = .;
      . = ALIGN(4);
      KEEP(*(.vectors))         /* Startup code */
      . = ALIGN(4);
    } > FLASH_INT

    .flash_config :
    {
      . = ALIGN(4);
      KEEP(*(.FlashConfig))    /* Flash Configuration Field (FCF) */
      . = ALIGN(4);
    } > FLASH_CONFIG

     .text :
    {
        _stext = .;           /* Provide the name for the start of this section */
  
        *(.text)
        *(.text.*)              /*  cpp namespace function      */
        *(.romrun)              /*  rom中必须的函数             */
        
        . = ALIGN(4);           /* Align the start of the rodata part */
        *(.rodata)              /*  read-only data (constants)  */
        *(.rodata*)
        *(.glue_7)
        *(.glue_7t)
    } > FLASH_TEXT

    /* section information for simple shell symbols */
    .text :
    {
        . = ALIGN(4);
        __shellsym_tab_start = .;
        KEEP(*(.shellsymbol))
        __shellsym_tab_end = .;
    } >FLASH_TEXT

    /* .ARM.exidx is sorted, so has to go in its own output section */
    . = ALIGN(4);
     __exidx_start = .;
     PROVIDE(__exidx_start = __exidx_start);
    .ARM.exidx :
    {
        /* __exidx_start = .; */
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
        /* __exidx_end = .;   */
    } > FLASH_TEXT
    . = ALIGN(4);
     __exidx_end = .;
     PROVIDE(__exidx_end = __exidx_end);

    /*
     * C++ 全局对象构造与析构函数表
     * 这里放在 .text 和 .ARM.exidx 之后, .data 之前,
     * 这里的  LMA 和 VMA 相同, 如果放在 .data 之后, LMA 与 VMA 不同,
     * 则需要启动程序从装载区搬运到运行区
     */

    . = ALIGN(4);
    .ctors :
    {
        KEEP (*cppRtBegin*.o(.ctors))
        KEEP (*(.preinit_array))
        KEEP (*(.init_array))
        KEEP (*(SORT(.ctors.*)))
        KEEP (*(.ctors))
        KEEP (*cppRtEnd*.o(.ctors))
    }

    .dtors :
    {
        KEEP (*cppRtBegin*.o(.dtors))
        KEEP (*(.fini_array))
        KEEP (*(SORT(.dtors.*)))
        KEEP (*(.dtors))
        KEEP (*cppRtEnd*.o(.dtors))
    }

    /* .data 段数据初始化内容放在这里 */
    . = ALIGN(16);
    _etext = . ;
    PROVIDE (etext = .);
    
   /*
    * The ".data" section is used for initialized data
    * and for functions (.fastrun) which should be copied 
    * from flash to ram. This functions will later be
    * executed from ram instead of flash.
    */
   .data : AT (_etext)
   {
      . = ALIGN(4);        /* Align the start of the section */
      _sdata = .;          /* Provide the name for the start of this section */
      
      *(.data)
      *(.data.*)
      
      . = ALIGN(4);        /* Align the start of the fastrun part */
      *(.fastrun)
      *(.fastrun.*)
      
      . = ALIGN(4);        /* Align the end of the section */
   } > 
   
   _edata = .;             /* Provide the name for the end of this section */
   
   USB_RAM_GAP = DEFINED(__usb_ram_size__) ? __usb_ram_size__ : 0x800;
   /*
    * The ".bss" section is used for uninitialized data.
    * This section will be cleared by the startup code.
    */
   .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 */
   
    /* 系统堆 */
    . = ALIGN(4);
    PROVIDE (__heap_start__ = .);
    .heap (NOLOAD) :
    {

    } > RAM
    . = ORIGIN(RAM) + LENGTH(RAM) - STACK_SIZE;
    . = ALIGN(4);
    PROVIDE (__heap_end__ = .);
   
   /* 
    * The ".stack" section is our stack.
    * Here this section starts at the end of the ram segment.
    */
   _estack = ORIGIN(RAM) + LENGTH(RAM);

   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)
   }
}

/*** EOF **/
 

猜你喜欢

转载自www.cnblogs.com/cloneycs/p/12315895.html
LD
今日推荐