U-BOOT启动过程分析 经典文章汇集

来源:互联网 发布:软件开发小组取名 编辑:程序博客网 时间:2024/05/22 02:20

 

 

U-Boot启动过程

尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。

开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数。函数调用顺序如图6.3所示。

看一下board/smsk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面详细分析一下程序跳转和函数的调用关系以及函数实现。

1.cpu/arm920t/start.S

这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。

 

 

 

图6.3  U-Boot启动代码流程图

 

_start: b       reset        //复位向量

       ldr   pc, _undefined_instruction

       ldr   pc, _software_interrupt

       ldr   pc, _prefetch_abort

       ldr   pc, _data_abort

       ldr   pc, _not_used

       ldr   pc, _irq      //中断向量

       ldr   pc, _fiq      //中断向量

 /* the actual reset code  */

reset:          //复位启动子程序

       /* 设置CPU为SVC32模式 */

       mrs   r0,cpsr

       bic   r0,r0,#0x1f

       orr   r0,r0,#0xd3

       msr   cpsr,r0

/* 关闭看门狗 */

 

/* 这些初始化代码在系统重起的时候执行,运行时热复位从RAM中启动不执行 */

#ifdef CONFIG_INIT_CRITICAL

       bl    cpu_init_crit

#endif

 

relocate:                       /* 把U-Boot重新定位到RAM */

       adr   r0, _start          /* r0是代码的当前位置 */

       ldr   r1, _TEXT_BASE      /* 测试判断是从Flash启动,还是RAM */

       cmp     r0, r1          /* 比较r0和r1,调试的时候不要执行重定位 */

       beq     stack_setup    /* 如果r0等于r1,跳过重定位代码 */

       /* 准备重新定位代码 */

       ldr   r2, _armboot_start

       ldr   r3, _bss_start

       sub   r2, r3, r2          /* r2 得到armboot的大小   */

       add   r2, r0, r2          /* r2 得到要复制代码的末尾地址 */

copy_loop: /* 重新定位代码 */

       ldmia r0!, {r3-r10}   /*从源地址[r0]复制 */

       stmia r1!, {r3-r10}   /* 复制到目的地址[r1] */

       cmp   r0, r2          /* 复制数据块直到源数据末尾地址[r2] */

       ble   copy_loop

 

       /* 初始化堆栈等    */

stack_setup:

       ldr   r0, _TEXT_BASE              /* 上面是128 KiB重定位的u-boot */

       sub   r0, r0, #CFG_MALLOC_LEN     /* 向下是内存分配空间 */

       sub   r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo结构体地址空间  */

#ifdef CONFIG_USE_IRQ

       sub   r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

       sub   sp, r0, #12     /* 为abort-stack预留3个字 */

clear_bss:

       ldr   r0, _bss_start      /* 找到bss段起始地址 */

       ldr   r1, _bss_end        /*  bss段末尾地址   */

       mov   r2, #0x00000000     /* 清零 */

clbss_l:str r2, [r0]        /* bss段地址空间清零循环...  */

       add   r0, r0, #4

       cmp   r0, r1

       bne   clbss_l

       /* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */

       ldr   pc, _start_armboot

_start_armboot: .word start_armboot     //start_armboot函数在lib_arm/board.c中实现

/* 关键的初始化子程序 */

cpu_init_crit:

……  //初始化CACHE,关闭MMU等操作指令

       /* 初始化RAM时钟。

       * 因为内存时钟是依赖开发板硬件的,所以在board的相应目录下可以找到memsetup.S文件。

       */

       mov   ip, lr

       bl    memsetup        //memsetup子程序在board/smdk2410/memsetup.S中实现

       mov   lr, ip

       mov   pc, lr

 

2.lib_arm/board.c

start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。

 

 

void start_armboot (void)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong size;

       init_fnc_t **init_fnc_ptr;

       char *s;

       /* Pointer is writable since we allocated a register for it */

       gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

       /* compiler optimization barrier needed for GCC >= 3.4 */

       __asm__ __volatile__("": : :"memory");

       memset ((void*)gd, 0, sizeof (gd_t));

       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

       memset (gd->bd, 0, sizeof (bd_t));

       monitor_flash_len = _bss_start - _armboot_start;

       /* 顺序执行init_sequence数组中的初始化函数 */

       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                      hang ();

              }

       }

       /*配置可用的Flash */

       size = flash_init ();

       display_flash_config (size);

       /* _armboot_start 在u-boot.lds链接脚本中定义 */

       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

       /* 配置环境变量,重新定位 */

       env_relocate ();

       /* 从环境变量中获取IP地址 */

       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

       /* 以太网接口MAC 地址 */

       ……

       devices_init ();      /* 获取列表中的设备 */

       jumptable_init ();

       console_init_r ();    /* 完整地初始化控制台设备 */

       enable_interrupts (); /* 使能例外处理 */

       /* 通过环境变量初始化 */

       if ((s = getenv ("loadaddr")) != NULL) {

               load_addr = simple_strtoul (s, NULL, 16);

       }

       /* main_loop()总是试图自动启动,循环不断执行 */

       for (;;) {

               main_loop ();      /* 主循环函数处理执行用户命令 -- common/main.c */

       }

       /* NOTREACHED - no way out of command loop except booting */

}

 

