参考:https://www.404bugs.com/index.php/details/1084978780534788096
在介绍SECTIONS的用法之前,我们先对之前提到的LMA和VMA进行说明:每个output section都有一个LMA和一个VMA,LMA是其存储地址(即.data数据存储在fls中的地址),而VMA是其运行时地址(加载到ram中的地址),例如将全局变量g_Data所在数据段.data的LMA设为0x80000020(属于ROM地址),VMA设为0xD0004000(属于RAM地址),那么g_Data的值将存储在ROM中的0x80000020处,而程序运行时,用到g_Data的程序会到RAM中的0xD0004000处寻找它。
ENTRY(Reset_Handler) //设置入口地址,ENTRY是 LD文件的关键字
HEAP_SIZE = DEFINED(__heap_size__) ? __heap_size__ : 0x0400; //设置堆大小 ;DEFINED是 LD文件的关键字,类似ifdef
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x0400; //设置栈大小 ;DEFINED是 LD文件的关键字
MEMORY //定义链接地址空间
{
/*定义只读空间m_interrupts ,起始地址0x00000000, 大小0x00000400 */,这里是对存储空间的命名,这里定义了4片空间,取了4个名字
m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
/*定义只读空间m_text,起始地址0x00000400, 大小0x0001FC00*/
m_text (RX) : ORIGIN = 0x00000400, LENGTH = 0x0001FC00
/*定义读写空间m_data,起始地址0x20000000, 大小0x00020000*/
m_data (RW) : ORIGIN = 0x20000000, LENGTH = 0x00020000
/*定义读写空间m_data2s ,起始地址0x20200000, 大小0x00000400*/
m_data2 (RW) : ORIGIN = 0x20200000, LENGTH = 0x00040000
}
SECTIONS //定义输出段
{
__NCACHE_REGION_START = ORIGIN(m_data2); //将m_data2的起始地址赋值给__NCACHE_REGION_START, ORIGIN是LD文件的关键字,取原始值之意; = 就是赋值语句
__NCACHE_REGION_SIZE = 0;//__NCACHE_REGION_SIZE赋值0
.interrupts : //输出段描述,表示这一段是中断向量表,就是一个名字,大家可以自己取,是其下{}内所有内容的别名
{
__VECTOR_TABLE = .; //把当前位置计数器的值赋值给__VECTOR_TABLE ,位置计数器的值一开始默认为0
__Vectors = .;//把当前位置计数器的值赋值给__VECTOR_TABLE ,位置计数器的值一开始默认为0
. = ALIGN(4); //这句话不是再给位置计数器赋值,而是给它增加限制条件,表示其增加一次增加4,在内存中的表现即为4字节对齐
KEEP(*(.isr_vector))//放置所有文件中的.isr_vector section,*是通配符,表示所有,就跟我们搜索文件使用*时一样的。使用KEEP的意思就是告诉编译器这段数据非常重要,不要把它当成垃圾优化掉了
. = ALIGN(4);
} > m_interrupts //将这一输出段链接至m_interrupts区域处,所以中断向量表的链接地址就是m_interrupts的起始地址,m_interrupts是上面MEMORY定义的空间名称之一
// > m_interrupts 只有这个表示 LMA 和VMA是一个地址,不需要从fls搬到ram,如果是 > m_interrupts AT > m_data 的形式就表示LMA 和 VMA不是一个地址,VMA位于m_interrupts中,LMA位于m_data中
.text : //定义输出段,就是一个名字,大家可以自己取
{
. = ALIGN(4); //4字节对齐
*(.text) /*放置所有文件的.text段(code) */
*(.text*) /*放置所有文件的.text*段(code) */
*(.rodata) /*放置所有文件的.rodata段(constants, strings, etc.) */
*(.rodata*) /*放置所有文件的.rodata*段(constants, strings, etc.) */
*(.glue_7) /*同上*/
*(.glue_7t) /*同上*/
*(.eh_frame) /*同上*/
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
} > m_text //将这一输出段链接至m_text区域处
.ARM.extab : //定义输出段,就是一个名字,大家可以自己取
{
*(.ARM.extab* .gnu.linkonce.armextab.*) //
} > m_text //将这一输出段链接至m_text区域处
.ARM : //定义输出段,就是一个名字,大家可以自己取
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} > m_text //将这一输出段链接至m_text区域处
.ctors : //定义输出段,就是一个名字,大家可以自己取
{
__CTOR_LIST__ = .;
KEEP (*crtbegin.o(.ctors)) //放置*crtbegin.o中的.ctors段,并保证不被优化
KEEP (*crtbegin?.o(.ctors)) //同上
/*下面的语句中出现了EXCLUDE_FILE函数,这个函数的意思就是把括号里面的除外,
意思就是说放置所有文件除了*crtend?.o *crtend.o文件的 .ctors段,
因为在上面已经放置过了*/
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors))
/*下面的语句中出现了SPORT函数,SOPT是SORT_BY_NAME的别名,
意思是放置.ctors.*段的时候,按照名字的排列顺序来放置*/
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
__CTOR_END__ = .;
} > m_text //将这一输出段链接至m_text区域处
.dtors : //定义输出段,就是一个名字,大家可以自己取
{
__DTOR_LIST__ = .;
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
__DTOR_END__ = .;
} > m_text
.preinit_array : //定义输出段,就是一个名字,大家可以自己取
{
/*下面的出现了PROVIDE_HIDDEN, 意思就是后面的这个符号__preinit_array_start 只能在
链接器中被使用,外部文件是不能调用的,与它相反的还有PROVIDE, PROVIDE表示这个符号可以
被外部调用,而且如果外部文件也定义了同样的符号也不会发生冲突,优先使用外部定义值,
后面会出现很多PROVIDE*/
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} > m_text
.init_array : //定义输出段,就是一个名字,大家可以自己取
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} > m_text
.fini_array : //定义输出段,就是一个名字,大家可以自己取
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} > m_text
__etext = .;
__DATA_ROM = .;
__VECTOR_RAM = ORIGIN(m_interrupts);
__RAM_VECTOR_TABLE_SIZE_BYTES = 0x0;
.data : AT(__DATA_ROM) //AT的作用就是给当前输出段指定加载地址
{
. = ALIGN(4);
__DATA_RAM = .;
__data_start__ = .;
*(m_usb_dma_init_data)
*(.data)
*(.data*)
KEEP(*(.jcr*))
. = ALIGN(4);
__data_end__ = .;
} > m_data
__NDATA_ROM = __DATA_ROM + (__data_end__ - __data_start__);
.ncache.init : AT(__NDATA_ROM)
{
__noncachedata_start__ = .;
*(NonCacheable.init)
. = ALIGN(4);
__noncachedata_init_end__ = .;
} > m_data
. = __noncachedata_init_end__;
.ncache :
{
*(NonCacheable)
. = ALIGN(4);
__noncachedata_end__ = .;
} > m_data
__DATA_END = __NDATA_ROM + (__noncachedata_init_end__ - __noncachedata_start__);
text_end = ORIGIN(m_text) + LENGTH(m_text);
ASSERT(__DATA_END <= text_end, "region m_text overflowed with text and data")
/* Uninitialized data section */
.bss :
{
/* This is used by the startup in order to initialize the .bss section */
. = ALIGN(4);
__START_BSS = .;
__bss_start__ = .;
*(m_usb_dma_noninit_data)
*(.bss)
*(.bss*)
/*放置COMMON块,关于COMMON块是链接器为弱符号所制定的编译解决方案,
本质上其实就是bass段,感兴趣的小伙伴可以自行去搜一搜*/
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
__END_BSS = .;
} > m_data
.heap :
{
. = ALIGN(8);
__end__ = .;
PROVIDE(end = .);
__HeapBase = .;
. += HEAP_SIZE;
__HeapLimit = .;
__heap_limit = .;
} > m_data
.stack :
{
. = ALIGN(8);
. += STACK_SIZE;
} > m_data
__StackTop = ORIGIN(m_data) + LENGTH(m_data);
__StackLimit = __StackTop - STACK_SIZE;
PROVIDE(__stack = __StackTop);
.ARM.attributes 0 : { *(.ARM.attributes) }
/*ASSERT表示断言,跟C中的assert功能是一摸一样的*/
ASSERT(__StackLimit >= __HeapLimit, "region m_data overflowed with stack and heap")
}