Linker Script,LMA,VMA

本文深入探讨了在嵌入式系统开发中使用LD Script指定输出段的加载内存地址(LMA)和虚拟内存地址(VMA)的重要性。通过实例展示了如何通过AT指令控制不同段的LMA,以实现更紧凑的二进制文件生成,特别适用于RAM资源受限的情况。

以前在学ld的script时两个比较重要的概念,即指定一个输出section的lma和vma(分别是load memory address和virtual memory address),vma的作用是很明显地,就是决定run time address嘛,但lma有什么用呢?恩,对运行在linux这样的操作系统上面的应用程序来说,是没什么用的,毕竟应用程序都是被'load'到虚拟地址空间中。但是在嵌入式底层firmware, bootloader开发来说,这个关键字不再打酱油。

看下面这么一段代码,汇编:

  1. .section .text  
  2. _symbol_in_text:  
  3. mov $1, %eax  
  4. .section .data  
  5. _symbol_in_data:  
  6. .long 0x90909090  

.section .text_symbol_in_text:mov $1, %eax.section .data_symbol_in_data:.long 0x90909090

简单得不能再简单了,定义了两个段,.text和.data,.text里面就一条指令,.data里面也只有一个字。

先编译成可重定向的.o目标文件:

  1. gcc -o xxx.o -c xxx.S  

gcc -o xxx.o -c xxx.S

接着用ld将其链接成可运行文件,这里其实只有一个目标文件参与链接,所以说链接其实是不对的,ld只是做做重定向或者叫定址的工作而已,外加在elf文件中产生描述文件的segment和section的信息等。

链接时首先提供一个最简单的ld script:

  1. OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")  
  2. OUTPUT_ARCH(i386)  
  3. SECTIONS  
  4. {  
  5.         .text 0x5000                    : { *(.text) }  
  6.         .data 0x8000                    : { *(.data) }  
  7. }  

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")OUTPUT_ARCH(i386)SECTIONS{ .text 0x5000 : { *(.text) } .data 0x8000 : { *(.data) }}

在上面这个ld script中只定义了vma;根据ld的规则:如果没有用AT指令定义lma的话,那么lma默认等于vma。

这里为什么两个段(.data和.text)的vma不一样?试想在嵌入式系统中是不是会遇到这种情况,即Flash(Rom)空间较大,Ram空间相对较小,于是我们只希望让数据装载进Ram空间,代码就直接运行在Flash(Rom)中。比如Flash(Rom)的起始地址0x5000,Ram的起始地址0x8000,所以这两个段的vma就必须对应到相应Region的起始地址上。不然会怎么着?不是跑飞就是读写的数据找不到。

链接:

  1. ld ./lma_vma.o -T ./lma.equal.vma.lds  

ld ./lma_vma.o -T ./lma.equal.vma.lds

生成a.out可执行文件。

注意,这个a.out是‘可执行’的elf文件。对于bootloader或者firmware来说,一般是直接把一个binary文件‘burn’到板子上的。把elf文件剥离成一个binary文件,在万能的gnu tool帮助下,非常简单,一个objcopy便可搞定:

  1. objcopy -O binary ./a.out  

objcopy -O binary ./a.out

好了,用file看一下文件类型:

  1. ./a.out: COM executable for MS-DOS  

./a.out: COM executable for MS-DOS

应该就是'bin'文件了。

且慢,还记得之前我们没有用AT指令指定两个段的lma么。那么,问题来了,我们先用ls -l看一下有什么异样:

  1. [root@localhost ldscript_test]# ls -lh ./a.out  
  2. -rwxr-xr-x 1 root root 13K 11-04 20:55 ./a.out  

[root@localhost ldscript_test]# ls -lh ./a.out-rwxr-xr-x 1 root root 13K 11-04 20:55 ./a.out

文件足足有13k大小。别忘了,我们的源程序只有一条指令和一个32位的字,并且是纯数据的bin文件,为什么有这么大?

看看这个纯数据的文件里面有些啥,借助于hexdump,真相一目了然:

  1. [root@localhost ldscript_test]# hexdump ./a.out  
  2. 0000000 01b8 0000 0000 0000 0000 0000 0000 0000  
  3. 0000010 0000 0000 0000 0000 0000 0000 0000 0000  
  4. *  
  5. 0003000 9090 9090  

