参考目录 #
【sourceware.org】
https://sourceware.org/binutils/docs/ld/index.html#SEC_Contents
https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_toc.html
背景 #
总所周知,一个完整的程序编译过程分析: 预编译-编译-链接。 其中编译可以通过makefile来控制,而链接就通过链接脚本link script来进行控制。
值得一提的是,不同的编译套件,使用不同的链接脚本,比如:
- GNU gcc 使用常用的 link script ld文件
- ARM Clang 使用特殊的 scatter 文件: https://developer.arm.com/documentation/dui0803/b/CHDDJEFE
使用 #
link script的基本语法,请见 “参考目录” 中的两篇文章
keep() #
keep告诉编译器在链接时不要优化这些代码
SECTIONS
{
.text :
{
__rtmsymtab_start = .;
KEEP(*(RTMSymTab))
__rtmsymtab_end = .;
/* section information for finsh shell */
. = ALIGN(4);
__fsymtab_start = .;
KEEP(*(FSymTab))
__fsymtab_end = .;
. = ALIGN(4);
__vsymtab_start = .;
KEEP(*(VSymTab))
__vsymtab_end = .;
. = ALIGN(4);
. = ALIGN(4);
_etext = .;
} > CODE = 0
C/link脚本的变量互相使用 #
C中使用link脚本中的变量,注意使用 & 符号
CCMRAM_reserved_for_js_file = 0x10008000;
extern const uint32_t CCMRAM_reserved_for_js_file;
printf("CCMRAM_reserved_for_js_file:0x%x \r\n", &CCMRAM_reserved_for_js_file);
AT > FLASH 、VMA、LMA #
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM AT> FLASH
在link script中有LMA(load memory address)和VMA(virtual memory address)两个概念。 在linux elf场景下,会解析elf并按照数据的LMA地址加载到对应的VMA地址,并执行。
由于linux中有虚拟内存的概念,所有程序都能看到从 0x0 ~ 0xffffffff全部的内存,所以linux中的绝大部分应用程序不关心LMA和VMA,所有的text和data段都从0x0开始按顺序编排地址即可。
但是在MCU场景下,RAM的地址一般不是固定的,例如STM32一般从 0x20000000 开始,长度为128K~512K。 此时就需要指定地址的开始。
又考虑到MCU场景下,所有数据都是保存到FLASH中,然后将data段从FLASH中拷贝到RAM中,text段仍旧保留在FLASH中。 针对这种场景,就引入了LMA和VMA两个地址。 LMA指编译出的数据在FLASH中的位置,VMA指编译出的数据在RAM中的位置。
对于text段,LMA和VMA相同,从0x0800000开始;对于RAM段,LMA值为0x08xxxxx, VMA要指定为0x2000000,
代码体积优化 #
- 参考文章:https://interrupt.memfault.com/blog/best-firmware-size-tools *
在开发单片机项目时,由于单片机的ROM限制,我们可能需要优化代码体积。 优化的原理主要有两点:
- 丢弃掉没有用到的代码
- 减少C语言编译成汇编语言时,汇编语言的行数
针对第1点:需要使用编译选项:
CFLAGS: -fdata-sections -ffunction-sections
和 链接选项
LINKFLAG: -Wl,--gc-sections
但是值得一提的是,可能会误删除某些代码,特别是使用了 __attribute__((section("xxx")))
来定义某些全局静态数据。此时需要参考上述的 keep() 来避免被删除
需要注意的是,__attribute__的位置应该在最后,参考
//官方文档:
//https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html
//https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
//搜索section
struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
extern void foobar (void) __attribute__ ((section ("bar")));
针对第2点:需要更改GCC的优化等级,修改编译选项
CFLAGS: -O3 -Os
可以使用 arm-none-eabi-nm –print-size –size-sort –radix=d firmware.elf 命令来查看 固件中的每段的占用大小。
可以使用 arm-none-eabi-size firmware.elf 查看text、bss、data的整体占用
编译时可以使用-Wl,–print-memory-usage打印使用的内存百分比