与 等同
[(type)] 关键字 的定义和使用 :
FILL 关键字 用于内存对齐产生的空位填充,图例用0x90字节填充空位
/DISCARD/ 关键字 用以丢弃部分不要的输入段
[address] 是VMA的地址值, AT(中的值)是LMA的地址值;AT(lma) 等同于AT>lma_region; 若既没有指定VMA也没指定LMA,则认为VMA=LMA,且值为LMA的值
.mdata段的vma是0x2000,lma是AT表达式的值,即0x1000+SIZEOF(.text)
ADDR 关键字 指示一个段的绝对VMA值; LOADADDR (.text0) 关键字 指示一个段的绝对LMA值
空位用0x90909090填充
KEEP 关键字 的作用是标记那些(垃圾)段不应该被删除
LONG 关键字 用于在rom/ram中分配4字节的空间,并在这4字节的地址内存入(中)表达式的值,同时使本地计数器+4; 可以将各段的起始地址和长度信息等存入ROM中
PROVIDE 关键字 对外提供一个符号symbol,其值等于 expression 的计算结果;这个symbol是在ld文件中定义的,而非其他文件中
EXCLUDE_FILE 关键字 用于汇总 除(内)文件外 的其余所有 .ctors 输入段
符号的定义和赋值可以在 SECTIONS 的 {内/外} 进行,但本地计数器 . 的赋值操作只能在SECTIONS 的 {内} 进行
extern 了3个链接器符号变量,其数值会在ld文件中赋值, 一切皆文件,符号即地址; 在makefile,ld脚本,汇编代码中出现的符号都是地址数值的代名词
查看各输入段信息
任何后缀的文件都可被 INCLUDE 进来
.data段存放在REGION_DATA的存储区域中
.data段在REGION_DATA的存储区域中的存放起始地址是rodata_end标号的值,AT 指示段的LMA地址
.bss段相对于.data段的优势在于.bss段不占用ROM空间; .rodata段相对于.data段的优势在于.rodata段不占用RAM空间,rodata段即用const声明的.data段,而非.text段
SECTIONS是关键词,定义了4个输出段,输出段的名字分别为 : 前的字符串,这些输出段中分别放置 ( 内 ) 的输入段,而这些输入段又是各c文件编译为.o文件后生成的各段。
输出段 来告诉链接器如何在内存中布局您的程序; 输入段 描述来告诉链接器如何将输入文件映射到内存(rom and ram)布局中。
SECTIONS语句 { 内的 . } 表示绝对地址,输出段.data语句 { 内的 . } 表示从该段开始的字节偏移量,而不是绝对地址;包括表达式也是如此,即:
出现在输出段定义中的表达式值是相对于输出段基地址的偏移值,而在在其他地方出现的表达式值将是绝对值。可通过ABSOLUTE关键字将输出段中的相对偏移值转换为绝对值
第一个 { } 解读为 最终目标文件中一个名为 .text 的输出段中,囊括了所有源文件中的.text段,而这些 .text段 作为目标文件的输入段。 而这个 .text 的输出段 被放置( > )在 REGION_TEXT 区域中。
第二个 { } 中出现了对链接器变量 rodata_end 的赋值
attr 的定义
MEMORY 关键字定义芯片内各存储区域的起始地址和长度, REGION_ALIAS 关键字 为这些存储区起了别名
参考:https://www.404bugs.com/index.php/details/1084978780534788096
https://www.cnblogs.com/uestcliming666/p/11456217.html
1:每个output section都有一个LMA和一个VMA,LMA加载地址,往哪里存放;VMA虚拟地址,在哪里使用;LMA是其存储地址(即.data数据存储在fls中的地址),而VMA是其运行时地址(加载到ram中的地址),例如将全局变量g_Data所在数据段.data的LMA设为0x80000020(属于ROM地址),VMA设为0xD0004000(属于RAM地址),那么g_Data的值将存储在ROM中的0x80000020处,而程序运行时,用到g_Data的程序会到RAM中的0xD0004000处寻找它。
2:ENTRY(Reset_Handler) //设置入口地址,ENTRY是 LD文件的关键字
HEAP_SIZE = DEFINED(__heap_size__) ? __heap_size__ : 0x0400; //设置堆大小 ;DEFINED是 LD文件的关键字,类似ifdef
3:SECTION { 中 } AT后面的都是LMA地址/区域,不带AT都是VMA地址/区域;
VMA是必需的,如果指定了就用指定的值,如果没指定vma但指定了region,则基于region会计算一个vma值;如果region也没指定,则基于本地计数器计算一个vma值;如果输出段没有分配VMA值,则链接器也将使用lma_region作为vma_region, 并在vma_region中计算一个合适的值作为VMA的值
4:ld文件中不同section{ }段中的 . 不是递增的;重定位到相同MEMORY分区中各section{ }段中的 . 是递增的,且初始 . 值为MEMORY分区各段的ORG值,而非0; SECTION { 中 } 各输出段外层的 . 值是绝对地址值;输出段中的 . 值是一个相对MEMORY起始地址的偏移值;. 在不同的MEMORY中不会递增,在相同的MEMORY中会记录上次的结束值并递增,且在相同的MEMORY中 . 的值不能回退(减小);因此一个MEMORY中的输出段最好按照地址递增的方式先后出现在SECTION中,如果先写了MEMORY中靠近结尾处的输出段,则会使后面的 . 值变得回退,且这里不能指定输出region,见下图
5:不能访问链接器脚本定义的符号的值,它没有值;只能访问链接器脚本定义的符号的地址。
6:map文件中输出段内的 . = 0x2F4000 ; 是将当前 . 值加了0x2F4000,不是变为0x2F4000
7:.shstrtab段保存着各Section的名字,.strtab段保存着程序中用到的符号的名字。每个名字都是以'\0'结尾的字符串。
8:在加载时.bss段和.data段一样都是可读可写的数据,但是在ELF文件中.data段需要占用一部分空间保存初始值,而.bss段则不需要。.bss段在文件中只占一个Section Header而没有对应的Section,程序加载时.bss段占多大内存空间会在Section Header中描述。
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) */
*(.rodata) /*放置所有文件的.rodata段(constants, strings, etc.) */
KEEP (*(.init))
KEEP (*(.fini))
} > 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区域处
.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
__etext = .;
__DATA_ROM = .;
__VECTOR_RAM = ORIGIN(m_interrupts);
__RAM_VECTOR_TABLE_SIZE_BYTES = 0x0;
ASSERT(__StackLimit >= __HeapLimit, "region m_data overflowed with stack and heap") /*ASSERT表示断言,跟C中的assert功能是一摸一样的*/
}