链接器ld

 

 

链接器的作用是什么呢?还记得在Linux学习(十二)中gcc编译的过程吗?

gcc编译分为四个过程:预编译、编译、汇编、链接

链接是gcc编译的最后一步,就是把生成的所有可执行文件.o按照一定的规则合并成一个文件,而链接脚本就是这个规则文件。

链接器ld_第1张图片

 

1、重定位的概念

链接生成的可执行文件虽然是放在文件中的,但当程序运行时需要加载到内存当中。各段应放在内存空间的什么位置是由可执行程序文件内的头部信息决定的。

当一个源文件被编译成目标文件时,此时目标中的各段并没有具体的地址,在目标文件中只记录了程序中的符号和各个符号在段中的相对位置。当链接器将所有的目标文件合成一个可执行文件时,各目标文件中的各段将会真正获得内存的具体地址。链接生成可执行程序时,需要根据每一个符号所对应的真实地址而更新相应的指令,从而实现真正的函数调用和变量引用的功能,这一动作就是重定位。

2、一个简单的链接脚本如下

链接器ld_第2张图片

 

SECTIONS是命令,表示定义可执行程序中的各个段

默认情况下进入SECTIONS地址为0.我们可以对这个地址进行设置,就像第三行那样,表示设置当前地址为0x1000000.后面的.text就从0x1000000处存放。

第6行表示所有可执行文件的text段都放入可执行程序的text段。第10,14行也是类似意思

第8行表示data段从0x8000000地址处开始

3、链接脚本中的命令

链接脚本由各种命令组成,比如上面简单的脚本中只有一个SECTIONS命令。脚本中还有其他什么常用的命令呢

  1、ALIGN命令

ALIGN (_align)

返回 位置指针之后的第一个满足边界对齐字节数_align的地址值

2、BYTE\SHORT\LONG\LONG

BYTE(_value)

SHORT(_value)

LONG(_value)

LONG(_value)

依次表示在输出的可执行文件中插入value*(1/2/4/8)个字节的大小

链接器ld_第3张图片

上图中在.text段和data段之间插入4个字节的空间。

3、ENTRY命令

格式:

ENTRY (_symbol)

这个命令经常也会用到,这个命令用于指定可执行程序的入口点。入口点是指程序被加载到内存后,运行第一条指令所在的内存地址

4、MEMORY指令

命令格式:

链接器ld_第4张图片

在默认情况下,ld认为整个可执行程序都是放入同一个存储空间的。如果存在不同的存储空间,就可以使用MEMORY命令进行存储区域的指定。

_attr是这块内存区域的属性设置,具体值如下,设置不区分大小写。

链接器ld_第5张图片

ORIGIN是存储区域的开始地址,LENGTH是指存储区域的大小。

链接器ld_第6张图片

上述制定了两块内存区域,一个是从0地址开始的128M空间,一个是从0X40000000开始的16K的地址

5、SECTIONS命令

SECTIONS命令用于告诉ld如何将各个目标文件中的段映射到输出的可执行文件中,以及如何在内存中布局可执行程序的各个段。这也是ld脚本中最常用的命令。该命令的格式为

链接器ld_第7张图片

sectionss-command可以是如下的几种格式

链接器ld_第8张图片

 

我们在最一开始看到的简单的ld脚本文件中,

链接器ld_第9张图片

.text :
{
    *(.text)
}

这一部分就属于输出段描述,输出段描述的具体格式如下

链接器ld_第10张图片

section 是输出段的名称

_address 和_lma分别是程序运行时虚拟内存地址vma和load地址lma,两个地址大部分情况下是一致的,但也会出现不一致的情况。我们待会儿看个例子。

最后一行中的_region 和_lma_region指的虚拟内存和lma的区域,可以配合MEMORY设定的内存区域使用

_section_align是段的对齐方式

_subsection_align表示子段的对齐方式。

output-section-command就是形如*(.text)这样的内容,它的具体形式可以是:

链接器ld_第11张图片

 输入段的作用就是为了告诉ld如何将所有目标文件中的段映射到内存当中,链接脚本中的内容大部分都是输入段描述,输入段描述常见格式如下

链接器ld_第12张图片

链接器ld_第13张图片

链接器ld_第14张图片

 

 

一个简答的例子

MEMORY
{
    sdram (rwx) : ORIGIN = 0x0, LENGTH = 128M
    sram (rwx) : ORIGIN = 0x50000000, LENGTH = 32K
}

OUTPUT_ARCH(arm)
ENTRY(__start)
SECTIONS
{
	.text 0xc000 : {
		*(.text )
	} > sdram


	.data : {
		*(.data )
	} > sdram

	_eronly = ABSOLUTE(.);

	.ramfunc 0x50000000:AT (0X100000) {
		_sramfuncs = ABSOLUTE(.);
		*(.ramfunc  .ramfunc.*)
		_eramfuncs = ABSOLUTE(.);
	} > sram AT > sdram

	_framfuncs = LOADADDR(.ramfunc);

	.bss : {
		*(.bss )
	} > sdram

}

首先定义了两块内存区域。

OUTPUT_ARCH(arm) 表示输出的指令集架构为arm

ENTRY指定了程序的入口

在SECTIONS命令当中,指定了text段的起始地址为0xc000。

.ramfunc使=是我们自己定义的段,这一段比较特殊,VMA和LMA的地址是不一致的,VMA指定为0x50000000,在sram上,而LMA则是0x100000.在sdrm区域上。

ABSOLUTE(.)表示的是当前的绝对地址,也就是VMA,LOADADDR(.ramfuc)表示则是.ramfunc段起始的lma地址,如果VMA地址和LMA地址不一致,我们在程序初始的时候就需要有一次搬运的过程,将指定的段从LMA地址搬运到VMA地址上

 

参考文献:《专业嵌入式开发》

你可能感兴趣的:(Linux,学习)