您的位置:首页 > 其它

关于u-boot的链接地址和运行地址分析

2015-04-10 19:54 211 查看
Bootloader是在操作系统内核启动之前运行的一段小程序。通过这段程序,我们可以初始化硬件设备,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境,最后从存储设备上读取内核映像到主存并跳到入口地址。

u-boot是遵循GPL条款的开放源码项目。它是目前功能最多、灵活性最强,并且开发最积极的开源Bootloader。它的启动过成可以分为两个阶段:

第一阶段:运行依赖于CPU体系架构的代码,这部分主要完成cpu从上电后的初始化过程,使用汇编语言编写,在对于u-boot是/cpu/xxxx/start.S。

第二阶段一般用C语言来实现,这样可以实现复杂的功能,并且具有良好的可移植性和可读性。C语言代码部分 lib_arm/board.c中的start_armboot是C语言开始的函数也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数只要完成如下操作:

1)调用一系列的初始化函数。

2)初始化Flash设备。

3)初始化系统内存分配函数。

4)如果目标系统拥有NAND设备,则初始化NAND设备。(移植注意)

5)如果目标系统有显示设备,则初始化该类设备。

6)初始化相关网络设备,填写IP、MAC地址等。

7)进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应main_loop();/common/main.c 的工作。启动的第二个阶段在许多的参考书中都有介绍,这里就不再敖述。

对于第一阶段,有些问题,在此做一总结。

通常在一个典型的嵌入式系统中,bootloader代码放在NOR Flash或NAND Flash里面,系统加电或复位后,首先运行这段代码。U-boot默认的把bootloader代码放在NOR Flash中,我们可以通过修改u-boot的源码让它支持从NAND Flash启动。在这里由于nandflash的访问需要一定的访问顺序,在上电后由半导体厂家内部烧入的固件完成把nandflash前4K的代码搬移到片上4K的SRAM中。

在uboot第一阶段的代码就可以运行了,这部分中主要完成些初始化工作,还有就是就是搬运程序,即地址重定位(relocate)。那我们为什么需要relocate?主要是速度方面的原因。程序在NOR Flash里执行的速度远远小于在SDRAM中执行的速度,为了追求更高的速度,需要relocate,让程序在SDRAM里面执行。

对于ARM架构的CPU,上电后PC寄存器是指向0地址处的,从这个地址开始运行程序,那么运行了启动代码后会把程序搬移到内存中去运行,这样就是产生程序会在运行时有个两地址,而由源码编译为可执行文件时只会指定一个链接地址,指定的这个地址通常是在内存中运行时的运行地址,那么刚上电启动时的程序运行地址怎么办呐?这里要先介绍汇编语言中的位置无关码。

使用C/C++或者其他高级语言编程,最后会被编译器工具转换为汇编代码,最后再翻译成机器码存储在内存、硬盘或者其他存储器上。机器码的构造不同的CPU有不同的规则,在这里就不再讨论,我们去关注汇编代码。在嵌入式开发中汇编程序通常用于非常关键的地方,比如系统启动阶段,中断向量表等。在启动阶段会经常使用到b、bl两条跳转指令。这两条语句到时完成跳转的工作,但是bl在跳转时会将跳转处的地址存放在ARM的LR寄存器中,方便在后面跳转回来。在使用b或bl跳转时,下一条指令的地址是这样计算的:将指令中24位带符号的补码扩展为32位;将此32位数左移两位;将得到的值加入到PC寄存器中,即可得到跳转的目标地址。这样可以发现,b跳转指令依赖于当前PC寄存器的值,这个特性使得使用b指令的程序不依赖于代码存储的位置---即不管这条代码放在什么位置,b指令都可以跳到正确的位置,这类指令被称为位置无关码。

所以u-boot程序刚开始执行时,他所处的地址不等于运行地址,不管是norflash启动还是nandflash启动,刚开始运行都是从0地址,那么在开头就要先使用b、bl、mov等“位置无关”的指令完成将代码从Flash等设备中复制到内存的“运行地址”处,然后再跳到“运行地址去执行”。

那么程序的运行地址是哪,u-boot的程序是怎么组织的呢?由源码到可执行程序通常会经过这样几个步骤:预处理、编译、汇编、链接


最后的链接阶段通常使用arm-linux-ld选项用于将多个目标文件、库文件链接成可执行文件,其中“-T”选项,可以直接用它来指定代码段、数据段、bss段的起始地址,也可以用来指定一个链接脚本,在连接脚本里进行更复杂的地址设置。


注意:-T 选项只用于链接Bootloader、内核等“没有底层软件支持”的软件;链接运行于操作系统上的应用程序时,无需指定”-T”,他们有自己默认的方式进行链接

1.直接指定代码段,数据段,BSS段的起始地址:

-Ttest startaddr -Tdata startaddr -Tbss startaddr

示例:arm-linux-ld –Ttext 0x0000000 led_on.o -o led_on_elf

他表示代码段的运行地址为0x0000000,由于没有定义数据段、bss段的起始地址他们依次被放在代码段的后面

2..使用连接脚本设置地址:arm-linux-ld -T u-boot.lds -o u-boot

这里的链接脚本就是u-boot.lds这个文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。

先看一下GNU官方网站上对.lds文件形式的完整描述:

SECTIONS {

...

secname start BLOCK(align) (NOLOAD) : AT ( ldadr )

{ contents } >region :phdr =fill

...

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

1、secname:段名

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

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

4、AT(ldadr):定义本段存储(加载)的地址。

下面,结合u-boot.lds看看一个正式的连接脚本文件。

OUTPUT_FORMAT("elf32­littlearm", "elf32­littlearm", "elf32­littlearm")

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

OUTPUT_ARCH(arm)

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

ENTRY(_start)

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

SECTIONS

{

. = 0x00000000 ; 从0x0位置开始

. = ALIGN(4) ; 代码以4字节对齐

.text : ;指定代码段

{

cpu/arm920t/start.o (.text) ; 代码的第一个代码部分

*(.text) ;其它代码部分

}

. = ALIGN(4)

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

. = ALIGN(4);

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

. = ALIGN(4);

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

__u_boot_cmd_start = . ;把__u_boot_cmd_start赋值为当前位置, 即起始位置

.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在该段.

__u_boot_cmd_end = .;把__u_boot_cmd_end赋值为当前位置,即结束位置

. = ALIGN(4);

__bss_start = .; 把__bss_start赋值为当前位置,即bss段的开始位置

.bss : { *(.bss) }; 指定bss段

_end = .; 把_end赋值为当前位置,即bss段的结束位置

}
以上就是u-boot整个工程的链接脚本,它来指定生成的u-boot可执行程序的组成架构。下面列出在u-boot工程编译过程的的最后一步生成u-boot.bin的过程:

arm-linux-ld -Bstatic -T /u-boot-1.3.4/board/mini2440/u-boot.lds -Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o --start-group lib_generic/libgeneric.a cpu/arm920t/libarm920t.a cpu/arm920t/s3c24x0/libs3c24x0.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a ……….(省略若干内容) board/mini2440/libmini2440.a --end-group -L /usr/local/arm/4.3.2/bin/../lib/gcc/arm-none-linux-gnueabi/4.3.2/armv4t -lgcc -Map u-boot.map -o u-boot

arm-linux-objcopy --gap-fill=0xff -O srec u-boot u-boot.srec

arm-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin

上面可以看到在最后的链接阶段参考了链接脚本的规划,指定了程序的运行地址为0x33f80000,上电启动后,第一阶段的代码就会把u-boot自身搬运到内存的0x33f80000位置去运行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: