跳过正文

MCU link链接及代码体积优化

·233 字·2 分钟
jiladahe1997
作者
jiladahe1997
To see the world as it is, and to love it

参考目录
#

【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来进行控制。

值得一提的是,不同的编译套件,使用不同的链接脚本,比如:




使用
#

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限制,我们可能需要优化代码体积。 优化的原理主要有两点:

  1. 丢弃掉没有用到的代码
  2. 减少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打印使用的内存百分比