现在的位置: 首页 > 综合 > 正文

GNU开发工具简介(二)

2013年08月07日 ⁄ 综合 ⁄ 共 5636字 ⁄ 字号 评论关闭

 第三节 链接器ld

   ld软件的作用是把各种目标文件(.o文件)和库文件链接在一起,并定位数据和函数地址,最终生成执行程序。ld软件识别一种用链接命令语言(Linker Command Language)表示的链接描述(Linker Script)文件来显式地控制链接的过程。通过BFD(Binary Format Description)库,ld可以读取和操作COFF、ELF、a.out等各种执行文件格式的目标文件。ld的一个简单使用例子如下:
$ld -o hello /lib/crt0.o hello.o -lc
  它的意思是把crt0.o、hello.o和库文件libc链接起来产生可执行程序hello。这里的crt0.o是应用程序编译连接时需要的启动文件,在程序连接阶段被链接,主要工作是初始化应用程序栈,初始化程序的运行环境和在程序退出时清除和释放资源。libc库中包含了C程序中常用的函数实现,如printf、gets等。

   gcc可以间接地调用ld,这主要是通过给gcc传递一个参数-Wl(是“ld”的“l”,而不是“123”的“1”)来完成的,放到Wl后面的参数gcc不会处理,而是交给ld进行处理。例如:
$gcc -Wl,--startgroup foo.o bar.o -Wl--endgroup
等同于执行如下的ld命令:
$ld --startgroup foo.o bar.o --endgroup

ld具有许多选项,执行如下命令:
$ld --help
就可以列出ld常用的选项(下面的选项列表中,“,”表示“或”的意思):

 链接命令语言(Linker Scripts)表示的链接描述文件显式地控制了ld的链接过程。ld命令选项-T FILE, --script FILE指定了链接描述文件名。链接描述文件描述了各个输入文件的各节如何映射到输出文件的各节中,并控制输出文件中各个节或符号的内存布局。如果不指定链接描述文件,则ld会使用一个默认的描述文件来产生执行文件。
 
 目标文件是由多个节(Section)构成的,如data节一般保存了有初值的全局变量,text节保存了执行代码,bss节保存了无初值的全局变量。目标文件中的每节有名字和大小,而且节可以标识为loadable,这表示这个节中的内容可以加载到内存中,如果节不是loadable或allocatable,则这种节可能包含调试信息。通过如下命令可以看到执行程序test的各个节的信息:
 $objdump -h test
 每个有loadable或allocatable标识的输出节有两种地址,一种地址是VMA(Virtual Memory Address),这种地址是输出文件运行时节的运行地址;另一种地址是LMA(Load Memory Address),这种地址是加载输出文件时节的加载地址。一般情况下,这两种地址是相同的,但在嵌入式系统中,经常存在运行地址和加载地址不一致的情况,例如把输出文件加载到开发板的Flash存储器中(地址由LMA指定),但运行时,要把Flash存储器中的输出文件复制到SDRAM中运行(地址由VMA指定)。
 
 每个目标文件有许多符号,每个符号有一个名字和一个地址,一个符号可以是定义的(Defined)或未定义的(Undefined)。一个符号可以是函数名、全局变量、静态变量等。可通过nm命令或objdump -t命令来查看目标文件或执行文件中符号信息。
 
 链接描述文件是一个文本文件,它主要由一系列的命令组成,每个命令可以是一个带参数的关键字或赋值语句。各个命令通过分号分隔。“/*”和“*/”之间的字符是注释。

 链接描述文件的命令主要包括如下几类:
设置入口点(Entry Point)的命令(可执行程序的第一条执行指令称为入口点)
处理文件的命令
处理文件格式的命令
其它命令

下面就一些常用命令进行介绍。

ENTRY(Symbol):设置symbol的值为可执行程序的入口点。ld有多种方法设置可执行程序的入口点,它按如下的顺序来确定程序的入口点:
1) ld的命令行选项-e指定的值
2) ld的链接描述文件中的ENTRY(symbol)指定的值
3) .text节的起始地址
4) 入口点为0

