最近在看u-boot、osekOS的啓動代碼,其中涉及到lds文件,經過參考其餘網友的文章,但願對lds文件有個明晰的認識,爲了鞏固及加深影響,特將相關博客內容重寫一遍。html
原始文章: http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml。原始文章講解更清楚,不懂的地方能夠返回參考。linux
1、概念c++
1.1 lds文件函數
lds文件與scatter文件類似,定義了整個程序編譯以後的鏈接過程,都是決定一個可執行程序的各個段的存儲位置,以及入口地址,這也是連接定位的做用。網站
每個連接過程都由連接腳本(linker script, 通常以lds做爲文件的後綴名)控制。連接器經過連接腳本中指定的規則把一個或多個輸入文件(能夠是目標文件或連接腳本文件)的section合成一個輸出文件(能夠是目標文件或可執行文件)。spa
1.2 section.net
爲了區分不一樣文件的section,目標文件中的每一個section須要至少包含名字和大小,大部分還包含與其相關聯的一塊數據section contents。一個section可被標記爲「loadable」 (可加載的,在輸出文件運行時,該section被載入進程地址空間)或「allocatable」 (可分配的,輸出文件運行時,在進程地址空間中預留section大小的部分)。命令行
在目標文件中, loadable或allocatable的輸出section有兩種地址: VMA(virtual Memory Address,進程執行時使用的地址)和LMA(Load Memory Address,加載到進程地址空間的地址)。通常而言, 某section的VMA = LMA. 但在嵌入式系統中, 常常存在加載地址和執行地址不一樣的狀況: 好比將輸出文件加載到開發板的flash中(由LMA指定), 而在運行時將位於flash中的輸出文件複製到SDRAM中(由VMA指定)。unix
1.3 符號指針
每一個目標文件都有符號表(SYMBOL TABLE), 包含已定義的符號(對應全局變量和static變量和定義的函數的名字)和未定義符號(未定義的函數的名字和引用但沒定義的符號)信息。每一個符號對應一個地址, 即符號值(這與c程序內變量的值不同, 某種狀況下能夠把它當作變量的地址)。
2、語法
2.1 腳本格式
連接腳本由一系列命令組成, 每一個命令由一個關鍵字(通常在其後緊跟相關參數)或一條對符號的賦值語句組成。命令由分號‘;’分隔開。文件名或格式名內若是包含分號’;'或其餘分隔符, 則要用引號‘」’將名字全稱引用起來,沒法處理含引號的文件名。
/* */之間的是註釋。
GNU官方網站上對.lds文件形式的完整描述: 腳本命令; 腳本命令; … SECTIONS { ... secname start BLOCK(align) (TYPE) : AT ( ldadr ) { contents }[>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP] ... }
說明:secname和contents是必須的,前者用來命名這個段,後者用來肯定代碼中的什麼部分放在這個段,如下是對這個描述中的一些關鍵字的解釋。
一、腳本命令:參見2.3節,能夠設置內存段、輸出格式、入口地址等。
二、secname:輸出section段名。
三、start BLOCK:是段的重定位地址,本段鏈接(運行)的地址,若是代碼中有位置無關指令,程序運行時這個段必須放在這個地址上。start能夠用任意一種描述地址的符號來描述。設置了輸出section的VMA地址,其中align將定位符號的值調整到知足輸出section對齊要求後的值。
四、TYPE:輸出section類型,指明是否在程序運行時載入內存。
五、AT(ldadr):定義本段存儲(加載)的地址,默認狀況下,LMA等於VMA,但能夠經過關鍵字AT()指定LMA。用關鍵字AT()指定,括號內包含表達式,表達式的值用於設置LMA。若是不用AT()關鍵字,那麼可用AT>LMA_REGION表達式設置指定該section加載地址的範圍。經過這個選項能夠控制各段分別保存於輸出文件中不一樣的位置。
六、contents:決定哪些內容放在本段,能夠是整個目標文件,也能夠是目標文件中的某段(代碼段、數據段等),詳情參考2.4節。
七、region:若是start沒有定義,則以region地址爲VMA,若是region也沒定義,以定位符號‘.’的值肯定VMA。
八、phdr:沒在具體的實例中看到過。
九、FILLEXP:指明空閒空間(如按align對齊後的空閒部分)的填充字段。
2.2 符號及表達式
2.2.1 符號
沒有被引號「」包圍的符號,以字母、下劃線或‘.’開頭,可包含字母、下劃線、’.'和’-'。當符號名被引號包圍時,符號名能夠與關鍵字相同。
(1). 是一個特殊的符號,它是定位器,一個位置指針,指向程序地址空間內的某位置(或某section內的偏移,若是它在SECTIONS命令內的某section描述內),該符號只能在SECTIONS命令內使用。
(2)PROVIDE關鍵字:用於定義在目標文件內被引用,但沒有在任何目標文件內被定義的符號。
2.2.2 表達式
(1)賦值
在目標文件內定義的符號能夠在連接腳本內被賦值,但只有對全局變量賦值纔有效,並且此處的賦值是更改這個符號對應的地址,而不是變量的值。賦值語句能夠出如今鏈接腳本的三處地方:SECTIONS命令內,SECTIONS命令內的section描述內和全局位置;經常使用賦值操做有:
SYMBOL = EXPRESSION ; SYMBOL += EXPRESSION ; SYMBOL -= EXPRESSION ; SYMBOL *= EXPRESSION ; SYMBOL /= EXPRESSION ; SYMBOL >= EXPRESSION ; SYMBOL &= EXPRESSION ; SYMBOL |= EXPRESSION ;
注意:除了第一類表達式外, 使用其餘表達式須要SYMBOL被定義於某目標文件。
(2)運算符優先級
優先級 結合順序 操做符 1 left ! – ~ (1) 2 left * / % 3 left + - 4 left >> = 6 left & 7 left | 8 left && 9 left || 10 right ? : 11 right &= += -= *= /= (2)
(3)內建函數
ABSOLUTE(EXP) :轉換成絕對值 ADDR(SECTION) :返回某section的VMA值。 ALIGN(EXP) :返回定位符’.'的修調值,對齊後的值,(. + EXP – 1) & ~(EXP – 1) BLOCK(EXP) :如同ALIGN(EXP),爲了向前兼容。 DEFINED(SYMBOL) :若是符號SYMBOL在全局符號表內,且被定義了,那麼返回1,不然返回0。 LOADADDR(SECTION) :返回三SECTION的LMA MAX(EXP1,EXP2) :返回大者 MIN(EXP1,EXP2) :返回小者 NEXT(EXP) :返回下一個能被使用的地址,該地址是EXP的倍數,相似於ALIGN(EXP)。除非使用了MEMORY命令定義了一些非連續的內存塊,不然NEXT(EXP)與ALIGH(EXP)必定相同。 SIZEOF(SECTION) :返回SECTION的大小。當SECTION沒有被分配時,即此時SECTION的大小還不能肯定時,鏈接器會報錯。 SIZEOF_HEADERS : sizeof_headers :返回輸出文件的文件頭大小(仍是程序頭大小),用以肯定第一個section的開始地址(在文件內)。
2.3 腳本命令
2.3.1 簡單命令
命令 |
說明 |
備註 |
ENTRY(SYMBOL) |
將符號SYMBOL的值設置成入口地址(進程執行的第一條用戶空間的指令在進程地址空間的地址)。 |
進程入口地址優先級:ld命令行的-e選項>鏈接腳本的ENTRY(SYMBOL)命令>若是定義了start符號, 使用start符號值>若是存在.text section, 使用.text section的第一字節的位置值>使用值0。 |
INCLUDE filename |
包含其餘名爲filename的連接腳本 |
能夠嵌套使用, 最大深度爲10。 |
INPUT(files) |
將括號內的文件作爲連接過程的輸入文件。 |
ld首先在當前目錄下尋找該文件, 若是沒有, 則在由-L指定的搜索路徑下搜索 |
GROUP(files) |
指定須要重複搜索符號定義的多個輸入文件 |
file必須是庫文件,被ld重複掃描,直到不在有新的未定義的引用出現。 |
OUTPUT(FILENAME) |
定義輸出文件的名字 |
|
SEARCH_DIR(PATH) |
定義搜索路徑 |
|
STARTUP(filename) |
指定filename爲第一個輸入文件 |
連接過程當中, 每一個輸入文件是有順序的,此命令設置filename爲第一個輸入文件。 |
TARGET(BFDNAME) |
設置輸入文件的BFD格式。 |
|
OUTPUT_FORMAT(BFDNAME) |
設置輸出文件使用的BFD格式。 |
|
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) |
定義三種輸出文件的格式(大小端)。 |
如有命令行選項-EB, 則使用第2個BFD格式; -EL,則使用第3個BFD格式.不然默認第一個。 |
ASSERT(EXP, MESSAGE) |
若是EXP不爲真,終止鏈接過程
|
|
EXTERN(SYMBOL SYMBOL …) |
在輸出文件中增長未定義的符號 |
|
FORCE_COMMON_ALLOCATION |
爲common symbol(通用符號)分配空間,即便用了-r鏈接選項也爲其分配 |
|
NOCROSSREFS(SECTION SECTION …) |
檢查列出的輸出section,若是發現他們之間有相互引用,則報錯。 |
對於某些系統,特別是內存較緊張的嵌入式系統,某些section是不能同時存在內存中的,因此他們之間不能相互引用。 |
OUTPUT_ARCH(BFDARCH) |
設置輸出文件的machine architecture(體系結構) |
BFDARCH爲被BFD庫使用的名字之一。 |
man -S 1 ld |
查看ld的聯機幫助, 裏面也包括了對以上命令的介紹. |
|
2.3.2 其它命令
(1)內存區域命令
在默認情形下,鏈接器能夠爲section分配任意位置的存儲區域。你也能夠用MEMORY命令定義存儲區域,並經過輸出section描述的> REGION屬性顯示地將該輸出section限定於某塊存儲區域,當存儲區域大小不能知足要求時,鏈接器會報告該錯誤。
MEMORY命令的文法以下: MEMORY { NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2 NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2 … }
說明以下:
NAME :存儲區域的名字,這個名字能夠與符號名、文件名、section名重複,由於它處於一個獨立的名字空間。
ATTR :定義該存儲區域的屬性,在講述SECTIONS命令時提到,當某輸入section沒有在SECTIONS命令內引用時,鏈接器會把該輸入 section直接拷貝成輸出section,而後將該輸出section放入內存區域內。若是設置了內存區域設置了ATTR屬性,那麼該區域只接受知足該屬性的section(怎麼判斷該section是否知足?輸出section描述內好象沒有記錄該section的讀寫執行屬性)。
ATTR屬性內能夠出現如下7個字符:
R 只讀section W 讀/寫section X 可執行section A ‘可分配的’section I 初始化了的section L 同I ! 不知足該字符以後的任何一個屬性的section
ORIGIN :關鍵字,區域的開始地址,可簡寫成org或o
LENGTH :關鍵字,區域的大小,可簡寫成len或l
(2)PHDRS命令
在鏈接腳本內不指定PHDRS命令時,鏈接器可以很好的建立程序頭,可是有時須要更精確的描述程序頭,那麼PAHDRS命令就派上用場了。
注意:一旦在鏈接腳本內使用了PHDRS命令,那麼鏈接器**僅會**建立PHDRS命令指定的信息,因此使用時須謹慎。
PHDRS命令文法以下:
PHDRS
{
NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]
[ FLAGS ( FLAGS ) ] ;
}
其中FILEHDR、PHDRS、AT、FLAGS爲關鍵字。
NAME :爲程序段名,此名字能夠與符號名、section名、文件名重複,由於它在一個獨立的名字空間內。此名字只能在SECTIONS命令內使用。
一個程序段能夠由多個‘可加載’的section組成。經過輸出section描述的屬性:PHDRS能夠將輸出section加入一個程序段,: PHDRS中的PHDRS爲程序段名。在一個輸出section描述內能夠屢次使用:PHDRS命令,也便可以將一個section加入多個程序段。
若是在一個輸出section描述內指定了:PHDRS屬性,那麼其後的輸出section描述將默認使用該屬性,除非它也定義了:PHDRS屬性。顯然當多個輸出section屬於同一程序段時可簡化書寫。
在TYPE屬性後存在FILEHDR關鍵字,表示該段包含ELF文件頭信息;存在PHDRS關鍵字,表示該段包含ELF程序頭信息。
TYPE能夠是如下八種形式: PT_NULL 0表示未被使用的程序段 PT_LOAD 1表示該程序段在程序運行時應該被加載 PT_DYNAMIC 2表示該程序段包含動態鏈接信息 PT_INTERP 3表示該程序段內包含程序加載器的名字,在linux下常見的程序加載器是ld-linux.so.2 PT_NOTE 4表示該程序段內包含程序的說明信息 PT_SHLIB 5一個保留的程序頭類型,沒有在ELF ABI文檔內定義 PT_PHDR 6表示該程序段包含程序頭信息。 EXPRESSION 表達式值
以上每一個類型都對應一個數字,該表達式定義一個用戶自定的程序頭。
AT(ADDRESS)屬性定義該程序段的加載位置(LMA),該屬性將**覆蓋**該程序段內的section的AT()屬性。
默認狀況下,鏈接器會根據該程序段包含的section的屬性(什麼屬性?好象在輸出section描述內沒有看到)設置FLAGS標誌,該標誌用於設置程序段描述的p_flags域。
(3)版本號命令
請參看原文介紹
2.4 SECTIONS詳解
SECTIONS命令告訴ld如何把輸入文件的sections映射到輸出文件的各個section: 如何將輸入section合爲輸出section; 如何把輸出section放入程序地址空間(VMA)和進程地址空間(LMA),格式以下:
SECTIONS { ENTRY命令/符號賦值語句/一個輸出section的描述(output section description)/一個section疊加描述(overlay description) }
其中,一個輸出section描述的格式爲:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)] { 符號賦值語句/一個輸入section描述/直接包含的數據值/一個特殊的輸出section關鍵字 } [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]
說明以下:
(1)輸出section名字(SECTION):
輸出section名字必須符合輸出文件格式要求,好比:a.out格式的文件只容許存在.text、.data和.bss section名。而有的格式只容許存在數字名字,那麼此時應該用引號將全部名字內的數字組合在一塊兒;另外,還有一些格式容許任何序列的字符存在於 section名字內,此時若是名字內包含特殊字符(好比空格、逗號等),那麼須要用引號將其組合在一塊兒。
(2)輸出section地址(ADDRESS):
ADDRESS是一個表達式,它的值用於設置VMA。若是沒有該選項且有REGION選項,那麼鏈接器將根據REGION設置VMA;若是也沒有 REGION選項,那麼鏈接器將根據定位符號‘.’的值設置該section的VMA,將定位符號的值調整到知足輸出section對齊要求後的值,輸出 section的對齊要求爲:該輸出section描述內用到的全部輸入section的對齊要求中最嚴格的。
注意:設置ADDRESS值,將更改定位符號的值。
(3)TYPE :
每一個輸出section都有一個類型,若是沒有指定TYPE類型,那麼鏈接器根據輸出section引用的輸入section的類型設置該輸出section的類型。它能夠爲如下五種值,
NOLOAD :該section在程序運行時,不被載入內存。
DSECT,COPY,INFO,OVERLAY :這些類型不多被使用,爲了向後兼容才被保留下來。這種類型的section必須被標記爲「不可加載的」,以便在程序運行不爲它們分配內存。
(4)輸出section的LMA :
默認狀況下,LMA等於VMA,但能夠經過關鍵字AT()指定LMA。
用關鍵字AT()指定,括號內包含表達式,表達式的值用於設置LMA。若是不用AT()關鍵字,那麼可用AT>LMA_REGION表達式設置指定該section加載地址的範圍。
這個屬性主要用於構件ROM境象。
(5)輸入section描述:
最多見的輸出section描述命令是輸入section描述。輸入section描述是最基本的鏈接腳本描述。
1)輸入section描述基礎:
基本語法:FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)
FILENAME文件名,能夠是一個特定的文件的名字,也能夠是一個字符串模式。
SECTION名字,能夠是一個特定的section名字,也能夠是一個字符串模式
例如:
*(.text) :表示全部輸入文件的.text section (*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的全部輸入文件的.ctors section。 data.o(.data) :表示data.o文件的.data section data.o :表示data.o文件的全部section *(.text .data) :表示全部文件的.text section和.data section,順序是:第一個文件的.text section,第一個文件的.data section,第二個文件的.text section,第二個文件的.data section,... *(.text) *(.data) :表示全部文件的.text section和.data section,順序是:第一個文件的.text section,第二個文件的.text section,...,最後一個文件的.text section,第一個文件的.data section,第二個文件的.data section,...,最後一個文件的.data section
2)字符串模式內可存在如下通配符:
* :表示任意多個字符 ? :表示任意一個字符 [CHARS] :表示任意一個CHARS內的字符,可用-號表示範圍,如:a-z :表示引用下一個緊跟的字符 SORT():對知足字符串模式的全部名字進行遞增排序,如SORT(.text*)。
在文件名內,通配符不匹配文件夾分隔符/,但當字符串模式僅包含通配符*時除外。另外,任何一個文件的任意section只能在SECTIONS命令內出現一次。
3)通用符號(common symbol)的輸入section
在許多目標文件格式中,通用符號並無佔用一個section。鏈接器認爲:輸入文件的全部通用符號在名爲COMMON的section內。
4)輸入section和垃圾回收
在鏈接命令行內使用了選項–gc-sections後,鏈接器可能將某些它認爲沒用的section過濾掉,此時就有必要強制鏈接器保留一些特定的 section,可用KEEP()關鍵字達此目的。如KEEP(*(.text))或KEEP(SORT(*)(.text))。
5)在輸出section存放數據命令
可以顯示地在輸出section內填入你想要填入的信息(這樣是否是能夠本身經過鏈接腳本寫程序?固然是簡單的程序)。
BYTE(EXPRESSION) 1 字節 SHORT(EXPRESSION) 2 字節 LOGN(EXPRESSION) 4 字節 QUAD(EXPRESSION) 8 字節 SQUAD(EXPRESSION) 64位處理器的代碼時,8 字節 注意,這些命令只能放在輸出section描述內,其餘地方不行。 錯誤:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } } 正確:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }
6)FILL(EXPRESSION)和=FILEEXP屬性
在當前輸出section內可能存在未描述的存儲區域(好比因爲對齊形成的空隙),能夠用FILL(EXPRESSION)命令決定這些存儲區域的內容, EXPRESSION的前兩字節有效,這兩字節在必要時能夠重複被使用以填充這類存儲區域。如FILE(0×9090)。在輸出section描述中能夠 有=FILEEXP屬性,它的做用如同FILE()命令,可是FILE命令只做用於該FILE指令以後的section區域,而=FILEEXP屬性做用 於整個輸出section區域,且FILE命令的優先級更高!!!
7)輸出section內命令的關鍵字
CREATE_OBJECT_SYMBOLS :爲每一個輸入文件創建一個符號,符號名爲輸入文件的名字。每一個符號所在的section是出現該關鍵字的section。
CONSTRUCTORS :與c++內的(全局對象的)構造函數和(全局對像的)析構函數相關。具體介紹可參看原文 http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml。
8)輸出section的丟棄
例子,.foo { *(.foo) },若是沒有任何一個輸入文件包含.foo section,那麼鏈接器將不會建立.foo輸出section。可是若是在這些輸出section描述內包含了非輸入section描述命令(如符號 賦值語句),那麼鏈接器將老是建立該輸出section。
有一個特殊的輸出section,名爲/DISCARD/,被該section引用的任何輸入section將不會出如今輸出文件內,
(6)覆蓋圖(overlay)描述
覆蓋圖描述使兩個或多個不一樣的section佔用同一塊程序地址空間。覆蓋圖管理代碼負責將section的拷入和拷出。文法以下,
SECTIONS { … OVERLAY [START] : [NOCROSSREFS] [AT ( LDADDR )] { SECNAME1 { OUTPUT-SECTION-COMMAND OUTPUT-SECTION-COMMAND … } [:PHDR...] [=FILL] SECNAME2 { OUTPUT-SECTION-COMMAND OUTPUT-SECTION-COMMAND … } [:PHDR...] [=FILL] … } [>REGION] [:PHDR...] [=FILL] … }
由以上文法能夠看出,同一覆蓋圖內的section具備相同的VMA。SECNAME2的LMA爲SECTNAME1的LMA加上SECNAME1的大 小,同理計算SECNAME2,3,4…的LMA。SECNAME1的LMA由LDADDR決定,若是它沒有被指定,那麼由START決定,若是它也沒有 被指定,那麼由當前定位符號的值決定。
NOCROSSREFS關鍵字指定各section之間不能交叉引用,不然報錯。
對於OVERLAY描述的每一個section,鏈接器將定義兩個符號__load_start_SECNAME和__load_stop_SECNAME,這兩個符號的值分別表明SECNAME section的LMA地址的開始和結束。
鏈接器處理完OVERLAY描述語句後,將定位符號的值加上全部覆蓋圖內section大小的最大值。
2.5 暗含的連接腳本
輸入文件能夠是目標文件,也能夠是鏈接腳本,此時的鏈接腳本被稱爲暗含的鏈接腳本。若是鏈接器不認識某個輸入文件,那麼該文件被看成鏈接腳本被解析。一個暗含的鏈接腳本不會替換默認的鏈接腳本,僅僅是增長新的鏈接而已。在鏈接命令行中,每一個輸入文件的順序都被固定好了,暗含的鏈接腳本在鏈接命令行內佔住一個位置,這個位置決定了由該鏈接腳本指定的輸入文件在鏈接過程當中的順序。
3、實例
例1:
如下腳本將輸出文件的text section定位在0×10000, data section定位在0×8000000:
SECTIONS { . = 0×10000; .text : { *(.text) } . = 0×8000000; .data : { *(.data) } .bss : { *(.bss) } }
解釋一下上述的例子:
. = 0×10000 : 把定位器符號置爲0×10000 (若不指定, 則該符號的初始值爲0).
.text : { *(.text) } : 將全部(*符號表明任意輸入文件)輸入文件的.text section合併成一個.text section, 該section的地址由定位器符號的值指定, 即0×10000.
. = 0×8000000 :把定位器符號置爲0×8000000
.data : { *(.data) } : 將全部輸入文件的.data section合併成一個.data section, 該section的地址被置爲0×8000000.
.bss : { *(.bss) } : 將全部輸入文件的.bss section合併成一個.bss section,該section的地址被置爲0×8000000+.data section的大小.
鏈接器每讀完一個section描述後, 將定位器符號的值*增長*該section的大小. 注意: 此處沒有考慮對齊約束。
例2
SECTIONS{ … OVERLAY 0×1000 : AT (0×4000) { .text0 { o1/*.o(.text) } .text1 { o2/*.o(.text) } } … }
.text0 section和.text1 section的VMA地址是0×1000,.text0 section加載於地址0×4000,.text1 section緊跟在其後。
程序代碼,拷貝.text1 section代碼:
extern char __load_start_text1, __load_stop_text1; memcpy ((char *) 0×1000, &__load_start_text1, &__load_stop_text1 – &__load_start_text1);
例3
PHDRS { headers PT_PHDR PHDRS ; interp PT_INTERP ; text PT_LOAD FILEHDR PHDRS ; data PT_LOAD ; dynamic PT_DYNAMIC ; } SECTIONS { . = SIZEOF_HEADERS; .interp : { *(.interp) } :text :interp .text : { *(.text) } :text .rodata : { *(.rodata) } /* defaults to :text */ … . = . + 0×1000; /* move to a new page in memory */ .data : { *(.data) } :data .dynamic : { *(.dynamic) } :data :dynamic … }
例4
MEMORY { rom (rx) : ORIGIN = 0, LENGTH = 256K ram (!rx) : org = 0×40000000, l = 4M }