作为嵌入式软件工程师,应该要清楚程序的每一条指令在哪里,什么时候会被加载到内存,什么时候会被执行。链接脚本会明确告诉你程序的代码和数据在内存中的分布。精确控制代码和数据在内存中的分布是高效利用内存资源的前提。自定义链接脚本是资深嵌入式软件工程师的必备技能,更是嵌入式架构师的最基本要求。此外,灵活定制链接脚本在编程方面有更高级的应用。
一、编译链接原理
简单讲述编译链接的基本原理有助于后面内容的理解。
a. 简单点说,一个可执行程序包括文件头、代码段(.text)、数据段(.bss)、符号段等信息,在Linux GCC工具链中,该执行程序文件的格式是elf,而在elf格式中,段称为section。现加上某个程序有file1.c和file2.c两个文件。
b. 一个file1.c文件中有代码(函数),也有数据(全局变量,假设都没有初始化的),因此在编译(arm-linux-gcc)之后就会产生一个对应的file1.o,在该文件中会有产生.text section,其会将file.c中所有的函数代码编译后的指令放到该区域,假设长度是0x100字节;同样会产生.bss section,会将所有的全局变量定义到该区域,假设长度是0x20字节。这时,file1.o是可重定位的文件,即.text段是从地址0开始的,其真正的虚拟运行地址还需要在链接阶段重定位完成。
c. 对于另一个file2.c文件,同样会产生对应的file2.o,而该文件中会有对应的.text section(假设长度是0x200字节)和.bss section(假设长度是0x40字节)。其.text段同样是从地址0开始。
d. 链接的最终结果就是产生唯一的一个可执行文件result,而该文件只有一个.text段和一个.bss段,而且.text段会按照指定的链接地址重定位好,即.text段不再是从0开始。那么从这个结果来看,链接的过程应该包括这两个步骤:
d1. 合并file1.o的.text和file2.o的.text到result的.text,其长度为0x100+0x200=0x300字节;合并file1.o的.bss和file2.o的.bss到result的.bss,长度是0x20+0x40=0x60字节
d2. 根据指定的链接地址重定位result的.text段和.bss段,即.text段从指定的地址开始,各个函数的起始地址也会根据该地址进行重新定位。
二、链接脚本基本语法
这里以最基础的链接脚本语法作为示例,GCC工具链下的链接脚本后缀一般是.lds。
ENTRY(_start)//指定_start为程序的第一条指令
SECTIONS//指定内存分布
{
//顿号是地址标识符,这里指定为0x40000000是虚拟运行地址
. = 0x40000000;
//即.text的地址是从0x40000000开始,.text的内容包括file1.o和file2.o的.text
.text :
{