GNU ARM 链接脚本 ld
来源:互联网 发布:逐字稿 知乎 编辑:程序博客网 时间:2024/05/18 17:26
1. 概论
2. 基本概念
3. 脚本格式
4. 简单例子
5. 简单脚本命令
6. 对符号的赋值
7. SECTIONS命令
8. MEMORY命令
9. PHDRS命令
10. VERSION命令
11. 脚本内的表达式
12. 暗含的连接脚本
1.概论
每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制.链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局.但你也可以用连接命令做一些其他事情.
连接器有个默认的内置连接脚本, 可用ld --verbose查看.连接选项-r和-N可以影响默认的连接脚本(如何影响?).
-T选项用以指定自己的链接脚本,它将代替默认的连接脚本。你也可以使用<暗含的连接脚本>以增加自定义的链接命令.
以下没有特殊说明,连接器指的是静态连接器.
2.基本概念
链接器把一个或多个输入文件合成一个输出文件.
输入文件: 目标文件或链接脚本文件.
输出文件: 目标文件或可执行文件.
目标文件(包括可执行文件)具有固定的格式, 在UNIX或GNU/Linux平台下, 一般为ELF格式.
有时把输入文件内的section称为输入section(input section),把输出文件内的section称为输出section(output sectin).
目 标文件的每个section至少包含两个信息: 名字和大小. 大部分section还包含与它相关联的一块数据, 称为sectioncontents(section内容).一个section可被标记为“loadable(可加载的)”或“allocatable(可分配的)”.
loadable section: 在输出文件运行时, 相应的section内容将被载入进程地址空间中.
allocatable section: 内容为空的section可被标记为“可分配的”. 在输出文件运行时,在进程地址空间中空出大小同section指定大小的部分. 某些情况下, 这块内存必须被置零.
如果一个section不是“可加载的”或“可分配的”, 那么该section通常包含了调试信息. 可用objdump-h命令查看相关信息.
每 个“可加载的”或“可分配的”输出section通常包含两个地址: VMA(virtual memoryaddress虚拟内存地址或程序地址空间地址)和LMA(load memory address加载内存地址或进程地址空间地址).通常VMA和LMA是相同的.
在目标文件中, loadable或allocatable的输出section有两种地址: VMA(virtual MemoryAddress)和LMA(Load Memory Address). VMA是执行输出文件时section所在的地址,而LMA是加载输出文件时section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系统中,经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash中(由LMA指定),而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定).
可这样来理解VMA和LMA, 假设:
(1) .data section对应的VMA地址是0x08050000, 该section内包含了3个32位全局变量, i、j和k,分别为1,2,3.
(2) .text section内包含由"printf( "j=%d ", j );"程序片段产生的代码.
连接时指定.data section的VMA为0x08050000,产生的printf指令是将地址为0x08050004处的4字节内容作为一个整数打印出来。
如果.data section的LMA为0x08050000,显然结果是j=2
如果.data section的LMA为0x08050004,显然结果是j=1
还可这样理解LMA:
.text section内容的开始处包含如下两条指令(intel i386指令是10字节,每行对应5字节):
jmp0x08048285
movl $0x1,%eax
如 果.text section的LMA为0x08048280, 那么在进程地址空间内0x08048280处为“jmp0x08048285”指令, 0x08048285处为movl $0x1,%eax指令. 假设某指令跳转到地址0x08048280,显然它的执行将导致%eax寄存器被赋值为1.
如果.text section的LMA为0x08048285, 那么在进程地址空间内0x08048285处为“jmp0x08048285”指令, 0x0804828a处为movl $0x1,%eax指令. 假设某指令跳转到地址0x08048285,显然它的执行又跳转到进程地址空间内0x08048285处, 造成死循环.
符号(symbol): 每个目标文件都有符号表(SYMBOL TABLE),包含已定义的符号(对应全局变量和static变量和定义的函数的名字)和未定义符号(未定义的函数的名字和引用但没定义的符号)信息.
符号值: 每个符号对应一个地址, 即符号值(这与c程序内变量的值不一样, 某种情况下可以把它看成变量的地址). 可用nm命令查看它们.(nm的使用方法可参考本blog的GNU binutils笔记)
3.脚本格式
链接脚本由一系列命令组成, 每个命令由一个关键字(一般在其后紧跟相关参数)或一条对符号的赋值语句组成.命令由分号‘;’分隔开.
文件名或格式名内如果包含分号';'或其他分隔符, 则要用引号‘"’将名字全称引用起来. 无法处理含引号的文件名.
之间的是注释。
4.简单例子
在介绍链接描述文件的命令之前, 先看看下述的简单例子:
以下脚本将输出文件的text section定位在0x10000, data section定位在0x8000000:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
解释一下上述的例子:
. = 0x10000 : 把定位器符号置为0x10000 (若不指定, 则该符号的初始值为0).
.text : { *(.text) } : 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.textsection, 该section的地址由定位器符号的值指定, 即0x10000.
. = 0x8000000 :把定位器符号置为0x8000000
.data : { *(.data) } : 将所有输入文件的.text section合并成一个.data section,该section的地址被置为0x8000000.
.bss : { *(.bss) } : 将所有输入文件的.bss section合并成一个.bsssection,该section的地址被置为0x8000000+.data section的大小.
连接器每读完一个section描述后, 将定位器符号的值*增加*该section的大小. 注意: 此处没有考虑对齐约束.
5.简单脚本命令
- 1-
ENTRY(SYMBOL): 将符号SYMBOL的值设置成入口地址。
入口地址(entry point): 进程执行的第一条用户空间的指令在进程地址空间的地址)
ld有多种方法设置进程入口地址, 按一下顺序: (编号越前, 优先级越高)
1, ld命令行的-e选项
2, 连接脚本的ENTRY(SYMBOL)命令
3, 如果定义了start符号, 使用start符号值
4, 如果存在.text section, 使用.text section的第一字节的位置值
5, 使用值0
- 2 -
INCLUDE filename: 包含其他名为filename的链接脚本
相当于c程序内的的#include指令, 用以包含另一个链接脚本.
脚本搜索路径由-L选项指定. INCLUDE指令可以嵌套使用, 最大深度为10. 即: 文件1内INCLUDE文件2,文件2内INCLUDE文件3... , 文件10内INCLUDE文件11. 那么文件11内不能再出现INCLUDE指令了.
- 3-
INPUT(files):将括号内的文件做为链接过程的输入文件
ld首先在当前目录下寻找该文件, 如果没找到, 则在由-L指定的搜索路径下搜索. file可以为-lfile形式,就象命令行的-l选项一样. 如果该命令出现在暗含的脚本内,则该命令内的file在链接过程中的顺序由该暗含的脚本在命令行内的顺序决定.
- 4-
GROUP(files) :指定需要重复搜索符号定义的多个输入文件
file必须是库文件, 且file文件作为一组被ld重复扫描,直到不在有新的未定义的引用出现。
- 5-
OUTPUT(FILENAME): 定义输出文件的名字
同ld的-o选项, 不过-o选项的优先级更高. 所以它可以用来定义默认的输出文件名. 如a.out
- 6-
SEARCH_DIR(PATH):定义搜索路径,
同ld的-L选项, 不过由-L指定的路径要比它定义的优先被搜索。
- 7-
STARTUP(filename): 指定filename为第一个输入文件
在链接过程中, 每个输入文件是有顺序的. 此命令设置文件filename为第一个输入文件。
- 8-
OUTPUT_FORMAT(BFDNAME) :设置输出文件使用的BFD格式
同ld选项-o format BFDNAME, 不过ld选项优先级更高.
- 9-
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE): 定义三种输出文件的格式(大小端)
若有命令行选项-EB, 则使用第2个BFD格式;若有命令行选项-EL,则使用第3个BFD格式.否则默认选第一个BFD格式.
TARGET(BFDNAME):设置输入文件的BFD格式
同ld选项-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令,则最用一个TARGET命令设置的BFD格式将被作为输出文件的BFD格式.
另外还有一些:
ASSERT(EXP,MESSAGE):如果EXP不为真,终止连接过程
EXTERN(SYMBOL SYMBOL...):在输出文件中增加未定义的符号,如同连接器选项-u
FORCE_COMMON_ALLOCATION:为commonsymbol(通用符号)分配空间,即使用了-r连接选项也为其分配
NOCROSSREFS(SECTION SECTION...):检查列出的输出section,如果发现他们之间有相互引用,则报错。对于某些系统,特别是内存较紧张的嵌入式系统,某些section是不能同时存在内存中的,所以他们之间不能相互引用。
OUTPUT_ARCH(BFDARCH):设置输出文件的machinearchitecture(体系结构),BFDARCH为被BFD库使用的名字之一。可以用命令objdump -f查看。
可通过 man -S 1 ld查看ld的联机帮助, 里面也包括了对这些命令的介绍.
6. 链接脚本相关:
(1).编译工具介绍
GNU提供的编译工具包括汇编器as、C编译器gcc、C++编译器g++、连接器ld和二进制转换工具objcopy。基于ARM平台的工具分别为arm-linux-as、arm-linux-gcc、arm-linux-g++、arm-linux-ld和arm-linux- objcopy。GNU的编译器功能非常强大,共有上百个操作选项,这也是这类工具让初学者头痛的原因。不过,实际开发中只需要用到有限的几个,大部分可以采用缺省选项。GNU工具的开发流程如下:编写C、C++语言或汇编源程序,用gcc或g++生成目标文件,编写连接脚本文件,用连接器生成最终目标文件(elf格式),用二进制转换工具生成可下载的二进制代码。
(2)编写C、C++语言或汇编源程序
通常汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,读者可以从GNU的站点(www.gnu.org)上下载有关规范。汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点(见下文关于连接脚本的说明)。
(3)用gcc或g++生成目标文件
如果应用程序包括多个文件,就需要进行分别编译,最后用连接器连接起来。如笔者的引导程序包括3个文件:init.s(汇编代码、初始化硬件)xmrecever.c(通信模块,采用Xmode协议)和flash.c(Flash擦写模块)。
分别用如下命令生成目标文件: arm-linux-gcc-c-O2-oinit.oinit.s arm-linux-gcc-c-O2-oxmrecever.oxmrecever.c arm-linux-gcc-c-O2-oflash.oflash.c 其中-c命令表示只生成目标代码,不进行连接;-o命令指明目标文件的名称;-O2表示采用二级优化,采用优化后可使生成的代码更短,运行速度更快。如果项目包含很多文件,则需要编写makefile文件。关于makefile的内容,请感兴趣的读者参考相关资料。
(4) 编写链接脚本
gcc等编译器内置有缺省的连接脚本。如果采用缺省脚本,则生成的目标代码需要操作系统才能加载运行。为了能在嵌入式系统上直接运行,需要编写自己的连接脚本文件。编写连接脚本,首先要对目标文件的格式有一定了解。GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特殊指明,由C源程序生成的目标代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符串;.bss(未初始化数据段)包含未初始化的变量、数组等。C++源程序生成的目标代码中还包括.fini(析构函数代码)和. init(构造函数代码)等。连接器的任务就是将多个目标文件的.text、.data和.bss等段连接在一起,而连接脚本文件是告诉连接器从什么地址开始放置这些段。
例如,连接文件link.lds为:
ENTRY(begin)
SECTION
{
.=0x30000000;
.text:{*(.text)}
.data:{*(.data)}
.bss:{*(.bss)}
}
其中,ENTRY(begin)指明程序的入口点为begin标号;.= 0x30000000指明目标代码的起始地址为0x30000000;.text:{*(.text)}表示从0x30000000开始放置所有目标文件的代码段,随后的.data:{* (.data)}表示数据段从代码段的末尾开始,再后是.bss段。
(5)用连接器生成最终目标文件
有了连接脚本文件,如下命令可生成最终的目标文件:
arm-linux-ld –no stadlib –o bootstrap.elf -Tlink.lds init.o xmrecever.o flash.o
其中,ostadlib表示不连接系统的运行库,而是直接从begin入口;-o指明目标文件的名称;-T指明采用的连接脚本文件(也可以使用-Ttext address,address表示执行区地址);最后是需要连接的目标文件列表。
(6)生成二进制代码
连接生成的elf文件还不能直接下载执行,通过objcopy工具可生成最终的二进制文件:
arm-linux-objcopy –O binary bootstrap.elf bootstrap.bin
其中-O binary指定生成为二进制格式文件。Objcopy还可以生成S格式的文件,只需将参数换成-O srec。还可以使用-S选项,移除所有的符号信息及重定位信息。如果想将生成的目标代码反汇编,还可以用objdump工具:
arm-linux-objdump -D bootstrap.elf
至此,所生成的目标文件就可以直接写入Flash中运行了。
【例2】makefile举例:
example: head.s main.c
arm-linux-gcc -c -o head.o head.s
arm-linux-gcc -c -o main.o main.c
arm-linux-ld -Tlink.lds head.o ain.o -o example.elf
arm-linux-objcopy -O binary -S example_tmp.o example
arm-linux-objdump -D -b binary -m arm example >ttt.s
7.段与重定位
1. 概述
段是具有相同属性的一段内容。
链接器ld用于把多个objectfile(partial program)合并为一个可执行文件。as生成的目标文件(partialprogram)都假定从地址0开始,ld为其指定最终的地址。
链接器ld把objectfile中的每个section都作为一个整体,为其分配运行的地址(memorylayout),这个过程就是重定位(relocation)。
as所产生的一个目标文件至少有text、data和bss这3个段,每个段都可以为空。如果为COFF或ELF格式的目标文件,as还可以根据源文件中使用'.section'伪指令所指定的任意名字的段。源文件中使用'.text'或'.data'所指定的内容会分别分配到text和data段中。源文件中的这些段都属于input section。
在目标文件中,textsection从地址0开始,随后是data section,最后是bss section(这与ARMADS中描述的映像文件的RO、RW和ZI段的排列是一致的)。
2. Linker Sections
ld处理下面4类section:
(1)named sections, text section 和data section
这些段存放着你的程序。程序运行时,textsection是unalterable的(RO段),包含了指令、常量等;datasection则是alterable的(RW段),例如C变量就放在这里。
(2)bss section
该段实际为ZI段,全为0,用于存放未初始化的变量。
(3)absolute section
该段中的地址0总是被“重定位”到运行时的地址0。当需要指定一个地址不希望在ld重定位时被改变时,这很有用。这时我们也说absoluteaddress是unrelocateable的。
(4)undefined section
其他目标文件的地址信息。
3. Sub-section
汇编程序最后变为text和data两个段(当然还有自命名的段)。同一属性和名字的段可以分布在程序中的多个地方,这可以使用数字来对subsection编号来实现(0到8192)。例如,你想把text段中的code放在一起,把常量放在一起,最后再组成textsection;这时,你可以把code使用'.text 0'标记,把常量使用'.text 1'标记即可。
- GNU ARM 链接脚本 ld
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU ld链接脚本学习
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析
- GNU-ld链接脚本浅析 .
- GNU-ld链接脚本浅析
- GNU ld链接脚本学习
- HDU 1032 3N+1
- Java 异常 小知识点 关键地方已标出
- WinCE系统 USB Serial实现
- ios中有些过时的方法整理
- UITabBarController详解(一)UITabBarController的介绍和设置(偷了点懒,直接用了ARC)
- GNU ARM 链接脚本 ld
- 自动化第二周c++作业
- 率失真
- 第二周作业
- python--python读写文件
- 怎么让IIS 支持php zend加密程序运行
- 三月面试总结
- 一周项目回顾
- JavaScript动画工作原理之(一)