连接脚本将我整整蒙了1天零一个上午,做了很多实验,看了人家不少例子代码
勉强能驾驭了,让linker按照我想要的来处理,做个笔记。
1,什么叫输入段,什么叫输出段
不知道怎么回事,我对GCC系列的输入和输出两个单词总是进入思维死角,很简单
就是 input section 和 output section,这里不是说翻译的问题,我觉得是一种
思考的方式的问题。
我的问题就是:既然叫输入端,那输入什么?同理,输出的是什么?不知道其他人
不会不理解这个问题,我自己的话是理解了不少时间了 -v-
所谓的输出段,是指生成的文件,例如 elf 中的每个段
所谓的输入段,是指连接的时候提供LD的所有目标文件(OBJ)中的段。
2,lma 和 vma
lma = load memory address
vma = vitual memory address
如果有研究过ADS的估计有印象,那里有个 RO BASE 和 RW BASE 和 ZI BASE,也
就是说,lma 是装载地址,vma 是运行地址,想搞清楚这两个问题,可以阅读一下
《ARM学习报告(杜云海)》作者写的很好,将这个问题分析的很透澈。lma 和vma
只是GCC的叫法而已,其实原理是一样的。
3,两个基本架构
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm",
"elf32-littlearm")
OUTPUT_ARCH(arm)
一句话,照抄......因为我们没有修改的余地,都是系统默认的关键字。第一句
指示系统可以有生成两种格式,默认是 elf32-arm,端格式是 little endian
4,ENTRY(__ENTRY)
指定入口点,LD的手册说,ENTRY POINT 就是程序第一条执行的指令,但是,说老
实话,我并不理解,因为这里跟我的理解矛盾了,首先,通常情况,系统需要一个
初始化的 STARTUP.S文件来初始化硬件,也就是 bootloader的第一阶段了。那么
很自然,入口点需要设置在这段代码的第一条指令中,那么正常运行的时候从第一
条指令开始运行。所以这里设置了__ENTRY为入口点,这个在汇编代码中必须得先
声明一下为全局,才能用,否则系统找不到。例如:
.global __ENTRY
但是问题是,如果我用同样的办法,设置另外一个不是第一条指令的入口点,LD并
没有报错,但是问题来了,生成的文件和刚才设置入口点为 __ENTRY 的时候一模一
样,这就蒙了,到底这个入口点是怎么回事?
记得以前ADS的时候也碰到过 entry point的问题,下载仿真的时候确实是自动跳转
到 entry point中运行。
我想到的可能的原因,第一,生成 elf 文件并不是能直接用在嵌入式平台上面裸跑
的,因为我们并没有操作系统,我们不需要elf文件头的那些指示信息提供给操作
系统,指示系统怎么去加载文件,在嵌入式上面的完全没有那个必要,只需要将实
际的代码提取出来,直接运行就OK,也就是 objcopy的操作,所以我觉得,在裸奔
的嵌入式系统上面,entry point是没有意义的,只需要指向整个代码最开始的指
令就OK了。
暂时我还是不能清晰的理解这个东西。先放下。以后碰到问题再分析。
5,一个输出段的标准格式
section [address] [(type)] : [AT(lma)]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
前面也说了,所谓的输出段是指最终生成的文件里面的段,所以一个输出段就可以
理解为最终文件里面的一个块,那么多个块合起来就是一个完成文件了。
而每个小块又分别有什么文件来组成呢?那就是输入段了。
我自己实际用到有下面的一些,其他暂时不会用。
section_name vma : AT(lma)
{
output-section-command
output-section-command
...
} [AT>lma_region]
section_name 根据ld手册说是有个确定的名字,其他没啥,自己添加一些新段也是
可以的。
默认的4个段是必须有的
.text 代码
.rodata 常量,例如字符串什么的
.data 初始化的全局变量
.bss 没有初始化的全局变量
其实没什么,可以说,都是固定的,所以一句话,照抄。
段名字后面紧跟的是 vma ,也就是这个段在程序运行的时候的地址,例如
.text 0x30000000 :
{
*(.text)
}
表示的是代码的运行时地址为 0x30000000 假如你的ROM在 0x0 地址,程序放在ROM
中,那个时候程序是不能正常运行的(位置无关代码除外),必须将代码COPY到VMA
也就是 0x30000000 中才能正常运行。
至于那个 AT(lma) 的关键字,只指示代码连接的时候应该放在什么地方,注意好
这个英文是 load memory address,是指程序应该装载在什么地方,而不是指这个段
应该在最后生成的bin文件的位置!!!这个东西蒙骗了我,让我郁闷了1天。elf格
式的文件里面不但包含了代码,还包含了各种各样的信息,例如上面说的每个段的
lma 和vma,还有其他信息都包含在里面了。
默认状态下,lma 是等于当前的vma的,例如
.text 0x30000000 :
{
*(.text)
*(.rodata)
}
.data 0x33ffff00 :
{
*(.data)
}
例如我们基本的两个段,我们指定了.text 和.data段的vma,但是没有指定lma,那么
lma到底应该是多少?很简单,ld认为当前的lma和vma是相同的,所以lma应该分别是
0x30000000 0x33ffff00,编译生成的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文件就是这样来的。
可以验证一下,如果手动指定开始的lma为0的话
.text 0x30000000 : AT(0)
{
*(.text)
*(.rodata)
}
.data 0x33ffff00 :
{
*(.data)
}
其中.text段的lma被AT强制指定为0,那么objcop出来的bin文件相当的华丽,达到了
700多MB,为什么?都说了,从0开始到 0x33ffff00,中间补零,字节数相当的可观呢。 |