Chinaunix首页 | 论坛 | 博客
  • 博客访问: 239506
  • 博文数量: 69
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 15
  • 用 户 组: 普通用户
  • 注册时间: 2013-02-23 13:55
文章分类

全部博文(69)

文章存档

2016年(11)

2013年(58)

我的朋友

分类: LINUX

2013-03-28 20:13:21

编写此文档记录学习uboot的过程

 

每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情.
连接器有个默认的内置连接脚本, 可用ld --verbose查看. 连接选项-r-N可以影响默认的连接脚本(如何影响?).
-T
选项用以指定自己的链接脚本, 它将代替默认的连接脚本。你也可以使用<暗含的连接脚本>以增加自定义的链接命令.
以下没有特殊说明,连接器指的是静态连接器.

 

(1)链接器把一个或多个输入文件合成一个输出文件.

输入文件: 目标文件或链接脚本文件.
输出文件: 目标文件或可执行文件.

目标文件(包括可执行文件)具有固定的格式, UNIXGNU/Linux平台下, 一般为ELF格式. 若想了解更多, 可参考 UNIX/Linux平台可执行文件格式分析


有时把输入文件内的section称为输入section(input section), 把输出文件内的section称为输出section(output sectin).

目标文件的每个section至少包含两个信息: 名字和大小. 大部分section还包含与它相关联的一块数据, 称为section contents(section内容). 一个section可被标记为“loadable(可加载的)”“allocatable(可分配的)”.

loadable section:
在输出文件运行时, 相应的section内容将被载入进程地址空间中.

allocatable section:
内容为空的section可被标记为可分配的”. 在输出文件运行时, 在进程地址空间中空出大小同section指定大小的部分. 某些情况下, 这块内存必须被置零.

如果一个section不是可加载的可分配的”, 那么该section通常包含了调试信息. 可用objdump -h命令查看相关信息.
(2) VMA(virtual memory address
虚拟内存地址或程序地址空间地址)LMA(load memory address加载内存地址或进程地址空间地址)
每个可加载的可分配的输出section通常包含两个地址: VMA(virtual memory address虚拟内存地址或程序地址空间地址)LMA(load memory address加载内存地址或进程地址空间地址). 通常VMALMA是相同的.

在目标文件中, loadableallocatable的输出section有两种地址: VMA(virtual Memory Address)LMA(Load Memory Address). VMA是执行输出文件时section所在的地址, LMA是加载输出文件时section所在的地址. 一般而言, sectionVMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash(LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM(VMA指定).


可这样来理解VMALMA, 假设:
(1) .data section
对应的VMA地址是0x08050000, section内包含了332位全局变量, ijk, 分别为1,2,3.
(2) .text section
内包含由"printf( "j=%d ", j );"程序片段产生的代码.

连接时指定.data sectionVMA0x08050000, 产生的printf指令是将地址为0x08050004处的4字节内容作为一个整数打印出来。

如果.data sectionLMA0x08050000,显然结果是j=2
如果.data sectionLMA0x08050004,显然结果是j=1

还可这样理解LMA:
.text section
内容的开始处包含如下两条指令(intel i386指令是10字节,每行对应5字节):

jmp 0x08048285
movl $0x1,%eax


如果.text sectionLMA0x08048280, 那么在进程地址空间内0x08048280处为“jmp 0x08048285”指令, 0x08048285处为movl $0x1,%eax指令. 假设某指令跳转到地址0x08048280, 显然它的执行将导致%eax寄存器被赋值为1.

如果.text sectionLMA0x08048285, 那么在进程地址空间内0x08048285处为“jmp 0x08048285”指令, 0x0804828a处为movl $0x1,%eax指令. 假设某指令跳转到地址0x08048285, 显然它的执行又跳转到进程地址空间内0x08048285, 造成死循环.

3)符号(symbol): 每个目标文件都有符号表(SYMBOL TABLE), 包含已定义的符号(对应全局变量和static变量和定义的函数的名字)和未定义符号(未定义的函数的名字和引用但没定义的符号)信息.

符号值: 每个符号对应一个地址, 即符号值(这与c程序内变量的值不一样, 某种情况下可以把它看成变量的地址). 可用nm命令查看它们. (nm的使用方法可参考本blogGNU binutils笔记)

链接脚本由一系列命令组成, 每个命令由一个关键字(一般在其后紧跟相关参数)或一条对符号的赋值语句组成. 命令由分号‘;’分隔开.

文件名或格式名内如果包含分号';'或其他分隔符, 则要用引号‘"’将名字全称引用起来. 无法处理含引号的文件名.
/* */
之间的是注释。

 

在目标文件内定义的符号可以在链接脚本内被赋值. (注意和C语言中赋值的不同!) 此时该符号被定义为全局的. 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.

e.g.
通过下面的程序查看变量a的地址:
/* a.c */
#include
int a = 100;
int main(void)
{
    printf( "&a=0x%p ", &a );
    return 0;
}

/* a.lds */
a = 3;

$ gcc -Wall -o a-without-lds a.c
&a = 0x8049598

$ gcc -Wall -o a-with-lds a.c a.lds
&a = 0x3

注意: 对符号的赋值只对全局变量起作用!


