WEBKT

彻底搞懂 LMA 与 VMA:GNU LD 链接脚本与 ARMCC 分散加载深度对比

3 0 0 0

在嵌入式开发领域,将代码和数据从非易失性存储(Flash)“搬运”到高速缓存(RAM)运行是家常便饭。对于习惯了 ARMCC(Keil MDK)的开发者来说,Scatter File(分散加载文件)像是一个黑盒,一切都能自动完成;而转到 GNU LD(GCC)后,往往会被 Linker Script 那晦涩的语法折磨。

本文将深入探讨这两者在处理 LMA(Load Memory Address,加载域地址)VMA(Virtual Memory Address,运行域地址) 上的本质区别,帮助你理清底层重定位的逻辑。


1. 核心概念:LMA vs VMA

在嵌入式系统中,程序镜像存储在 Flash 中,但其中的 .data 段(已初始化的全局变量)和一些对速度要求极高的代码段(如中断向量表或某些算法)必须搬移到 RAM 中运行。

  • LMA (Load Memory Address):程序物理存储的位置(通常在 Flash 内)。
  • VMA (Virtual/Execution Memory Address):程序实际运行时的位置(通常在 RAM 内)。

链接器的核心任务,就是告诉程序:“你现在存在 A 处,但你运行的时候得认为自己在 B 处。”


2. ARMCC (Keil):声明式的“全自动”体验

在 ARMCC 的 .sct 文件中,LMA 和 VMA 的关系是通过嵌套结构定义的。

语法示例:

LR_IROM1 0x08000000 0x00080000  {    ; Load Region (LMA)
  ER_IROM1 0x08000000 0x00080000  {  ; Execution Region 1 (Flash 运行)
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00020000  {  ; Execution Region 2 (RAM 运行)
   .ANY (+RW +ZI)
  }
}

关键特征:

  1. 分层结构LR_xxx(Load Region)定义了 LMA 的起始位置;内部的 ER_xxx(Execution Region)定义了 VMA。
  2. 符号自动生成:ARMCC 链接器会自动生成类似 Image$$RW_IRAM1$$BaseImage$$RW_IRAM1$$Length 等符号。
  3. 库函数接管:ARMCC 最“神奇”的地方在于,你不需要手动写代码从 Flash 拷贝 .data 段到 RAM。在进入 main 函数之前,C 库中的 __main 函数(非 main)会读取链接器生成的 Region Table,自动完成数据的搬移(Copy)和 ZI 段的清零(Zero-init)。

3. GNU LD (GCC):指令式的“纯手工”逻辑

GNU LD 的 .ld 文件更加底层,它不关心你的库函数是否强大,它只负责建立映射关系。

语法示例:

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    .text : {
        *(.text*)
        *(.rodata*)
    } > FLASH

    /* 注意这里的 AT 关键字 */
    .data : {
        _sdata = .;         /* VMA 起始 */
        *(.data*)
        _edata = .;         /* VMA 结束 */
    } > RAM AT > FLASH      /* 运行在 RAM,存储在 FLASH */

    _sidata = LOADADDR(.data); /* 获取 .data 段在 Flash 中的 LMA */
}

关键特征:

  1. AT 关键字:这是定义 LMA 的关键。> RAM 指定了 VMA,而 AT > FLASH(或 AT(addr)) 强制指定了该段的物理加载地址。
  2. 符号手动定义:你需要显式地定义 _sdata_edata_sidata(通常使用 LOADADDR 函数获取)。
  3. 手动搬运:GCC 编译器本身不会帮你生成搬运逻辑。你必须在 startup_xxx.s 或者 SystemInit 之后的 C 代码中,通过一段 memcpy 风格的代码,将数据从 _sidata 拷贝到 _sdata 之间。

4. 深度对比:两者最大的鸿沟在哪里?

特性 ARMCC (Scatter File) GNU LD (Linker Script)
思维模型 容器模型(大盒子套小盒子) 流式映射(从输入段到输出段)
LMA 指定方式 定义在父级 Load Region 使用 ATAT> 指令
数据搬运 自动化。由 __main 依靠 Region Table 完成 手动化。由开发者在 Startup 逻辑中实现
灵活性 较低。受制于 ARMLib 的启动流程 极高。可以精准控制任意 Section 的位置
符号引用 自动生成 Image$$... 格式 需手动在脚本中使用 .PROVIDE 定义

5. 实战避坑指南

坑一:LMA 地址对齐

在 GNU LD 中,如果你对 .data 段设置了 VMA 对齐,一定要注意 LMA 也会随之产生空隙。如果搬运代码没有处理好 LMA 的偏移,会导致数据错位。建议使用 ALIGN 时同时显式对齐 VMA 和 LMA。

坑二:ARMCC 的“隐藏”压缩

ARMCC 为了节省 Flash 空间,有时会对 RW 段进行压缩(RW Data Compression)。如果你在某些裸机启动代码中绕过了 __main 直接调 main,不仅数据没搬过去,甚至解压逻辑都没触发,会导致程序运行瞬间崩溃。

坑三:VMA 确定与调试

在调试时,查看生成的 .map 文件。

  • ARMCC Map:关注 Execution Region 的 Base 地址。
  • GCC Map:关注 VMALMA 两列是否一致。如果不一致,检查 startup.s 里的搬运逻辑是否覆盖了该段。

总结

ARMCC 的 Scatter File 像是一个智能管家,它通过高度封装简化了嵌入式开发的门槛;而 GNU LD 像是一套精密的手动挡工具,它把内存布局的所有权完全交给了开发者。

理解 LMA 与 VMA 的差异,本质上是理解程序从存储状态到运行状态的转换过程。无论使用哪种工具,核心都在于确保运行时刻 RAM 里的数据与链接器预设的 VMA 完全吻合。

底层架构师 嵌入式开发链接脚本ARM架构

评论点评