参考资料:
<<程序员的自我修养–链接,装载与库>>
1 常用参数
各个链接器平台的链接控制过程各不相同,这里介绍的为ld。
-
-static
表示使用静态链接方式来链接程序,而不是用默认的动态链接的方式。 -
-e
指定该程序的入口函数。 -
-o
指定输出文件名。 -
-T
指定链接控制脚本。 -
-Tbss
-Tbss ADDRESS,设置.bss section地址。 -
-Tdata
-Tdata ADDRESS,设置.data section地址。 -
-Ttext
-Ttext ADDRESS,设置.text section地址。
2 链接控制脚本
链接器一般都提供多种控制整个链接过程的方法,以产生用户所需的文件,一般有如下三种方法:
- 使用命令行给链接器指定参数,如 -e指定入口函数。
- 将链接指令存放在目标文件里,编译器经常会通过这种方法给链接器传递指令。
- 使用链接控制脚本,这种比较灵活和强大,使用-T指定脚本。
无论是输入文件还是输出文件,它们的主要数据就是文件中的各种section,我们把输入文件中的section称为input sections(输入段),输出文件中的section称为output sections(输出段)。简单来说,控制链接过程无非就是控制输入段如何变成输出段,如哪些section要丢弃,合并的顺序等。
3 链接控制脚本语言
链接控制脚本是用特殊的语言写成的,并不复杂,只有为数不多的几种操作。
3.1 语法介绍
ld链接器的链接脚本语法继承于AT&T链接器命令语言的语法。链接脚本由一系列语句组成,语句分两种,一种是命令语句、另一种是赋值语句。
语句之间用";“作为分割符,命令语句可以用换行来结束该语句。赋值语句只能用”;"结尾。
表达式与运算符可以使用C语言类似的表达式和运算操作符.如:+,-,*,/,+=,>>,<<,|,&等。注释和字符引用 使用/**/作为注释。
3.2 常用的命令语句
- ENTRY(symbol)
指定符号symbol的值为入口地址(Entry Point),ld有多种方法可以设置入口地址,它们之间的优先级顺序如下:
- ld -e参数,优先级最高。
- 链接脚本里的ENTRY(symbol)命令。
- 如果定义了_start符号,则使用_start。
- 如果存在.text section,则使用.text section的第一个字节地址。
- 使用值0。
-
STARTUP(filename)
将文件filename作为链接过程的第一个输入文件,输入文件顺序不同会生成不同的输出文件。(运行地址和section偏移可能不一样)。 -
SEARCH_DIR(path)
将路径path加入到ld链接器的库查找目录。跟"-Lpath"差不多。 -
INPUT(file, file)
将指定文件作为链接过程的输入文件。 -
INCLUDE(filename)
指定的文件包含进链接脚本。 -
OUTPUT_FORMAT(default,big,little)
定义三种输出文件的格式(大小端)。 -
OUTPUT_ARCH(bfdarch)
设置输出文件的体系结构,BFDARCH为被BFD库使用的名字之一。 -
SECTIONS
{
…
secname : { contents }
…
}
secname表示输出段的段名,secname后面必须有一个空格,防止输出section有歧义。
contents中可以包含若干个条件,每个条件之间用空格隔开,如果输入段符合这些条件中的任意一个即表示这个输入段符contents规则。条件写法如下:
filename(sections)
filename表示输入文件字,sections表示输入段名。例如: -
file1.o(.data)
表示输入文件中名为file.o的文件中名为.data的section符合条件。 -
file1.o(.data .rodata)
表示输入文件中名为file.o的文件中名为.data或.rodata的section符合条件。 -
file1.o
如果直接指定文件名而忽略后面的小括号和section名,则表示file1.o的所有section都符合条件。 -
(.data)
所有输入文件中的名为.data的section的文件符合条件。*是通配符
链接脚本以hi3516dv300的u-boot.lds为例,这里就不具体分析了。
$cat u-boot.lds
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x80700000; /*将当前地址设置为0x80700000*/
__image_copy_start =.; /*__image_copy_start = 0x80700000*/
. = ALIGN(4); /*对当前地址进行4字节对齐*/
.text : /*.text section*/
{
__text_start = .;
start.o (.text*) /*start.o中的.text* section放在放在开头地址*/
init_registers.o (.text*) /*由于生成的是.bin镜像,不是elf文件,所以必须是start.o放在开始地址*/
lowlevel_init_v300.o (.text*)
ddr_training_impl.o (.text*)
ddr_training_console.o (.text*)
ddr_training_ctl.o (.text*)
ddr_training_boot.o (.text*)
ddr_training_custom.o (.text*)
uart.o (.text*)
div0.o (.text*)
emmc_boot.o (.text*)
image_data.o (.text*)
startup.o(.text*)
reset.o(.text*)
__init_end = .;
ASSERT(((__init_end - __text_start) < 0x6000), "init sections too big!");
*(.text*)
}
__text_end = .;
. = ALIGN(4);
.image : { *(.image) }
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = ALIGN(4);
__image_copy_end =.;
__bss_start = .;
.bss : { *(.bss) }
__bss_end = .;
_end = .;
}