最近在学习《MIT6.828》,因此顺便简单学习一下ld脚本。主要参考gnu的官方文档,对其用法做一个简单的概括。LD,同时参考了一些网上的资料,
ld - 链接脚本学习笔记与实践过程。
本文的布局以及内容基本按照LD进行描述,强烈建议读者自行阅读官方文档LD。
链接脚本概述
当链接器将可重定位文件通过重定位等操作生成可执行程序的时候,链接脚本可以控制输入文件中的各个段在生成文件中的位置,并且可以规定生成的可执行文件加载入内存之后的空间布局。
链接器ld默认使用其内部的连接脚本,因此在一般情况下,我们都不需要对其进行修改。在使用ld的时候,通过-T选项,可以使用自己写的链接脚本完成链接过程,否则会使用默认的链接脚本。
一些基本概念
ELF文件是由一些section组成的,最重要的是3个:.text段,即代码段,保存着运行时的指令的二进制代码;.data段,即数据段,保存着已经初始化的全局变量以及局部静态变量;.bss段,即未初始化的数据段,保存着未指定初始值或初始值为0的全局变量以及局部静态变量,将.bss和.data区段分开的原因:.bss在磁盘上可以只保留标记,而不必真正的分配那么大的磁盘空间给这部分区域,只需在加载入内存之后,分配这些区域,这样可以有效地节省磁盘空间。
section可以被标记为loadable,意味着当输出文件运行起来的时候,这个section的content信息应当被加载到memory中。
section没有content信息,并且被标记为allocatable,意味着会有一块内存会被创建,但是却没有任何内容放在上面,程序运行起来的时候无需要加载任何信息到memory中。
如果section既不是loadable也不是allocatable,那么意味着它包含了一些debug的信息。
链接脚本的输出文件是一个elf文件,里面包含了多个section,包括loadable的section,也包括allocatable section。每一个section包含两个地址,分别是VMA以及LMA。
VMA是virtual memory address,LMA是load memory address。大多数两类地址是相同的,但是在嵌入式开发中不大相同,LMA是flash地址,而VMA是将flash加载到ram里面运行的ram地址。
一个简单例子
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
-
SECTIONS字段描述了输出的ELF文件的内存布局。 -
.符号是一个定位符,描述了当前位置的地址。如果不使用"."来指定开始地址,那么将会从0开始分配地址。 -
.text表示输出文件中将会生成一个.text段。*号是一个通配符,匹配所有的输入elf文件,*(.text)表示所有的输入elf文件中的.text段。由于定位符.被设置为0x10000,因此在输出的文件中,.text段的起始地址将被设置为0x10000,(每个sectiond都有一个Addr字段,用来表明在加载到内存中的地址)可以通过readelf命令查看。 -
同样的,
.data段的地址被设置为0x8000000,.bss段的地址被设置为0x8000000 + sizoef(.data),即直接被放置到了.data的后面。
代码举例
- 我们定义两个文件,main.c和add.c,其中main.c调用了add.c中的代码,我们将分别查看使用了上诉链接脚本和默认链接脚本时,其地址的变化。
//main.c
int initial_data = 100;
extern int uinitial_data;
int add(int lhs, int rhs);;
void main() {
uinitial_data = 20;
int c = add(initial_data, uinitial_data);
}
//add.c
int uinitial_data;
int add(int lhs, int rhs) {
return lhs + rhs;
}
- 先编译出可重定位文件
main.o和add.o,并分别查看其segment:gcc -c main.c和gcc -c add.c。


从节头中可以看出,由于未进行链接,所有的Address全为0。
- 使用默认链接脚本进行链接,
ld -o main main.o add.o,并显示其节头。(这个并不是gcc中使用的用法,此处只是进行举例)

发现其.text地址Address为0x4000e8。
- 使用自定义的连接脚本,
ld -T my.ld -o main main.o add.o,并显示其节头

从图中可以看出.text和.data,.bss的节的地址和链接脚本中的配置是一致的。
我们还可以改变.bss和.data的相对地址,并将.data改名为.dataTest。

连接脚本的一些基本语法
设置程序执行入口
- 在程序中执行的第一条指令称为程序入口。可以用下述命令指定程序的入口。
ENTRY(symbol)
- 程序入口的设置规则,优先级由高到低
ld -e选项指定ENTRY(symbol)设置- 特定的符号的值。例如很多程序使用符号
start的值作为入口地址。 - 代码段的第一个地址
- 地址0
设置变量
PROVIDE
provide在脚本中定义了一个变量,之后,C代码中可以通过extern来使用这个变量。
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
- 如果程序中定义了变量
_etext,那么链接的时候会报重复定义的错误。如果程序中定义了etext,程序不会报错,而是使用程序自己定义的那个变量。如果程序未定义etext而使用了它,那么程序就会使用链接脚本中定义的变量。
理解LD链接脚本:内存布局与程序入口

本文介绍了LD链接脚本在生成可执行程序时的作用,包括控制输入文件段的位置、内存布局以及设置程序入口。通过一个简单例子展示了如何自定义链接脚本,解释了段、数据段和未初始化数据段的概念,并探讨了如何使用`PROVIDE`设置变量。
1140

被折叠的 条评论
为什么被折叠?



