前言:arm linux的连接工具可以使用arm-linux-ld,在进行连接时可以使用-T命令采用脚本控制,如不指明脚本,则使用默认的脚本文件,参见arm-linux-ld的缺省linker script。
一.目标文件格式与类型本文引用地址:http://www.eepw.com.cn/article/201611/317234.htm
GNU C compiler根据源文件的后缀名来对文件进行预处理、汇编或编译操作。在编译链接时,生成的目标文件都是ELF格式的(可执行链接格式,Executable and Linking Format)。Object文件格式有三种类型:
(1)可重定位(relocatable)文件:用来和其他的object文件一起链接为一个可执行文件(executable)或一个共享文件(.so文件,shared object)。
(2)可执行(executable)文件;
(3)共享目标文件(shared object file):用于被下面的两个链接器链接。一是链接编辑器(ld),可以和其他的relocatable或shared object file来创建其他的目标文件,例如.so共享库(可用file命令查看其属性);二是动态链接器,联合一个可执行文件和其他的shared object file来创建一个进程映像。
二.链接与链接脚本
链接器ld把object文件中的每个section都作为一个整体,为其分配运行的地址(memory layout),这个过程就是重定位(relocation);最后把所有目标文件合并为一个目标文件。
链接通过一个linker script来控制,这个脚本描述了输入文件的sections到输出文件的映射,以及输出文件的memory layout。
因此,linker总会使用一个linker script,如果不特别指定,则使用默认的script;可以使用‘-T’命令行选项来指定一个linker script。
1. 映像文件的输入段与输出段
linker把多个输入文件合并为一个输出文件。输出文件和输入文件都是目标文件(object file),输出文件通常被称为可执行文件(executable)。
每个目标文件都有一系列section,输入文件的section称为input section,输出文件的section则称为output section。
一个section可以是loadable的,即输出文件运行时需要将这样的section加载到memory(类似于RO&RW段);也可以是allocatable的,这样的section没有任何内容,某些时候用0对相应的memory区域进行初始化(类似于ZI段);如果一个section既非loadable也非allocatable,则它通常包含的是调试信息。
每个loadable或allocatable的output section都有两个地址,一是VMA(virtual memory address),是该section的运行时域地址;二是LMA(load memory address),是该section的加载时域地址。
可以通过objdump工具附加-h选项来查看目标文件中的sections。
2. 简单的Linker script
(1) SECTIONS命令:
The SECTIONS command tells the linker how to map input sections into output sections, and how to place the output sections in memory.
命令格式如下:
SECTIONS
{
sections-command
sections-command
......
}
其中sections-command可以是ENTRY命令,符号赋值,输出段描述,也可以是overlay描述。
(2)地址计数器‘.’(location counter):
该符号只能用于SECTIONS命令内部,初始值为‘0’,可以对该符号进行赋值,也可以使用该符号进行计算或赋值给其他符号。它会自动根据SECTIONS命令内部所描述的输出段的大小来计算当前的地址。
(3)输出段描述(output section description):
前面提到在SECTIONS命令中可以作输出段描述,描述的格式如下:
sectionname [address] [(type)] : [AT(lma)]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
很多附加选项是用不到的。其中的output-section-command又可以是符号赋值,输入段描述,要直接包含的数据值,或者某一特定的输出段关键字。
3. 举例
下面看一个常用的用于分散加载(即存储或加载地址和链接或运行地址不同)的格式:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
secname和contents是必须的,其他的都是可选的。下面挑几个常用的看看:
(1)secname:段名
(2)contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
(3)start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。
(4)AT(ldadr):定义本段存储(加载)的地址。
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
以上,head.o放在0x00000000地址开始处,init.o放在head.o后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);main.o放在4096(0x1000,是AT指定的,存储地址)开始处,但是它的运行地址在0x30000000,运行之前需要从0x1000(加载处)复制到0x30000000(运行处),此过程也就用到了读取Nand flash。
编写好的.lds文件,在用arm-linux-ld连接命令时带-Tfilename来调用执行,如
arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也用-Ttext参数直接指定连接地址,如
arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。
4. ARM汇编中实现跳转
由于会使用分散加载,因此在使用汇编实现跳转时应该注意。ARM汇编中,常有两种跳转方法:b跳转指令、ldr指令向PC赋值。
(1) b step1 :b跳转指令是相对跳转,依赖当前PC的值,偏移量是通过该指令本身的bit[23:0]算出来的,这使得使用b指令的程序不依赖于要跳到的代码的位置,只看指令本身。
(2) ldr pc, =step1 :该指令是从内存中的某个位置(step1)读出数据并赋给PC,同样依赖当前PC的值,但是偏移量是那个位置(step1)的连接地址(运行时的地址),因此不管最终程序在什么地方运行,所得到的都是同样的地址(绝对地址),所以可以用它实现从Flash到RAM的程序跳转。
参考资料:
1.arm-linuxbootloader预备之GNUld机理,http://blog.21ic.com/user1/1028/archives/2008/47653.html
2. 对.lds连接脚本文件的分析,http://blog.chinaunix.net/u1/58780/showart.php?id=462971
3. 用GNU工具开发基于ARM的嵌入式系统,http://blog.163.com/liren0@126/blog/static/32897598200821211144696/