1. 程式人生 > >簡單的ld連結指令碼學習

簡單的ld連結指令碼學習

一、 連結指令碼的整體認識

什麼是連結檔案呢?作用是什麼呢? 當編寫了多個C檔案時,我們將他們編譯連結成一個可執行的檔案,此時就需要用到連結指令碼檔案(ld)。ld指令碼主要功能就是:將多個目標檔案(.o)和庫檔案(.a)連結成一個可執行的檔案。

連結指令碼檔案主要有什麼內容呢? 為了規範,我們分為三個部分:

  1. 連結配置(可有可無)

如一些符號變數的定義、入口地址、輸出格式等

STACK_SIZE = 0X200;
OUTPUT_FORMAT(elf32-littlearm)
OUTPUT_ARCH(arm)
ENTRY(_start)
  1. 記憶體佈局定義

指令碼中以MEMORY命令定義了儲存空間,其中以ORIGIN定義地址空間的起始地址,LENGTH定義地址空間的長度。

MEMORY
{
FLASH (rx) : ORIGIN = 0, LENGTH = 64K
}
  1. 段連結定義

指令碼中以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
}
  1. 定位符號‘.’的使用

‘.’表示當前地址,它可以被賦值也可以賦值給某個變數。 如下為將當前地址賦值給某個變數(連結器連結是按照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段連結到輸出檔案.datad段中, 特別注意的是,之前連結的就不會再連結,這樣做的目的是可以將某些特殊的目標檔案連結到地址前面。

我們繼續來講一下其他引數。

  • start :表示將某個段強制連結到的地址。
  • AT(addr):實現存放地址和載入地址不一致的功能,AT表示在檔案中存放的位置,而在記憶體裡呢,按照普通方式儲存。
  • region:這個region就是前面說的MEMORY命令定義的位置資訊。
  1. PROVIDE關鍵字:

該關鍵字定義一個(目標檔案內被引用但沒定義)符號。相當於定義一個全域性變數,其他C檔案可以引用它。

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

如上,目標檔案可以引用etext符號,其地址為定義為.text section之後的第一個位元組的地址。C檔案中引用。

int main()
{
    //引用該變數
    extern char  _etext;
    //...
}
  1. KEEP 關鍵字

在連線命令列內使用了選項–gc-sections後,聯結器可能將某些它認為沒用的section過濾掉,此時就有必要強制聯結器保留一些特定的 section,可用KEEP()關鍵字達此目的。如KEEP(* (.text))或KEEP(SORT(*)(.text))

三、簡單示例

下面以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 **/