對于.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):定義本段存儲(加載)的位址。
看一個簡單的例子:(摘自《2410完全開發》)
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(運作處),此過程也就用到了讀取Nand 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 –o xy.o。
既然程式有了兩種位址,就涉及到一些跳轉指令的差別,這裡正好寫下來,以後萬一忘記了也可檢視,以前不少東西沒記下來現在忘得差不多了。。。
ARM彙編中,常有兩種跳轉方法:b跳轉指令、ldr指令向PC指派。
我自己經過歸納如下:
(1) b step1 :b跳轉指令是相對跳轉,依賴目前PC的值,偏移量是通過該指令本身的bit[23:0]算出來的,這使得使用b指令的程式不依賴于要跳到的代碼的位置,隻看指令本身。
(2) ldr pc, =step1 :該指令是從記憶體中的某個位置(step1)讀出資料并賦給PC,同樣依賴目前PC的值,但是偏移量是那個位置(step1)的連接配接位址(運作時的位址),是以可以用它實作從Flash到RAM的程式跳轉。
(3) 此外,有必要回味一下adr僞指令,U-boot中那段relocate代碼就是通過adr實作目前程式是在RAM中還是flash中。仍然用我當時的注釋:
relocate:
adr r0, _start
ldr r1, _TEXT_BASE
cmp r0, r1
下面,結合u-boot.lds看看一個正式的連接配接腳本檔案。這個檔案的基本功能還能看明白,雖然上面分析了好多,但其中那些GNU風格的符号還是着實讓我感到迷惑。。。
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段的結束位置
}