1. 程式人生 > >u-boot.lds檔案分析

u-boot.lds檔案分析

u-boot.lds決定了u-boot可執行映像的連線方式,以及各個段的裝載地址(裝載域)和執行地址(執行域)。

GNU官方網站上對.lds檔案形式的完整描述:

SECTIONS{
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{contents } >region :phdr=fill
...
}
secname和contents是必須的,前者用來命名這個段,後者用來確定程式碼中的什麼部分放在這個段,以下是對這個描述中的一些關鍵字的解釋。
secname:段名
contents:決定哪些內容放在本段,可以是整個目標檔案,也可以是目標檔案中的某段(程式碼段、資料段等)
start:是段的重定位地址,本段連線(執行)的地址,如果程式碼中有位置無關指令,程式執行時這個段必須放在這個地址上。start可以用任意一種描述地址的符號來描述。
AT(ldadr):定義本段儲存(載入)的地址,如果不使用這個選項,則載入地址等於執行地址,通過這個選項可以控制各段分別保存於輸出檔案中不同的位置。
例:

/*nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o}
second 0x30000000 : AT(4096) { main.o}
}
以上,head.o放在0x00000000地址開始處,init.o放在head.o後面,他們的執行地址也是0x00000000,即連線和儲存地址相同(沒有AT指定);

main.o放在4096(0x1000,是AT指定的,儲存地址)開始處,但它的執行地址在0x30000000,執行之前需要從0x1000(載入地址處)複製到0x30000000(執行地址處),此過程也就需要讀取flash,把程式拷貝到相應位置才能執行。這就是儲存地址和執行地址的不同,稱為載入時域和執行時域,可以在.lds連線指令碼檔案中分別指定。

裝載地址---》執行之前各段的地址

執行地址---》執行時各段的地址

 

編寫好的.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 -oxy.o。
既然程式有了兩種地址,就涉及到一些跳轉指令的區別。
ARM彙編中,常有兩種跳轉方法:b跳轉指令、ldr指令向PC賦值。
要特別注意這兩條指令的意思:
(1)bstep:b跳轉指令是相對跳轉,依賴當前PC的值,偏移量是通過該指令本身的bit[23:0]算出來的,這使得使用b指令的程式不依賴於要跳到的程式碼的位置,只看指令本身。
(2)ldrpc, =step :該指令是一個偽指令編譯後會生成以下程式碼:
ldrpc,0x30008000
<0x30008000>
step是從記憶體中的某個位置(step)讀出資料並賦給PC,同樣依賴當前PC的值,但是偏移量是step的連線地址(執行時的地址),所以可以用它實現從Flash到RAM的程式跳轉。
(3)此外,有必要回味一下adr偽指令,U-boot中那段relocate程式碼就是通過adr實現當前程式是在RAM中還是flash中:
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中去執行的程式碼段的開始)*/


ldrr1, _TEXT_BASE /* 測試判斷是從Flash啟動,還是RAM*/
/* 此句執行的結果r1始終是0x33FF80000,因為此值是 連結指定的*/
cmpr0, r1 /* 比較r0和r1,除錯的時候不要執行重定位*/
 

下面是u-boot-1.3.4的u-boot.lds(/cpu/arm920t/),簡單分析如下:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*指定輸出可執行檔案是elf格式,32位ARM指令,小端 */
/*OUTPUT_FORMAT("elf32-arm","elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)   /* 指定輸出檔案的平臺體系是ARM */
ENTRY(_start)             /*指定可執行映像檔案的起始段的段名是_start*/
SECTIONS
{
/*指定可執行image檔案的全域性入口點,通常這個地址都放在ROM(flash)0x0位置。必須使編譯器知道這個地址,通常都是修改此處來完成*/
. = 0x00000000;         /* 起始地址為0x00000000 */

. = ALIGN(4);             /* 字對齊,即就是4位元組對齊*/
.text :                          /* 程式碼段*/
{
cpu/arm920t/start.o (.text)                  /* 程式碼段第一部分程式碼*/
board/fs2410/lowlevel_init.o (.text)     /* 程式碼段第二部分,這段由自己新增,由於在編譯連線時發現,lowlevel_init.o程式碼段總是被連線在4kB之後,導致start.s執行到該段程式碼時,總是無法找到這段程式碼(註明:從nandflash啟動才會存在這個問題)。*/
*(.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段,所有的u-boot命令相關的定義都放在這個位置,因為每個命令定義等長,所以只要以__u_boot_cmd_start為起始地址進行查詢就可以很快查詢到某一個命令的定義,並依據定義的命令指標呼叫相應的函式進行處理使用者的任務*/
__u_boot_cmd_end = .;   /*u_boot_cmd段結束位置,由此可以看出,這段空間的長度並沒有嚴格限制,使用者可以新增一些u-boot的命令,最終都會在連線是存放在這個位置。*/

. = ALIGN(4);
__bss_start = .;               /*把__bss_start賦值為當前位置,即bss段的開始位置*/
.bss (NOLOAD) : { *(.bss) } /*指定bss段,這裡NOLOAD的意思是這段不需裝載,僅在執行域中才會有這段*/
_end = .;                         /*把_end賦值為當前位置,即bss段的結束位置*/
}

      上面start.o中的程式碼裝載地址和執行地址都為0x00000000,但是在start.o中會有一個u-boot自拷貝及重定位過程,start.o執行到最後時,整個u-boot已經被複制到了記憶體的TEXT_BASE(0x33f80000)位置,開始執行下面的跳轉語句:
ldr pc, _start_armboot  /*將標號_start_armboot的值傳給pc,實際上是將start_armboot函式的首地址傳給pc但是此時的start_armboot應該是在記憶體中,因為start_armboot一定是在4kB之後,而nandflash4kB之後的程式碼是無法直接訪問的,必須先讀入記憶體。而這時候u-boot的程式碼已經被拷貝並重定位到記憶體中,所以此處加在到pc的地址應當是記憶體中的地址,即33f800之後的某一地址*/
_start_armboot: .word start_armboot