链接器 Linker:是一个程序,将一个或多个编译器或汇编生成的目标文件,及依赖库,链接为一个可执行文件。
GNU Linker 采用 AT&T 链接脚本语言;
链接脚本文件:包含 ld 程序链接的规则,其决定输出可执行文件的内存布局;
LD 命令:arm64 版本的连接器是 aarch64-linux-gnu-ld
查看命令参数:
aarch64-linux-gnu-ld --help
LD 命令的参数有很多,常用的如下:
$(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -Map xxx.map -o $(BUILD_DIR)/xxx.elf $(OBJ_FILES)
常用参数:
-T:指定连接脚本;
-Map:输出一个符号表文件;
-o: 输出最终可执行二进制文件;
基本概念
输入段(input section),输出段(output section)
每个段包括 name 和大小;
段的属性:
loadable: 运行时会加载这些段内容到内存中;
allocatable: 运行时不加载段的内容;
段的地址:
VMA(virtual memory address): 虚拟地址,运行地址;
LMA(load memory address): 加载地址
通常 ROM 的地址为加载地址,而 RAM 地址为 VMA;
链接脚本命令
Entry(symbol): 设置程序的入口函数;
GNU-AS 程序入口点有以下几种:
a. 使用 - e 参数;
b. 使用 ENTRY(symbol);
c. 在.text 的最开始的地方;
d.0 地址;
INCLUDE filename: 引入 filename 的链接脚本;
OUTPUT filename:输出二进制文件,类似命令行的 "-o filename"
OUTPUT_FORMAT(bfd):输出 BFD 格式;
OUTPUT_ARCH(bfdarch): 输出处理器体系架构格式
OUTPUT_ARCH(aarch64)
ENTRY(_text)
赋值符号
符号可以像 C 语言一样赋值;
"." 表示当前位置;
symbol = expression;
symbol += expression;
symbol -= expression;
symbol *= expression;
symbol /= expression;
symbol <<= expression;
symbol >>= expression;
symbol &= expression;
symbol |= expression;
符号的引用
在 C 语言定义一个变量并初始化,编译器会分配一个符号,且在内存中分配空间存储;
在链接脚本中定义的变量,仅仅是在符号表里定义这个符号,没有分配存储空间;
xxx.ld
start_of_ROM = .ROM;
end_of_ROM = .ROM + sizeof(.ROM)
可以在每个段中设置一些符号,以方便 C 语言访问每个段的起始地址和结束地址;
C 语言中,对链接脚本里定义的符号引用
extern char start_of_ROM[],end_of_ROM[];
char buf[4096];
memcpy(buf, start_of_ROM, end_of_ROM - start_of_ROM);// 引用的是地址
SECTIONS 命令
告诉链接器如何把输入段 (input sections) 映射到输出段(output sections),决定输出程序的内存布局;
输出 section 的描述符:
LMA 加载地址
每个段都有 VMA 和 LMA;
在输出段描述符中使用“AT”指定 LMA, 如果没有指定,通常 LMA=VMA;
构造一个基于 ROM 的映像文件,常常会设置输出端的虚拟地址和加载地址不一样;
SECTIONS
{.text 0x1000:{*(.text)_etext=.;}
.mdata 0x2000:
AT(ADDR(.text)+SIZEOF(.text)) // 指定加载地址
{
_data=.;
*(.data);
_edata=.; //_data 和_edata 只是一个符号,用来记录 mdata 段的 VMA 地址
}
.bss 0x3000;
{
_bstart=.;
*(.bss)*(COMMON);
-bend=.;
}
}
mdata 段的加载地址和链接地址不一样,因此程序初始化代码需要把 data 段内容复制到 SDRAM 中的虚拟地址中;
常见的内建函数(builtin functions)
ADDR(section): 返回前面已经定义过的段的 VMA 地址;
ALIGN(n): 返回下一个与 n 字节对齐的地址;
SIZEOF(section): 返回一个段的大小;
MAX/MIN(exp1,exp2): 返回较大 / 较小值;
实例练习:设置代码段的 LMA!=VMA
TEXT_ROM = 0X90000;
SECTIONS
{
/*
* -->location, current addr
* 0x80000 is entrance address
*/
. = 0x80000,
/*
* first instruction
**/
_text_boot = .;
.text.boot : {*(.text.boot) }
_etext_boot = .;
/*
* code segment
*/
_text = .;
.text : AT(TEXT_ROM)
{*(.text) }
_etext = .;
/*
* readonly data segment
*/
_rodata = .;
.rodata : AT(ADDR(.rodata))
{*(.rodata) }
_erodata = .;
/*
* data segment
*/
_data = .;
.data : {*(.data) }
_edata = .;
/*
* bss segment
* 8bytes algine
*/
. = ALIGN(0x8);
_bss = .;
.bss : {*(.bss*) }
_ebss = .;
/*
* alloc a page memory, store page table
* aligne 4096
*/
. = ALIGN(4096);
init_pg_dir = .;
. +=4096;
}
在启动代码,将代码段从 LMA(ROM 地址)拷贝到 VMA(SDRAM 地址)
master:
/*
* copy code segment from rom(LMA) TO ram(VMA)
*/
adr x0, TEXT_ROM
adr x1, _text
adr x2, _etext
1:
ldr x4, [x0], #8
str x4, [x1], #8
cmp x1, x2
b.cc 1b