預設的linker會將所有的memory space視為可以分配的。然而現實生活這個假設不一定成立,例如你寫資料到ROM的記憶體就保證GG。所以linker script提供了MEMORY
命令讓你畫地盤,告訴linker那塊地盤有什麼樣的特性。該命令會描述
命令語法如下:
MEMORY
{
name [(attr)] : ORIGIN = origin, LENGTH = len
...
}
每個欄位說明如下
name
REGION_ALIAS
命令attr
R
: Read only * W
: 可讀寫 * X
: executable * A
: 可allocate * I
和L
: Initialized section (三小?) * !
: 將該符號後面所有的屬性inverse
ORIGIN
LENGTH
下面的範例可以看到
rom
的資訊:
0
ram
的資訊:
0x40000000
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0x40000000, l = 4M
}
region和section的合體部份前面有提過了。如果沒有指定region的話,linker會從目前region挑一個給你用。除此之外,你的section空間region塞不下的話linker會幫你偵測出來。
另外ORIGIN和LENGTH可以當作查詢region的資訊,範例如下
_fstack = ORIGIN(ram) + LENGTH(ram) - 4;
資源回收上一篇講的東西。基本上我不知道elf是三小,所以很有可能這部份有錯誤,請自行斟酌!
PHDR 是ELF的program header縮寫,又稱為segment(以後以segment稱之)。當ELF loader載入ELF執行檔的時候,會看這些segment決定要如何把讀入的檔案放在記憶體中,這部份和ABI有關係,按下不表,等我那天心情好再來看ELF和ABI。你可以透過objdump -p
觀察program header。
一般來說,linker預設都幫你弄好elf相關的segment。但是如果你因故需要自幹的話,就可以用PHDRS
命令,一旦使用了這個命令,linker預設的相關segment設定將被取消。另外這個命令只對elf格式輸出有意義,非elf格式輸出這部份的指令一律失效。
PHDRS
命令格式如下:
PHDRS
{
name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ]
[ FLAGS ( flags ) ] ;
}
name
type
PT_NULL
(對應值: 0)
PT_LOAD
(對應值: 1)
PT_DYNAMIC
(對應值: 2)
PT_INTERP
(對應值: 3)
readelf -l ls
可以看到該INTERP segment的資料是是/lib64/ld-linux-x86-64.so.2
,這邊似乎有些好玩的線索,一樣等到想起來再來看看。PT_NOTE
(對應值: 4)
man elf
說這個是存放輔助資料PT_SHLIB
(對應值: 5)
PT_PHDR
(對應值: 6)
type
後面都可以加上FILEHDR
或PHDRS
,其中
FILEHDR
:表示該segment應該內含ELF file headerPHDRS
:表示該segment應該內含ELF program headerAT
FLAGS(數字)
p_flags
。man elf
可以查到p_flags
定義,數值我猜要去看程式碼或是ELF規格了。
PF_X
: executable segmentPF_W
: write segmentPF_R
: read segment單個segment通常map到一個section,linker依照順序處理header給之後的loader使用。另外要注意的是如果你在某個section指定了:phdr
後,之後的section就算沒指令,都會放在該segment。如果之後的section有:phdr
設成:NONE
的話,linker才不會把之後的section放到任何segment。
如果有需要,你可以指定不同的segment都要有某個section的內容,使用方式就是在section
命令中用多個:phdr
。範例如下:
.interp : { *(.interp) } :text :interp
手冊上面提供了一個比較完整的範例。望文生義應該不難理解,所以就不解釋了。
PHDRS
{
headers PT_PHDR PHDRS ;
interp PT_INTERP ;
text PT_LOAD FILEHDR PHDRS ;
data PT_LOAD ;
dynamic PT_DYNAMIC ;
}
SECTIONS
{
. = SIZEOF_HEADERS;
.interp : { *(.interp) } :text :interp
.text : { *(.text) } :text
.rodata : { *(.rodata) } /* defaults to :text */
...
. = . + 0x1000; /* move to a new page in memory */
.data : { *(.data) } :data
.dynamic : { *(.dynamic) } :data :dynamic
...
}
ELF檔案格式支援動態link的時候指定shared library版本。這項功能需要linker配合,VERSION
命令就是來描述版本資訊。
語法如下
VERSION [extern "lang"] { version-script-commands }
其中 extern "lang" 的lang有支援
C
C++
Java
至於version-script-commands,手冊上面說和Sun(已被併購)在Solaris 2.5上的linker語法相同,估狗查version-script-commands沒查到語法,只能從手冊提供的範例來看。如果有人知道語法link請跟我說。手冊上面說這是一個樹狀結構,基本單位為一個version node。你可以在version node中設定
從手冊範例可以推測version node格式如下
name {
[global:]
symbol1;
...
[local:]
symbol_a;
...
} [depend_name];
好啦,有這樣的概念後我們來看手冊範例
VERS_1.1 {
global:
foo1;
local:
old*;
original*;
new*;
};
VERS_1.2 {
foo2;
} VERS_1.1;
VERS_2.0 {
bar1; bar2;
extern "C++" {
ns::*;
"f(int, double)";
};
} VERS_1.2;
OK,開始解釋:
VERS_1.1
, VERS_1.2
, VERS_2.0
VERS_1.1
沒有相依性,VERS_1.2
相依於VERS_1.1
, VERS_2.0
相依於VERS_1.2
VERS_1.1
中
foo1
和VERS_1.1
有關VERS_1.2
中
foo2
和VERS_1.2
有關VERS_2.0
中
bar1
和bar2
和VERS_2.0
有關看完些描述後,可以問**啊沒有指定和version node相關的symbol怎麼辦?**手冊說會分配給library的base version(好吧我不知道base version是三小。)。如果你要將沒指定version node的symbol全部設成和某個version node有關的話,請在該version node中加上以下的描述:
global: *;
一般來說這個描述加再最後的version node才有意義,否則在前面的version node中把所有的symbol都被設定完畢的話,那接下來的version node就沒有辦法設定symbol關聯性的。
手冊中指出version node名稱是給人看的,對於linker在乎的只有他們的關係。所以你要故意取成讓人看不懂的名稱也可以滴。
如果你要指定所有的版本都使用同樣的symbol設定,那麼寫一份就好。重點是這份描述不用寫version node名稱,範例如下。
{ global: foo; bar; local: *; };
至於在程式碼中指定版本的方式,你需要使用GNU extention語法,例如加入下面的咒與描述到你的程式碼中。 語法如下:
__asm__(.symver name, name2@version_node_name);
.symver
:你應該用的指令name
:你程式用到的symbolname2@version_node_name
:實際上你真正用的symbol以及對應的version node範例如下:
__asm__(".symver original_foo,foo@VERS_1.1");
你也可以分別指定自己程式的symbol對應到不同版本的symbol,範例如下:
__asm__(".symver original_foo,foo@");
__asm__(".symver old_foo,foo@VERS_1.1");
__asm__(".symver old_foo1,foo@VERS_1.2");
__asm__(".symver new_foo,foo@@VERS_2.0");
foo@
表示未指定版號的symbol就用該symbolfoo@@VERS_2.0
的@@
表示預設使用該設定.symver
詳細的語法說明可以看這邊
下面這段是囈言囈語,因為我在描述一個我不知道什麼、以及不知道我在描述什麼的東西,請當作夢話跳過! 當你的程式要使用shared library的symbol的時候,你的程式應該要知道要用哪個版本的symbol以及這些symbol是在哪個version node宣告(怎麼做?我寫程式還要管shared library symbol版本,看linker script?不合理)。所以runtime的時候dynamic loader可以幫你搞定resolve symbol的事情。
跳過解釋需要version 的原因,想知道的可以看原文,看懂順便跟我說。
Demangled names的注意事項懶得看,一併跳過。
Linker script 的 expression有幾點特性
接下來我們來討論linker 中Expression可以使用的內建功能
設定常數規則如下
0
開頭o
結尾, O
結尾0x
開頭, 0X
開頭:h
結尾, H
結尾d
結尾, D
結尾K
M
K
和M
不能跟下面的描述混用
o
結尾, O
結尾h
結尾, H
結尾d
結尾, D
結尾範例:
_fourk_1 = 4K;
_fourk_2 = 4096;
_fourk_3 = 0x1000;
_fourk_4 = 10000o;
指令:
CONSTANT(name)
name
如下,可以望文生義所以就不解釋
MAXPAGESIZE
COMMONPAGESIZE
範例:指定.text
section要和最大的page size對齊。
.text ALIGN (CONSTANT (MAXPAGESIZE)) : { *(.text) }
沒有被"
引用的情況下:
_
.
_
.
-
如果你symbol名稱要用奇怪的字元或是和keyword相同的話,請將symbol名稱的開始結尾加上"
符號,範例如下:
"SECTION" = 9;
"with a space" = "also with a space" + 10;
由於symbol名稱可以有非英文字母和數字,所以不建議中間有空白字元。舉例來說,A-B
是一個符號,但是A - B
就是一個expression操作,表示symbol A
減去symbol B
孤兒 Section 指的是在輸入object檔案中的section,而這些section linker script裡面並沒有描述該怎麼處理。遇到這種狀況,linker還是會把這些section放到輸出object檔案中,規則為:
如果這些孤兒section符合C語言identifier規範(通常不是以.
開頭),linker會幫忙加入兩個symbol:__start_SECNAME
和__stop_SECNAME
表示該section的起始和結束位址。而SECNAME
就是該孤兒section名稱。
前面有提過.
這個符號是location counter。而location counter本身的涵意就是目前輸出位置。而.
可以出現在SECTIONS
命令中的任何expression。
除了使用.
來代表目前輸出位置外,你也可以直接更改.
的數值,這樣做就是更動目前輸出位置。不過要注意的是不要用做減法運算,這樣代表把目前位置往前移動,往前移動表示接下來寫入的東西就很有可能蓋掉前面重疊部份的資料。另外你可以直接把.
的值加上你要的數量,那麼下一個symbol或是section就會和目前位置有一段保留空間可以使用。我們看下面的範例:
SECTIONS
{
output :
{
file1(.text)
. = . + 1000;
file2(.text)
. += 1000;
file3(.text)
} = 0x12345678;
}
這個範例我們可以看到
output
output
section裡面存放了
file1
的.text
sectionfile2
的.text
sectionfile3
的.text
sectionfile1
的.text
section和file2
的.text
section中間相距1000file2
的.text
section和file3
的.text
section中間也相距10000x12345678
,想要劇情回顧的請看這邊.
雖然是location counter,然而在不同的區塊使用會有不同的意義。 先看一個例子
SECTIONS
{
. = 0x100
.text: {
*(.text)
. = 0x200
}
. = 0x500
.data: {
*(.data)
. += 0x600
}
}
我們可以看到.
出現的地方有四個地方,在.text
和.data
以內有各有一個,不在.text
和.data
以內有兩個。也就是說一種是在section描述(就是.text
和.data
)之內,另外就是section描述之外。
.
放在section描述裡面的話它的location是從section開頭開始算.
放在section描述外面的話它的location是從0有了這樣的概念後,我們再回去看這範例在講三小?
.text
和.data
.text
section請放到.text
中.data
section請放到.data
中.text
section 起始點為0x100.
被設成0x200,以致於輸入object檔案的.text
存放超過0x100 + 0x200的空間都有可能被後面的資料複寫掉。.text
section 結束後,請保留0x500的空間.text
section 最後0x500的位置為.data
section的起始點.data
section放入輸出object的.data
section後,再從section中目前位置保留0x600的空間。手冊中有特別提到在section描述外面使用.
需要特別注意的地方,它舉的例子如下:
SECTIONS
{
start_of_text = . ;
.text: { *(.text) }
end_of_text = . ;
start_of_data = . ;
.data: { *(.data) }
end_of_data = . ;
}
這個範例在.data
和text
前後都加了一個symbol,數值為當時location counter的位置。看起來一切安好,然而如果輸入object檔案中有section不是.data
也不是.text
,例如放"Hello world\n"的.rodata
([參考資料](http://www.lisha.ufsc.br/teaching/os/exercise/hello.html 你知道你寫的"Hello world"放在什麼地方嘛?)),linker還是要把這些section放到輸出object檔案中。你覺得他會放在邊呢?手冊上說linker script中的symbol會被視為接在前一個section 後面,所以最後就會變成這樣:
SECTIONS
{
start_of_text = . ;
.text: { *(.text) }
end_of_text = . ;
start_of_data = . ;
.rodata: { *(.rodata) }
.data: { *(.data) }
end_of_data = . ;
}
如此一來,如果你以為start_of_data
就是.data
開始位址,在你的程式中拿來做事,保證GG。因為start_of_data
現在變成.rodata
的起始位址了。
要確保start_of_data
一定在.data
section前面的話,正確的做法是在start_of_data = . ;
前面加上. = .
強迫更新location counter,列出完整script如下:
SECTIONS
{
start_of_text = . ;
.text: { *(.text) }
end_of_text = . ;
. = . ;
start_of_data = . ;
.data: { *(.data) }
end_of_data = . ;
}
和C相容,請自己看,反正沒多少英文。
<a name="expr-eval >
原來標題是「Evaluation」直接翻成評估,估算都很詭異。自作主張就是計算結果,反正這個章節就是在講這回事。
linker很懶惰,所以不到要用的時候就不會去算expression的結果。以下是他的計算順序
.
相關的expression同要等該section link完畢才可能計算由於有這樣的先後關係,如果你的script沒寫好可能就會遇到時間順序不同造成需要的expression裡面的element還沒計算完畢,然後你的linker就會噴錯誤出來。
手冊的範例看不懂,不解釋了。
** 這邊我不是很確定我有理解正確,請自行斟酌。另外看完後感覺上relative/absolute symbol和relative/absolute address是相同的東西,但是手冊上又沒有明講。所以我這邊語法會有點混亂。 **
這邊要先定義兩個名詞才能理解這個section在講三小。列出如下
這兩個東西都是在講輸出object檔案的SECTIONS
命令中的symbol或address。而這些symbol或 address可能宣告在section
的裡面或外面。知道這樣的前提後,我們可以開始定義:
知道的這樣定義後,我們可以再問,然後呢?
然後有相對特性的在relocate的時候只要更動section數值就好,而寫死的就沒有辦法動手腳。所以, relative symbol可以relocate而absolute symbol不行。
還不是很瞭長怎麼樣嘛?先看手冊的的範例好了:
SECTIONS
{
. = 0x100;
__executable_start = 0x100;
.data :
{
. = 0x10;
__data_start = 0x10;
*(.data)
}
...
}
我們可以看到
__executable_start
為0x100,這是一個absolute address/symbol.data
的真正資料開始位置距離.data
位置0x10,__data_start
也是相同。這兩個是relative address/symbol好啦,知道這兩個關係後,我們再回來討論計算symbol值的expression。由於linker script 命令處理回來的值有些是relative有些是absolution,所以在寫script的時候要注意。手冊描述的地方目前看不懂,懶得搞懂。不論如何,手冊提供了linker處理expresssion時對於absolute 和 relative的行為準則。
sub-expression(就是express裡面合法的express如a+b-c,可以拆成a+b,他的結果再跟c相加,而a+b就是一個sub-expression),的處理absolute/relative規範如下:
如果有需要,你可以使用ABSOLUTE()
命令強迫section裡面的symbol值為absolutio,範例如下。
SECTIONS
{
.data : { *(.data) _edata = ABSOLUTE(.); }
}
_edata
沒用ABSOLUTE()
命令的話會是一個relative symbol,因為加了ABSOLUTE()
命令所以linker把他視為absolution symbol。
ABSOLUTE(expr)
ADDR(section)
前面兩個命令可以用下面範例說明
SECTIONS
{
...
.output1 :
{
start_of_output_1 = ABSOLUTE(.);
...
}
.output :
{
symbol_1 = ADDR(.output1);
symbol_2 = start_of_output_1;
...
}
...
}
這邊我們可以看到: start_of_output_1
,symbol_1
, 和symbol_2
的值理論上是相同的。但是性質上symbol_1
是relative,而其他兩個symbol是absolute。
ALIGN(align)
ALIGN(exp,align)
先講ALIGN(exp,align)
,這個命令是計算expr後,回傳align
位址後面第一個符合alignment的位址。而ALIGN(align)
可以視為ALIGN(., align)
,也就是說這個命令會回傳.
的後面符合alignment的位址。手冊提供的範例如下,因為很容易望文生義,就不解釋了:SECTIONS
{
...
.data ALIGN(0x2000):
{
*(.data)
variable = ALIGN(0x8000);
}
...
}
ALIGNOF(section名稱)
取得section
後面符號alignment的位置。要注意的是section要已經被分配出來,否則linker會噴錯誤給你看。範例一樣容易望文生義,不解釋。SECTIONS
{
...
.output
{
LONG (ALIGNOF (.output))
...
}
...
}
BLOCK(exp)
和ALIGN()
相同。是舊版的linker使用的命令。
DATA_SEGMENT_ALIGN(maxpagesize, commonpagesize)
DATA_SEGMENT_END(exp)
DATA_SEGMENT_RELRO_END(offset, exp)
看不懂,不想弄懂。跳過。
DEFINED(symbol)
如果symbol已經被收進symbol table就回傳1,否則回傳0。手冊示範如果沒定義該symbol就自己生一個如下。SECTIONS
{
...
.text :
{
begin = DEFINED(begin) ? begin : . ;
...
}
...
}
LENGTH(region)
回傳你在MEMORY
命令中設定的region size
LOADADDR(section名稱)
回傳section的名稱的LMA位址
LOG2CEIL(exp)
取exp的log,不知道用在啥子地方。
MAX(exp1, exp2)
回傳exp1和exp2比較大的數值
MIN(exp1, exp2)
回傳exp1和exp2比較小的數值
NEXT(exp)
回傳exp計算結果的數值記憶體之後的可使用的空間。如果沒有使用MEMORY
命令設定不連續的空間,這指令效果和ALIGN
命令相同。
ORIGIN(region名稱)
回傳你在MEMORY
命令設定的region的起始位址
SEGMENT_START(segment名稱, default)
回傳segment
的起始位置。還記得ELF program header的segment?我不知道和這個是不是相同。default
除非有透過ld -T
參數更動,否則就是預設值。手冊沒有寫預設值是多少。但是從ld --verbose看到的使用範例是用在指定程式碼開始執行的地方。有沒有覺得0x400000很眼熟呢?不熟?那算了。
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
. = SEGMENT_START("ldata-segment", .);
SIZEOF(section名稱)
回傳section的size,如果該section還沒被分配,linker就吐錯誤給你看。 下面的例子中的symbol_1
和symbol_2
的值是相同的。這蠻容易理解,我列出來主要是要讓大家多看例子。.start
, .end
的用法我看過好幾次。SECTIONS
{
...
.output
{
.start = . ;
...
.end = . ;
}
symbol_1 = .end - .start ;
symbol_2 = SIZEOF(.output);
...
}
SIZEOF_HEADERS
sizeof_headers
取得輸出object檔案的header size。如果你使用ELF格式,又有自行加programer header的話,ld會噴錯誤。原因是ld預期的是ELF規範的program header,因此放不下新增的program header。所以你有多的program header的話,請不要用這個指令。linker吃的檔案處理順序如下
1 object檔,開始link 2 不是object檔,就當linker script吃進去 3 不是object檔案也不是linker script檔案,噴錯誤然後 GG
所以,Implicit Linker Script指的是項目2吃進來的script。linker會把這個當作目前linker script的補強,而不是取代。另外由於吃進來的script順序不同,可能會出現先讀入並link 三個object檔案後,才讀到Implicit Linker Script,所以這個Implicit Linker Script無法對已經link處理。