U-BOOT啓動過程分析 經典文章彙集

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語言的引用攪 在一起,一些概念被偷換了而已。 *
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章