GNU链接脚本使用
链接脚本主要作用是描述输入文件的section是如何映射到输出文件的内存布局中。通俗的讲法就是编译完成后的各个obj文件,按照哪种顺序放与bin的哪个地址中;同时将运行时变量符号与VMA关联。
主要参考了下边几个博客:
1. 链接脚本的 __attribute__ 属性
2. GNU-ld链接脚本浅析
使用案例
1. 将变量或函数存放在用户定义的区域
主要目的是可以将变量或函数,运行时链接到用户定义的区域中,比如SRAM,某些CPU比直接放在FLASH中运行快(不一定,比如STM32,片内FLASH在ICODE总线上,指令预取功能使能将实现类似多级流水线的操作,SRAM则不再ICODE上,需要 "取指->译码->执行"无法加速。
主要使用链接脚本语法有:
MEMORY命令定义存储区域,通过输出section描述的 >REGION 显式的将该段限定到具体的存储区域。 如下:最后的区域为stm32f746ng最后64k,用来存放测试的变量。
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 960K
USER_AREA(rx) : ORIGIN = 0x80F0000, LENGTH = 64K
}
在链接脚本的SECTIONS 段内添加下列描述:
_siusrdata = LOADADDR(.usrdata); /* LOADADDR描述了.usrdata段的LMA,可在程序中声明:extern char _siusrdata; 使用时只能只用_siusrdata的地址:char *src = &_siusrdata; src实际等于0x80F0000*/
.usrdata :
{
. = ALIGN(4);
_susrdata = .; /* create a global symbol at usrdata start;VMA地址*/
*(.usrdata) /* .usrdata sections */
*(.usrdata*) /* .usrdata* sections */
. = ALIGN(4);
_eusrdata = .; /* define a global symbol at usrdata end */
} >RAM AT> USER_AREA
主程序中在声明处使用 attribute属性声明section, 使用extern 声明链接脚本中的变量:
int data_usrarea __attribute__((section(".usrdata"))) = 6666 ;
extern char _siusrdata,_susrdata,_eusrdata;
使用时:
printf("usrarea LMA = %X, VMA = %X\r\n", &_siusrdata, &_susrdata);
printf(usrdata addr = %X, value=%d\r\n", &data_usrarea,data_usrarea);
打印结果如下:
usrarea LMA = 80F0000, VMA = 20000070
usrdata addr = 20000070, value=-536140031
地址是对的,但是value怎么是错的,实际是因为,我们自定义的区域并沒有自己将数据加载到正确的VMA地址。而系统默认的.data区域是由系统启动时加载的,不同的工程实现方法不同,如stm32 cubumx 生成的makefile工程中是这样做的
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyDataInit:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyDataInit
ldr r2, =_sbss
b LoopFillZerobss
这段汇编的目的就是加载.data区至SRAM中,即已初始化的全局变量区。我们也可以在汇编中添加自己的代码,但是最好还是在main函数中去实现不破坏原有文件的完整性。
char *srcdata_ptr = &_siusrdata;
char *dstdata_ptr = &_susrdata;
while(dstdata_ptr < &_eusrdata)
{
*dstdata_ptr++ = *srcdata_ptr++;
}
添加上述代码完成.usrdata区域从 LMA处加载到VMA处,以后就可以快乐的运行了,打印信息如下。
usrarea LMA = 80F0000, VMA = 20000070
usrdata addr = 20000070, value=6666
思考,有了这个功能,实际上我们可以将程序分段更新,如定义一些配置参数存放在特定区域,然后在更新时可以直接这个区域更新。更加灵活的完成在线升级。