1.u-boot程序的入口地址
要理解程序的入口地址,自然想到的是連接文件,首先看連接文件"/board/smdk2410/u-boot.lds"
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
(1) 從ENTRY(_start)可以看出u-boot的入口函數是_start
(2) 從. = 0x00000000也許可以看出_start的地址是0x00000000,事實並不是這樣的,這裡的0x00000000無效,在連接的時候最終會被TETX_BASE所代替的,具體請參考u-boot根目錄下的config.mk.
(3) 網上很多說法是 _start=TEXT_BASE,我想這種說法也是正確的,實際上,不是TEXT_BASE這段代碼映射到0x0的地方,其實編譯器進行編譯,是按照鏈接文件進行的。也就是說,編譯的時候所有的地址都是相對於TEXT_BASE計算出來的。而這個地址是在連接文件裡面指定的為0x338f0000,你可以看鏈接文件,可以看反匯編裡面的地址,全是0x33f8XXXX的地址。
1.1 cpu/arm920t/start.S部分代碼釋疑
(1)
[html] view plaincopy _TEXT_BASE:
.word TEXT_BASE
此處定義一匯編語言標簽_TEXT_BASE,它的值就是 “.word TEXT_BASE” 這句代碼所在的鏈接地址值; TEXT_BASE定義在board\smdk2410\config.mk文件中
(即0x33F80000)。以後通過引用_TEXT_BASE即可得到TEXT_BASE的值。
(2)
[html] view plaincopy .globl _armboot_start
_armboot_start:
.word _start
定義外部可以引用的變量_armboot_start,其值為代碼“.word _start"所在的鏈接地址值。
圖1 u-boot.map
編譯完成後,會生成u-boot.map 文件,如圖1所示。圖1顯示_armboot_start 所指的地址值是0x33f80044。用UltraEdit打開u-boot.bin,定位到偏移0x44處(0x44由_armboot_start 所在地址0x33f80044-TEXT_BASE得到),如圖2所示,此值就是_start 所指的鏈接地址的值 33 F8 00 00(小端模式)。
圖2 u-boot.bin
(3)
[html] view plaincopy .globl _bss_start
_bss_start:
.word __bss_start
__bss_start 的值定義在board/smdk2410/u-boot.lds文件中:
[html] view plaincopy . = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
__bss_start = .; 表示__bss_start 值就是當前位置的值。當前位置是多少呢?從下面一句 .bss : { *(.ss) } 知道。緊接該位置後面馬上就是放 bss 段數據了。所以,__bss_start 當然就是bss段的起始地址。_end 就是bss段的結束地址。
bss是這個鏈接腳本的最後一個段。start.S 就是以這個段的起始地址來計算要搬運的 u-boot 代碼的大小,即這個段前面的所有數據都將被搬到TEXT_BASE處,然後跳到start_armboot 處,即C語言的入口代碼。__bss_start這個值是多少? 我編譯後的值是0x33f979d4。可以u-boot.map文件中查到,如圖3所示,bss段是從0x33f979d4開始分配的。
圖3 u-boot.map
由圖1可知,_bss_start 所指的鏈接地址為0x33f80048, 該處即存儲了bss段的起始地址。打開u-boot.bin,定位到偏移0x48處(0x48由_bss_start所在地址0x33f80048-TEXT_BASE得到)如圖2所示,此值確為 33 F9 79 D4(小端模式)。
2.SDRAM初始化,lowleverl_init.S
_TEXT_BASE:
.word TEXT_BASE
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
不理解SMRDATA與_TEXT_BASE這兩個地址相減之後,到底是一個什麼值。為什麼要相減呢?
我們的程序是存放在Flash中的,這裡面的地址稱為加載地址,當然是從0x0這個地址開始,而程序中所用的標號編譯時都是基於_TEXT_BASE地址,我們稱為鏈接或運行地址。這時加載地址和運行地址不相同,所以要求我們的代碼在還沒有搬移到TEXT_BASE(0x33f80000)這個位置以前是不能使用這些標號。如果直接調用標號,程序就飛了,只有運行在SDRAM中才可以調用標號,因為0x33f80000在SDRAM中。所以只能找到一個相對於0x0的偏移地址出來,才能得到真正的SMRDATA定義數據,也就是說此部分代碼與地址無關,是基於PC的偏移來進行的。下面來分析代碼:
ldr r0, =SMRDATA取得標號SMRDATA的絕對地址,它是大於_TEXT_BASE;ldr r1, _TEXT_BASE 取基地址(0x33f80000)。sub r0, r0, r1相減後得到SMRDATA相對於_TEXT_BASE偏移。
3.UBOOT啟動代碼重定位
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
一直迷惑r0,r1什麼時候才相等,什麼時候才不等,看看adr與ldr偽指令區別
adr是小范圍的地址讀取偽指令,指令將基於PC相對偏移的地址值讀取到寄存器中;ldr是大范圍的讀取地址偽指令,用於加載32為立即數或一個地址到指定的寄存器中。
_start的鏈接地址在鏈接的時候與各指令之間的偏移地址已經確定,與位置無關,該偏移地址是負值,因為_start始終是代碼段的起始地址。假設PC所指指令的鏈接地址與_start之間的偏移量為x,該值為負,所以在執行adr r0,_start時,相當於ldr r0,=PC+x(x<0),這個時候r0中的值即為代碼段的起始地址。因為PC中的地址是當前代碼的加載地址,和它所指的指令的鏈接地址基於_start(代碼的鏈接起始地址)的偏移相加後,就是代碼所存儲的起始地址,即加載起始地址。