本文框架
- 1. Why<为什么需要链接文件>
- 2.What<是什么及组成>
- 2.1 MEMORY介绍
- 2.2 SECTIONS介绍
- 3.How<链接文件应用>
- 3.1 定义特定字段
- 3.2 将变量定义在指定段
- 3.3 将变量定义在不同的段
- 3.4 将变量定义在指定地址
- 3.5 将函数定义在指定段
- 3.6 将函数定义在指定地址
本文将对链接脚本文件从Why/What/How角度进行入门介绍,辅助对链接脚本的初步上手,如您对汽车电子BSW部分Autosar全模块实战感兴趣,可参读热销专栏:AutoSar实战进阶系列导读,本文大纲如下:
1. Why<为什么需要链接文件>
在嵌入式软件编译过程中,需要经历预处理→编译→汇编→链接的过程,那在链接过程中,各个段在可执行文件如elf中的先后组装顺序也是一个需要考虑的问题,一个可执行程序肯定会有入口地址的,一般先执行的代码要放到前面。那么如何指定程序的链接地址和各个段的组装顺序呢?这就是链接脚本的作用了。
链接脚本对于嵌入式系统的程序开发尤其重要,为了系统的正常加载运行,开发者需要精确控制代码和数据在内存中的位置及顺序。
2.What<是什么及组成>
链接脚本由链接器(链接程序,如 GNU 的 ld)用于控制链接过程,最终生成可执行文件。链接脚本本质上是一个脚本文件,在这个脚本文件里,不仅规定了各个段的组装顺序、起始地址、位置对齐等信息,同时对输出的可执行文件格式、运行平台、入口地址等信息做了详细的描述。
链接文件主要包括两部分:MEMORY及SECTIONS
2.1 MEMORY介绍
MEMORY定义在链接脚本中用来描述目标系统的物理内存布局,它定义了一组内存区域,每个区域都有一个名称、起始地址、长度和属性。链接器会根据这些信息,将各个段放置在合适的内存区域中。
ORIGIN是一个关于内存区域地始地址的表达式。在内存分配执行之前,这个表达式必须被求值产生一个常数,这意味着你不可以使用任何节相关的符号。关键字’ORIGIN’可以被缩写为’org’或’o’(但是,不可以写为,比如‘ORG’)
LEN是一个关于内存区域长充(以字节为单位)的表达式。就像ORIGIN表达式,这个表达式在分配执行前也必须被求得为一个常数值。关键字’LENGTH’可以被简写为‘len’或’l’。
MEMORY
{
Flash0 : org = 0x8000, l = 2M
}
2.2 SECTIONS介绍
SECTIONS关键字用于的作用是定义输出文件中的段,并控制这些段的内容、布局和属性,包括output section(输出段)的相应input section(输入段)、LMA和VMA,是整个连接脚本中最为重要的部分。
LMA和VMA说明如下:每个output section都有一个LMA和一个VMA,LMA是其存储地址,而VMA是其运行时地址,例如将全局变量g_data所在数据段.data的LMA设为0x80000020(属于ROM地址),VMA设为0xD0004000(属于RAM地址),那么g_data的值将存储在ROM中的0x80000020处,而程序运行时,用到g_Data的程序会到RAM中的0xD0004000处寻找它。
如下介绍三种不同的case:
SECTIONS
{
// case1:指定固定地址
.my_data1 ( 0xD0004000 ) : AT ( 0x80000020 )
{
*(.myData1)
} ...
// case2:仅指定内存空间,具体地址紧接着上一个output section的末尾地址。
.my_data2 :
{
*(.myData2)
} > ram AT> rom ...
//case3:对于代码段.text这种LMA与VMA相同的情况,可只定义VMA而不必指明LMA
.my_data_start align(4) :>Flash0//这是一个自定义的section名称
.my_data3:
{
*(.mydata3)// 这是一个通配符表达式,用于匹配所有以.sdata.ptavect开头的输入文件中的符号,并将它们添加到输出文件的.rosdata section中。
} > Flash0 //表示将.rosdata section的内容放置在名为ASW0_Flash的存储器区域中。
.my_data_end :>Flash0 链接段解析
}
3.How<链接文件应用>
在嵌入式软件实际开发中,会遇到一些场景需要将数据放入特定的字段,如对于XCP开发时,需要将标定量放到DS段中等,在后续介绍中会对一些常见场景及使用方法进行介绍。
3.1 定义特定字段
SECTIONS
{
.rosdata:
{
*(.sdata.cal)
} > Flash0
}
3.2 将变量定义在指定段
可使用#pragma或attribute section两种方式:
/* case1 use #pragma */
#pragma section ".sdata.cal" awB
int testVar;
#pragma section
/* case2 use attribute */
int testVar __attribute__((section(".sdata.cal" ,"f=awB")) );
//a表示allocatable,w表示writable,B表示uninitialized(bss)
3.3 将变量定义在不同的段
当变量在section1存不下时,保存到section2,此时需要在两个段中都定义。
MEMORY
{
...
MYRAM1 : org = D0000000, len = 8K
MYRAM2 : org = D0004000, len = 8K
...
}
SECTIONS
{
.mydata1 :
{
∗(.mydata)
} > MYRAM1
.mydata2 :
{
∗(.mydata)
} > MYRAM2
}
所有的数组都被放在.mydata段中,但是.mydata1和.mydata2分别被映射到MYRAM1和MYRAM2中,如果MYRAM1满了后,剩余的值将会置于MYRAM2中。
/* use #pragma */
#pragma section ".mydata" awB
int testVar;
#pragma section
3.4 将变量定义在指定地址
定义时,可将段的地址明确,在将地址放入段时地址就明确了。
SECTIONS
{
.user_defined_bss 0x90001000 : ALIGN(4) FLAGS(aw)
{
*(.sdata.cal)
} > CPU0_DLMU
}
3.5 将函数定义在指定段
__attribute__((section(".mydata"))) void myFunction()
{
// 函数实现
}
3.6 将函数定义在指定地址
只需要在section定义时加上地址即可。