linux 下的連結檔案詳解
阿新 • • 發佈:2018-12-08
轉載來自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml
轉載來自:(這個哥們加工了的,各種顏色,美化)http://www.cnblogs.com/li-hao/p/4107964.html
最近在研究uboot:看了很多有關他的介紹,都是從xxx.lds這個檔案開始說起,對於這個檔案不是很瞭解,於是乎搜怎麼用的啊,猛然看到這篇部落格,寫的那是一個詳細啊,果斷就轉載了。
一、 概論 每一個連結過程都由 連結指令碼 (linker script, 一般以lds作為檔案的字尾名)控制. 連結指令碼
主要用於規定如何把輸入檔案內的section放入輸出檔案內, 並控制輸出檔案內各部分在程式地址空間內的佈局. 但你也可以用連線命令做一些其他事情.
聯結器有個預設的內建連線指令碼, 可用ld –verbose檢視. 連線選項-r和-N可以影響預設的連線指令碼(如何影響?).
-T選項用以指定自己的連結指令碼, 它將代替預設的連線指令碼。你也可以使用以增加自定義的連結命令.
以下沒有特殊說明,聯結器指的是靜態聯結器.
二、基本概念
連結器把一個或多個輸入檔案合成一個輸出檔案.
輸入檔案: 目標檔案或連結指令碼檔案.
輸出檔案: 目標檔案或可執行檔案.
目標檔案(包括可執行檔案)具有固定的格式, 在UNIX或GNU/Linux平臺下, 一般為ELF格式
有時把輸入檔案內的section稱為
輸入section
(input section), 把輸出檔案內的section稱為
輸出section
(output sectin).
目標檔案的每個section至少包含兩個資訊: 名字和大小. 大部分section還包含與它相關聯的一塊資料, 稱為section contents (section內容). 一個section可被標記為“loadable(可載入的)”或“allocatable(可分配的)”.
loadable section: 在輸出檔案執行時, 相應的section內容將被載入程序地址空間中.
allocatable section: 內容為空的section可被標記為“可分配的”. 在輸出檔案執行時, 在程序地址空間中空出大小同section指定大小的部分. 某些情況下, 這塊記憶體必須被置零.
如果一個section不是“可載入的”或“可分配的”, 那麼該section通常包含了除錯資訊. 可用objdump -h命令檢視相關資訊.
每個“可載入的”或“可分配的”輸出section通常包含兩個地址
: VMA(virtual memory address虛擬記憶體地址或程式地址空間地址)和LMA(load memory address載入記憶體地址或程序地址空間地址). 通常VMA和LMA是相同的.
在目標檔案中, loadable或allocatable的輸出section有兩種地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是執行輸出檔案時section所在的地址, 而LMA是載入輸出檔案時section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系統中, 經常存在載入地址和執行地址不同的情況: 比如將輸出檔案載入到開發板的flash中(由LMA指定), 而在執行時將位於flash中的輸出檔案複製到SDRAM中(由VMA指定).
可這樣來理解VMA和LMA, 假設:
(1) .data section對應的VMA地址是0×08050000, 該section內包含了3個32位全域性變數, i、j和k, 分別為1,2,3.
(2) .text section內包含由”printf( “j=%d “, j );”程式片段產生的程式碼.
連線時指定.data section的VMA為0×08050000, 產生的printf指令是將地址為0×08050004處的4位元組內容作為一個整數打印出來。
如果.data section的LMA為0×08050000,顯然結果是j=2
如果.data section的LMA為0×08050004,顯然結果是j=1
還可這樣理解LMA:
.text section內容的開始處包含如下兩條指令(intel i386指令是10位元組,每行對應5位元組):
jmp 0×08048285
movl $0×1,%eax
如果.text section的LMA為0×08048280, 那麼在程序地址空間內0×08048280處為“jmp 0×08048285”指令, 0×08048285處為movl $0×1,%eax指令. 假設某指令跳轉到地址0×08048280, 顯然它的執行將導致%eax暫存器被賦值為1.
如果.text section的LMA為0×08048285, 那麼在程序地址空間內0×08048285處為“jmp 0×08048285”指令, 0×0804828a處為movl $0×1,%eax指令. 假設某指令跳轉到地址0×08048285, 顯然它的執行又跳轉到程序地址空間內0×08048285處, 造成死迴圈.
符號(symbol): 每個目標檔案都有符號表(SYMBOL TABLE), 包含已定義的符號(對應全域性變數和static變數和定義的函式的名字)和未定義符號(未定義的函式的名字和引用但沒定義的符號)資訊.
符號值: 每個符號對應一個地址, 即符號值(這與c程式內變數的值不一樣, 某種情況下可以把它看成變數的地址). 可用nm命令檢視它們. (nm的使用方法可參考本blog的GNU binutils筆記)
三、 指令碼格式
連結指令碼由一系列命令組成, 每個命令由一個關鍵字(一般在其後緊跟相關引數)或一條對符號的賦值語句組成. 命令由分號‘
;
’分隔開.
檔名或格式名內如果包含分號’
;
'或其他分隔符, 則要用引號‘
”
’將名字全稱引用起來. 無法處理含引號的檔名.
/* */之間的是註釋。
四、 簡單例子
在介紹連結描述檔案的命令之前, 先看看下述的簡單例子:
以下指令碼將輸出檔案的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的大小. 注意: 此處沒有考慮對齊約束.
五、 簡單指令碼命令
ENTRY(
SYMBOL
)
:將符號SYMBOL的值設定成入口地址。
入口地址
(entry point)是指程序執行的第一條使用者空間的指令在程序地址空間的地址
ld有多種方法設定程序入口地址, 按一下順序: (編號越前, 優先順序越高)
1, ld命令列的-e選項
2, 連線指令碼的ENTRY(SYMBOL)命令
3, 如果定義了start符號, 使用start符號值
4, 如果存在.text section, 使用.text section的第一位元組的位置值
5, 使用值0
INCLUDE
filename
: 包含其他名為filename的連結指令碼
相當於c程式內的的#include指令, 用以包含另一個連結指令碼.
指令碼搜尋路徑由-L選項指定. INCLUDE指令可以巢狀使用, 最大深度為10. 即: 檔案1內INCLUDE檔案2, 檔案2內INCLUDE檔案3… , 檔案10內INCLUDE檔案11. 那麼檔案11內不能再出現 INCLUDE指令了.
INPUT(
files
)
: 將括號內的檔案做為連結過程的輸入檔案
ld首先在當前目錄下尋找該檔案, 如果沒找到, 則在由-L指定的搜尋路徑下搜尋. file可以為 -lfile形式,就象命令列的-l選項一樣. 如果該命令出現在暗含的指令碼內, 則該命令內的file在連結過程中的順序由該暗含的指令碼在命令列內的順序決定.
GROUP(
files
)
: 指定需要重複搜尋符號定義的多個輸入檔案
file必須是庫檔案, 且file檔案作為一組被ld重複掃描,直到不在有新的未定義的引用出現。
OUTPUT(
FILENAME
)
: 定義輸出檔案的名字
同ld的-o選項, 不過-o選項的優先順序更高. 所以它可以用來定義預設的輸出檔名. 如a.out
SEARCH_DIR
(PATH
)
:定義搜尋路徑,
同ld的-L選項, 不過由-L指定的路徑要比它定義的優先被搜尋。
STARTUP(
filename
)
: 指定filename為第一個輸入檔案
在連結過程中, 每個輸入檔案是有順序的. 此命令設定檔案filename為第一個輸入檔案。
OUTPUT_FORMAT(
BFDNAME
)
: 設定輸出檔案使用的BFD格式
同ld選項-o format BFDNAME, 不過ld選項優先順序更高.
OUTPUT_FORMAT(
DEFAULT
,
BIG
,
LITTLE
)
: 定義三種輸出檔案的格式(大小端)
若有命令列選項-EB, 則使用第2個BFD格式; 若有命令列選項-EL,則使用第3個BFD格式.否則預設選第一個BFD格式.
TARGET(
BFDNAME
)
:設定輸入檔案的BFD格式
同ld選項-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 則最用一個TARGET命令設定的BFD格式將被作為輸出檔案的BFD格式.
ASSERT(
EXP, MESSAGE
)
:如果EXP不為真,終止連線過程
EXTERN(
SYMBOL SYMBOL …
)
:在輸出檔案中增加未定義的符號,如同聯結器選項-u
FORCE_COMMON_ALLOCATION
:為common symbol(通用符號)分配空間,即使用了-r連線選項也為其分配
NOCROSSREFS(
SECTION SECTION
…)
:檢查列出的輸出section,如果發現他們之間有相互引用,則報錯。對於某些系統,特別是記憶體較緊張的嵌入式系統,某些section是不能同時存在記憶體中的,所以他們之間不能相互引用。
OUTPUT_ARCH(
BFDARCH
)
:設定輸出檔案的machine architecture(體系結構),BFDARCH為被BFD庫使用的名字之一。可以用命令objdump -f檢視。
可通過 man -S 1 ld檢視ld的聯機幫助, 裡面也包括了對這些命令的介紹.
六、 對符號的賦值
在目標檔案內定義的符號可以在連結指令碼內被賦值. (注意和C語言中賦值的不同!) 此時該符號被定義為全域性的. 每個符號都對應了一個地址,
此處的賦值是更改這個符號對應的
地址
.
舉例. 通過下面的程式檢視變數a的地址:
a.c檔案
/* a.c */
#include <stdio.h>
int
a = 100;
int
main()
{
printf( "&a=%p\n",
&a
);
return 0;
}
a.lds檔案
/* 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
注意: 對符號的賦值只對全域性變數起作用!
對於一些簡單的賦值語句,我們可以使用任何c語言語法的賦值操作:
SYMBOL = EXPRESSION ;
SYMBOL += EXPRESSION ;
SYMBOL -= EXPRESSION ;
SYMBOL *= EXPRESSION ;
SYMBOL /= EXPRESSION ;
SYMBOL >= EXPRESSION ;
SYMBOL &= EXPRESSION ;
SYMBOL |= EXPRESSION ;
除了第一類表示式外, 使用其他表示式需要SYMBOL已經被在某目標檔案的原始碼中被定義。
. 是一個特殊的符號,它是定位器,一個位置指標,指向程式地址空間內的某位置(或某section內的偏移,如果它在SECTIONS命令內的某section描述內),該符號只能在SECTIONS命令內使用。
注意:賦值語句包含4個語法元素:符號名、操作符、表示式、分號;一個也不能少。
被賦值後,符號所屬的section被設值為表示式EXPRESSION所屬的SECTION(參看11. 指令碼內的表示式)
賦值語句可以出現在連線指令碼的三處地方:SECTIONS命令內,SECTIONS命令內的section描述內和全域性位置。
示例1:
floating_point = 0;
/* 全域性位置 */
SECTIONS
{
.text :
{
*(.text)
_etext = .;
/* section描述內 */
}
_bdata = (. + 3) & ~ 4;
/* SECTIONS命令內 */
.data : { *(.data) }
}
PROVIDE關鍵字
該關鍵字用於定義這類符號:在目標檔案內被引用,但沒有在任何目標檔案內被定義的符號。
示例2:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE
(
etext
=
.
);
}
}
這裡,當目標檔案內引用了etext符號,卻沒有定義它時,etext符號對應的地址被定義為.text section之後的第一個位元組的地址。
七、 SECTIONS命令
SECTIONS
命令告訴ld如何把輸入檔案的sections對映到輸出檔案的各個section: 如何將輸入section合為輸出section; 如何把輸出section放入程式地址空間(VMA)和程序地址空間(LMA).
該命令格式如下:
SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND
…
}
SECTION-COMMAND有四種:
(1) ENTRY命令
(2) 符號賦值語句
(3) 一個輸出section的描述(output section description)
(4) 一個section疊加描述(overlay description)
如果整個連線指令碼內沒有SECTIONS命令, 那麼ld將所有同名輸入section合成為一個輸出section內, 各輸入section的順序為它們被聯結器發現的順序.
如果某輸入section沒有在SECTIONS命令中提到, 那麼該section將被直接拷貝成輸出section。
7.1、輸出section描述(基本)
輸出section描述具有如下格式:
SECTION-NAME
[
ADDRESS
] [(
TYPE
)]
:
[
AT
(
LMA
)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
…
} [
>REGION
] [AT>LMA_REGION] [:PHDR HDR ...] [=
FILLEXP
]
[ ]
內的內容為可選選項, 一般不需要.
SECTION-NAME
:section名字.SECTION-NAME左右的空白、圓括號、冒號是必須的,換行符和其他空格是可選的。
7.1.1、輸出section名字
輸出section名字SECTION-NAME必須符合輸出檔案格式要求,比如:a.out格式的檔案只允許存在.text、.data和.bss section名。而有的格式只允許存在數字名字,那麼此時應該用引號將所有名字內的數字組合在一起;另外,還有一些格式允許任何序列的字元存在於section名字內,此時如果名字內包含特殊字元(比如空格、逗號等),那麼需要用引號將其組合在一起。
7.1.2、輸出section地址
輸出section地址[ADDRESS]是一個表示式,它的值用於設定VMA。如果沒有該選項且有REGION選項,那麼聯結器將根據REGION設定VMA;如果也沒有REGION選項,那麼聯結器將根據定位符號‘.’的值設定該section的VMA,將定位符號的值調整到滿足輸出section對齊要求後的值,這時輸出 section的對齊要求為:該輸出section描述內用到的所有輸入section的對齊要求中最嚴格的對齊要求。
例子:
.text
.
:
{ *(.text) }
和.text : { *(.text) }
這兩個描述是截然不同的,第一個將.text section的VMA設定為定位符號的值,而第二個則是設定成定位符號的修調值,滿足對齊要求後的。
ADDRESS
可以是一個任意表達式,比如,
ALIGN(0×10)
這將把該section的VMA設定成定位符號的修調值,滿足
16
位元組對齊後的
。
注意:設定ADDRESS值,將更改定位符號的值。
7.1.3、輸出section描述
輸出section描述OUTPUT-SECTION-COMMAND為以下四種之一:
(1).符號賦值語句
(2).輸入section描述
(3).直接包含的資料值
(4).一些特殊的輸出section關鍵字
7.1.3.1、符號賦值語
符號賦值語句
已經在《
Linux下的lds連結指令碼基礎(一)》前文介紹過,這裡就不累述。
7.1.3.2、輸入section描述:
最常見的輸出section描述命令是
輸入section描述
。
輸入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
下面看聯結器是如何找到對應的檔案的。
當
FILENAME
是一個特定的檔名時,聯結器會檢視它是否在連線命令列內出現或在INPUT命令中出現。
當
FILENAME
是一個字串模式時,聯結器僅僅只檢視它是否在連線命令列內出現。
注意:如果聯結器發現某檔案在INPUT命令內出現,那麼它會在-L指定的路徑內搜尋該檔案。
字串模式內可存在以下萬用字元:
*
:表示任意多個字元
?
:表示任意一個字元
[CHARS]
:表示任意一個CHARS內的字元,可用-號表示範圍,如:a-z
:
表示引用下一個緊跟的字元
在檔名內,萬用字元不匹配資料夾分隔符/,但當字串模式僅包含萬用字元*時除外。
任何一個檔案的任意section只能在SECTIONS命令內出現一次。
看如下例子
SECTIONS
{
.data
:
{ *(.data) }
.data1
:
{ data.o(.data) }
}
data.o檔案的.data section在第一個OUTPUT-SECTION-COMMAND命令內被使用了,那麼在第二個OUTPUT-SECTION-COMMAND命令內將不會再被使用,也就是說即使聯結器不報錯,輸出檔案的.data1 section的內容也是空的。
再次強調:聯結器依次掃描每個OUTPUT-SECTION-COMMAND命令內的檔名,任何一個檔案的任何一個section都只能使用一次。
讀者可以用-M連線命令選項來產生一個map檔案,它包含了所有輸入section到輸出section的組合資訊。
再看個例子,
SECTIONS
{
.text
: { *(.text) }
.DATA
: { [A-Z]*(.data) }
.data
: { *(.data) }
.bss
: { *(.bss) }
}
這個例子中說明,所有檔案的輸入.text section組成輸出.text section;所有以大寫字母開頭的檔案的.data section組成輸出.DATA section,其他檔案的.data section組成輸出.data section;所有檔案的輸入.bss section組成輸出.bss section。
可以用SORT()關鍵字對滿足字串模式的所有名字進行遞增排序
,如SORT(.text*)。
通用符號(common symbol)的輸入section
在許多目標檔案格式中,通用符號並沒有佔用一個section。聯結器認為:輸入檔案的所有通用符號在名為COMMON的section內。
例子,
.bss
{ *(.bss) *(COMMON) }
這個例子中將所有輸入檔案的所有通用符號放入輸出.bss section內。可以看到COMMOM section的使用方法跟其他section的使用方法是一樣的。
有些目標檔案格式把通用符號分成幾類
。例如,在MIPS elf目標檔案格式中,把通用符號分成standard common symbols(標準通用符號)和small common symbols(微通用符號,不知道這麼譯對不對?),此時聯結器認為所有standard common symbols在COMMON section內,而small common symbols在.scommon section內。
在一些以前的連線指令碼內可以看見[COMMON],相當於*(COMMON),不建議繼續使用這種陳舊的方式。
輸入section和垃圾回收
在連線命令列內使用了選項–gc-sections後,聯結器可能將某些它認為沒用的section過濾掉,此時就有必要強制聯結器保留一些特定的 section,可用
KEEP()
關鍵字達此目的。如
KEEP
(*(.text))或KEEP(SORT(*)(.text))
最後我們看個簡單的輸入section相關例子:
SECTIONS
{
outputa
0×10000
:
{
all.o
foo.o
(
.input1
)
}
outputb
:
{
foo.o
(
.input2
)
foo1.o
(
.input1
)
}
outputc
:
{
*
(
.input1
)
*
(
.input2
)
}
}
本例中,將all.o檔案的所有section和foo.o檔案的所有(一個檔案內可以有多個同名section).input1 section依次放入輸出outputasection內,該section的VMA是0×10000;將foo.o檔案的所有.input2 section和foo1.o檔案的所有.input1 section依次放入輸出outputb section內,該section的VMA是當前定位器符號的修調值(對齊後);將其他檔案(非all.o、foo.o、foo1.o)檔案的. input1section和.input2 section放入輸出outputc section內。
7.1.3.3、直接包含資料值
可以顯示地在輸出section內填入你想要填入的資訊
(這樣是不是可以自己通過連線指令碼寫程式?當然是簡單的程式)。
BYTE
(EXPRESSION) 1 位元組
SHORT
(EXPRESSION) 2 位元組
LOGN
(EXPRESSION) 4 位元組
QUAD
(EXPRESSION) 8 位元組
SQUAD
(EXPRESSION) 64位處理器的程式碼時,8 位元組
輸出檔案的位元組順序big endianness 或little endianness,可以由輸出目標檔案的格式決定;如果輸出目標檔案的格式不能決定位元組順序,那麼位元組順序與第一個輸入檔案的位元組順序相同。
如:BYTE(1)、LANG(addr)。
注意,這些命令只能放在輸出section描述內,其他地方不行。
錯誤:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
正確:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }
在當前輸出section內可能存在未描述的儲存區域(比如由於對齊造成的空隙),可以用
FILL
(
EXPRESSION
)命令決定這些儲存區域的內容, EXPRESSION的前兩位元組有效,這兩位元組在必要時可以重複被使用以填充這類儲存區域。如FILE(0×9090)。在輸出section描述中可以有
=FILEEXP
屬性,它的作用如同FILE()命令,但是FILE命令只作用於該FILE指令之後的section區域,而
=FILEEXP
屬性作用於整個輸出section區域,且FILE命令的優先順序更高!!!
7.1.3.4、特殊的輸出section關鍵字
在輸出section描述OUTPUT-SECTION-COMMAND中還可以使用一些特殊的輸出section關鍵字。
CREATE_OBJECT_SYMBOLS
:為每個輸入檔案建立一個符號,符號名為輸入檔案的名字。每個符號所在的section是出現該關鍵字的section。
CONSTRUCTORS
:與c++內的(全域性物件的)建構函式和(全域性對像的)解構函式相關,下面將它們簡稱為全域性構造和全域性析構。
對於a.out目標檔案格式,聯結器用一些不尋常的方法實現c++的全域性構造和全域性析構。
當聯結器生成的目標檔案格式
不支援任意section名字時
,比如說
ECOFF
、
XCOFF
格式,聯結器將通過名字來識別全域性構造和全域性析構,對於這些檔案格式,
聯結器把與全域性構造和全域性析構的相關資訊放入出現 CONSTRUCTORS關鍵字的輸出section內
。
符號
__CTORS_LIST__
表示全域性構造資訊的的開始處,
__CTORS_END__
表示全域性構造資訊的結束處。
符號
__DTORS_LIST__
表示全域性構造資訊的的開始處,
__DTORS_END__
表示全域性構造資訊的結束處。
這兩塊資訊的開始處是一字長的資訊,表示該塊資訊有多少項資料,然後以值為零的一字長資料結束。
一般來說,GNU C++在函式__main內安排全域性構造程式碼的執行,而__main函式被初始化程式碼(在main函式呼叫之前執行)呼叫。是不是對於某些目標檔案格式才這樣???
對於支持任意section名的目標檔案格式,比如COFF、ELF格式,GNU C++將全域性構造和全域性析構資訊分別放入.ctors section和.dtors section內,然後在連線指令碼內加入如下,
__CTOR_LIST__
= .;
LONG
(
(__CTOR_END__ – __CTOR_LIST__) / 4 – 2
)
*(.ctors)
LONG
(0)
__CTOR_END__
= .;
__DTOR_LIST__
= .;
LONG
(
(__DTOR_END__ – __DTOR_LIST__) / 4 – 2
)
*(.dtors)
LONG
(0)
__DTOR_END__
= .;
如果使用GNU C++提供的初始化優先順序支援(它能控制每個全域性建構函式呼叫的先後順序),那麼請在連線指令碼內把CONSTRUCTORS替換成SORT (CONSTRUCTS),把*(.ctors)換成*(SORT(.ctors)),把*(.dtors)換成*(SORT(.dtors))。一般來說,預設的連線指令碼已作好的這些工作。
修改定位器
我們可以對定位器符合。進行賦值來修改定位器的值。
示例
SECTIONS
{
.
= SIZEOF_HEADERS;
.text : { *(.text) }
. = 0×10000;
.data : { *(.data) }
. = 0×8000000;
.bss : { *(.bss) }
}
輸出section的丟棄
對於.foo: { *(.foo) },如果沒有任何一個輸入檔案包含.foo section,那麼聯結器將不會建立.foo輸出section。但是如果在這些輸出section描述內包含了非輸入section描述命令(如符號賦值語句),那麼聯結器將總是建立該輸出section。
另外,有一個特殊的輸出section,名為
/DISCARD/
,
被該section引用的任何輸入section將不會出現在輸出檔案內
,這就是DISCARD的意思吧。
如果/DISCARD/ section被它自己引用呢?想想看。
7.2、輸出section描述(進階)
我們再回顧以下輸出section描述的文法:
SECTION-NAME
[
ADDRESS
] [(
TYPE
)]
:
[
AT
(
LMA
)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
…
} [>REGION] [AT>LMA_REGION] [
:
PHDR HDR ...
] [=
FILLEXP
]
前面我們介紹了SECTION、ADDRESS、OUTPUT-SECTION-COMMAND相關資訊,下面我們將介紹其他屬性。
7.2.1、輸出section的型別
可以通過
[(
TYPE
)]
設定輸出section的型別
。如果沒有指定TYPE型別,那麼聯結器根據輸出section引用的輸入section的型別設定該輸出section的型別。它可以為以下五種值,
NOLOAD
:該section在程式執行時,不被載入記憶體。
DSECT,COPY,INFO,OVERLAY
:這些型別很少被使用,為了向後相容才被保留下來。這種型別的section必須被標記為“不可載入的”,以便在程式執行不為它們分配記憶體。
預設值是多少呢?Puzzle!
7.2.2、輸出section的LMA
預設情況下,LMA等於VMA,但可以通過
[
AT
(
LMA
)]
項,即關鍵字
AT()
指定LMA
。
用關鍵字AT()指定,括號內包含表示式,表示式的值用於設定LMA。如果不用AT()關鍵字,那麼可用
AT>
LMA_REGION
表達式設定指定該section載入地址的範圍。這個屬性主要用於構件ROM境象。
例子,
SECTIONS
{
.text
0×1000 : {_etext = . ;*(.text); }
.mdata
0×2000
:
AT
(
ADDR (.text) + SIZEOF (.text)
)
{
_data
= .
;
*(.data)
;
_edata
= .
; }
.bss
0×3000
:
{
_bstart
= .
;
*(.bss) *(COMMON)
; _bend
= .
;}
}
程式如下,
extern char
_etext
,
_data
,
_edata
,
_bstart
,
_bend
;
char *src = &_etext;
char *dst = &_data;
/* ROM has data at end of text; copy it. */
while (dst rom }
7.2.3、設定輸出section所在的程式段
可以通過
[
:
PHDR HDR ...
]
項將輸出section放入預先定義的程式段(program segment)內。如果某個輸出section設定了它所在的一個或多個程式段,那麼接下來定義的輸出section的預設程式段與該輸出 section的相同。除非再次顯示地指定
。例子,
PHDRS
{
text
PT_LOAD ; }
SECTIONS
{ .text : { *(.text) } :
text
}
可以通過:NONE指定聯結器不把該section放入任何程式段內。詳情請檢視PHDRS命令
7.2.4、設定輸出section的填充模版
這個在前面提到過,任何輸出section描述內的未指定的記憶體區域,聯結器用該模版填充該區域。我們可以通過
[=
FILLEXP
]
項設定填充值。用法:
=FILEEXP
,前兩位元組有效,當區域大於兩位元組時,重複使用這兩位元組以將其填滿。
例子,
SECTIONS { .text : { *(.text) } =
0×9090
}
7.3、覆蓋圖(overlay)描述
覆蓋圖
描述使兩個或多個不同的section佔用同一塊程式地址空間。覆蓋圖管理程式碼負責將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。這裡VMA由[START] 決定。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大小的最大值。
示例:
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);
八、 記憶體區域命令
在預設情形下,聯結器可以為section在程式地址空間內分配任意位置的儲存區域。
並通過輸出
section描述的
>
REGION屬性
顯示地將該輸出section限定於在程式地址空間內的某塊儲存區域,當儲存區域大小不能滿足要求時,聯結器會報告該錯誤
。
你也可以用MEMORY命令讓
在SECTIONS命令內*未*引用的selection分配在程式地址空間內的某個儲存區域內。
注意:以下儲存區域指的是在程式地址空間內的。
MEMORY命令
的文法如下,
MEMORY
{
NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN1
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
示例
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0×40000000, l = 4M
}
此例中,把在SECTIONS命令內*未*引用的且具有讀屬性或寫屬性的輸入section放入rom區域內,把其他未引用的輸入section放入 ram。如果某輸出section要被放入某記憶體區域內,而該輸出section又沒有指明ADDRESS屬性,那麼聯結器將該輸出section放在該區域內下一個能使用位置。
九、 PHDRS命令
該命令僅在產生ELF目標檔案時有效。
ELF目標檔案格式用program headers程式頭(程式頭內包含一個或多個segment程式段描述)來描述程式如何被載入記憶體
。可以用objdump -p命令檢視。
當在本地ELF系統執行ELF目標檔案格式的程式時,系統載入器通過讀取程式頭資訊以知道如何將程式載入到記憶體。要了解系統載入器如何解析程式頭,請參考ELF ABI文件。
在連線指令碼內不指定
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
可以是以下八種形式,
PT_NULL 0
表示未被使用的程式段
PT_LOAD 1
表示該程式段在程式執行時應該被載入
PT_DYNAMIC
表示該程式段包含動態連線資訊
PT_INTERP 3
表示該程式段內包含程式載入器的名字,在linux下常見的程式載入器是ld-linux.so.2
PT_NOTE 4
表示該程式段內包含程式的說明資訊
PT_SHLIB 5
一個保留的程式頭型別,沒有在ELF ABI文件內定義
PT_PHDR 6
表示該程式段包含程式頭資訊。
EXPRESSION
表示式值
以上每個型別都對應一個數字,該表示式定義一個使用者自定的程式頭。
在TYPE屬性後存在FILEHDR關鍵字,表示該段包含ELF檔案頭資訊;存在PHDRS關鍵字,表示
轉載來自:(這個哥們加工了的,各種顏色,美化)http://www.cnblogs.com/li-hao/p/4107964.html
最近在研究uboot:看了很多有關他的介紹,都是從xxx.lds這個檔案開始說起,對於這個檔案不是很瞭解,於是乎搜怎麼用的啊,猛然看到這篇部落格,寫的那是一個詳細啊,果斷就轉載了。
一、 概論 每一個連結過程都由 連結指令碼 (linker script, 一般以lds作為檔案的字尾名)控制. 連結指令碼