RISC-V MCU ld鏈接腳本說明 -- 以CH32V103為例


RISC-V MCU ld鏈接腳本說明 -- 以CH32V103為例

1、什么是ld鏈接腳本?

通常,程序編譯的最后一步就是鏈接,此過程根據“*.ld”鏈接文件將多個目標文件(.o)和庫文件(.a)輸入文件鏈接成一個可執行輸出文件(.elf)。涉及到對空間和地址的分配以及符號解析與重定位

而ld鏈接腳本控制這整個鏈接過程,主要用於規定各輸入文件中的程序、數據等內容段在輸出文件中的空間和地址如何分配。通俗的講,鏈接腳本用於描述輸入文件中的段,將其映射到輸出文件中,並指定輸出文件中的內存分配。

image

2、ld鏈接腳本的主要內容

2.1 鏈接配置(可選)

常見的配置有入口地址、輸出格式、符號變量的定義等。如:

ENTRY( _start ) /* 入口地址 */ 

__stack_size = 2048; /* 定義棧大小 */
PROVIDE( _stack_size = __stack_size );/* 定義_stack_size符號,類似於全局變量 */

2.2 內存布局定義

對MCU的Flash及RAM空間進行分配,其中以ORIGIN定義地址空間的起始地址,LENGTH定義地址空間的長度。

語法如下:

MEMORY
{
	name[(attr)] : ORIGIN = origin, LENGTH = length
	...
}

這里的attr只能由以下特性組成

'R' - Read-only section

'W' - Read/write section

'X' - Executable section

'A' - Allocatable section

'I' - Initialized section

'L' - Same as I

'!' - Invert the sense of any of the attributes that follow

2.3 段鏈接定義

用於定義目標文件(.o)textdatabss等段的鏈接分布。語法如下:

SECTIONS
{
	section [address] [(type)] :
    [AT(lma)]
    [ALIGN(section_align) | ALIGN_WITH_INPUT]
    [SUBALIGN(subsection_align)]
    [constraint]
    {
        output-section-command
        output-section-command
        ...
    } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] [,]
    
    ...
}

/* 大多數的段僅使用了上述部分屬性,可以簡寫成如下形式 */
SECTIONS
{
      ...
      secname :
      { 
        output-section-command 
      } 
      ...
}

鏈接腳本本質就是描述輸入和輸出。secname表示輸出文件的段,而output-section-command用來描述輸出文件的這個段從哪些文件里抽取而來,即輸入目標文件(.o)和庫文件(.a)

Section 分為loadable(可加載)和allocatable(可分配)兩種類型。不可加載也不可分配的內存段,通常包含一些調試等信息。
loadable:程序運行時,該段應該被加載到內存中。
allocatable:該段內容被預留出,同時不應該加載任何其他內容(某些情況下,這些內存必須歸零)。

loadable和allocatable的section都有兩個地址:"VMA"和"LMA"。
VMA (the vortual memory address):運行輸出文件時,該section的地址。可選項,可不配置。
LAM (load memory address):加載section時的地址。
在大多數情況下,這兩個地址時相同的。但有些情況下,需將代碼從Flash中加載至RAM運行,此時Flash地址為LAM,RAM地址為VMA。如:

.data :
    {
        *(.data .data.*)
        . = ALIGN(8);
        PROVIDE( __global_pointer$ = . + 0x800 );
        *(.sdata .sdata.*)
        *(.sdata2.*)
        . = ALIGN(4);
        PROVIDE( _edata = .);
   } >RAM AT>FLASH

上述示例中,.data段的內容會放在Flash中,但是運行時,會加載至RAM中(通常為初始化全局變量),即.data段的VMA為RAM,LMA為Flash

3、常用關鍵字及命令

3.1 ENTRY

語法:ENTRY(symbol),程序中要執行的第一個指令,也稱入口點。示例:

/* Entry Point */
ENTRY( _start ) /* CH32V103為啟動文件 j handle_reset 指令*/

3.2 PROVIDE

語法:PROVIDE (symbol = expression),用於定義一個可被引用的符號,類似於全局變量。示例:

PROVIDE( end = . ); 

3.3 HIDDEN

語法:HIDDEN (symbol = expression),對於ELF目標端口,符號將被隱藏且不被導出。示例:

HIDDEN (mySymbol = .);

3.4 PROVIDE_HIDDEN

語法:PROVIDE_HIDDEN (symbol = expression),是PROVIDE 和HIDDEN的結合體,類似於局部變量。示例:

PROVIDE_HIDDEN (__preinit_array_start = .);

3.5 點位符號 '.'

‘.’表示當前地址,它是一個變量,總是代表輸出文件中的一個地址(根據輸入文件section的大小不斷增加,不能倒退,且只用於SECTIONS指令中)。它可以被賦值也可以賦值給某個變量;也可進行算術運算用於產生指定長度的內存空間。示例:

PROVIDE( end = . );   /* 當前地址賦值給 end符號 */

.stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
{
    . = ALIGN(4); 
    PROVIDE(_susrstack = . );
    . = . + __stack_size;    /* 當前地址加上__stack_size長度,產生__stack_size長度的空間*/
    PROVIDE( _eusrstack = .);
} >RAM 

3.6 KEEP

當鏈接器使用('--gc-sections')進行垃圾回收時,KEEP()可以使得被標記段的內容不被清楚。示例

.init :
{
    _sinit = .;
    . = ALIGN(4);
    KEEP(*(SORT_NONE(.init))) 
    . = ALIGN(4);
    _einit = .;
} >FLASH AT>FLASH

3.7 ASSERT

語法:ASSERT(exp, message),確保exp是非零值,如果為零,將以錯誤碼的形式退出鏈接文件,並輸出message。主要用於添加斷言,定位問題。

示例:

/* The usage of ASSERT */
PROVIDE (__stack_size = 0x100);

.stack
{
	PROVIDE (__stack = .);
	ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
}
/* 當"__stack" 大於 "_end + __stack_size"時,在鏈接時,會出現錯誤,並提示"Error: No room left for the stack" */

4、完整ld鏈接腳本示例

以RISC-V MCU CH32V103為例。

ENTRY( _start )  

__stack_size = 2048;

PROVIDE( _stack_size = __stack_size );


MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000 , LENGTH = 0x10000
  RAM (xrw) : ORIGIN = 0x20000000 , LENGTH = 0x5000
}


SECTIONS
{

	.init :
	{
		_sinit = .;
		. = ALIGN(4);
		KEEP(*(SORT_NONE(.init)))
		. = ALIGN(4);
		_einit = .;
	} >FLASH AT>FLASH

  .vector :
  {
      *(.vector);
	  . = ALIGN(64);
  } >FLASH AT>FLASH

  .flag :
  {
	  . = ORIGIN(FLASH)+0x8000;
	  KEEP(*(SORT_NONE(.myBufSection)))
  }>FLASH AT>FLASH

	.text :
	{
		. = ALIGN(4);
		*(.text)
		*(.text.*)
		*(.rodata)
		*(.rodata*)
		*(.glue_7)
		*(.glue_7t)
		*(.gnu.linkonce.t.*)
		. = ALIGN(4);
	} >FLASH AT>FLASH 

	.fini :
	{
		KEEP(*(SORT_NONE(.fini)))
		. = ALIGN(4);
	} >FLASH AT>FLASH

	PROVIDE( _etext = . );
	PROVIDE( _eitcm = . );	

	.preinit_array  :
	{
	  PROVIDE_HIDDEN (__preinit_array_start = .);
	  KEEP (*(.preinit_array))
	  PROVIDE_HIDDEN (__preinit_array_end = .);
	} >FLASH AT>FLASH 
	
	.init_array     :
	{
	  PROVIDE_HIDDEN (__init_array_start = .);
	  KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
	  KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
	  PROVIDE_HIDDEN (__init_array_end = .);
	} >FLASH AT>FLASH 
	
	.fini_array     :
	{
	  PROVIDE_HIDDEN (__fini_array_start = .);
	  KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
	  KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
	  PROVIDE_HIDDEN (__fini_array_end = .);
	} >FLASH AT>FLASH 
	
	.ctors          :
	{
	  /* gcc uses crtbegin.o to find the start of
	     the constructors, so we make sure it is
	     first.  Because this is a wildcard, it
	     doesn't matter if the user does not
	     actually link against crtbegin.o; the
	     linker won't look for a file to match a
	     wildcard.  The wildcard also means that it
	     doesn't matter which directory crtbegin.o
	     is in.  */
	  KEEP (*crtbegin.o(.ctors))
	  KEEP (*crtbegin?.o(.ctors))
	  /* We don't want to include the .ctor section from
	     the crtend.o file until after the sorted ctors.
	     The .ctor section from the crtend file contains the
	     end of ctors marker and it must be last */
	  KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
	  KEEP (*(SORT(.ctors.*)))
	  KEEP (*(.ctors))
	} >FLASH AT>FLASH 
	
	.dtors          :
	{
	  KEEP (*crtbegin.o(.dtors))
	  KEEP (*crtbegin?.o(.dtors))
	  KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
	  KEEP (*(SORT(.dtors.*)))
	  KEEP (*(.dtors))
	} >FLASH AT>FLASH 

	.dalign :
	{
		. = ALIGN(4);
		PROVIDE(_data_vma = .);
	} >RAM AT>FLASH	

	.dlalign :
	{
		. = ALIGN(4); 
		PROVIDE(_data_lma = .);
	} >FLASH AT>FLASH

	.data :
	{
    	*(.gnu.linkonce.r.*)
    	*(.data .data.*)
    	*(.gnu.linkonce.d.*)
		. = ALIGN(8);
    	PROVIDE( __global_pointer$ = . + 0x800 );
    	*(.sdata .sdata.*)
		*(.sdata2.*)
    	*(.gnu.linkonce.s.*)
    	. = ALIGN(8);
    	*(.srodata.cst16)
    	*(.srodata.cst8)
    	*(.srodata.cst4)
    	*(.srodata.cst2)
    	*(.srodata .srodata.*)
    	. = ALIGN(4);
		PROVIDE( _edata = .);
	} >RAM AT>FLASH

	.bss :
	{
		. = ALIGN(4);
		PROVIDE( _sbss = .);
  	    *(.sbss*)
        *(.gnu.linkonce.sb.*)
		*(.bss*)
     	*(.gnu.linkonce.b.*)		
		*(COMMON*)
		. = ALIGN(4);
		PROVIDE( _ebss = .);
	} >RAM AT>FLASH

	PROVIDE( _end = _ebss);
	PROVIDE( end = . );

    .stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
    {
        . = ALIGN(4);
        PROVIDE(_susrstack = . );
        . = . + __stack_size;
        PROVIDE( _eusrstack = .);
    } >RAM 

}


免責聲明!

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



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