链接脚本.lds(详细)总结附实例快速掌握
目錄
- 一、簡介
- 二、常用關(guān)鍵字詳解
- 2.1 ENTRY(進(jìn)入)
- 2.2 OUTPUT_FORMAT(輸出格式)
- 2.3 STARTUP (啟動(dòng))
- 2.4 SEARCH_DIR(搜索目錄)
- 2.5 INPUT(輸入)
- 2.6 OUTPUT (輸出)
- 2.7 MEMORY (記憶存儲(chǔ))
- 2.8 SECTIONS命令
- 2.8.1 KEEP (保持)
- 2.8.2 PROVIDE
- 2.8.3 TYPE
- 2.8.4 AT( LAM_ADDR )
- 2.8.5 REGION
- 2.8.6 ALIGN 關(guān)鍵字
- 2.8.7 輸入section描述
- 2.9 ASSERT(斷言)
- 2.10 內(nèi)建函數(shù)
- 2.11 Symbols (象征)
- 三、實(shí)例解析
- 3.1 常用實(shí)例詳解1
- 3.2 uboot下lds實(shí)例分析2
- 四、lds的其他用法
- 4.1 提供全局變量
- 4.2 調(diào)用lds地址變量
- 五、其他相關(guān)知識(shí)鏈接
一、簡介
鏈接器:把一個(gè)或多個(gè)輸入文件合并成一個(gè)輸出文件,輸入文件是目標(biāo)文件或者鏈接腳本文件,輸出文件是目標(biāo)文件(庫文件)或者可執(zhí)行文件,鏈接器從鏈接腳本讀完一個(gè) section 后,將定位器符號(hào)的值增加該 section 的大小。
鏈接腳本:鏈接腳本的一個(gè)主要目的是描述輸入文件中的各個(gè)段(數(shù)據(jù)段,代碼段,堆,棧,bss)如何被映射到輸出文件中,并控制輸出文件的各部分在程序地址空間內(nèi)的布局,地址空間包括 ROM 和 RAM。
鏈接器總是使用鏈接腳本的,如果你不提供,則鏈接器會(huì)使用一個(gè)缺省的腳本,這個(gè)腳本是被編譯進(jìn)鏈接器可執(zhí)行文件的。
二、常用關(guān)鍵字詳解
2.1 ENTRY(進(jìn)入)
ENTRY(main) ENTRY(MultibootEntry)ENTRY關(guān)鍵字用于定義應(yīng)用程序的入口點(diǎn),即輸出文件中的第一條可執(zhí)行指令。該關(guān)鍵字接受鏈接程序/內(nèi)核入口點(diǎn)的符號(hào)名作為單個(gè)參數(shù)。所提供的符號(hào)名指向的代碼將是。ELF和PE二進(jìn)制文件中的文本部分。
ENTRY(SYMBOL) :將符號(hào)SYMBOL的值設(shè)置成入口地址。
入口地址(entry point)是指進(jìn)程執(zhí)行的第一條用戶空間的指令在進(jìn)程地址空間的地址
ld有多種方法設(shè)置進(jìn)程入口地址, 按一下順序: (編號(hào)越前, 優(yōu)先級(jí)越高)
1、ld命令行的-e選項(xiàng)
2、鏈接腳本的ENTRY(SYMBOL)命令
3、如果定義了start符號(hào), 使用start符號(hào)值
4、如果存在.text section, 使用.text section的第一字節(jié)的位置值
5、使用值0
2.2 OUTPUT_FORMAT(輸出格式)
OUTPUT_FORMAT(elf64-x86-64) OUTPUT_FORMAT("pe-i386")OUTPUT_FORMAT指令只接受一個(gè)參數(shù)。它指定可執(zhí)行文件的輸出格式。要了解系統(tǒng)binutils和GCC支持哪些輸出格式,可以使用objdump-i命令。
2.3 STARTUP (啟動(dòng))
STARTUP(Boot.o) STARTUP(crt0.o)啟動(dòng)需要一個(gè)參數(shù)。它是要鏈接到可執(zhí)行文件開頭的文件。對(duì)于userland項(xiàng)目,這通常是crt0。o或crtbegin。o、 對(duì)于內(nèi)核,通常是包含程序集樣板的文件啟動(dòng)堆棧,在某些情況下是GDT之類的,然后調(diào)用kmain()。
2.4 SEARCH_DIR(搜索目錄)
SEARCH_DIR(Directory)這將為您的庫搜索目錄添加路徑。-nostlib標(biāo)志將導(dǎo)致在該路徑中找到的任何庫被有效忽略。我不知道為什么,這似乎就是ld的工作原理。它將鏈接器腳本指定的搜索目錄視為標(biāo)準(zhǔn)目錄,因此會(huì)忽略它們,而不使用默認(rèn)的libs和此類標(biāo)志
2.5 INPUT(輸入)
INPUT(File1.o File2.o File3.o ...) INPUT (File1.oFile2.oFile3.o... )INPUT是一個(gè)“鏈接器腳本中”替換項(xiàng),用于將對(duì)象文件添加到命令行。您通常會(huì)指定類似于ld File1的內(nèi)容。o文件2。o、 可以使用輸入部分在鏈接器腳本中執(zhí)行此操作。
2.6 OUTPUT (輸出)
OUTPUT(Kernel.bin)OUTPUT命令指定要生成的文件作為鏈接過程的輸出。這是最終創(chuàng)建的二進(jìn)制文件的名稱。此命令的效果與-o filename命令行標(biāo)志的效果相同,后者會(huì)覆蓋它。
2.7 MEMORY (記憶存儲(chǔ))
MEMORY {ROM (rx) : ORIGIN = 0, LENGTH = 256kRAM (wx) : org = 0x00100000, len = 1M }MEMORY聲明一個(gè)或多個(gè)內(nèi)存區(qū)域,其屬性指定該區(qū)域是否可以寫入、讀取或執(zhí)行。這主要用于不同地址空間區(qū)域可能包含不同訪問權(quán)限的嵌入式系統(tǒng)。
上面的示例腳本告訴鏈接器有兩個(gè)內(nèi)存區(qū)域:
a) “ROM”從地址0x00000000開始,長度為256kB,可以讀取和執(zhí)行。
b) “RAM”從地址0x00100000開始,長度為1MB,可以寫入、讀取和執(zhí)行。
2.8 SECTIONS命令
SECTIONS命令告訴ld如何把輸入文件的sections映射到輸出文件的各個(gè)section: 如何將輸入section合為輸出section; 如何把輸出section放入程序地址空間(VMA)和進(jìn)程地址空間(LMA).
該命令格式如下:
SECTIONS { SECTIONS-COMMANDSECTIONS-COMMAND … }SECTION-COMMAND有四種:
(1) ENTRY命令
(2) 符號(hào)賦值語句
(3) 一個(gè)輸出section的描述(output section description)
(4) 一個(gè)section疊加描述(overlay description)
如果整個(gè)連接腳本內(nèi)沒有SECTIONS命令, 那么ld將所有同名輸入section合成為一個(gè)輸出section內(nèi), 各輸入section的順序?yàn)樗鼈儽贿B接器發(fā)現(xiàn)的順序.如果某輸入section沒有在SECTIONS命令中提到, 那么該section將被直接拷貝成輸出section。
2.8.1 KEEP (保持)
鏈接器腳本中的KEEP語句將指示鏈接器保留指定的節(jié),即使其中沒有引用任何符號(hào)。此語句在鏈接器腳本的節(jié)中使用。當(dāng)在鏈接時(shí)執(zhí)行垃圾收集時(shí),這一點(diǎn)就變得很重要,在鏈接命令行內(nèi)使用了選項(xiàng) -gc-sections 后,鏈接器可能將某些它認(rèn)為沒用的 section 過濾掉,此時(shí)就有必要強(qiáng)制讓鏈接器保留一些特定的 section,可用 KEEP() 關(guān)鍵字達(dá)此目的。說的通俗易懂就是:防止被優(yōu)化。
該語句常見于針對(duì)ARM體系結(jié)構(gòu)的鏈接器腳本中,用于將中斷向量表放置在偏移量0x00000000處。如果沒有這個(gè)指令,代碼中可能不會(huì)顯式引用的表將被刪除。
2.8.2 PROVIDE
為在任何鏈接目標(biāo)中沒有定義但是被引用的一個(gè)符號(hào),而在鏈接腳本定義一個(gè)符號(hào)。 PROVIDE(symbol = expression)。提供定義‘_exfun’的例子:
SECTIONS {.text :{*(.text)_exfun = .;PROVIDE(_exfun = .);} }如果程序定義了’ _exfun ‘(帶有前導(dǎo)下劃線),鏈接器將給出重復(fù)定義錯(cuò)誤。另一方面,如果程序定義了’ exfun‘(沒有前導(dǎo)下劃線),鏈接器會(huì)默認(rèn)使用程序中的定義。如果程序引用了’ exfun '但沒有定義它,鏈接器將使用鏈接器腳本中的定義。
PROVIDE指令將考慮定義一個(gè)普通符號(hào),即使這樣的符號(hào)可以與PROVIDE將創(chuàng)建的符號(hào)組合在一起。當(dāng)考慮構(gòu)造函數(shù)和析構(gòu)函數(shù)列表符號(hào)時(shí),這一點(diǎn)尤其重要,因?yàn)樗鼈兺ǔ1欢x為普通符號(hào)。
2.8.3 TYPE
每個(gè)輸出section都有一個(gè)類型,如果沒有指定TYPE類型,那么連接器根據(jù)輸出section引用的輸入section的類型設(shè)置該輸出section的類型。它可以為以下五種值
NOLOAD 該section在程序運(yùn)行時(shí),不被載入內(nèi)存。 DSECT,COPY,INFO,OVERLAY :這些類型很少被使用,為了向后兼容才被保留下來。這種類型的section必須被標(biāo)記為“不可加載的”,以便在程序運(yùn)行不為它們分配內(nèi)存。2.8.4 AT( LAM_ADDR )
section包含兩個(gè)地址:VMA(virtual memory address虛擬內(nèi)存地址或程序地址空間地址)和LMA(load memory address加載內(nèi)存地址或進(jìn)程地址空間地址)。默認(rèn)情況下 LMA 等于 VMA,但可以通過關(guān)鍵字 AT() 指定 LMA。
用關(guān)鍵字 AT()指定,括號(hào)內(nèi)包含表達(dá)式,表達(dá)式的值用于設(shè)置LMA。如果不用AT()關(guān)鍵字,那么可用AT>LMA_REGION表達(dá)式設(shè)置指定該section加載地址的范圍。這個(gè)屬性主要用于構(gòu)件ROM境象。
一般而言, 某section的VMA == LMA. 但在嵌入式系統(tǒng)中, 經(jīng)常存在加載地址和執(zhí)行地址不同的情況: 比如將輸出文件加載到開發(fā)板的flash中(由LMA指定), 而在運(yùn)行時(shí)將位于flash中的輸出文件復(fù)制到SDRAM中(由VMA指定)。
例子,
c程序如下:
extern char _etext, _data, _edata, _bstart, _bend; char *src = &_etext; char *dst = &_data; while (dst rom }2.8.5 REGION
這個(gè)region就是前面說的MEMORY命令定義的位置信息。
2.8.6 ALIGN 關(guān)鍵字
表示字節(jié)對(duì)齊, 如 “ . = ALIGN(4);”表示從該地址開始后面的存儲(chǔ)進(jìn)行4字節(jié)對(duì)齊。
2.8.7 輸入section描述
輸入section描述基本語法:
FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)FILENAME文件名,可以是一個(gè)特定的文件的名字,也可以是一個(gè)字符串模式。
SECTION名字,可以是一個(gè)特定的section名字,也可以是一個(gè)字符串模式。
具體示例解析:
1、*(.text) :表示所有輸入文件的.text section
2、(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的所有輸入文件的.ctors section。
3、data.o(.data) :表示data.o文件的.data section
4、data.o :表示data.o文件的所有section
5、*(.text .data) :表示所有文件的.text section和.data section,順序是:第一個(gè)文件的.text section,第一個(gè)文件的.data section,第二個(gè)文件的.text section,第二個(gè)文件的.data section,…
6、*(.text) *(.data) :表示所有文件的.text section和.data section,順序是:第一個(gè)文件的.text section,第二個(gè)文件的.text section,…,最后一個(gè)文件的.text section,第一個(gè)文件的.data section,第二個(gè)文件的.data section,…,最后一個(gè)文件的.data section
下面看連接器是如何找到對(duì)應(yīng)的文件的。
當(dāng)FILENAME是一個(gè)特定的文件名時(shí),連接器會(huì)查看它是否在連接命令行內(nèi)出現(xiàn)或在INPUT命令中出現(xiàn)。
當(dāng)FILENAME是一個(gè)字符串模式時(shí),連接器僅僅只查看它是否在連接命令行內(nèi)出現(xiàn)。
注意:如果連接器發(fā)現(xiàn)某文件在INPUT命令內(nèi)出現(xiàn),那么它會(huì)在-L指定的路徑內(nèi)搜尋該文件。
字符串模式內(nèi)可存在以下通配符:
* :表示任意多個(gè)字符 ? :表示任意一個(gè)字符 [CHARS] :表示任意一個(gè)CHARS內(nèi)的字符,可用-號(hào)表示范圍,如:a-z :表示引用下一個(gè)緊跟的字符在文件名內(nèi),通配符不匹配文件夾分隔符/,但當(dāng)字符串模式僅包含通配符*時(shí)除外。
任何一個(gè)文件的任意section只能在SECTIONS命令內(nèi)出現(xiàn)一次。
看如下例子
data.o文件的.data section在第一個(gè)OUTPUT-SECTION-COMMAND命令內(nèi)被使用了,那么在第二個(gè)OUTPUT-SECTION-COMMAND命令內(nèi)將不會(huì)再被使用,也就是說即使連接器不報(bào)錯(cuò),輸出文件的.data1 section的內(nèi)容也是空的。
2.9 ASSERT(斷言)
ASSERT(exp, message)2.10 內(nèi)建函數(shù)
lds中有以下一些內(nèi)建函數(shù):
ABSOLUTE(EXP) :轉(zhuǎn)換成絕對(duì)值
ADDR(SECTION) :返回某section的VMA值。
ALIGN(EXP) :返回定位符’.'的按照EXP進(jìn)行對(duì)齊后的修調(diào)值,對(duì)齊后的修調(diào)值算法為:(. + EXP – 1) & ~(EXP – 1)
BLOCK(EXP) :如同ALIGN(EXP),為了向前兼容。
DEFINED(SYMBOL) :如果符號(hào)SYMBOL在全局符號(hào)表內(nèi),且被定義了,那么返回1,否則返回0
LOADADDR(SECTION) :返回三SECTION的LMA
MAX(EXP1,EXP2) :返回大者
MIN(EXP1,EXP2) :返回小者
NEXT(EXP) :返回下一個(gè)能被使用的地址,該地址是EXP的倍數(shù),類似于ALIGN(EXP)。除非使用了MEMORY命令定義了一些非連續(xù)的內(nèi)存塊,否則NEXT(EXP)與ALIGH(EXP)一定相同
SIZEOF(SECTION) :返回SECTION的大小。當(dāng)SECTION沒有被分配時(shí),即此時(shí)SECTION的大小還不能確定時(shí),連接器會(huì)報(bào)錯(cuò)
SIZEOF_HEADERS :返回輸出文件頭部的字節(jié)數(shù)。這些信息出現(xiàn)在輸出文件的開始處。當(dāng)設(shè)置第一個(gè)段的開始地址時(shí),你可以使用這個(gè)數(shù)字。如果你選擇了加速分頁,當(dāng)產(chǎn)生一個(gè)ELF輸出文件時(shí),如果鏈接器腳本使用SIZEOF_HEADERS內(nèi)建函數(shù),連接器必須在它算出所有段地址和長度之前計(jì)算程序頭部的數(shù)值。如果連接器后來發(fā)現(xiàn)它需要附加程序頭,它將報(bào)告一個(gè)“not enough room for program headers”錯(cuò)誤。為了避免這樣的錯(cuò)誤,你必須避免使用SIZEOF_HEADERS函數(shù),或者你必須修改你的連接器腳本去避免強(qiáng)制連接器去使用附加程序頭,或者你必須使用PHDRS命令去定義你自己的程序頭
2.11 Symbols (象征)
可以在鏈接器腳本中定義任意符號(hào)。這些符號(hào)被添加到程序的符號(hào)表中。表中的每個(gè)符號(hào)都有一個(gè)名稱和一個(gè)關(guān)聯(lián)的地址。鏈接器腳本中已賦值的符號(hào)將被賦予外部鏈接,并可在程序代碼中作為指針訪問。
floating_point = 0; SECTIONS {.text :{*(.text)_etext = .;}_bdata = (. + 3) & ~ 3;.data : { *(.data) } }在上面的示例中,符號(hào)浮點(diǎn)被定義為零。符號(hào)_etext被定義為最后一個(gè)字符后面的地址。文本輸入部分。符號(hào)_bdata被定義為以下地址:。文本輸出部分向上對(duì)齊到4字節(jié)邊界。
三、實(shí)例解析
3.1 常用實(shí)例詳解1
SECTIONS{.= 0x10000; //把定位器符號(hào)置為 0x10000(若不指定,則該符號(hào)的初始值為0).text : { *(.text) } //*符號(hào)代表所有的輸入文件,此句表示獲取所有輸入文件的 .text section放在一塊連續(xù)的地址空間,首地址由上一句的定位器符號(hào)確定,即 0x10000.= 0×8000000; //把定位器符號(hào)置為 0x8000000.data : { *(.data) } //獲取所有輸入文件的 .data section 放在一塊連續(xù)的地址空間,該 section 的首地址為 0x8000000.bss : { *(.bss) } //獲取所有輸入文件的 .bss section 放在一塊連續(xù)的地址空間,該 section 的首地址為 0x8000000 + .data section 的大小}SECTIONS {. = 0x30000000; //指定當(dāng)前的鏈接地址=0x30000000.text : { head.o(.text) //添加第一個(gè)目標(biāo)文件,里面會(huì)調(diào)用這些函數(shù) init.o(.text) //添加第二個(gè)目標(biāo)文件,里面存放關(guān)看門狗,初始化SDRAM等函數(shù) nand.o(.text) //添加第三個(gè)目標(biāo)文件,里面存放初始化nand函數(shù) *(.text) // *(.text) 表示添加剩下的全部文件的.text代碼段 }.rodata ALIGN(4) : {*(.rodata)} //指定只讀數(shù)據(jù)段.data ALIGN(4) : { *(.data) } //指定讀寫數(shù)據(jù)段, *(data):添加所有文件的數(shù)據(jù)段__bss_start = .; //把__bss_start賦值為當(dāng)前地址位置,即bss段的開始位置.bss ALIGN(4) : { *(.bss) *(COMMON) } //指定bss段,里面存放未被使用的變量__bss_end = .; //把_end賦值為當(dāng)前地址位置,即bss段的結(jié)束位置}上面的鏈接地址=0x30000000,表示程序運(yùn)行的地方應(yīng)該位于0x30000000處,0x30000000就是我們的SDRAM基地址,而一上電后,nand的前4k地址會(huì)被2440自動(dòng)裝載到內(nèi)部ram中,所以我們初始化了sdram和nand后,就需要把程序所有內(nèi)容都復(fù)制到鏈接地址0x30000000上才行。
3.2 uboot下lds實(shí)例分析2
OUTPUT_ARCH(arm) //設(shè)置輸出文件的體系架構(gòu)。 ENTRY(_start) //將_start這個(gè)全局符號(hào)設(shè)置成入口地址。 SECTIONS //輸出文件內(nèi)容布局 {. = 0x00000000; //指定地址0x00000000. = ALIGN(4); //代碼以4字節(jié)對(duì)齊.text : //指定.text section段(位于0x00000000) {cpu/arm920t/start.o (.text) //添加第一個(gè)目標(biāo)文件: cpu/arm920t/start.o里面的.text代碼段board/100ask24x0/boot_init.o (.text) //添加第二個(gè)目標(biāo)文件: board/100ask24x0/boot_init.o里面的.text代碼段*(.text) // *(.text) 表示添加剩下的全部文件的.text代碼段}. = ALIGN(4);.rodata : { *(.rodata) } //指定.rodata section段(位于0x00000000+.text section),將所有的.rodata只讀數(shù)據(jù)段合并成一個(gè).rodata只讀數(shù)據(jù)段 . = ALIGN(4);.data : { *(.data) } //指定讀寫數(shù)據(jù)段, *(data):添加所有文件的數(shù)據(jù)段. = ALIGN(4);.got : { *(.got) } //指定got段,got段是uboot自定義的一個(gè)段. = .;__u_boot_cmd_start = .; //把__u_boot_cmd_start賦值為當(dāng)前位置, 即起始位置.u_boot_cmd : { *(.u_boot_cmd) } // u_boot_cmd段,所有的u-boot命令相關(guān)的定義都放在這個(gè)位置__u_boot_cmd_end = .; // u_boot_cmd段結(jié)束位置. = ALIGN(4);__bss_start = .; //把__bss_start賦值為當(dāng)前位置,即bss段的開始位置.bss : { *(.bss) } //指定bss段,這里NOLOAD的意思是這段不需裝載,僅在執(zhí)行域中才會(huì)有這段_end = .; //把_end賦值為當(dāng)前位置,即bss段的結(jié)束位置 }四、lds的其他用法
4.1 提供全局變量
//a.lds文件 a = 3; //編譯命令: gcc -Wall -o a-without-lds.exe a.c 運(yùn)行結(jié)果: &a = 0×601020 //編譯命令: gcc -Wall -o a-with-lds.exe a.c a.lds //運(yùn)行結(jié)果: &a = 0×3 //注意: 對(duì)符號(hào)的賦值只對(duì)全局變量起作用!4.2 調(diào)用lds地址變量
SECTIONS {...... = ALIGN(4);.rodata : { *(.rodata) }. = ALIGN(4);.data : { *(.data) }. = ALIGN(4);.got : { *(.got) }. = ALIGN(4);__bss_start = .;.bss : { *(.bss) }_end = .; } void clean_bss(void) {extern int __bss_start, _end;int *p = &__bss_start;for (; p < &_end; p++)*p = 0; }五、其他相關(guān)知識(shí)鏈接
1、Makefile語法詳細(xì)總結(jié)
2、gcc編譯流程、參數(shù)實(shí)例總結(jié)
3、Linux下gcc交叉編譯工具鏈總結(jié)
4、交叉編譯linux內(nèi)核實(shí)例講解
總結(jié)
以上是生活随笔為你收集整理的链接脚本.lds(详细)总结附实例快速掌握的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 神奇宝贝HTML游戏代码,《我的世界》神
- 下一篇: 5700新手学堂,疑难杂症问题解决荟萃