INCLUDE filename:包含其他名为filename的链接描述文件
INPUT(file, file, …):指定多个输入文件名
GROUP(file, file, …):需要重复搜索符号定义的多个输入文件名
OUTPUT(filename):指定输出文件名
SEARCH_DIR(path):指定输入文件搜索路径
STARTUP(filename):指定第一个链接的输入文件名
OUTPUT_FORMAT(bfdname):指定输出文件的BFD格式
TARGET(bfdname):指定输入文件的BFD格式

简单赋值语句(或称为符号赋值命令):
symbol = expression;
symbol += expression;
symbol -= expression;
symbol *= expression;
symbol /= expression;
symbol <<= expression;
symbol >>= expression;
symbol &= expression;
symbol |= expression;

PROVIDE(symbol=expression):定义了一个变量symbol,且它的值为expression,这个变量可以被输入文件引用。

MEMORY命令:MEMORY命令在用于嵌入式系统的链接描述文件中经常出现,它描述了各个内存块的起始地址和大小,它的格式如下:
MEMORY
{
 name[(attr)]:ORIGIN = origin,LENGTH = len
 …
}
name可以理解为描述的内存块名字(region)。len表示这个内存块的大小。attr表示内存块的属性,它的值可以是:
R:只读
W:可读/写
X:可执行
A:可分配
I:用于初始化的内存,初始化后,可以回收为空闲内存
!:表示非,和上述各个值配合使用。

SECTIONS命令告诉ld如何把输入文件的各个输出节映射到输出文件的各个输入节中,SECTIONS命令的格式如下:
SECTIONS
{
 sections-command
 sections-command
 …
}
每个SECTIONS相关的命令可以是如下命令:
ENTRY命令
符号赋值(Symbol Assignment)命令
输出文件节描述(Output Section Description)
覆盖描述(Overlay Description)
输出文件节描述的格式如下:
section [address][(type)] : [AT(lma)]
{
 output-section-command
 output-secton-command
 …
}[>region][AT>lma_region][:phdr :phdr …][=fillexp]
上述“[”和“]”之间的属性是可选的,一般可以不用。常用属性介绍如下:
1、address:指定输出节的起始地址

2、type:每个输出节可以有一个属性type,属性的格式如下:
(type_keyword)
type_keyword可以是如下值:
NOLOAD:表示这个节在输出文件运行时是不加载到内存中的。(Not loadable)
DESCT、COPY、INFO、OVERLAY:这几个值的意思都是一样的,都表示这个节在输出文件运行时是不分配内存的(Not Allocatable)

3、>region:表示输出节的起始地址和大小由MEMORY命令中的region定义指定。下面是一个简单的例子:
MEMORY{rom:ORIGIN=0x1000,LENGTH=0x1000}
SECTION{ROM:{*(.text)} > rom}

输出文件节描述可以包括如下命令:
符号赋值(symbol assignment)命令
输入文件节描述
输出节数据值定义
输出节关键字(一般很少用到)
输出文件节的名字一般包括.text、.data、.bss等常见的节名字,也可以定义其他一些特定的名字。如果输出节的名字是“/DISCARD/”,它表示所有映射到这个输出节的输入节都不会在输出文件中存在。输入文件描述的格式如下:
filename(Sectionname)
filename可以由通配符“*”表示,即表示所有的输入文件,也可以是多个或单个输入文件的名字。Sectionname是输入文件中输入节的名字。下面是一些例子:
data.o(.data)
*(.text)
*(.init)