[root@localhost ldscript_test]# hexdump ./a.out0000000 01b8 0000 0000 0000 0000 0000 0000 00000000010 0000 0000 0000 0000 0000 0000 0000 0000*0003000 9090 9090

最开始01b8应该就是mov $1, $eax的instruction code。而0x3000位置的90909090显然就是我们定义在数据段的字了。因为链接器脚本中没有用AT指令专门为两个段指定lma,所以其lma与vma相等,两个段相差了0x3000 bytes的长度。.text段之前没有其他段了,所以最终的bin文件中一开始就是.text段的内容,虽然只有2个字节,但仍然要过0x3000 bytes才是.data段。中间那些未知数就填0了。

这样有什么问题呢?因为我们知道0x8000已经是Ram了,难道我们要将全局数据烧到一断电内容就消失的Ram中?并且,Flash(Rom)和Ram之间相隔的0x3000 bytes不一定就对应实际的存储区域(比如也在Flash中),有可能根本就是hole。那么‘烧’这些0下去有可能会造成问题。

我们希望的结果是,烧写的data和text都在Flash(Rom)中,运行后再将data自搬运到Ram中。最好bin文件中两个段紧挨着,保持文件尽可能小的size。

下面的ld script在定义.data段时增加了AT指令来描述其lma,这样表示.data段的lma紧接在.text段的后面:

  1. OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")  
  2. OUTPUT_ARCH(i386)  
  3. SECTIONS  
  4. {  
  5.         .text 0x5000           :  
  6.         {  
  7.                 *(.text)  
  8.         }  
  9.         .data 0x8000                : AT(ADDR(.text) + SIZEOF(.text))  
  10.         {  
  11.                 *(.data)  
  12.         }  
  13. }  

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")OUTPUT_ARCH(i386)SECTIONS{ .text 0x5000 : { *(.text) } .data 0x8000 : AT(ADDR(.text) + SIZEOF(.text)) { *(.data) }}

这次链接、objcopy后生成的a.out文件看一下:

  1. [root@localhost ldscript_test]# hexdump ./a.out  
  2. 0000000 01b8 0000 9000 9090 0090  
  3. 0000009  

[root@localhost ldscript_test]# hexdump ./a.out0000000 01b8 0000 9000 9090 00900000009

只有9个字节大小,里面的内容正好是一条指令加上后面的0x90909090(指令后面的两个0x00是为了4字节对齐.data的pad)

这个bin文件就可以放心的烧写到Flash(Rom)中去了。不过,将.data段搬运到Ram的代码还是得自己写的。更系统的学习ld script,请info ld。

 

 

 

 

 

问题终于解决了!谢谢各位!!!
我开始没有理解snowweihua兄弟写的那个linker script的意思。后面我仔细想了下,由连接脚本(linker script)完全可以解决这个问题:

一般可执行文件的每个段都可以分为加载域(LMA)和运行域(VMA),加载域是系统memory中实际放的地址,运行域是这一段要运行的地址。一般我们编译的时候,LMA和VMA两个地址都是一致的。
但是象我现在所说的这种情况,如果把data段的运行域定义在ram地址0x20000000,而把.data段的加载域定义在rom地址(我这里rom地址为0x00000000~0x00008000),那么转换成bin的时候,text和data就不会有那么大的间隙,bin文件就会和实际的差不多大。但这样做需要程序运行时把data段拷贝到data段的VML地址去(也就是拷贝到ram地址去)。

具体的做法是,在连接的时候调用一个连接脚本:
SECTIONS
{
. = 0x00000000
.text : {*(.text)}
data_lma . ;
. = 0x20000000
.data : AT(data_lma)
{*(.data)}
_end = .;
}
这里面“AT(data_lma)”是指定data段的加载域,紧跟text段后面。这样转换成bin文件后,就不会造成bin文件非常大的情况。但一定要注意在开始的boot程序里写一段代码把data段拷贝到它的运行地址0x20000000。
完毕。


