u-boot.lds
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
1、secname:段名
2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
3、start:是段的重定位地址,本段连接(运行)的地址,如果代码中有位置无关指令,程序运行时这个段必须放在这个地址上。start可以用任意一种描述地址的符号来描述。
4、AT(ldadr):定义本段存储(加载)的地址,如果不使用这个选项,则加载地址等于运行地址,通过这个选项可
/* nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
编写好的.lds文件,在用arm-linux-ld连接命令时带-Tfilename来调用执行,如
arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也用-Ttext参数直接指定连接地址,如
arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。
既然程序有了两种地址,就涉及到一些跳转指令的区别。
ARM汇编中,常有两种跳转方法:b跳转指令、ldr指令向PC赋值。
(1) b step:b跳转指令是相对跳转,依赖当前PC的值,偏移量是通过该指令本身的 bit[23:0]算出来的,这使得使用b指令的程序不依赖于要跳到的代码的位置,只看指令本身。
(2) ldr pc, =step :该指令是一个伪指令编译后会生成以下代码:
ldr pc, 0x30008000
<0x30008000>
step
是从内存中的某个位置(step)读出数据并赋给PC,同样依赖当前PC的值,但是偏移量是step的连接地址(运行时的地址),所以可以用它实现从Flash到RAM的程序跳转。
relocate: /* 把U-Boot重新定位到RAM */
adr r0, _start /* r0是代码的当前位置 */
/* adr伪指令,汇编器自动通过当前PC的值算出这条指令中“_start"的值,执行到_start时PC的值放到r0中:
当此段在flash中执行时r0 = _start = 0;当此段在RAM中执行时_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x33F80000,即u-boot在把代码拷贝到RAM中去执行的代码段的开始) */
ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */
/* 此句执行的结果r1始终是0x33FF80000,因为此值是链接指定的 */
cmp r0, r1 /* 比较r0和r1,调试的时候不要执行重定位 */
结合u-boot.lds谈谈连接脚本。
//;三个分别指定在缺省、大端、小端情况下的输出可执行文件格式,这里都指定输出格式是elf32,小端和arm体系结构。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
/* 指定可执行映像文件的起始段的段名是_start */
ENTRY(_start)
SECTIONS
{
/* 指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成 */
. = 0x00000000; /* 起始地址为0x00000000 */
;地址进行4字节对齐调整。
. = ALIGN(4);
.text :
{
/* 代码段第一部分代码*/
cpu/arm920t/start.o (.text)
/* 代码段第二部分,这段由自己添加,由于在编译连接时发现,lowlevel_init.o代码段总是被连接在4kB之后,导致start.s执行到该段代码时,总是无法找到这段代码(注明:从nandflash启动才会存在这个问题)。*/
board/samsung/smdk2410/lowlevel_init.o(.text)
board/samsung/smdk2410/nand_read.o(.text)
/*其余代码段*/
*(.text)
}
. = ALIGN(4);
/* 只读数据段 ,所有的只读数据段都放在这个位置*/
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
/* 可读写数据段,所有的可读写数据段都放在这里 */
.data : { *(.data) }
. = ALIGN(4);
/*指定got段, got段式是uboot自定义的一个段, 非标准段*/
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .; /*把__u_boot_cmd_start赋值为当前位置, 即起始位置*/
.u_boot_cmd : { *(.u_boot_cmd) } /* u_boot_cmd段,所有的u-boot命令相关的定义都放在这个位置,因为每个命令定义等长,所以只要以__u_boot_cmd_start为起始地址 进行查找就可以很快查找到某一个命令的定义,并依据定义的命令指针调用相应的函数进行处理用户的任务*/
__u_boot_cmd_end = .; /* u_boot_cmd段结束位置,由此可以看出,这段空间的长度并没有严格限制,用户可以添加一些u-boot的命令,最终都会在连接是存放在这个位置。*/ . = ALIGN(4);
/*把__bss_start赋值为当前位置,即bss段的开始位置*/
__bss_start = .;
/*指定bss段,这里NOLOAD的意思是这段不需装载,仅在执行域中才会有这段*/
.bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
/*把_end赋值为当前位置,即bss段的结束位置*/
_end = .;
}
说明1:标准应用程序包括 3 类标准段空间:.text 运行代段;.data 全局变量等具有初始值的数据空间;.bss暂态变量,堆栈等数据空间;
说明 2:.rodata,.got,.u_boot_cmd 等段空间由程序员设计需要而自行定义的段空间;
/board/samsung/smdk2410/config.mk 文件中定义了
#
# SMDK2410 has 1 bank of 64 MB DRAM
#
# 3000'0000 to 3400'0000
#
# Linux-Kernel is expected to be at 3000'8000, entry 3000'8000
# optionally with a ramdisk at 3080'0000
#
# we load ourself to 33F8'0000
#
# download area is 3300'0000
#
TEXT_BASE = 0x33F80000
在顶层配置文件config.mk中154定义了CPPFLAGS 变量,其中指定了程序的链接基址为 TEXT_BASE
CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS) -D__KERNEL__
ifneq ($(TEXT_BASE),)
CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE)
Endif
下面翻译下:
#SMDK2410 每个bank有 64 MB DRAM
#3000'0000到3400'0000
#
#Linux的内核,预计将在3000'8000,进入3000'8000
#选择一个虚拟磁盘时3080'0000
#
#我们载入我们自己来33F8'0000
#
#下载区3300'0000
下面再来看下编译连接后的的system.map文件
33f80000 T _start //看到了吧,这就是链接基地址,由编译器指定的TEXT_BASE
33f80000 t $a
33f80020 t $d
33f80020 t _undefined_instruction
33f80024 t _software_interrupt
33f80028 t _prefetch_abort
33f8002c t _data_abort
33f80030 t _not_used
33f80034 t _irq
33f80038 t _fiq
33f80040 t _TEXT_BASE
33f80044 T _armboot_start
33f80048 T _bss_start
33f8004c T _bss_end
33f80050 t $a
33f80050 t start_code
33f800b8 t relocate
33f800d8 t copy_loop
33f800ec t nand_boot