链接描述文件例子
链接描述文件一般都比较简单,可能只包含一个链接命令:SECTIONS。假设ld要输出的一个执行程序包含初始化数据的.data节,未初始化数据的.bss节和包含代码的.text节,而且ld处理的输入目标文件也只包含这3个节,则这个简单的链接描述文件例子如下所示:
SECTIONS
{
 .=0x1000000;
 .text:{*(.text)}
 .=0x8000000;
 .data:{*(.data)}
 .bss:{*(.bss)}
}
第一行是关键字SECTIONS,紧接着是一系列的符号赋值,而输出文件中的节描述包含在“(”和“)”中。
大括号中的第一条语句是一个赋值语句,它表示起始的加载地址为0x1000000,这里“.”表示当前位置的地址。第二条语句定义了输出文件的.text节包含的内容,*(.text)表示把所有输入文件中的.text收集起来,构成输出文件的.text内容。“*”表示ld命令行上描述的所有输入文件。第三条语句是一个地址赋值语句,它定义了输入文件接下来的.data节的起始地址为0x8000000。而输入文件的.bss节的起始地址为0x8000000加上所有输入文件的.data节的大小总和。

下面是用于描述用于某个嵌入式设备的uClinux内核布局的链接描述文件(“//”后的语句是附加的注释,在实际的文件中并不存在)
//标注嵌入式设备中各个内存块的地址划分情况
MEMORY
{
 //表示flash中断向量表的起始地址为0x01000000,长度为0x00400
 romvec:ORIGIN=0x01000000,LENGTH=0x00400
 //标注flash的起始地址为0x01000400,长度为0x011fffff - 0x01000400
 flash:ORIGIN=0x01000400,LENGTH=0x011fffff - 0x01000400
 //标注flash的结束的位置在0x011fffff
 eflash:ORIGIN=0x011fffff,LENGTH=1
 //标注ram中中断向量表的起始地址为0x00000000,长度为1024
 ramvec:ORIGIN=0x00000000,LENGTH=1024
 //标注ram其他的可用的内存,长度为4M-1K
 ram:ORIGIN=0x00000400,LENGTH=0x003fffff - 0x00000400
 //标注ram内存的结束位置
 eram:ORIGIN=0x003fffff,LENGTH=1
}

SECTIONS
{
 //定义输出文件的.ramvec节
 .ramvec:
{
 //设定一个变量_ramvec来代表当前的位置,即ramvec节的开始处
 _ramvec=.;
}>ramvec
//把该节定义到MEMORY中定义的ramvec所代表内存块中,即从0x00000000开始的1024
//字节
//定义输出文件的.data节
 .data:
{
 //设定一个变量_data_start来代表当前的位置,即data节的开始处
 _data_start=.;
 //把所有输入文件中的.data节的数据放在此处
 *(.data)
 //定义edata变量的值为.data节的结束地址
 edata=.;
 //把edata按16位对齐
 edata=ALIGN(0x10);
}>ram
//把.data节的内容放到ram定义的MEMORY中。
//定义未初始化数据的.bss节
 .bss
{
 //记录该节的起始位置
 _bss_start=ALIGN(0x10);
 //记录.bss节的结束位置
 _data_end=ALIGN(0x10);
 //把所有.bss数据放在此处
 *(.bss)
 //把所有COMMON的数据放在此处
 *(COMMON)
 //记录.bss节结束的位置,16位对齐
 end=ALIGN(0x10);
 //记录.bss节结束的位置,16位对齐
 _end=ALIGN(0x10);
}>ram
//把.bss节的内容放到MEMORY中定义的ram所代表的内存块中
//定义输出文件中的.eram节
 .eram:
{
 //记录该节结束的位置
 _ramend=.;
}eram
//把.eram节的内容放到MEMORY中定义的eram所代表的内存块中
//定义输出文件的.romvec节,用于记录flash中中断向量表
 .romvec:
{
 //记录该节的位置
 _romvec=.;
}>romvec
//将该节内容放到MEMORY中定义的romvec中
//定义输出文件的.text节,即程序段
 .text:
{
 //记录.text的起始位置
 text_start=.;
 //将所有输入文件的.text节的内容放到这里
 *(.text)
 //记录.text节结束的位置
 _etext=.;
 //标志.text节中的数据结束的位置
 _data_rom_start=ALIGN(4);
}>flash
//把.text节的内容放到MEMORY中定义的flash表示的内存块中
//定义flash结束的节
.eflash:
{
//记录当前的位置
_flashend=.;
}>eflash
//记录该节放入MEMORY中的位置
}
//描述文件结束

抱歉!评论已关闭.