3.init_sequence[]

init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。

 

init_fnc_t *init_sequence[] = {

       cpu_init,             /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */

       board_init,           /* 基本的板级相关配置 -- board/smdk2410/smdk2410.c */

       interrupt_init,       /* 初始化例外处理 -- cpu/arm920t/s3c24x0/interrupt.c */

       env_init,             /* 初始化环境变量 -- common/cmd_flash.c */

       init_baudrate,        /* 初始化波特率设置 -- lib_arm/board.c */

       serial_init,          /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c */

       console_init_f,       /* 控制台初始化阶段1 -- common/console.c */

       display_banner,       /* 打印u-boot信息 -- lib_arm/board.c */

       dram_init,            /* 配置可用的RAM -- board/smdk2410/smdk2410.c */

       display_dram_config,  /* 显示RAM的配置大小 -- lib_arm/board.c */

       NULL,

};

 

U-BOOT start_armboot浅析

 

start_armboot浅析

ARM920t架构的CPU在完成基本的初始化后(ARM汇编代码),就进入它的C语言代码,而C语言代码的入口就是start_armboot, start_armboot在lib_arm/board.c中。start_armboot将完成以下工作。

1.全局数据结构的初始化

比如gd_t结构的初始化:

251         gd = (gd_t*)(_armboot_start – CFG_MALLOC_LEN – sizeof(gd_t));

_armboot_start是u-boot在RAM中的开始地址(对于u-boot最终搬移到RAM中运行的情况),CFG_MALLOC_LEN在include/configs/<board name>.h中定义。

 

bd_t结构的初始化:

272         gd->bd = (bd_t*)((char*)gd-sizeof(bd_t));

u-boot把bd_t结构紧接着gd_t结构存放。

 

内存分配的初始化

316         mem_malloc_init(_armboot_start-CFG_MALLOC_LEN);

经过以上的初始化后,u-boot在内存中的布局为(在底端为低地址)

-----------------------------

BSS

-----------------------------

U-BOOT TEXT/DATA

-----------------------------

CFG_MALLOC_LEN

-----------------------------

gd_t

-----------------------------

bd_t

-----------------------------

STACK

-----------------------------

2.调用通用初始化函数

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                     hang ();

              }

       }

init_sequence[]是init_fnc_t函数指针数组,这个数组包含了众多初始化函数,比如cpu_init,board_init等。

 

3.初始化具体设备

这一部分包括对Flash,LCD,网络的初始化等,例如

318 #if (CONFIG_COMMANDS & CFG_CMD_NAND)

       puts ("NAND: ");

       nand_init();            /* go init the NAND */

#endif

 

367 devices_init();

 

386 #ifdef CONFIG_DRIVER_CS8900

       cs8900_get_enetaddr (gd->bd->bi_enetaddr);

#endif

4.初始化环境变量

环境变量在通用初始化函数里面,已经初始化一次(env_init),这里调用env_relocate对环境变量进行重新定位。在我的另一篇文章”U-BOOT ENV 实现”中有对环境变量实现的讨论。

 

5.进入主循环

当然start_armboot除了以上工作外,还完成其它的初始化工作,具体参考lib_arm/board.c,在一切准备就绪之后,就进入u-boot的主循环:

416 for (;;) {

              main_loop ();

       }

main_loop的代码比较长,基本是就是执行用户的输入命令。

 

 

 

下内容来自笔者在中国Linux论坛Linux嵌入技术讨论区的张贴:x`"m

 

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  Uw/%#*

--------------------------------------------------------------------------------"

aaronwong: u-boot中代码的疑问(_armboot_start与_start)?12Gm

---------------------------=j

我使用的是u-boot-1.3.0-rc2。在cpu/pxa/start.S中,有如下的标号定义: w'

_TEXT_BASE: 7B

.word TEXT_BASE /*uboot映像在SDRAM中的重定位地址,我设置为0xa170 0000 */ k&BnQf

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  A%

.globl _armboot_start /'b&%

_armboot_start: 50m B8

.word _start /*_start是程序入口,链接完毕它的值应该是0xa170 0000=TEXT_BASE*/ 2inlX

/* 这句话的意思应该是在_armboot_start标号处,保存了_start的值,也就是说,_armboot_start是存放_start的地址,该地址对应的存储单元内容是0xa170 0000*/ ~1

/* ©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  [-S(

* These are defined in the board-specific linker script. 下面的定义与上面应该是一个意思。 y1sDB

*/ ©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  ?Ud=F}

.globl _bss_start W8

_bss_start: 4V1kfj

.word __bss_start 5`

