GCCLD 连接脚本分析ubootld.docx
《GCCLD 连接脚本分析ubootld.docx》由会员分享,可在线阅读,更多相关《GCCLD 连接脚本分析ubootld.docx(8页珍藏版)》请在冰豆网上搜索。
GCCLD连接脚本分析ubootld
GCC-LD连接脚本分析--uboot.ld
GCC-LD连接脚本分析--uboot.lds
(2011-03-1809:
48)
一键转载
标签:
转载
原文地址:
GCC-LD连接脚本分析--uboot.lds作者:
Embedded_Li1CommandLanguage
Thecommandlanguageprovidesexplicitcontroloverthelinkprocessallowingcomplete
specificationofthemappingbetweenthelinkersinputfilesanditsoutput.Itcontrols:
inputfiles
fileformats
outputfilelayout
addressesofsections
placementofcommonblocks
1.1inputsectionandoutputsection
所谓的输出段,是指生成的文件,例如elf中的每个段
所谓的输入段,是指连接的时候提供LD的所有目标文件(OBJ)中的段
1.2imaandvma
lma=loadmemoryaddress
vma=vitualmemoryaddress
1.3relatewithsystemandentrypoint
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
1.4输出段的标准格式
section[address][(type)]:
[AT(lma)]
{
output-section-command
output-section-command
...
}[>region][AT>lma_region][:
phdr:
phdr...][=fillexp]
前面也说了,所谓的输出段是指最终生成的文件里面的段,所以一个输出段就可以理解为
最终文件里面的一个块,那么多个块合起来就是一个完成文件了。
section_namevma:
AT(lma)
{
output-section-command
output-section-command
...
}[AT>lma_region]
section_name根据ld手册说是有个确定的名字,其他没啥,自己添加一些新段也是可以的。
默认的4个段是必须有的:
.text代码
.rodata常量,例如字符串什么的
.data初始化的全局变量
.bss没有初始化的全局变量
其实没什么,可以说,都是固定的,所以一句话,照抄。
段名字后面紧跟的是vma,
也就是这个段在程序运行的时候的地址,例如
.text0x30000000:
{
*(.text)
}
表示的是代码的运行时地址为0x30000000假如你的ROM在0x0地址,程序放在ROM中,
那个时候程序是不能正常运行的(位置无关代码除外),必须将代码COPY到VMA也就是
0x30000000中才能正常运行。
至于那个AT(lma)的关键字,只指示代码连接的时候应该
放在什么地方,注意好这个英文是loadmemoryaddress,是指程序应该装载在什么地方。
elf格式的文件里面不但包含了代码,还包含了各种各样的信息,例如上面说的每个段的lma
和vma,还有其他信息都包含在里面了。
默认状态下,lma是等于当前的vma的。
ecname定义了段名,其实最开始就忽略了一个重要的因素,arm-gcc-ld脚本需要描述
输入和输出,而表面上一看却看不出来什么是输入什么事输入,其实secname和contents
就是描述这两个信息的参数。
secname是输出文件的段,即输出文件有哪些段,而contents就
是描述输出文件的这个段从哪些文件里抽取而来。
明确这个了就不难理解为什么SECTIONS命令
什么都可以不要就是不能没有这两个参数了。
secname:
定义段,但是别以为定义的段一定要
是教科书上写的.data,.text这些科班的必须品,你甚至可以创建一个段来放一个的图片。
contents:
它的语法开始复杂起来了,但是你可以简单的把输入文件写到代码中:
.data:
{main.oled.o}
但是结果被列的目标文件中所有的代码都被链接到.data中去了,显然不大符合我们的要求啊。
那么还有一种写法:
.data:
{
main.o(.data)
main.o(.text)//也可以这样写main.o(.data.text)或者main.o(.data,.text)
led.o(.data)
}
这个写法让只有被选中的文件的特殊段被链接到输出文件的.data段了。
当然,我们似乎还有更好的写法:
.data:
{
*(.data)
}
这样的话,所有目标文件的.data段都被连接到了输出文件中了(这似乎是最常用的方法)。
核心的部分讲完了,开始回顾前面说到了的那些参数:
start:
强制链接地址。
也许没有讲清楚的是,在SECTIONS中,各个段是按次序排列的,前一个段用到什么地方下一个段接着用,而start就是强迫链接器将当前的段连接到指定的地址中。
.data0x400000000:
{.....}
BLOCK(align):
在32位的ARM芯片上,指令都必须是align(4)对齐,不然无法执行。
AT(addr):
实现存放地址和加载地址不一致的功能,AT表示在文件中存放的位置,即实时地址。
表示指令或数据在二进制文件中的位置。
1、5ENTRY(symbol)
这个symbol应该是某个函数,或者是汇编代码里的一个入口。
然而,其实ARM-GCC-LD有很多种方式定义入口,所以当你看到你的脚本里没有这句话而板子运行的很正常的时候别大吃一斤。
1:
在连接的时候使用-e参数。
2:
在脚本里使用ENTRY
3:
如果定义过start这个入口(如果你在汇编里如果本身就有这个名字叫start的入口,那么不用特别的声明也可以)
4:
SECTION中.text的第一个入口函数
5:
地址为0的指令其实看了这个我们可以理解是ARM对入口的一个选择优先级,1和2是一样的,显示的指明入口,这也是推荐的方法,没人会觉得程序员故弄玄虚
是什么好事情。
3是连接器的智能吧,而4和5就是无奈的选择了,程序员没干好的事情CPU只要猜着来处理了,有.text段的话就从它开始执行吧,
连.text都没有的就从0x00000000开始执行。
关
于定义变量,其实一般的脚本都会有的,目的只有一个,给汇编启动代码提地址信息。
比如说,一段需要清零的区域在脚本里定义了,而脚本自己不是变形金刚,他
不能主动给你清零的,需要你自己的启动代码来清零,清零的代码当然在汇编的启动代码里,它怎么知道需要清零的内存在什么地方?
就靠脚本里定义的变量了。
没
错,事情就是这样的,那也就说一定会有一个提取地址的方法将地址赋给变量了哦,就是小小的一个点"."。
RAM_START=.;
定义了一个RAM_START变量,地址是当前的地址,什么是当前的地址啊?
就是链接器在连接的时候根据前面的段排列后的当前位置,如:
.=0x00000000
定义当前地址为0x0。
2、实时地址与虚拟地址引起的一个问题
2、1分析下面连接代码
.text0x30000000:
{
*(.text)
*(.rodata)
}
.data0x33ffff00:
{
*(.data)
}
例如我们基本的两个段,我们指定了.text和.data段的vma,但是没有指定lma,那么
lma到底应该是多少?
很简单,ld认为当前的lma和vma是相同的,所以lma应该分别是0x300000000x33ffff00,编译生成的elf文件很小,但是objcopy出来的文件却非常
巨大,达到了60多MB,这是什么问题?
elf文件很聪明,他只是保存了信息,.text段的lma
是0x30000000,那么elf就保存了知道本程序的代码应该加载到0x30000000,然后又保存了
.data的lma,我们留意到中间有很多的地址空间是空的,并没有实际的代码,elf怎么处理?
elf只保存了两个地址和实际的代码,而对于其他空间里面的代码他并不处理,所以可以看出,
最后生成的elf文件并不大,也就100多KB而已,但是后来的OBJCOPY操作中,从elf文件中copy
出程序代码,这下就糟了,objcopy是从最开始的lma开始,这里是0x30000000一直复制到最
尾段的lma,这里是0x33ffff00,中间没有代码地方全部补零,那么60多MB的大bin文件就是
这样来的。
2、1data段分离出来,连接到不同的vma运行时地址。
.text0x30000000:
{
*(.text)
*(.rodata)
}
.data0x31000000:
AT(LOADADDR(.text)+SIZEOF(.text))
{
*(.data)
}
.bss:
{
*(.bss)*(.COMMON)
}
其
实也不难解决,像上面的代码那样做就OK了,上面也分析了,如果vma不同的话,objcopy会一直复制,这样生成的bin文件会很大,怎么解决?
很简
单,手工指定.data段的lma地址让.data段的lma紧紧跟着.text段的末尾,这样生成的bin文件就会很漂亮,跟第一种办法
生成的bin文件结构一模一样!
!
AT(LOADADDR(.text)+SIZEOF(.text))
AT是指定lma的,然后里面用了两个指令LOADADDR,和名字一样这个指令是用来求lma地址的!
SIZEOF也就是名字那样,求大小的LOADADDR(.text)求出.text段的lma,注意是开始地址
SIZEOF(.text)求出.text段的大小AT(LOADADDR(.text)+SIZEOF(.text))的效果就是,指定.data段的lma在.text段lma的结尾处!
这里补充一下,还有一个指令ADDR(.text)这个是求vma的,不是求lma。
另外,注意一下.bss段的lma和.data段的lma是一样的,这也反映了一个实质问题.bss段只分配运行地址vma,并不实际占空间的。
3、下面分析下U-boot.lds连接脚本
OUTPUT_FORMAT("elf32littlearm","elf32littlearm","elf32littlearm")
;指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm)
;指定输出可执行文件的平台为ARM
ENTRY(_start)
;指定输出可执行文件的起始代码段为_start.
SECTIONS
{
.=0x00000000;从0x0位置开始
.=ALIGN(4);代码以4字节对齐
.text:
;指定代码段
{
cpu/arm920t/start.o(.text);代码的第一个代码部分
*(.text);其它代码部分
}
.=ALIGN(4)
.rodata:
{*(.rodata)};指定只读数据段
.=ALIGN(4);
.data:
{*(.data)};指定读/写数据段
.=ALIGN(4);
.got:
{*(.got)};指定got段,got段式是uboot自定义的一个段,非标准段
__u_boot_cmd_start=.;把__u_boot_cmd_start赋值为当前位置,即起始位置
.u_boot_cmd:
{*(.u_boot_cmd)};指定u_boot_cmd段,uboot把所有的uboot命令放在该段.
__u_boot_cmd_end=.;把__u_boot_cmd_end赋值为当前位置,即结束位置
.=ALIGN(4);
__bss_start=.;把__bss_start赋值为当前位置,即bss段的开始位置
.bss:
{*(.bss)};指定bss段
_end=.;把_end赋值为当前位置,即bss段的结束位置
}
参考西比爱斯的ARM-GCC-LD脚本而写成。
前一篇:
GCC-LD连接脚本分析--uboot.lds