鏈接腳本文件(.ld .lds)詳解


鏈接腳本官方文檔:

https://sourceware.org/binutils/docs-2.39/ld.html 

鏈接腳本實例:(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) */
        *(.rodata*)
        *(.glue_7)
        *(.glue_7t)
        *(.gnu.linkonce.t*)

        /* section information for finsh shell */
        . = ALIGN(4);
        __fsymtab_start = .;
        KEEP(*(FSymTab))
        __fsymtab_end = .;

        . = ALIGN(4);
        __vsymtab_start = .;
        KEEP(*(VSymTab))
        __vsymtab_end = .;

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

        /* section information for at server */
        . = ALIGN(4);
        __rtatcmdtab_start = .;
        KEEP(*(RtAtCmdTab))
        __rtatcmdtab_end = .;
        . = ALIGN(4);

        /* section information for initial. */
        . = ALIGN(4);
        __rt_init_start = .;
        KEEP(*(SORT(.rti_fn*)))
        __rt_init_end = .;

        . = ALIGN(4);

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

        . = ALIGN(4);

        _etext = .;
    } > ROM = 0

    /* .ARM.exidx is sorted, so has to go in its own output section.  */
    __exidx_start = .;
    .ARM.exidx :
    {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)

        /* This is used by the startup in order to initialize the .data secion */
        _sidata = .;
    } > ROM
    __exidx_end = .;

    /* .data section which is used for initialized data */

    .data : AT (_sidata) // 設置加載首地址為 _sidata,位於ROM區
    {
        . = ALIGN(4);
        /* This is used by the startup in order to initialize the .data secion */
        _sdata = . ;

        *(.data)
        *(.data.*)
        *(.gnu.linkonce.d*)


        PROVIDE(__dtors_start__ = .);
        KEEP(*(SORT(.dtors.*)))
        KEEP(*(.dtors))
        PROVIDE(__dtors_end__ = .);

        . = ALIGN(4);
        /* This is used by the startup in order to initialize the .data secion */
        _edata = . ;
    } >RAM // 設置運行地址位於RAM區

    .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段只設置運行地址位於RAM區,不占用ROM區
    __bss_end = .;

    _end = .;

    /* Stabs debugging sections.  */
    .stab          0 : { *(.stab) }
    .stabstr       0 : { *(.stabstr) }
    .stab.excl     0 : { *(.stab.excl) }
    .stab.exclstr  0 : { *(.stab.exclstr) }
    .stab.index    0 : { *(.stab.index) }
    .stab.indexstr 0 : { *(.stab.indexstr) }
    .comment       0 : { *(.comment) }
    /* DWARF debug sections.
     * Symbols in the DWARF debugging sections are relative to the beginning
     * of the section so we begin them at 0.  */
    /* DWARF 1 */
    .debug          0 : { *(.debug) }
    .line           0 : { *(.line) }
    /* GNU DWARF 1 extensions */
    .debug_srcinfo  0 : { *(.debug_srcinfo) }
    .debug_sfnames  0 : { *(.debug_sfnames) }
    /* DWARF 1.1 and DWARF 2 */
    .debug_aranges  0 : { *(.debug_aranges) }
    .debug_pubnames 0 : { *(.debug_pubnames) }
    /* DWARF 2 */
    .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
    .debug_abbrev   0 : { *(.debug_abbrev) }
    .debug_line     0 : { *(.debug_line) }
    .debug_frame    0 : { *(.debug_frame) }
    .debug_str      0 : { *(.debug_str) }
    .debug_loc      0 : { *(.debug_loc) }
    .debug_macinfo  0 : { *(.debug_macinfo) }
    /* SGI/MIPS DWARF 2 extensions */
    .debug_weaknames 0 : { *(.debug_weaknames) }
    .debug_funcnames 0 : { *(.debug_funcnames) }
    .debug_typenames 0 : { *(.debug_typenames) }
    .debug_varnames  0 : { *(.debug_varnames) }
}

 

 特別注意:

1     .text :{}   .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 的值

 

 注:對於使用BIN文件的STM32,芯片決定了固定從0x0000_0004地址取值初始化PC,然后運行,因為0x0000_0004存的是復位中斷地址,所以程序從復位中斷開始運行;對於可執行文件是ELF,可以通過ENTRY()設置入口地址。

 

 

內存布局

腳本中以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\n", __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\n", 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命令定義的位置信息,用於指定section在哪個memory執行,也就是VMA。如果不指定LMA,LMA = VMA。

VMA 和 LMA

section包含兩個地址:VMA(virtual memory address虛擬內存地址)和LMA(load memory address加載內存地址)。通常VMA和LMA是相同的。

  • VMA是執行輸出文件時section所在的地址
  • LMA是加載輸出文件時section所在的地址

要作為運行地址,首先PC指針要能在這個地址空間內跑動,所以這段地址空間必須是可隨機尋址的,也就是說可以訪問想要訪問的地址,如RAM,NorFlash(Norflash帶有SRAM接口,有足夠的地址引腳來尋址,可以很容易地存取其內部的每一個字節);如果是NandFlash,則不行,因為NandFlash只能通過一個塊一個塊的讀/寫,不能做到隨機尋址。

VMA和LMA大多數情況下是相等的,但也可以不相等。通常,當LMA地址空間不支持隨機尋址,或者是嫌棄LMA地址空間的訪問速度比較慢時(比如NorFlash速度比SDRAM慢),則會將VMA設置到RAM中,這時,VMA與LMA就不相等了。

例:NandFlash因為不能隨機訪問想要訪問的每個地址,不能作為運行地址,所以這里想要把在NandFlash的代碼復制到SDRAM中。這里除了代碼中要加入復制模塊外,還要在鏈接腳本中使NandFlash部分的VMA設為SDRAM地址。大多數情況下,是在起始代碼中初始化時就將需要復制的部分復制的到VMA地址空間中。


還有一種情況就是當嵌入式系統中先都將代碼和數據加載到了ROM中,此時的地址就是LMA,但是當開始運行之后,需要將data數據部分拷貝到RAM中,此時數據的地址就是VMA,本文最開始的鏈接腳本,就是把.data的LMA設置在ROM,VMA設置在RAM。

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM