相信很多小伙伴第一次使用STM32CubeIDE进行开发遇到GNU LD脚本时都是一脸懵逼,在Keil中我们会使用分散加载文件进行类似操作,那么GNU LD链接器使用的链接脚本是怎样呢?
本篇就根据CubeIDE中的ld脚本说明链接脚本文件的组成(更加详细的内容请参考下面的链接)。
参考文档
GNU LD脚本命令语言(一)_coder.mark的博客-CSDN博客https://blog.csdn.net/tianizimark/article/details/125865933 GNU LD脚本命令语言(二)_coder.mark的博客-CSDN博客https://blog.csdn.net/tianizimark/article/details/125899178
下面就让我们针对具体的链接脚本进行讲解。
/*
******************************************************************************
**
** File : LinkerScript.ld
**
** Author : STM32CubeIDE
**
** Abstract : Linker script for STM32H7 series
** 128Kbytes FLASH and 1056Kbytes RAM
**
** Set heap size, stack size and stack location according
** to application requirements.
**
** Set memory bank area and size if external memory is used.
**
** Target : STMicroelectronics STM32
**
** Distribution: The file is distributed as is, without any warranty
** of any kind.
**
*****************************************************************************
** @attention
**
** Copyright (c) 2022 STMicroelectronics.
** All rights reserved.
**
** This software is licensed under terms that can be found in the LICENSE file
** in the root directory of this software component.
** If no LICENSE file comes with this software, it is provided AS-IS.
**
****************************************************************************
*/
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM_D1) + LENGTH(RAM_D1); /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200 ; /* required amount of heap */
_Min_Stack_Size = 0x400 ; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
}
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .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
/* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.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);
/* 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 */
*(.RamFunc) /* .RamFunc sections */
*(.RamFunc*) /* .RamFunc* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM_D1 AT> FLASH
/* Uninitialized data section */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss section */
_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_D1
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM_D1
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
ENTRY(RESET_Handler)定义了程序执行的第一条指令的地址,即复位中断处理函数。
_estack = ORIGIN(RAM_D1) + LENGTH(RAM_D1); 定义了一个名为_estack的符号,其值为RAM_D1区的最后地址。ORIGIN函数用来REGION的首地址,LENGTH用来获取REGION的长度。
_Min_Heap_Size = 0x200 ;定义了最小堆大小
_Min_Stack_Size = 0x400;定义了最小栈大小
MEMORY定义了各个Region的首地址和大小,从脚本上STM32H750有如下地址空间
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
SECTIONS定义了输入段如何映射到输出段中,我们看一下第一条输出段描述:
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
将所有输入文件中的.isr_vector段都放到输出文件中的.isr_vector段中,并且设置该输出段的地址为Flash Region的首地址;KEEP命令的作用是即使此输入段没有被其他输入段引用也要保留在输出文件中;ALIGN(4)指对当前地址进行4字节对齐;此处的当前地址即Flash Region的首地址。
这个输出段描述使用">FLASH"用于指定VMA地址,所以location counter的初始值不再是0,而是在FLASH REGION的地址空间中,并且由于.isr_vector输出段是FLASH REGION的第一个输出段,所以.isr_vector就放在FLASH的首地址中。
/* 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 */
*(.RamFunc) /* .RamFunc sections */
*(.RamFunc*) /* .RamFunc* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM_D1 AT> FLASH
上面的示例中是定义一个输出段.data,需要特别说明的是>RAM_D1 AT> FLASH
其中>RAM_D1是指输出段的VMA地址是在RAM_D1的地址空间中,也就是说程序执行时这个段的内容要拷贝到RAM_D1的地址空间中;
AT> FLASH是指输出段的LMA地址是在FLASH中,也就是说这些数据需要被烧写在指定的Flash地址空间中;
关于LMA和VMA的区别请查看参考文档中的说明,这边只要记住RW段需要加载到Flash中的LMA地址处,在程序执行时数据需要从Flash中拷贝到RAM中的VMA地址处。
关于data段VMA和LMA的区别我们也可以从MAP文件中进一步加深了解
上图中data段的LMA地址为0x08012a24,VMA地址为0x24000000
KEEP (*(SORT(.fini_array.*)))
在进行输出段描述时,我们可以将输入段进行排序然后在映射到输出段,此处的SORT关键字,就是对所有输入段中满足.fini_array.开头的段进行字母升序排序,然后映射到输出段中;KEEP的含义上面已经说明。
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
字面上的意思是把所有输入文件中的.ARM.attributes的段都放到.ARM.attributes输出段中,并且设置VMA为0,因为我们不会在代码中访问任何地址是0的符号(函数或者数据),所以有点类似于"/DISCARD/"。如果有小伙伴知道准确的含义可以留言告诉我,谢谢。
通过上面的讲解,相信大家应该有了一个初步的认识,若有不清楚的地方,请再次详细阅读一下参考资料。