Skip to main content

stm32f429 Linker Script簡介

Opass
A life well lived

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

進入點

/* Entry Point */
ENTRY(Reset_Handler)

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

配置runtime記憶體大小

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

/* Generate a link error if heap and stack don't fit into RAM
_Min_Heap_Size = 0; required amount of heap <em>
_Min_Stack_Size = 0x400; 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) }
}