====================== XM

按照上面的理解,__bss_start是uboot 的bss段起始地址,那么uboot映像的大小就是__bss_start - _start;在relocate代码段中计算uboot的大小时,也体现了这一点。 fHK'f0

实际上,_armboot_start并没有实际意义,它只是在"ldr r2, _armboot_start"中用來寻址_start的值而已,_bss_start也是一样的道理,真正有意义的应该是_start和 __bss_start本身。 ;{I

但是,令我不解的是,在C入口函数start_armboot()中(对应文件为lib_arm/board.c),有如下代码: =-yz!

void start_armboot (void) 6#F[C

{ ©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  dCb

......... *=

gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); //第一句话 7

.......... xfw,,

monitor_flash_len = _bss_start - _armboot_start; //第二句话 =r1m,

............... =cN^x+

mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //第三句话 W

.......... ?

} ©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  u

============================================== v#HG/

按 照上面的理解,_armboot_start与_bss_start都是没有实际意义的,它们只是一个地址,有实际意义的是地址中的内容_start和 __bss_start(虽然也还是地址)。象第一句话,其“意图”很明显,是把gd作为全局数据结构体的指针,并初始化为“SDRAM中的uboot起 始地址(即TEXT_BASE)-CFG_MALLOC_LEN-全局数据结构体大小”。 <BgA

要 实现这个“意图”,应该是写成:gd = (gd_t*)(_start - CFG_MALLOC_LEN - sizeof(gd_t));或者gd = (gd_t*)(TEXT_BASE- CFG_MALLOC_LEN - sizeof(gd_t));才对阿?用_armboot_start来作运算应该是没有任何意义才对!? #0gYd?

第二句话也是一样的道理,它的意图是要计算u-boot映像的大小,应该写成__bss_start - _start才对阿? @`PVq

我使用readelf工具查看编译所得到的uboot映像文件得到信息如下: NK7,G

[aaronwong@localhost build]$ readelf -s u-boot|grep _start G

1018: a1700048 0 NOTYPE GLOBAL DEFAULT 1 _bss_start !Qgo}

1083: a1700044 0 NOTYPE GLOBAL DEFAULT 1 _armboot_start W

1142: a1700000 0 NOTYPE GLOBAL DEFAULT 1 _start b9>

1197: a171b070 0 NOTYPE GLOBAL DEFAULT ABS __bss_start m[<B2Q

上面我删除了与该讨论无关的包含“_start""t的标号信息。 &:gP

显 然,我前面的理解应该是正确的(_start=TEXT_BASE=0xa170 0000)。那么u-boot源代码中的monitor_flash_len=_bss_start - _armboot_start=0xa1700048 - 0xa1700044 = 4,有什么意义?? p

迷茫中,期盼大虾指点迷津,谢谢~!!! <

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  M6fJvX

--------------------------------------------------------------------------------%:#-

eltshan: [Re: aaronwong]9o22#P

-----------------Zi

1018: a1700048 0 NOTYPE GLOBAL DEFAULT 1 _bss_start D3dY(

1083: a1700044 0 NOTYPE GLOBAL DEFAULT 1 _armboot_start _mAq>

1142: a1700000 0 NOTYPE GLOBAL DEFAULT 1 _start QNr+Pc

1197: a171b070 0 NOTYPE GLOBAL DEFAULT ABS __bss_start +=

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  E-Y>

我想: FCNh{M

_start所在的地址是a1700000, eHEsMt

_armboot_start 所在的地址是a1700044, ?

那么 根据这句: 7.Iy

_armboot_start: .word _start }<U

所以_armboot_start的值应该是a1700000 w34ok:

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  gh/

所以 ©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  ka

monitor_flash_len = _bss_start - _armboot_start = a171b070 - a1700000 = 1b070 ~=w

而不是你说的 = 4 FYxAA@

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  *E*4z

以上个人意见.Q~St

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  ^p`Sc

--------------------------------------------------------------------------------Nkh

aaronwong: [Re: eltshan];DQlk5

-------------------p4

谢 谢,eltshan!你的理解是正确的,不过我看了之后还是没能想得很明白,因为我在想,按你所说,那么_start的值应该是多少呢?难道是“b reset”这条指令的机器码?所以我对ELF格式的u-boot映像文件作了反汇编,分析之后终于找到了症结所在。以下是部分分析过程,首先是反汇编: 24

