1、什麼是ld?它有什麼作用?
ld是GNU binutils工具集中的一個,是衆多Linkers(鏈接器)的一種。完成的功能自然也就是鏈接器的基本功能:把各種目標文件和庫文件鏈接起來,並重定向它們的數據,完成符號解析。Linking其實主要就是完成四個方面的工作:storage allocation、symbol management、libraries、relocation。
ld可以識別一種Linker command Language表示的linker scriopt文件來顯式的控制鏈接的過程。通過BFD(Binary Format Description)庫,ld可以讀取和操作COFF(common object file format)、ELF(executable and linking format)、a.out等各種格式的目標文件。
2、常用的選項
-b TARGET 設置目標文件的文件格式
-e ADDRESS 設置目標文件的開始地址
-EB 鏈接big-endian的目標文件
-EL 鏈接small-endian的目標文件
-l LIBNAME 創建執行程序時要鏈接的庫文件(比如某個庫爲test,則可以爲-ltest)
-L DIRECTORY 尋找要鏈接的庫文件時搜索的文件路徑
-o FILE 設置輸出文件的名字
-s 去除輸出文件中的所有符號信息
-S 去除輸出文件中的調試符號信息
-T FILE 讀取鏈接描述腳本,以確定符號等的定位地址
-v 輸出ld的版本信息
-x 去除所有的局部符號信息
-X 去除臨時的局部符號信息,默認情況下會設置這個選項
-Bstatic 創建的輸出文件鏈接靜態鏈接庫
-Bdynamic 創建的輸出文件鏈接動態鏈接庫
-Tbss ADDRESS 設置section bss的起始地址
-Tdata ADDRESS 設置section data的起始地址
-Ttext ADDRESS 設置section text的起始地址
3、鏈接描述腳本
鏈接描述腳本描述了各個輸入文件的各個section如何映射到輸出文件的各section中,並控制輸出文件中section和符號的內存佈局。
目標文件中每個section都有名字和大小,而且可以標識爲loadable(表示該section可以加載到內存中)、allocatable(表示必須爲這個section開闢一塊空間,但是沒有實際內容下載到這裏)。如果不是loadable或者allocatable,則一般含有調試信息。
每個有loadable或allocatable標識的輸出section有兩種地址,一種是VMA(Virtual Memory Address),這種地址是輸出文件運行時section的運行地址;一種是LMA(Load Memory Address),這種地址是加載輸出文件時section的加載地址。一般,這兩種地址相同。但在嵌入式系統中,經常存在執行地址和加載地址不一致的情況。如把輸出文件加載到開發板的flash存儲器中(地址由LMA指定),但運行時,要把flash存儲器中的輸出文件複製到SDRAM中運行(地址有
VMA指定)。
在鏈接腳本中使用註釋,可以用“/*...*/”。
每個目標文件有許多符號,每個符號有一個名字和一個地址,一個符號可以是定義的,也可以是未定義的。對於普通符號,需要一個特殊的標識,因爲在目標文件中,普通符號沒有一個特定的輸入section。鏈接器會把普通符號處理成好像它們都在一個叫做COMMON的section中。
下面給出vivi的ld script的內容及分析。(1)[Makefile]
LINKFLAGS
= -Tarch/vivi.lds -Bstatic
|
可見,鏈接的腳本是arch/vivi.lds,而且鏈接靜態庫。但是在arch下沒有vivi.lds,而是有vivi.lds.in。看了一下vivi.lds.in的內容,
SECTIONS
{
. = TEXTADDR;
.text : { *(.text)
}
.data ALIGN(4) : {
*(.data) }
.bss ALIGN(4) : {
*(.bss) *(COMMON)
} }
|
很明顯,這個就是原始的vivi的鏈接腳本。但是存在一個變量TEXTADDR沒有賦值,也就是說,這個量根據配置的不同是不同的,所以肯定就在Makefile中執行了生成方法。下一步就要看[arch/Makefile]
(2)[arch/Makefile]
LDSCRIPT
= arch/vivi.lds.in
|
ifeq
($(CONFIG_ARCH_S3C2410),y)
MACHINE = s3c2410
ifeq ($(CONFIG_S3C2410_NAND_BOOT),y)
TEXTADDR = 0x33f00000
else
TEXTADDR = 0x00000000
endif endif
|
vivi: $(HEAD) arch/vivi.lds
arch/vivi.lds: $(LDSCRIPT)
@sed s/TEXTADDR/$(TEXTADDR)/ $(LDSCRIPT)
>$@
|
很明顯,這步主要完成的工作就是要把vivi.lds.in文件中的TEXTADDR用配置後的實際值來代替。根據我的配置,這裏我的TEXTADDR就是0x33f00000.
SECTIONS
{
. = 0x33f00000;
.text : { *(.text)
}
.data ALIGN(4) : {
*(.data) }
.bss ALIGN(4) : {
*(.bss) *(COMMON)
} }
|
SECTIONS表示段。第一行表示當前地址爲0x33f00000,就是VMA,同時也是text段的起始地址。第二行用了通配符*表示所有字符,這裏的意思就是說指定的每個目標文件的text section的內容都放到同一個.text中。第三行表示指定的每個目標文件的data section的內容都放到同一個.data中,而且要四字節邊界對齊。第四行表示指定的每個目標文件的bss section的內容都放到同一個.bss中,所有的普通符號都放到COMMON中,而且要四字節邊界對齊。
這算是最爲簡單的ld scripts,不過也夠用了。如果不考慮對齊等因素,則可以直接在命令行中指定-Ttext 0x33f00000,就可以完成了。當然,對Linux kernel等,ld scripts要處理複雜的內存分配等操作,相應的要複雜一些,讀那些的方法就是查閱using ld手冊,同時還要研究MCU的內存分配,這樣才能作出合理的安排