lds 语法简介
lds 語法簡介
基本概念
link script 鏈接腳本
如何將輸入文件的section放進(jìn)輸出文件中,并控制輸出文件內(nèi)各部分在程序地址空間中的布局
鏈接器將一個或多個輸入文件合成一個輸出文件
輸入文件: 目標(biāo)文件 或者 鏈接腳本文件
輸出文件: 目標(biāo)文件 或 可執(zhí)行文件
目標(biāo)文件一般為 ELF 格式
ELF 格式
分四個部分
ELF Header: 標(biāo)識:1. 文件類型 2. 體系結(jié)構(gòu) 3. 文件版本 4. 程序入口地址 5. Program Header 的偏移量 6. Section Table 的偏移量 7. e_flags 8. ELF Header 大小 9. Program Header table 的條目大小 10. Program Header 條目數(shù)量 11. Section Table 條目大小 12. Section Table 條目數(shù)量 13. 包含節(jié)名稱的字符串是第幾個節(jié)
Program Header Table: 描述一個段在文件中的位置,大小,以及放進(jìn)內(nèi)存后所在的位置和大小
Section: 特別的在 .so文件中有符號表、字符串表、重定位表、GOT表等。
Section header Table: 描述Section的名字、類別、特性、在內(nèi)存中的虛擬地址、在文件中的偏移、大小等
【補(bǔ)充】ELF格式提供了兩種視圖:鏈接視圖、執(zhí)行視圖
鏈接視圖以節(jié)為單位,執(zhí)行視圖以段為單位。
可以用readelf -S 來查看Section,用 readelf -segments 來查看執(zhí)行視圖, -h 查看 header
當(dāng)ELF文件被加載到內(nèi)存中后,系統(tǒng)會將多個具有相同權(quán)限(flg值)section合并一個segment。操作系統(tǒng)往往以頁為基本單位來管理內(nèi)存分配,一般頁的大小為4096B,即4KB的大小。同時,內(nèi)存的權(quán)限管理的粒度也是以頁為單位,頁內(nèi)的內(nèi)存是具有同樣的權(quán)限等屬性,并且操作系統(tǒng)對內(nèi)存的管理往往追求高效和高利用率這樣的目標(biāo)。ELF文件在被映射時,是以系統(tǒng)的頁長度為單位的,那么每個section在映射時的長度都是系統(tǒng)頁長度的整數(shù)倍,如果section的長度不是其整數(shù)倍,則導(dǎo)致多余部分也將占用一個頁。而我們從上面的例子中知道,一個ELF文件具有很多的section,那么會導(dǎo)致內(nèi)存浪費嚴(yán)重。這樣可以減少頁面內(nèi)部的碎片,節(jié)省了空間,顯著提高內(nèi)存利用率。
Section
目標(biāo)文件的每個section至少包含兩個信息: 名字和大小. 大部分section還包含與它相關(guān)聯(lián)的一塊數(shù)據(jù), 稱為section contents(section內(nèi)容). 一個section可被標(biāo)記為“l(fā)oadable(可加載的)”或“allocatable(可分配的)”.
loadable section: 在輸出文件運行時, 相應(yīng)的section內(nèi)容將被載入進(jìn)程地址空間中.
allocatable section: 內(nèi)容為空的section可被標(biāo)記為“可分配的”. 在輸出文件運行時, 在進(jìn)程地址空間中空出大小同section指定大小的部分. 某些情況下, 這塊內(nèi)存必須被置零.
如果一個section不是“可加載的”或“可分配的”, 那么該section通常包含了調(diào)試信息. 可用objdump -h命令查看相關(guān)信息.
每個“可加載的”或“可分配的”輸出section通常包含兩個地址: VMA(virtual memory address虛擬內(nèi)存地址或程序地址空間地址) 和 LMA(load memory address加載內(nèi)存地址或進(jìn)程地址空間地址). 通常VMA和LMA是相同的.
在目標(biāo)文件中, loadable或allocatable的輸出section有兩種地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是執(zhí)行輸出文件時section所在的地址, 而LMA是加載輸出文件時section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系統(tǒng)中, 經(jīng)常存在加載地址和執(zhí)行地址不同的情況: 比如將輸出文件加載到開發(fā)板的flash中(由LMA指定), 而在運行時將位于flash中的輸出文件復(fù)制到SDRAM中(由VMA指定).
可這樣來理解VMA和LMA, 假設(shè):
(1) .data section對應(yīng)的VMA地址是0×08050000, 該section內(nèi)包含了3個32位全局變量, i、j和k, 分別為1,2,3.
(2) .text section內(nèi)包含由”printf( “j=%d “, j );”程序片段產(chǎn)生的代碼.
鏈接時指定.data section的VMA為0×08050000, 產(chǎn)生的printf指令是將地址為0×08050004處的4字節(jié)內(nèi)容作為一個整數(shù)打印出來。
如果.data section的LMA為0×08050000,顯然結(jié)果是j=2
如果.data section的LMA為0×08050004,顯然結(jié)果是j=1
還可這樣理解LMA:
.text section內(nèi)容的開始處包含如下兩條指令(intel i386指令是10字節(jié),每行對應(yīng)5字節(jié)):
jmp 0×08048285
movl $0×1,%eax
如果.text section的LMA為0×08048280, 那么在進(jìn)程地址空間內(nèi)0×08048280處為“jmp 0×08048285”指令, 0×08048285處為movl $0×1,%eax指令. 假設(shè)某指令跳轉(zhuǎn)到地址0×08048280, 顯然它的執(zhí)行將導(dǎo)致%eax寄存器被賦值為1.
如果.text section的LMA為0×08048285, 那么在進(jìn)程地址空間內(nèi)0×08048285處為“jmp 0×08048285”指令, 0×0804828a處為movl $0×1,%eax指令. 假設(shè)某指令跳轉(zhuǎn)到地址0×08048285, 顯然它的執(zhí)行又跳轉(zhuǎn)到進(jìn)程地址空間內(nèi)0×08048285處, 造成死循環(huán).
( 我理解這里應(yīng)該是說明了VMA是進(jìn)程自己的地址空間內(nèi)的,而 LMA 則是真實的執(zhí)行地址,反而與他前文所說不太一樣)
符號(symbol): 每個目標(biāo)文件都有符號表(SYMBOL TABLE), 包含已定義的符號(對應(yīng)全局變量和static變量和定義的函數(shù)的名字)和未定義符號(未定義的函數(shù)的名字和引用但沒定義的符號)信息.
符號值: 每個符號對應(yīng)一個地址, 即符號值(這與c程序內(nèi)變量的值不一樣, 某種情況下可以把它看成變量的地址). 可用nm命令查看它們. (nm的使用方法可參考本blog的GNU binutils筆記)
LDS 語法
SECTIONS
描述文件的內(nèi)存布局
例子:
SECTIONS {. = 0X10000;.text : { *(.text) }. = 0x8000000;.data : { *(.data) }.bss : { *(.bss) } }上例中, 在’SECTIONS’命令中的第一行是對一個特殊的符號’.‘賦值, 這是一個定位計數(shù)器. 如果你沒有以其它的方式指定輸出節(jié)的地址(其他方式在后面會描述), 那地址值就會被設(shè)為定位計數(shù)器的現(xiàn)有值. 定位計數(shù)器然后被加上輸出節(jié)的尺寸. 在’SECTIONS’命令的開始處, 定位計數(shù)器擁有值’0’.
第二行定義一個輸出節(jié),’.text’. 冒號是語法需要,現(xiàn)在可以被忽略. 節(jié)名后面的花括號中,你列出所有應(yīng)當(dāng)被放入到這個輸出節(jié)中的輸入節(jié)的名字. ’*‘是一個通配符,匹配任何文件名. 表達(dá)式’*(.text)‘意思是所有的輸入文件中的’.text’輸入節(jié). 這里的輸出的.text節(jié)將會是所有輸入文件的.text的合并
因為當(dāng)輸出節(jié)’.text’定義的時候, 定位計數(shù)器的值是’0x10000’,連接器會把輸出文件中的’.text’節(jié)的地址設(shè)為’0x10000’.
余下的內(nèi)容定義了輸出文件中的’.data’節(jié)和’.bss’節(jié). 連接器會把’.data’輸出節(jié)放到地址’0x8000000’處. 連接器放好’.data’輸出節(jié)之后, 定位計數(shù)器的是’0x8000000’加上’.data’輸出節(jié)的長度. 得到的結(jié)果是鏈接器會把’.bss’輸出節(jié)放到緊接’.data’節(jié)后面的位置.
這里也是說明了,鏈接器在讀完每一個section的描述之后,就會將定位器符號的值加上這個section的大小。
連接器會通過在必要時增加定位計數(shù)器的值來保證每一個輸出節(jié)具有它所需的對齊. 在這個例子中, 為’.text’和’.data’節(jié)指定的地址會滿足對齊約束, 但是連接器可能會需要在’.data’和’.bss’節(jié)之間創(chuàng)建一個小的缺口.
. 符號只能在SECTIONS內(nèi)使用
輸出Section的格式
SECTION-NAME [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
…
} [>REGION] [AT>LMA_REGION] [:PHDR HDR …] [=FILLEXP]
[ ]內(nèi)的內(nèi)容為可選選項, 一般不需要.
Entry(SYMBOL)
把SYMBOL的值設(shè)置為入口地址
這里ld 有多種方法設(shè)置入口地址,優(yōu)先級從高到低如下:
ld命令行的-e選項
連接腳本的ENTRY(SYMBOL)命令
如果定義了start符號, 使用start符號值
如果存在.text section, 使用.text section的第一字節(jié)的位置值
使用值0
INCLUDE filename
包含其他名為filename 的鏈接腳本
腳本搜索路徑由 -L 選項指定
INPUT files
設(shè)置files為輸入文件,會先在當(dāng)前目錄尋找,如果沒有,可以去-L指定的路徑下尋找
OUTPUT filename
輸出文件的名字,相當(dāng)于 ld -o,不過 ld -o 的優(yōu)先級更高
SEARCH_DIR path
相當(dāng)于 -L ,-L 優(yōu)先級更高
STARTUP filename
指定 filename 為第一個輸入文件
ASSERT exp, message
如果exp不為真,終止
EXTERN symbol
在輸出文件中增加未定義的符號
FORCE_COMMON_ALLOCATION
為通用符號分配空間
PROVIDE + symbol
對于在目標(biāo)文件內(nèi)被引用,但是沒有任何目標(biāo)文件內(nèi)被定義的符號,使用PROVIDE進(jìn)行定義
KEEP
可以保留一些特定的section不被鏈接器過濾掉
ABSOLUTE
在section的描述中,鏈接器取其相對值,也就是相對Section開始的偏移。如果希望取這個值的絕對值,就可以使用ABSOLUTE命令,就能取到相對值+Section所在的VMA值
實例
cortex-m3-emulator/link.lds
/* * Copyright (c) 2020 AIIT XUOS Lab * XiUOS is licensed under Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: * http://license.coscl.org.cn/MulanPSL2 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. */MEMORY {flash (rx) : ORIGIN = 0x00000000, LENGTH = 256Ksram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } OUTPUT_ARCH(arm) // 規(guī)定了輸出的體系結(jié)構(gòu)為ARM__SYSTEM_STACKSIZE__ = 0x1000;ENTRY(Reset_Handler) // 初始為Reset_Handler SECTIONS {.text :{. = ALIGN(4);KEEP(*(.isr_vector)) /* Startup code 中斷向量 */. = ALIGN(4);*(.text .text.*) *(.rodata .rodata*) /* read-only data (constants) */*(.glue_7)*(.glue_7t)/* section information for shell */. = ALIGN(4);_shell_command_start = .; // shell 執(zhí)行代碼KEEP (*(shellCommand))_shell_command_end = .;. = ALIGN(4);__isrtbl_idx_start = .; // 中斷向量表KEEP(*(.isrtbl.idx))__isrtbl_start = .;KEEP(*(.isrtbl))__isrtbl_end = .;. = ALIGN(4);PROVIDE(g_service_table_start = ABSOLUTE(.));KEEP(*(.g_service_table)) // 存儲了服務(wù)表PROVIDE(g_service_table_end = ABSOLUTE(.));PROVIDE(_etext = ABSOLUTE(.));_etext = .; // 在末端記錄.text的結(jié)束地址} > flash = 0__exidx_start = .;.ARM.exidx :{*(.ARM.exidx* .gnu.linkonce.armexidx.*)_sidata = .;} > flash__exidx_end = .;.data : AT (_sidata){. = ALIGN(4);_sdata = . ;*(.data)*(.data.*). = ALIGN(4);_edata = . ;} >sram__bss_start = .;.bss :{. = ALIGN(4);_sbss = .;*(.bss)*(COMMON). = ALIGN(4);_ebss = . ;} > sram__bss_end = .;_end = .;.stack ORIGIN(sram) + LENGTH(sram) - __SYSTEM_STACKSIZE__ :{PROVIDE( _heap_end = . );. = __SYSTEM_STACKSIZE__;PROVIDE( _sp = . );} >sram }存疑
總結(jié)
- 上一篇: CSS盒模型(详解)
- 下一篇: 德鲁克日志读后感之八十八