arm-iwmmxt-linux-gnueabi-objectdump -D u-boot > u-boot.s *{|(q#

并提取了monitor_flash_len = _bss_start - _armboot_start;这条语句相关的反汇编代码如下: /

============================== ^o#c7

a1700044 <_armboot_start>: b?

a1700044: a1700000 .word 0xa1700000 e{Zn

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  >

a1700048 <_bss_start>: l#"{w

a1700048: a171b070 .word 0xa171b070 U3sK

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  !.

a171b070 <monitor_flash_len>: R

a171b070: 00000000 .word 0x00000000 Q^$

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  MeE9

..... m4

a1700f40: e59f41d0 ldr r4, [pc, #464] ; a1701118 <start_armboot+0x1dc> lF-4

//r4=[a1701118]=a1700044 2/NL_;

..... EW0Th

a1700f7c: e59f3198 ldr r3, [pc, #408] ; a170111c <start_armboot+0x1e0> [T4Uwy

//r3=[a1700044]=a1700048 D

a1700f80: e5942000 ldr r2, [r4] 2/0N0

//r2=[a1700044]=a1700000 mV

a1700f84: e59f4194 ldr r4, [pc, #404] ; a1701120 <start_armboot+0x1e4> bWFU

//r4=[a1701120]=a1719d24 #Bnq

a1700f88: e5933000 ldr r3, [r3] *

//r3=[a1700048]=a171b070 <?

a1700f8c: e0623003 rsb r3, r2, r3 $e8I:

//r3= r3-r2 = a171b070-a1700000 = 1b070; q|

a1700f90: e59f218c ldr r2, [pc, #396] ; a1701124 <start_armboot+0x1e8> f1XV

//r2=[a1701124]=a171b070 }

a1700f94: e5823000 str r3, [r2] h`lC]

//monitor_flash_len=[r2]=r3=1b070 mJT:HJ

...... =op4

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  9

a1701118: a1700044 .word 0xa1700044 Z0

a170111c: a1700048 .word 0xa1700048 fr3g(

a1701120: a1719d24 .word 0xa1719d24 EpcDe

a1701124: a171b070 .word 0xa171b070 XT&

======================================== :

上面//是我自己的注释。这表明,你的理解的确是正确的。 :}6

经过这个过程之后,我终于认识到自己的误解在哪里了。原来,我是把"汇编语言中LDR伪指令对符号的引用"与"C语言中对汇编程序中符号/常量/变量的引用"搞混淆了。我想说明以下几点:`[I

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  WY

(1) readelf以及u-boot.map和System.map所给出的符号表中符号的值,实际上是表示符号所在的地址,而不是指符号本身的值。 E?F'R

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  u

(2) 汇编语言中没有指针的概念,因此对符号的引用是"赤裸裸"的。例如: M"

========== wM

.globl _armboot_start J%

_armboot_start: .word _start d_

ldr r2, _armboot_start Kf

========== ,

实际上反汇编以后是: 466

============ ;/g-oE

a1700044 <_armboot_start>: }b

a1700044: a1700000 .word 0xa1700000 R

a1700074: e51f2038 ldr r2, [pc, #-56] ; a1700044 <_armboot_start> b b}/4

============ [7A

也就是说,_armboot_start是一个地址0xa1700044,其中的内容是0xa1700000,上面对_armboot_start的引用是直接将其替换为其表示的地址0xa1700044,而非其中的内容0xa1700000。这就是"赤裸裸"的引用。 m

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  )bR;

(3) C语言则不同,对变量/符号/常量的引用必须要通过地址来寻址,不管是全局变量还是局部变量,不同的是局部变量在生命期结束后,所占的地址空间会被释放而 已。即使是函数调用时的参数传递,虽然是将实参的值"拷贝"给形参,但"拷贝"的过程也是通过实参和形参的地址来对两者进行访问的。 p

所 以,在C语言中的 "monitor_flash_len = _bss_start - _armboot_start" 这句话中对_armboot_star的引用,实际上是把它用作了指针,把它作为访问对象的地址来使用,通过这个地址即a1700044 来访问对应存储空间所存放的内容亦即0xa1700000,_bss_start也是同样的道理。所以这句话实际上是monitor_flash_len =[a1700048]-[0xa1700044]=a171b070-a1700000 = 1b070,这样就得到了正确的结果。 eNe#ij

©南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  MnK-47

现 在,我们再回答最前面的问题:_start的值是什么?_start表示地址0xa1700000 ,在汇编语言中,对_start的"绝对引用"(这里是与用相对寻址进行跳转进行区别)就是将其替换为0xa1700000,但其中存放的内容的的确确就 是"b reset"这条指令的机器码,所以如果在C语言中引用_start,得到的结果反而就是这个指令的机器码了。其实这个问题很简单,只是和C语言的引用搅 在一起,一些概念被偷换了而已。 *