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