这段时间在读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};
Staticchar c_test[0x100];
Intmain(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 = .;
}