lds文件分析
官方文檔:https://sourceware.org/binutils/docs-2.30/ld/index.html#SEC_Contents
所有創(chuàng)建可執(zhí)行文件的最后一步就是鏈接。它是由ld或者是用gcc間接調(diào)用ld來完成的。它主要任務(wù)和把外部庫和應(yīng)用程序的目標代碼放到text段正確位置。以及創(chuàng)建程序中其它段(如data/bss段)。
標準C程序的鏈接是一般是固定的。它是ld調(diào)用一個缺省的鏈接腳本來完成的。因此對于一般的應(yīng)用開發(fā)者,幾乎感覺不到ld以及鏈接腳本的存在。 但是如果在一些特殊情況下,主要是底層非操作系統(tǒng)程序。里面很多代碼,特別是匯編代碼。必須要鏈接到指定的位置。而且這個時候的程序入口不一定也不是main了。這種情況在bootloader,Linux內(nèi)核以及裸機程序(非操作系統(tǒng)的程序,bootloader可以看成這類程序一個特例)。這時,你就要手工編寫lds文件了。在lds文件中如果添加注釋,可以像C語言一樣,使用 /**/ 隔開即可。
從以前的經(jīng)驗,鏈接腳本是嵌入式開發(fā),單片機開發(fā)相當重要的一個東西。它完成的工作是做PC機軟件的同志們不用關(guān)心的,但是也是很復雜的一項工作。總結(jié)來看鏈接腳本要告訴連接器
1:輸出什么
2:輸入是什么,那么obj文件
3:要用什么庫,庫放在什么地方
4:內(nèi)存分布地址
5:提供啟動代碼一些全局地址變量
?
一般來說鏈接腳本需要搞清楚這幾樣事情后才能編寫,那arm-gcc-ld的腳本也一定要實現(xiàn)這些功能。對于大多數(shù)的鏈接器來說,對于簡單的項目不需要腳本,只是使用命令參數(shù)就可以完成了。
?
MEMORY:
它是用來補充SECTIONS命令的,用來描述目標CPU中可用的內(nèi)存區(qū)域。它是可選的,如果沒有這個命令,LD會認為SECTIONS描述的相鄰的內(nèi)存塊之間有足夠可用的內(nèi)存。其實很容易理解但是卻很少用(我沒用過,嘿嘿),在SECTIONS中每個段的分布都沒有考慮ARM能夠?qū)ぶ返牡刂分?#xff0c;ROM,RAM,FLASH是不是連續(xù)的。如果不是連續(xù)的怎么辦?MEMORY就是設(shè)置各個區(qū)的起始位置,大小,屬性的命令,在一個腳本中只能有一個。
?
舉一個例子:
如果你的板子有兩段存儲,而且很遺憾的是不是連續(xù)的,一段是從0x0開始,大小為256K,另一段是從0x40000000開始的大小為4M,你可以在腳本中寫入如下的代碼來描述你的板子的內(nèi)存信息。
1 MEMORY 2 { 3 rom (rx) : ORIGIN = 0, LENGTH = 256K 4 ram (!rx) : org = 0x40000000, l = 4M 5 }很顯然下面的一句用了簡略標簽,這并不重要,重要的是怎樣使用它,不過在那之前還是想再仔細研究下MEMORY命令的細節(jié)。
MEMORY命令的語法是:
MEMORY
{
????? name (attr) : ORIGIN = origin, LENGTH = len
????? ...
}
name:一個用戶定義的名字,Linker將在內(nèi)部使用它,所以別把它和SECTIONS里用到的文件名,段名等搞重復了,它要求是獨一無二的。
?
attr? :如同它的名字一樣,這是內(nèi)存段的屬性描述。
`R'??? Read-only sections.
`W'?? Read/write sections.
`X'??? Sections containing executable code.
`A'??? Allocated sections.
`I'???? Initialized sections.
`L'????Same as I.
`!'??? Invert the sense of any of the following attributes.
別怪我懶,確實不想再打一遍這個的翻譯,而且很久沒用英文的俺翻譯的估計也不好。總體來說,它是屬性就行了。?
ORIGIN:這是起始地址
LENGTH:段長
由此可見上面那段實例顯示ROM和RAM的明確位置,而且還顯示了他們的只能,一個存代碼,一個除了存代碼什么都可以。
?
接著就是老問題了,怎么用這個。如果僅僅是規(guī)定我的板子有什么特點又不用的話那就是脫了褲子放屁,多此一舉。這個問題留在SECTIONS命令中回顧。
?
SECTIONS:
它是腳本文件中最重要的元素,不可缺省。它的作用就是用來描述輸出文件的布局。
SECTIONS命令的語法:
SECTIONS
{
?????? ...
????? secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
????? { contents } >region :phdr =fill
????? ...
}
這么多的參數(shù)中,只有secname和contents是必須的,其他都是可選的參數(shù)。也就說它的最簡單的格式就是:
SECTIONS
{
?????? ...
????? secname? :?{
???????????????? contents
????? }?
????? ...
}
但是注意:secname前后的兩個空格是必須的,否則就是不合法輸入。
secname定義了段名,其實最開始就忽略了一個重要的因素,arm-gcc-ld腳本需要描述輸入和輸出,而表面上一看卻看不出來什么是輸入什么事輸入,其實secname和contents就是描述這兩個信息的參數(shù)。secname是輸出文件的段,即輸出文件有哪些段,而contents就是描述輸出文件的這個段從哪些文件里抽取而來。明確這個了就不難理解為什么SECTIONS命令什么都可以不要就是不能沒有這兩個參數(shù)了。
secname:定義段,但是別以為定義的段一定要是教科書上寫的.data,.text這些科班的必須品,你甚至可以創(chuàng)建一個段來放一個美女的圖片。
contents:它的語法開始復雜起來了,但是你可以簡單的把輸入文件寫到代碼中:
?????????????????? .data : { main.o led.o}
但是結(jié)果被列的目標文件中所有的代碼都被鏈接到.data中去了,顯然不大符合我們的要求啊。那么還有一種寫法:
?????????????? .data : {
??????????????????????????????? ?main.o(.data)
???????????????????????????????? main.o(.text) // 也可以這樣寫 main.o(.data? .text)或者main.o(.data , .text)
??????????????????????????????? led.o(.data)
???????????????????}
?
?這個寫法讓只有被選中的文件的特殊段被鏈接到輸出文件的.data段了。當然,我們似乎還有更好的寫法:
????????????? ?.data : {
???????????????????????????????? *(.data)
????????????????? ?}
??這樣的話,所有目標文件的.data段都被連接到了輸出文件中了(這似乎是最常用的方法)。
?
核心的部分講完了,開始回顧前面說到了的那些參數(shù):
start:強制鏈接地址。也許沒有講清楚的是,在SECTIONS中,各個段是按次序排列的,前一個段用到什么地方下一個段接著用,而start就是強迫鏈接器將當前的段連接到指定的地址中。
????????????????? .data? 0x400000000 : { ..... }
BLOCK(align):說實話,沒看懂。只知道用的時候用的比較多的是ALIGN(4)這樣的標記,表示排列地址的時候按4的倍數(shù)排列,這樣做的理由很簡單,系統(tǒng)會快。
AT(addr):實現(xiàn)存放地址和加載地址不一致的功能,AT表示在文件中存放的位置,而在內(nèi)存里呢,按照普通方式存儲。至于用處,目前在下不知。
>region:好戲來了,這個region就是前面說的MEMORY命令定義的位置信息。表明當前section所放置的mem有什么特點,如果不符合會怎么樣呢?不曉得嘛。
其他略了吧,累了,主要是沒中文資料(屁話,有了還用我刻薄嗎),其實有點日文資料也行啊,英文我比較苦手)。
?
注釋:
?和C語言一樣的哦,/**/
?
其它:
其實ARM-GCC-LD腳本還真的和別的不一樣,它的功能要強一些,從manual看,它有三大功能:
1:設(shè)置入口函數(shù)
2:定義一個變量并賦值
3:描述輸入輸出文件的鏈接規(guī)則
?
其實上面的介紹是功能3,1和2都沒有講過。對于arm-gcc-ld腳本來說設(shè)置入口函數(shù)和定義變量可以在SECTIONS命令大括號里,也可以在外面。語法是:
ENTRY(symbol)
這個symbol應(yīng)該是某個函數(shù),或者是匯編代碼里的一個入口。然而,其實ARM-GCC-LD有很多種方式定義入口,所以當你看到你的腳本里沒有這句話而板子運行的很正常的時候別大吃一斤。
1:在連接的時候使用-e參數(shù)。
2:在腳本里使用ENTRY
3:如果定義過start這個入口(如果你在匯編里如果本身就有這個名字叫start的入口,那么不用特別的聲明也可以)
4:SECTION中.text的第一個入口函數(shù)
5:地址為0的指令
其實看了這個我們可以理解是ARM對入口的一個選擇優(yōu)先級,1和2是一樣的,顯示的指明入口,這也是推薦的方法,沒人會覺得程序員故弄玄虛是什么好事情。3是連接器的智能吧,而4和5就是無奈的選擇了,程序員沒干好的事情CPU只要猜著來處理了,有.text段的話就從它開始執(zhí)行吧,連.text都沒有的就從0x00000000開始執(zhí)行,至于執(zhí)行到哪里去了,火星人知道。
?
關(guān)于定義變量,其實一般的腳本都會有的,目的只有一個,給匯編啟動代碼提地址信息。比如說,一段需要清零的區(qū)域在腳本里定義了,而腳本自己不是變形金剛,他不能主動給你清零的,需要你自己的啟動代碼來清零,清零的代碼當然在匯編的啟動代碼里,它怎么知道需要清零的內(nèi)存在什么地方?就靠腳本里定義的變量了。沒錯,事情就是這樣的,那也就說一定會有一個提取地址的方法將地址賦給變量了哦,yes!就是小小的一個點"."。
RAM_START = .;
定義了一個RAM_START變量,地址是當前的地址,什么是當前的地址啊?就是鏈接器在連接的時候根據(jù)前面的段排列后的當前位置。當然也可以設(shè)置當前位置的值,不過最好不要小于前面排列需要的最小內(nèi)存。
. = 0x00000000
定義當前地址為0x0。
?
常用的基本上就這么多了,看一個實例吧:
1 SECTIONS 2 { 3 .= 0x30000000; 4 .text : { *(.text) } 9 .data : { *(.data) } 10 .rodata : { *(.rodata) } 11 Image_ZI_Base = .; 12 .bss : { *(.bss) } 13 Image_ZI_Limit = .; 21 .debug_info 0 : { *(.debug_info) } 22 .debug_line 0 : { *(.debug_line) } 23 .debug_abbrev 0 : { *(.debug_abbrev)} 24 .debug_frame 0 : { *(.debug_frame) } 25 }18 PROVIDE (__stack = .); 19 end = .; 20 _end = .;14 __bss_start__ = .; 15 __bss_end__ = .; 16 __EH_FRAME_BEGIN__ = .; 17 __EH_FRAME_END__ = .; 5 Image_RO_Limit = .; 6 Image_RW_Base = .; 7 Image_RO_Base = .; 8 Image_RW_Limit = .;總結(jié)
- 上一篇: C语言程序可以没有main函数
- 下一篇: JMP跳转指令的指令长度、直接转移与间接