Linker Script,LMA,VMA
以前在学ld的script时两个比较重要的概念,即指定一个输出section的lma和vma(分别是load memory address和virtual memory address),vma的作用是很明显地,就是决定run time address嘛,但lma有什么用呢?恩,对运行在linux这样的操作系统上面的应用程序来说,是没什么用的,毕竟应用程序都是被'load'到虚拟地址空间中。但是在嵌入式底层firmware,bootloader开发来说,这个关键字不再打酱油。 看下面这么一段代码,汇编:
.section .text_symbol_in_text:mov $1,%eax.section .data_symbol_in_data:.long 0x90909090 简单得不能再简单了,定义了两个段,.text和.data,.text里面就一条指令,.data里面也只有一个字。 先编译成可重定向的.o目标文件:
gcc -o xxx.o -c xxx.S 接着用ld将其链接成可运行文件,这里其实只有一个目标文件参与链接,所以说链接其实是不对的,ld只是做做重定向或者叫定址的工作而已,外加在elf文件中产生描述文件的segment和section的信息等。 链接时首先提供一个最简单的ld script:
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的起始地址上。不然会怎么着?不是跑飞就是读写的数据找不到。 链接:
ld ./lma_vma.o -T ./lma.equal.vma.lds 生成a.out可执行文件。 注意,这个a.out是‘可执行’的elf文件。对于bootloader或者firmware来说,一般是直接把一个binary文件‘burn’到板子上的。把elf文件剥离成一个binary文件,在万能的gnu tool帮助下,非常简单,一个objcopy便可搞定:
objcopy -O binary ./a.out 好了,用file看一下文件类型:
./a.out: COM executable for MS-DOS 应该就是'bin'文件了。 且慢,还记得之前我们没有用AT指令指定两个段的lma么。那么,问题来了,我们先用ls -l看一下有什么异样:
[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,真相一目了然:
[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段的后面:
OUTPUT_FORMAT("elf32-i386","elf32-i386")OUTPUT_ARCH(i386)SECTIONS{ .text 0x5000 : { *(.text) } .data 0x8000 : AT(ADDR(.text) + SIZEOF(.text)) { *(.data) }} 这次链接、objcopy后生成的a.out文件看一下:
[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。 完毕。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |