0. Contents
1. 概論
2. 基本概念
3. 腳本格式
4. 簡(jiǎn)單例子
5. 簡(jiǎn)單腳本命令
6. 對(duì)符號(hào)的賦值
7. SECTIONS命令
8. MEMORY命令
9. PHDRS命令
10. VERSION命令
11. 腳本內(nèi)的表達(dá)式
12. 暗含的連接腳本
1. 概論
每一個(gè)鏈接過(guò)程都由鏈接腳本(linker script, 一般以lds作為文件的后綴名)控制. 鏈接腳本主要用于規(guī)定如何把輸入文件內(nèi)的section放入輸出文件內(nèi), 并控制輸出文件內(nèi)各部分在程序地址空間內(nèi)的布局. 但你也可以用連接命令做一些其他事情.
連接器有個(gè)默認(rèn)的內(nèi)置連接腳本, 可用ld --verbose查看. 連接選項(xiàng)-r和-N可以影響默認(rèn)的連接腳本(如何影響).
-T選項(xiàng)用以指定自己的鏈接腳本, 它將代替默認(rèn)的連接腳本。你也可以使用<暗含的連接腳本>以增加自定義的鏈接命令.
以下沒(méi)有特殊說(shuō)明,連接器指的是靜態(tài)連接器.
2. 基本概念
鏈接器把一個(gè)或多個(gè)輸入文件合成一個(gè)輸出文件.
輸入文件: 目標(biāo)文件或鏈接腳本文件.
輸出文件: 目標(biāo)文件或可執(zhí)行文件.
有時(shí)把輸入文件內(nèi)的section稱為輸入section(input section), 把輸出文件內(nèi)的section稱為輸出section(output sectin).
目標(biāo)文件的每個(gè)section至少包含兩個(gè)信息: 名字和大小. 大部分section還包含與它相關(guān)聯(lián)的一塊數(shù)據(jù), 稱為section contents(section內(nèi)容). 一個(gè)section可被標(biāo)記為“l(fā)oadable(可加載的)”或“allocatable(可分配的)”.
loadable section: 在輸出文件運(yùn)行時(shí), 相應(yīng)的section內(nèi)容將被載入進(jìn)程地址空間中.
allocatable section: 內(nèi)容為空的section可被標(biāo)記為“可分配的”. 在輸出文件運(yùn)行時(shí), 在進(jìn)程地址空間中空出大小同section指定大小的部分. 某些情況下, 這塊內(nèi)存必須被置零.
如果一個(gè)section不是“可加載的”或“可分配的”, 那么該section通常包含了調(diào)試信息. 可用objdump -h命令查看相關(guān)信息.
每個(gè)“可加載的”或“可分配的”輸出section通常包含兩個(gè)地址: 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í)行輸出文件時(shí)section所在的地址, 而LMA是加載輸出文件時(shí)section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系統(tǒng)中, 經(jīng)常存在加載地址和執(zhí)行地址不同的情況: 比如將輸出文件加載到開(kāi)發(fā)板的flash中(由LMA指定), 而在運(yùn)行時(shí)將位于flash中的輸出文件復(fù)制到SDRAM中(由VMA指定).
|
可這樣來(lái)理解VMA和LMA, 假設(shè):
(1) .data section對(duì)應(yīng)的VMA地址是0x08050000, 該section內(nèi)包含了3個(gè)32位全局變量, i、j和k, 分別為1,2,3.
(2) .text section內(nèi)包含由"printf( "j=%d ", j );"程序片段產(chǎn)生的代碼.
連接時(shí)指定.data section的VMA為0x08050000, 產(chǎn)生的printf指令是將地址為0x08050004處的4字節(jié)內(nèi)容作為一個(gè)整數(shù)打印出來(lái)。
如果.data section的LMA為0x08050000,顯然結(jié)果是j=2
如果.data section的LMA為0x08050004,顯然結(jié)果是j=1
還可這樣理解LMA:
.text section內(nèi)容的開(kāi)始處包含如下兩條指令(intel i386指令是10字節(jié),每行對(duì)應(yīng)5字節(jié)):
jmp 0x08048285
movl $0x1,%eax
如果.text section的LMA為0x08048280, 那么在進(jìn)程地址空間內(nèi)0x08048280處為“jmp 0x08048285”指令, 0x08048285處為movl $0x1,%eax指令. 假設(shè)某指令跳轉(zhuǎn)到地址0x08048280, 顯然它的執(zhí)行將導(dǎo)致%eax寄存器被賦值為1.
如果.text section的LMA為0x08048285, 那么在進(jìn)程地址空間內(nèi)0x08048285處為“jmp 0x08048285”指令, 0x0804828a處為movl $0x1,%eax指令. 假設(shè)某指令跳轉(zhuǎn)到地址0x08048285, 顯然它的執(zhí)行又跳轉(zhuǎn)到進(jìn)程地址空間內(nèi)0x08048285處, 造成死循環(huán).
符號(hào)(symbol): 每個(gè)目標(biāo)文件都有符號(hào)表(SYMBOL TABLE), 包含已定義的符號(hào)(對(duì)應(yīng)全局變量和static變量和定義的函數(shù)的名字)和未定義符號(hào)(未定義的函數(shù)的名字和引用但沒(méi)定義的符s號(hào))信息.
符號(hào)值: 每個(gè)符號(hào)對(duì)應(yīng)一個(gè)地址, 即符號(hào)值(這與c程序內(nèi)變量的值不一樣, 某種情況下可以把它看成變量的地址). 可用nm命令查看它們. (nm的使用方法可參考本blog的GNU binutils筆記)
3. 腳本格式
鏈接腳本由一系列命令組成, 每個(gè)命令由一個(gè)關(guān)鍵字(一般在其后緊跟相關(guān)參數(shù))或一條對(duì)符號(hào)的賦值語(yǔ)句組成. 命令由分號(hào)‘;’分隔開(kāi).
文件名或格式名內(nèi)如果包含分號(hào)';'或其他分隔符, 則要用引號(hào)‘"’將名字全稱引用起來(lái). 無(wú)法處理含引號(hào)的文件名.
/* */之間的是注釋。
4. 簡(jiǎn)單例子
在介紹鏈接描述文件的命令之前, 先看看下述的簡(jiǎn)單例子:
術(shù)語(yǔ):把定位器符號(hào)
一般就是那個(gè). s
以下腳本將輸出文件的text section定位在0x10000, data section定位在0x8000000:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
解釋一下上述的例子:
. = 0x10000 : 把定位器符號(hào)置為0x10000 (若不指定, 則該符號(hào)的初始值為0).
.text : { *(.text) } : 將所有(*符號(hào)代表任意輸入文件)輸入文件的.text section合并成一個(gè).text section, 該section的地址由定位器符號(hào)的值指定, 即0x10000.
. = 0x8000000 :把定位器符號(hào)置為0x8000000
.data : { *(.data) } : 將所有輸入文件的.text section合并成一個(gè).data section, 該section的地址被置為0x8000000.
.bss : { *(.bss) } : 將所有輸入文件的.bss section合并成一個(gè).bss section,該section的地址被置為0x8000000+.data section的大小.
連接器每讀完一個(gè)section描述后, 將定位器符號(hào)的值*增加*該section的大小. 注意: 此處沒(méi)有考慮對(duì)齊約束.
5. 簡(jiǎn)單腳本命令
- 1 -
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 -
INCLUDE filename : 包含其他名為filename的鏈接腳本
|
相當(dāng)于c程序內(nèi)的的#include指令, 用以包含另一個(gè)鏈接腳本.
腳本搜索路徑由-L選項(xiàng)指定. INCLUDE指令可以嵌套使用, 最大深度為10. 即: 文件1內(nèi)INCLUDE文件2, 文件2內(nèi)INCLUDE文件3... , 文件10內(nèi)INCLUDE文件11. 那么文件11內(nèi)不能再出現(xiàn) INCLUDE指令了.
- 3 -
INPUT(files): 將括號(hào)內(nèi)的文件做為鏈接過(guò)程的輸入文件
|
ld首先在當(dāng)前目錄下尋找該文件, 如果沒(méi)找到, 則在由-L指定的搜索路徑下搜索. file可以為 -lfile形式,就象命令行的-l選項(xiàng)一樣. 如果該命令出現(xiàn)在暗含的腳本內(nèi), 則該命令內(nèi)的file在鏈接過(guò)程中的順序由該暗含的腳本在命令行內(nèi)的順序決定.
- 4 -
GROUP(files) : 指定需要重復(fù)搜索符號(hào)定義的多個(gè)輸入文件
|
file必須是庫(kù)文件, 且file文件作為一組被ld重復(fù)掃描,直到不在有新的未定義的引用出現(xiàn)。
- 5 -
OUTPUT(FILENAME) : 定義輸出文件的名字
|
同ld的-o選項(xiàng), 不過(guò)-o選項(xiàng)的優(yōu)先級(jí)更高. 所以它可以用來(lái)定義默認(rèn)的輸出文件名. 如a.out
- 6 -
SEARCH_DIR(PATH) :定義搜索路徑,
|
同ld的-L選項(xiàng), 不過(guò)由-L指定的路徑要比它定義的優(yōu)先被搜索。
- 7 -
STARTUP(filename) : 指定filename為第一個(gè)輸入文件
|
在鏈接過(guò)程中, 每個(gè)輸入文件是有順序的. 此命令設(shè)置文件filename為第一個(gè)輸入文件。
- 8 -
OUTPUT_FORMAT(BFDNAME) : 設(shè)置輸出文件使用的BFD格式
|
同ld選項(xiàng)-o format BFDNAME, 不過(guò)ld選項(xiàng)優(yōu)先級(jí)更高.
- 9 -
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) : 定義三種輸出文件的格式(大小端)
|
若有命令行選項(xiàng)-EB, 則使用第2個(gè)BFD格式; 若有命令行選項(xiàng)-EL,則使用第3個(gè)BFD格式.否則默認(rèn)選第一個(gè)BFD格式.
TARGET(BFDNAME):設(shè)置輸入文件的BFD格式
|
同ld選項(xiàng)-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 則最用一個(gè)TARGET命令設(shè)置的BFD格式將被作為輸出文件的BFD格式.
另外還有一些:
ASSERT(EXP, MESSAGE):如果EXP不為真,終止連接過(guò)程
EXTERN(SYMBOL SYMBOL ...):在輸出文件中增加未定義的符號(hào),如同連接器選項(xiàng)-u
FORCE_COMMON_ALLOCATION:為common symbol(通用符號(hào))分配空間,即使用了-r連接選項(xiàng)也為其分配
NOCROSSREFS(SECTION SECTION ...):檢查列出的輸出section,如果發(fā)現(xiàn)他們之間有相互引用,則報(bào)錯(cuò)。對(duì)于某些系統(tǒng),特別是內(nèi)存較緊張的嵌入式系統(tǒng),某些section是不能同時(shí)存在內(nèi)存中的,所以他們之間不能相互引用。
OUTPUT_ARCH(BFDARCH):設(shè)置輸出文件的machine architecture(體系結(jié)構(gòu)),BFDARCH為被BFD庫(kù)使用的名字之一??梢杂妹?/span>objdump -f查看。
可通過(guò) man -S 1 ld查看ld的聯(lián)機(jī)幫助, 里面也包括了對(duì)這些命令的介紹.
6. 對(duì)符號(hào)的賦值
在目標(biāo)文件內(nèi)定義的符號(hào)可以在鏈接腳本內(nèi)被賦值. (注意和C語(yǔ)言中賦值的不同!) 此時(shí)該符號(hào)被定義為全局的. 每個(gè)符號(hào)都對(duì)應(yīng)了一個(gè)地址, 此處的賦值是更改這個(gè)符號(hào)對(duì)應(yīng)的地址.
e.g. 通過(guò)下面的程序查看變量a的地址:
/* a.c */
#include <stdio.h>
int a = 100;
int main(void)
{
printf( "&a=0x%p ", &a );
return 0;
}
/* a.lds */
a = 3;
$ gcc -Wall -o a-without-lds a.c
&a = 0x8049598
$ gcc -Wall -o a-with-lds a.c a.lds
&a = 0x3
注意: 對(duì)符號(hào)的賦值只對(duì)全局變量起作用!
|
一些簡(jiǎn)單的賦值語(yǔ)句
能使用任何c語(yǔ)言內(nèi)的賦值操作:
SYMBOL = EXPRESSION ;
SYMBOL += EXPRESSION ;
SYMBOL -= EXPRESSION ;
SYMBOL *= EXPRESSION ;
SYMBOL /= EXPRESSION ;
SYMBOL <<= EXPRESSION ;
SYMBOL >>= EXPRESSION ;
SYMBOL &= EXPRESSION ;
SYMBOL |= EXPRESSION ;
除了第一類表達(dá)式外, 使用其他表達(dá)式需要SYMBOL被定義于某目標(biāo)文件。
. 是一個(gè)特殊的符號(hào),它是定位器,一個(gè)位置指針,指向程序地址空間內(nèi)的某位置(或某section內(nèi)的偏移,如果它在SECTIONS命令內(nèi)的某section描述內(nèi)),該符號(hào)只能在SECTIONS命令內(nèi)使用。
注意:賦值語(yǔ)句包含4個(gè)語(yǔ)法元素:符號(hào)名、操作符、表達(dá)式、分號(hào);一個(gè)也不能少。
被賦值后,符號(hào)所屬的section被設(shè)值為表達(dá)式EXPRESSION所屬的SECTION(參看11. 腳本內(nèi)的表達(dá)式)
賦值語(yǔ)句可以出現(xiàn)在連接腳本的三處地方:SECTIONS命令內(nèi),SECTIONS命令內(nèi)的section描述內(nèi)和全局位置;如下,
floating_point = 0; /* 全局位置 */
SECTIONS
{
.text :
{
*(.text)
_etext = .; /* section描述內(nèi) */
}
_bdata = (. + 3) & ~ 4; /* SECTIONS命令內(nèi) */
.data : { *(.data) }
}
PROVIDE關(guān)鍵字
該關(guān)鍵字用于定義這類符號(hào):在目標(biāo)文件內(nèi)被引用,但沒(méi)有在任何目標(biāo)文件內(nèi)被定義的符號(hào)。
例子:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
當(dāng)目標(biāo)文件內(nèi)引用了etext符號(hào),確沒(méi)有定義它時(shí),etext符號(hào)對(duì)應(yīng)的地址被定義為.text section之后的第一個(gè)字節(jié)的地址。
7. SECTIONS命令
SECTIONS命令告訴ld如何把輸入文件的sections映射到輸出文件的各個(gè)section: 如何將輸入section合為輸出section; 如何把輸出section放入程序地址空間(VMA)和進(jìn)程地址空間(LMA).該命令格式如下:
SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND
...
}
SECTION-COMMAND有四種:
(1) ENTRY命令
(2) 符號(hào)賦值語(yǔ)句
(3) 一個(gè)輸出section的描述(output section description)
(4) 一個(gè)section疊加描述(overlay description)
如果整個(gè)連接腳本內(nèi)沒(méi)有SECTIONS命令, 那么ld將所有同名輸入section合成為一個(gè)輸出section內(nèi), 各輸入section的順序?yàn)樗鼈儽贿B接器發(fā)現(xiàn)的順序.
如果某輸入section沒(méi)有在SECTIONS命令中提到, 那么該section將被直接拷貝成輸出section。
輸出section描述
輸出section描述具有如下格式:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]
[ ]內(nèi)的內(nèi)容為可選選項(xiàng), 一般不需要.
SECTION:section名字
SECTION左右的空白、圓括號(hào)、冒號(hào)是必須的,換行符和其他空格是可選的。
每個(gè)OUTPUT-SECTION-COMMAND為以下四種之一,
符號(hào)賦值語(yǔ)句
一個(gè)輸入section描述
直接包含的數(shù)據(jù)值
一個(gè)特殊的輸出section關(guān)鍵字
輸出section名字(SECTION):
輸出section名字必須符合輸出文件格式要求,比如:a.out格式的文件只允許存在.text、.data和.bss section名。而有的格式只允許存在數(shù)字名字,那么此時(shí)應(yīng)該用引號(hào)將所有名字內(nèi)的數(shù)字組合在一起;另外,還有一些格式允許任何序列的字符存在于 section名字內(nèi),此時(shí)如果名字內(nèi)包含特殊字符(比如空格、逗號(hào)等),那么需要用引號(hào)將其組合在一起。
輸出section地址(ADDRESS):
ADDRESS是一個(gè)表達(dá)式,它的值用于設(shè)置VMA。如果沒(méi)有該選項(xiàng)且有REGION選項(xiàng),那么連接器將根據(jù)REGION設(shè)置VMA;如果也沒(méi)有 REGION選項(xiàng),那么連接器將根據(jù)定位符號(hào)‘.’的值設(shè)置該section的VMA,將定位符號(hào)的值調(diào)整到滿足輸出section對(duì)齊要求后的值,輸出 section的對(duì)齊要求為:該輸出section描述內(nèi)用到的所有輸入section的對(duì)齊要求中最嚴(yán)格的。
例子:
.text . : { *(.text) }
和
.text : { *(.text) }
這兩個(gè)描述是截然不同的,第一個(gè)將.text section的VMA設(shè)置為定位符號(hào)的值,而第二個(gè)則是設(shè)置成定位符號(hào)的修調(diào)值,滿足對(duì)齊要求后的。
ADDRESS可以是一個(gè)任意表達(dá)式,比如ALIGN(0x10)這將把該section的VMA設(shè)置成定位符號(hào)的修調(diào)值,滿足16字節(jié)對(duì)齊后的。
注意:設(shè)置ADDRESS值,將更改定位符號(hào)的值。
輸入section描述:
最常見(jiàn)的輸出section描述命令是輸入section描述。
輸入section描述是最基本的連接腳本描述。
輸入section描述基礎(chǔ):
基本語(yǔ)法:FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)
FILENAME文件名,可以是一個(gè)特定的文件的名字,也可以是一個(gè)字符串模式。
SECTION名字,可以是一個(gè)特定的section名字,也可以是一個(gè)字符串模式
例子是最能說(shuō)明問(wèn)題的,
*(.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,順序是:第一個(gè)文件的.text section,第一個(gè)文件的.data section,第二個(gè)文件的.text section,第二個(gè)文件的.data section,...
*(.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)一次??慈缦吕?,
SECTIONS {
.data : { *(.data) }
.data1 : { data.o(.data) }
}
data.o文件的.data section在第一個(gè)OUTPUT-SECTION-COMMAND命令內(nèi)被使用了,那么在第二個(gè)OUTPUT-SECTION-COMMAND命令內(nèi)將不會(huì)再被使用,也就是說(shuō)即使連接器不報(bào)錯(cuò),輸出文件的.data1 section的內(nèi)容也是空的。
再次強(qiáng)調(diào):連接器依次掃描每個(gè)OUTPUT-SECTION-COMMAND命令內(nèi)的文件名,任何一個(gè)文件的任何一個(gè)section都只能使用一次。
讀者可以用-M連接命令選項(xiàng)來(lái)產(chǎn)生一個(gè)map文件,它包含了所有輸入section到輸出section的組合信息。
再看個(gè)例子,
SECTIONS {
.text : { *(.text) }
.DATA : { [A-Z]*(.data) }
.data : { *(.data) }
.bss : { *(.bss) }
}
這個(gè)例子中說(shuō)明,所有文件的輸入.text section組成輸出.text section;所有以大寫字母開(kāi)頭的文件的.data section組成輸出.DATA section,其他文件的.data section組成輸出.data section;所有文件的輸入.bss section組成輸出.bss section。
可以用SORT()關(guān)鍵字對(duì)滿足字符串模式的所有名字進(jìn)行遞增排序,如SORT(.text*)。
通用符號(hào)(common symbol)的輸入section:
在許多目標(biāo)文件格式中,通用符號(hào)并沒(méi)有占用一個(gè)section。連接器認(rèn)為:輸入文件的所有通用符號(hào)在名為COMMON的section內(nèi)。
例子,
.bss { *(.bss) *(COMMON) }
這個(gè)例子中將所有輸入文件的所有通用符號(hào)放入輸出.bss section內(nèi)??梢钥吹?/span>COMMOM section的使用方法跟其他section的使用方法是一樣的。
有些目標(biāo)文件格式把通用符號(hào)分成幾類。例如,在MIPS elf目標(biāo)文件格式中,把通用符號(hào)分成standard common symbols(標(biāo)準(zhǔn)通用符