这段时间在读 u-boot 的 makefile ,看到链接的时候,发现 make 会调用 board/Samsung/smdk6410 下的 u-boot.lds 链接脚本,于是看了点关于链接器和加载器方面的东西,所以写下来,以防以后忘了。
在看链接脚本前,我们先来了解一些关于目标文件的知识。在我们将 c 源程序编译为可执行文件 ( 如 ELF) 时,实际上需要先经过编译器实现预处理生成 .i 或者 .ii 文件,再由汇编器编译生成目标文件,最后由链接器将各个目标文件和各种库文件连接成可执行文件。目标文件包含如下五类信息:
> 头信息 : 关于文件的整体信息,诸如代码大小,翻译成该目标文件的源文件名称和创建日期等。
> 目标代码 : 由编译器或者汇编器产生的二进制指令和数据
> 重定位信息 : 目标代码中的一个位置列表,链接器在修改目标代码的地址时会对他进行调整
> 符号 :该模块中定义的全局符号,以及从其他模块导入的或者由链接器定义的符号
> 调试信息 :目标代码中与链接无关但会被调试器使用到的其他信息,包括源代码文件和行号信息,本地符号,被目标代码使用的数据结构描述信息
并不是所有的目标格式都包含这几类信息,一个很有用的目标格式很少或者不包含任何信息都有可能。
下面是以前 unix 下使用比较多的 a.out 目标文件的格式 :
a.out header:
a.out 的头部根据 unix 版本的不同而略有变化, BSD 中的格式如下
int a_magic ; // 幻数
int a_text; // 文本段的大小
int a_data; // 初始化的数据段的大小
int a_bss; // 未初始化的数据段的大小
int a_syms; // 符号表的大小
int a_entry; // 入口点
int a_trsize; // 文本重定位段的大小
int a_drsize; // 数据重定位段的大小
text section: 存放程序代码
Data section: 存放数据
Bss section : 存放未初始化的数据,在镜像文件中,是不为 bss 段分配空间的,所以如果你开一个很大的全局的未初始化的数组,镜像文件的大小不会相应的变大。而只是在加载器将镜像加载进内存时,才会为 bss 段分配空间
对于哪些变量放入 bss 段,哪些数据放入 data 段的文件的问题,经过测试发现,对于为初始化的静态数据都是放入 bss 段的,而初始化过的全局数据和静态数据是放进 data 段的,但未初始化的全局数据这既不在 bss 段中,也不在 data 段中,只是在符号表中有一项指向该全局变量 :
#include<stdio.h>
Char a_test[0x100] ;
Char b_test[0x100] = {1};
Static char c_test[0x100];
Int main(void)
{
Static char a[0x100] ;
Static char b[0x100] = {1};
}
如以上程序中, c_test, b_test, a 中的数据是存放在 bss 段中的,而 b_test 和 b 所分配的数据是存放在 data 段中的,用 objdump 可以分析出 .o 文件的信息:
所以如果有朋友做裸机程序,并且用 gcc 编译时,定义未初始化的全局数组可能会出现问题,要小心使用。
介绍完目标文件的格式,我们开始切入正题:链接器。何为链接器呢,其实说的简单点就是把各个目标文件的各种段进行重新组合:
而链接脚本的作用就是程序员可以通过有限的控制命令,来指示链接器如何来进行工作:
/* 输出文件的运行环境,因为我们用的是 6410 ,是 arm 平台的 */
OUTPUT_ARCH(arm)
/* 程序入口点 */
ENTRY(_start)
/*section 命令用来设置段 */
SECTIONS
{
/* 将当前地址定位到 0x00000000 处, . 操作符用来表示当前地址 */
. = 0x00000000;
/* 将当前地址设为 4 个字节对齐处 , 即: . = (. + arg-1)&~(arg-1)*/
. = ALIGN(4);
/* 定义 text 段 , 定义段的格式为:
*SECTION[ADDRESS][(TYPE)]:[AT(LMA)]
*{
* .......
* OUTPUT-SECTION-COMMAND
* OUTPUT_SECTION_COMMAND
* .......
* }[>REGION][AT>LMA_REGION][:PHDR:PHDR....][=FILLEXP]
[] 内的内容通常是可选的
* SECTION: 段名,这个是必须的
* ADDRESS: 一个表达式,用来对 VMA 进行设置
* AT(LMA): 指定加载地址
*/
/*
* 定义 .text 段
*/
.text :
{
/*段由start.o,cpu_init.o,onenand_cp.o,nand_cp.o,movi.o中的.text段和div0.o中所有段组成*/
cpu/s3c64xx/start.o (.text)
cpu/s3c64xx/s3c6410/cpu_init.o (.text)
cpu/s3c64xx/onenand_cp.o (.text)
cpu/s3c64xx/nand_cp.o (.text)
cpu/s3c64xx/movi.o (.text)
*(.text)
lib_arm/div0.o
}
/*.rodata段由所有输入文件的.rodata段组成*/
. = ALIGN(4);
.rodata : { *(.rodata) }
/*.data段由所有输入文件的.data段组成*/
. = ALIGN(4);
.data : { *(.data) }
/*.got段由所有输入文件的.
vi@linux 13:43:01
got段组成*/
. = ALIGN(4);
.got : { *(.got) }
/*__u_boot_cmd_start的值被设置为了当前地址*/
__u_boot_cmd_start = .;
/*.u_boot_cmd段由所有文件的.u_boot_cmd段组成*/
.u_boot_cmd : { *(.u_boot_cmd) }
/*__u_boot_cmd_end变量的值被赋值为了当前地址*/
__u_boot_cmd_end = .;
/*.mmudata段由所有输入文件的.mmudata段组成 */
. = ALIGN(4);
.mmudata : { *(.mmudata) }
= ALIGN(4);
/*__bss_start变量被设置为了当前地址*/
__bss_start = .;
/*.bss段由所有输入文件的.bss段组成*/
.bss : { *(.bss) }
_end = .;
}