一些简单的赋值语句
能使用任何c语言内的赋值操作:

SYMBOL = EXPRESSION ;
SYMBOL += EXPRESSION ;
SYMBOL -= EXPRESSION ;
SYMBOL *= EXPRESSION ;
SYMBOL /= EXPRESSION ;
SYMBOL <<= EXPRESSION ;
SYMBOL >>= EXPRESSION ;
SYMBOL &= EXPRESSION ;
SYMBOL |= EXPRESSION ;


除了第一类表达式外, 使用其他表达式需要SYMBOL被定义于某目标文件。
.
是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。
注意:赋值语句包含4个语法元素:符号名、操作符、表达式、分号;一个也不能少。
被赋值后,符号所属的section被设值为表达式EXPRESSION所属的SECTION(参看11. 脚本内的表达式)
赋值语句可以出现在连接脚本的三处地方:SECTIONS命令内,SECTIONS命令内的section描述内和全局位置;如下,
floating_point = 0; /*
全局位置 */
SECTIONS
{
.text :
{
*(.text)
_etext = .; /* section
描述内 */
}
_bdata = (. + 3) & ~ 4; /* SECTIONS
命令内 */
.data : { *(.data) }
}

PROVIDE
关键字
该关键字用于定义这类符号:在目标文件内被引用,但没有在任何目标文件内被定义的符号。
例子:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
当目标文件内引用了etext符号,确没有定义它时,etext符号对应的地址被定义为.text section之后的第一个字节的地址。

 

GNU官网解释

先看一下.lds文件形式的完整描述:

SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  { contents } >region :phdr =fill
...
}

 

 secnamecontents是必须的,其他的都是可选的。下面挑几个常用的看看:

1secname:段名

2contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)

3start:本段连接(运行)的地址(VMA,如果没有使用ATldadr),本段存储的地址也是startGNU网站上说start可以用任意一种描述地址的符号来描述。

4ATldadr):定义本段存储(加载)的地址(LMA)

看一个简单的例子:(摘自《2410完全开发》)

/* nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}

    以上,head.o放在0x00000000地址开始处,init.o放在head.o后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);main.o放在40960x1000,是AT指定的,存储地址)开始处,但是它的运行地址0x30000000,运行之前需要从0x1000(加载处)复制到0x30000000(运行处),此过程也就用到了读取Nand flash

 

uboot中的lds文件分析

路径 /u-boot-2008.10\board\hi3515v100\

 

 

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")   

指定输出可执行文件是elf格式,32ARM指令,小端

OUTPUT_ARCH(arm)

指定输出可执行文件的平台为ARM

ENTRY(_start)

指定输出可执行文件的起始代码段为_start.

SECTIONS

{

. = 0x00000000;                   0x0位置开始(VMA地址)

. = ALIGN(4);                                          指定四字节对齐

.text :                                                指定代码段

{

  cpu/arm926ejs/start.o (.text)            代码段内容,start.o放置于段首是整个程序的入口,_start定义在里面

  board/hi3515v100/libhi3515v100.a (.text)

  common/dlmalloc.o (.text)

  common/console.o (.text)

  common/nand_boot.o (.text)

  drivers/mtd/libmtd.a (.text)

  drivers/mtd/nand/libnand.a (.text)

  lib_generic/libgeneric.a (.text)

  cpu/arm926ejs/libarm926ejs.a (.text)

  cpu/arm926ejs/hi3515v100/libhi3515v100.a (.text)

  lib_arm/libarm.a (.text)

  drivers/serial/libserial.a (.text)

}

. = ALIGN(4);

.rodata : { *(.rodata) }                        指定只读数据段

. = ALIGN(4);

.data : { *(.data) }                              指定读/写数据段

. = ALIGN(4);

.got : { *(.got) }                                指定got, got段式是uboot自定义的一个段, 非标准段

. = ALIGN(4);

.text1 :

{

  *(.text)                                                 指定其他代码段

}

. = ALIGN(4);

. = .;                                                        指定定位器为当前值(即为该语句相对段首偏移)

__u_boot_cmd_start = .;                            __u_boot_cmd_start赋值为当前位置

.u_boot_cmd : { *(.u_boot_cmd) }             指定u_boot_cmd段,uboot把所有命令都存放于此处

__u_boot_cmd_end = .;                      __u_boot_cmd_end赋值为当前位置

__img_end = .;

 

. = _start + 0x100000;                        指定定位器为开始地址+0x100000(偏移)处

. = ALIGN(32);                                  指定32位对齐

__bss_start = .;                                   指定bss

.bss1 (NOLOAD) : {*(.bss)}

_end = .;

}

 

 

对于lds链接脚本详细见博文http://blog.chinaunix.net/u2/85967/showart_1734680.html

关于定位器:在UBOOT MAKEFILE里面有一句arm-linux-ld –Tu-boot-1.1.8\board\Hisemdk-100\u-boot.lds –Ttext TEXTBASE,由于ld –T 命令的优先级比定位器的优先级高所以,实际上代码的VMA都是从TEXTBASE偏移开始

阅读(1403) | 评论(0) | 转发(0) |
0

上一篇:Linux的I2C驱动架构

下一篇:LCD驱动分析

给主人留下些什么吧!~~