stm32f429 Linker Script簡介

這篇文章將會簡單的說明Linker Script在嵌入式開發上,會寫些什麼內容,如何被使用。但我並不打算詳細的說明那些語法,因為那並不是我該做的事情。我會簡要的各個段落語法的用途與意義,讓你大致掌握Linker Script在做什麼事情。

我們將以一個運行在STM32F429 Discovery上的號誌燈專案的Linker Script做為例子。

進入點

/* Entry Point */
ENTRY(Reset_Handler)

定義程式執行的進入點,吃一個symbol作為參數。要指定進入點有好幾種方式,ENTRY只是常見的一種。

配置runtime記憶體大小

/* Highest address of the user mode stack <em>/
_estack = 0x20030000; /</em> end of 192K RAM */

/* Generate a link error if heap and stack don't fit into RAM <em>/
_Min_Heap_Size = 0; /</em> required amount of heap <em>/
_Min_Stack_Size = 0x400; /</em> required amount of stack */

指定runtime stack的記憶體位置,我們會事先指定一段heap和stack size,程式設計師必須自行保證使用的stack不會超過這範圍,如果配置太多在Link時就會炸掉

配置實體記憶體

/* Specify the memory areas */
MEMORY
{
  FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 2048K
  RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 192K
  MEMORY_B1 (rx)  : ORIGIN = 0x60000000, LENGTH = 0K
  CCMRAM (rw)     : ORIGIN = 0x10000000, LENGTH = 64K
}

這裡的記憶體大小必須要按照板子實際上的狀況來配置,可以看到不同的記憶體有不同的起始位置和大小。常見的有FLASH和RAM。一般來說FLASH是燒程式的地方,RAM是跑程式的地方,至於CCMRAM是幹啥的呢?CCMRAM全名是(Core Coupled Memory),他是一種SRAM,特性是無法被DMA存取,這塊記憶體一定要由CPU自己主動存取,用途是是當一般記憶體被DMA時是CPU是動不了的,此時CPU可以使用CCRAM來提昇效能。

你可能會有疑問,為什麼配置的ORIGIN並不是從0開始。這是因為板子本身的FLASH就不是從0開始,這麼做的原因是為了保留0起頭的一段記憶體位置,因為剛boot時板子一定會從0開始讀,透過Memory Alias讓板子可以從不同的記憶體區塊boot,詳情請查閱板子的記憶體配置文件。

定義section

接下來的區塊將開始定義執行檔的sections,我們可以知道執行檔是由多個object file所組合而成的,每個object file中都有自己的section,存放不同的程式內容。接下來我們將透過Linker Script告訴Linker要怎麼把大家的section組合成執行檔中的section。

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

我們首先建立一個被稱為.isr_vector的section,關於這個section內容要放置的東西會寫在大括號內。其實.isr_vector就是一開始的中斷向量表。

.是指location counter,代表目前的記憶體位址,ALIGN是對齊的意思,代表請對齊到最接近的4的倍數,相同或更大的記憶體位址。

KEEP意指請找出並保留(不管存不存在)所有object files內被稱為.isr_vector的section,並把這些section統統放進來
最後請把該section存到我們一開始定義的FLASH區塊內。

/* The program code and other data goes into FLASH */
.text :
{
  . = ALIGN(4);
  *(.text)      /* .text sections (code) */
  *(.text</em>) /* .text* sections (code) */
  *(.glue_7)    /* glue arm to thumb code */
  *(.glue_7t)   /* glue thumb to arm code */
  *(.eh_frame)

  KEEP (*(.init))
  KEEP (*(.fini))

  . = ALIGN(4);
  _etext = .;        /* define a global symbols at end of code */

} >FLASH

接下來我們要建立.text section,這裡通常是存放實際執行的程式碼,我們會蒐集所有object file中的.text, .text*, .glue_7, .glue_7t, ...放進這個區段內,並另外建立並保留init和fini區段。

.rodata

/* Constant data goes into FLASH */
.rodata :
{
  . = ALIGN(4);
  *(.rodata)         /* .rodata sections (constants, strings, etc.) */
  *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
  . = ALIGN(4);
} >FLASH

該section存放唯讀data, 一般來說是const變數和字串。

.ARM.extab : { *(.ARM.extab .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
  __exidx_start = .;
  *(.ARM.exidx*)
  _exidx_end = .;
} >FLASH

.preinit_array :
{
  PROVIDE_HIDDEN (__preinit_array_start = .);
  KEEP (*(.preinit_array*))
  PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
  PROVIDE_HIDDEN (__init_array_start = .);
  KEEP (*(SORT(.init_array.*)))
  KEEP (*(.init_array*))
  PROVIDE_HIDDEN (init_array_end = .);
} >FLASH
.fini_array :
{
  PROVIDE_HIDDEN (__fini_array_start = .);
  KEEP (*(SORT(.fini_array.*)))
  KEEP (*(.fini_array*))
  PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH

/* used by the startup to initialize data */
_sidata = LOADADDR(.data);

.data

/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
  . = ALIGN(4);
  _sdata = .; / create a global symbol at data start */
  *(.data) / .data sections /
  *(.data) /* .data* sections */

  . = ALIGN(4);
  _edata = .;        /* define a global symbol at data end */
} >RAM AT> FLASH

保存已經初始化的全域變數和static變數

CCRAM

_siccmram = LOADADDR(.ccmram);

/* CCM-RAM section
*
* IMPORTANT NOTE!
* If initialized variables will be placed in this section,
* the startup code needs to be modified to copy the init-values.
/
.ccmram :
{
  . = ALIGN(4);
  _sccmram = .; / create a global symbol at ccmram start /
  *(.ccmram)
  *(.ccmram)

  . = ALIGN(4);
  _eccmram = .;       /* create a global symbol at ccmram end */
} >CCMRAM AT> FLASH

/* Uninitialized data section */
. = ALIGN(4);

bss

.bss :
{
/* This is used by the startup in order to initialize the .bss secion /
_sbss = .; / define a global symbol at bss start /
bss_start = _sbss;
*(.bss)
*(.bss)
*(COMMON)

. = ALIGN(4);
_ebss = .;         /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM

.bss section,通常是蒐集與存放未初始化的全域變數和static變數。

.user_heap_stack

/* Userheapstack section, used to check that there is enough RAM left */
.userheapstack :
{
  . = ALIGN(4);
  PROVIDE ( end = . );
  PROVIDE ( _end = . );
  . = . + _MinHeapSize;
  . = . + _MinStack_Size;
  . = ALIGN(4);
} >RAM

/* MEMORYbank1 section, code must be located here explicitly */
/* Example: extern int foo(void) attribute ((section (".mb1text"))); */
.memoryb1text :
{
  *(.mb1text) /* .mb1text sections (code) */
  *(.mb1text*) /* .mb1text* sections (code) */
  *(.mb1rodata) /* read-only data (constants) */
  *(.mb1rodata*)
} >MEMORYB1

用於檢查我們需要的的heap和stack size是否會超過板子上的RAM的大小。如果超過的話該section會建不出來,就會噴錯。

/Discard/

/* Remove information from the standard libraries */
/DISCARD/ :
{
  libc.a ( * )
  libm.a ( * )
  libgcc.a ( * )
}

/DISCARD/是個特別的section name,被放在這裡的input section不會被輸出成output section。

.ARM.attributes 0 : { *(.ARM.attributes) }
}