<think>好的,用户问的是关于.data.extra段的VMALMA总是不对的问题。首先,我需要回忆链接脚本VMALMA的概念。VMA是虚拟内存地址,也就是程序运行时该段所在的地址;LMA是加载内存地址,即程序被加载到存储设备(比如Flash)中的地址。通常,初始化数据段.data的LMAFlash里,而VMA在RAM里,这样启动代码会将数据从Flash复制到RAM。但.data.extra可能是一个用户自定义的段,这里用户遇到了VMALMA设置不正确的情况。 接下来,我需要考虑用户可能的问题原因。常见的情况包括链接脚本中.data.extra的位置或属性错误,比如可能没有正确指定AT>指令来设置LMA,或者VMA没有分配到正确的RAM区域。另外,用户可能没有正确在代码中使用__attribute__((section(".data.extra")))来指定变量到该段,导致链接器没有正确处理。 然后,可能的解决步骤包括检查链接脚本,确保.data.extra的VMA在RAM中,LMAFlash或其他存储设备中,并且位置没有与其他段重叠。还要确认启动代码是否正确复制了.data.extra的内容,因为即使链接脚本正确,如果没有在启动时复制,数据也不会正确加载到RAM中。此外,使用objdump或readelf工具查看生成的elf文件,确认段的地址是否符合预期。 用户可能需要具体的例子来理解如何正确编写链接脚本。例如,在链接脚本中,.data.extra应该在.data之后定义,确保其VMA在RAM中,并且使用AT>将LMA设置在Flash中。启动代码中需要添加复制.data.extra的部分,使用符号__data_extra_lma_start和__data_extra_vma_start等来获取地址和大小,进行数据复制。 最后,需要提醒用户常见的错误,比如忘记在启动代码中复制数据,或者链接脚本中段的顺序导致地址计算错误。还可能存在存储区域定义不足,导致链接器无法分配正确的地址。建议用户逐步调试,检查链接脚本和启动代码,并使用工具验证段的地址信息。</think>在嵌入式开发中,.data.extra段的VMA(Virtual Memory Address)和LMA(Load Memory Address)设置错误通常与链接脚本linker script)配置密切相关。以下是逐步分析及解决方案: 1. **基础概念确认** - **VMA** = 运行时地址(RAM中的地址) - **LMA** = 存储地址(Flash中的地址) - `.data.extra`是用户自定义的数据段,需要显式声明在链接脚本中 2. **典型错误场景** ```ld /* 错误示例:未正确指定LMA */ .data.extra : { *(.data.extra) } > RAM /* 仅指定VMALMA默认等于VMA */ ``` 3. **正确配置模板** ```ld /* 正确配置:使用AT>指定LMA */ .flash_data_extra : ALIGN(4) { __data_extra_lma_start = .; } > FLASH .data.extra : ALIGN(4) { __data_extra_vma_start = .; *(.data.extra) . = ALIGN(4); __data_extra_vma_end = .; } > RAM AT> FLASH /* VMA在RAM,LMAFLASH */ ``` 4. **启动代码关键操作** ```c /* 必须手动复制数据 */ extern uint8_t __data_extra_lma_start[]; extern uint8_t __data_extra_vma_start[]; extern uint8_t __data_extra_vma_end[]; void _start(void) { uint8_t* src = __data_extra_lma_start; uint8_t* dest = __data_extra_vma_start; while(dest < __data_extra_vma_end) { *dest++ = *src++; } } ``` 5. **验证步骤** - **方法1**:使用objdump查看段信息 ```bash arm-none-eabi-objdump -h your_elf_file.elf ``` 确认.data.extra段的VMA属于RAM地址范围,LMA属于Flash地址范围 - **方法2**:检查map文件 ```bash arm-none-eabi-nm -n your_elf_file.elf > memory.map ``` 观察.data.extra相关符号的地址分布 6. **常见错误排查** - **Q**:启动代码忘记复制.data.extra段? - **A**:在调试器中检查RAM对应地址的数据是否与Flash内容一致 - **Q**:链接脚本存储区域定义不足? ```ld /* 错误配置:FLASH/RAM容量不足 */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K } ``` 使用`arm-none-eabi-size`工具检查总使用量 7. **高级技巧** - 使用KEEP保留未引用的数据 ```ld .data.extra : ALIGN(4) { KEEP(*(.data.extra)) /* 防止被优化 */ } > RAM AT> FLASH ``` - 对齐优化 ```ld . = ALIGN(4); /* 强制4字节对齐 */ ``` 通过以上步骤,可系统性地定位和修复.data.extra段的地址问题。建议每次修改链接脚本后,重新生成map文件进行验证。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值