|
我們在用RISC-V GCC做嵌入式開發(fā)的時候,免不了要和啟動文件和鏈接文件等打交道,本篇文章記錄了一些鏈接腳本相關(guān)的學習筆記。
1.基礎(chǔ)概念
鏈接腳本的主要作用是描述輸入文件中的段應(yīng)當如何映射到輸出文件中,并控制輸出文件的內(nèi)存布局。多數(shù)鏈接腳本都執(zhí)行類似功能。但是,如果需要,鏈接腳本也可以使用下面所描述的命令指揮鏈接器進行很多其他操作。
鏈接器通常使用一個鏈接腳本。如果沒有為其提供一個,鏈接器將會使用默認的編譯在鏈接器執(zhí)行文件內(nèi)部的腳本。可以使用命令’–verbose’顯示默認的鏈接腳本。
為了描述鏈接腳本語言,我們需要定義一些基本概念和詞匯。
鏈接器將許多輸入文件組合成一個輸出文件。輸出文件和每個輸入文件都有一個特定的已知格式成為目標文件格式。每個文件都被稱為目標文件。輸出文件通常叫做可執(zhí)行文件,但我們?nèi)詫⑵浞Q為目標文件。每個目標文件在其他東西之間,都有一個段列表。有時把輸入文件的段稱作輸入段,類似的,輸出文件的段稱作輸出段。
每個目標文件中的段都有名字和大小。多數(shù)段還有一個相關(guān)的數(shù)據(jù)塊,稱為 段內(nèi)容。一個段可能被標記為可加載,表示當輸出文件運行時,段內(nèi)容需要先加載到內(nèi)存中。一個沒有內(nèi)容的段可能是可分配段,即在內(nèi)存中留出一段空間(有時還需要清零)。一個即不是加載又不是可分配的段,通常含有一些調(diào)試信息。
每個加載或可分配輸出段有兩個地址。第一個地址為VMA,或者叫做虛地址。這是當輸出文件運行時段所擁有的地址。第二個地址是LMA,或者叫加載內(nèi)存地址。這是段將會被加載的地址。一個它們會產(chǎn)生區(qū)別的例子是,當一個數(shù)據(jù)段加載到ROM, 此后在程序啟動時被復制到RAM中(這個技術(shù)通常被用來初始化全局變量)。此種情況下,ROM使用LMA地址,RAM使用VMA地址。
如果想查看目標文件中的段,可以用objdump程序的’-h’選項。
每個目標文件還有一個符號列表,稱為符號列表。一個符號可能是被定義的或者未定義的。每個符號都有一個名字,且所有已定義的符號在其他信息中間都有一個地址。如果將一個c或者c++程序編譯成目標文件,會將所有定義過的函數(shù)和全局變量以及靜態(tài)變量作為已定義符號。所有輸入文件引用的未定義的函數(shù)或者全局變量會成為未定義符號。
2.常用關(guān)鍵詞與用法
ENTRY(symbol) 用來指定程序執(zhí)行的入口點
MEMORY 內(nèi)存分配命令
SECTIONS 段命令 描述輸出文件的內(nèi)存和布局
.text 程序代碼段
.rodata 只讀數(shù)據(jù)
.data 可讀寫且需要初始化的數(shù)據(jù)
.bss 可讀寫的清零初始化數(shù)據(jù)
ASSERT 斷言
PROVIDE(symbol=expression) 定義一個符號
AT 后跟MEMORY定義的內(nèi)存區(qū)域或者地址
ALIGN 字節(jié)對齊
3 . MEMORY
鏈接器默認的設(shè)置允許分配所有可用的內(nèi)存。你通過MEMORY命令可以重載這些。
MEMORY命令描述了一個內(nèi)存塊在目標中的位置和大小。你可以使用它描述一個可能會在鏈接器中使用的內(nèi)存區(qū)域,以及那些必須避免使用的內(nèi)存區(qū)域。此后你可以把段放到特定的內(nèi)存區(qū)域里。鏈接器將會基于內(nèi)存區(qū)域設(shè)置段地址,如果區(qū)域趨于飽和將會產(chǎn)生警告信息。鏈接器不會為了把段更好的放入內(nèi)存區(qū)域而打亂段的順序。
一個鏈接腳本可能含有許多MEMORY命令,但是,所有定義的內(nèi)存塊都被當作他們是在一個MEMORY命令中定義的一樣。MEMORY的語法是:
MEMORY
{
name [(attr)] : ORIGIN = origin, LENGTH =len
...
}
name是鏈接腳本用來引用內(nèi)存區(qū)域的名字。區(qū)域名在鏈接腳本外部沒有任何意義。區(qū)域名被存儲在一個獨立的名字空間,且不會與符號名,文件名,或者段名起沖突。每個內(nèi)存區(qū)域必須在MEMORY命令中有一個不同的名字。但是你此后可以使用REGION_ALIAS命令為已存在的內(nèi)存區(qū)域添加別名。
attr字符是一個可選的屬性列表,用來決定是否讓一個腳本中沒有顯式指定映射的輸入段使用一個特定的內(nèi)存區(qū)域。就像SECTIONS中進行過的說明,如果你不為一個輸入段指定一個輸出段,鏈接器將會創(chuàng)建一個與輸入段名字相同的輸出段。如果你定義了區(qū)域?qū)傩裕溄悠鲿褂盟麄儊頉Q定創(chuàng)建的輸出段存放的內(nèi)存區(qū)域。
attr字符串只能使用下面的字符組成:
‘R’只讀段
‘W’讀寫段
‘X’可執(zhí)行段
‘A’可分配段
‘I’已初始化段
‘L’類似于’I’
‘!’反轉(zhuǎn)其后面的所有屬性
如果一個未映射段匹配了上面除’!’之外的一個屬性,它就會被放入該內(nèi)存區(qū)域。’!’屬性對該測試取反,所以只有當它不匹配上面列出的行何屬性時,一個未映射段才會被放入到內(nèi)存區(qū)域。
origin是一個數(shù)字表達式,代表了內(nèi)存區(qū)域的起始地址。表達式必須等價于一個常數(shù)并且不能含有任何符號。關(guān)鍵字ORIGIN縮短為org或者o(但不能寫成ORG)。
len是一個表達式用來給出內(nèi)存區(qū)域中的字節(jié)數(shù)大小。類似于origin表達式,表達式必須只能為數(shù)字的切必須求值為常數(shù)。關(guān)鍵字LENGTH可以被縮寫為len或者l。
下面的例子里,我們制定了有兩個可分配的內(nèi)存區(qū)域:一個從’0’開始有256k字節(jié),另一個從’0x40000000’開始,由4兆字節(jié)。鏈接器把所有沒有顯式映射到一個內(nèi)存區(qū)域的段放到’rom’內(nèi)存區(qū)域內(nèi),段可以是只讀的或者可執(zhí)行的。鏈接器將把其它沒顯式指定內(nèi)存區(qū)域映射的段放到’ram’內(nèi)存區(qū)域。
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0x40000000, l = 4M
}
一旦你定義了一個內(nèi)存區(qū)域,你可以使用’>region’輸出段屬性指引鏈接器把特殊輸出段放到該內(nèi)存區(qū)域。例如,如果你擁有一個內(nèi)存區(qū)域名為’mem’,你可以在輸出段定義中使用’>mem’。參考Output Section Region。如果沒有給輸出段指出地址,鏈接器將會把地址放到最先符合要求的內(nèi)存區(qū)域中的可用地址。如果指引給一個內(nèi)存區(qū)域的組合輸出段比區(qū)域還大,鏈接器將會提交錯誤。
可以通過ORIGIN(memory)和LENGTH(memory)函數(shù)獲得內(nèi)存區(qū)域的起始地址以及長度:
_fstack = ORIGIN(ram) + LENGTH(ram) - 4;
4. 段描述
4.1輸出段
完整的輸出段描述如下
section [address] [(type)] :
[AT(lma)]
[ALIGN(section_align) | ALIGN_WITH_INPUT]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr:phdr ...] [=fillexp] [,]
地址(address)是一個輸出段VMA(虛地址)的表達式。此地址為可選參數(shù),但如果給出了地址,則輸出地址就會被精確的設(shè)置到給定值。
如果輸出的地址沒有給定,則依照下面的嘗試選擇一個地址。此地址將會被調(diào)整到符合輸出端要求的對齊地址。輸出段的對齊要求是所有輸入節(jié)中含有的對齊要求中最嚴格的一個。
輸出段地址探索如下:
如果為段設(shè)置了內(nèi)存區(qū)域,則段被放如該區(qū)域,并且段地址為區(qū)域中的下一個空閑位置。
如果使用MEMORY命令創(chuàng)建了一個內(nèi)存區(qū)域列表,此時第一個屬性匹配段的區(qū)域被選擇來加載段,段地址為區(qū)域中的下一個空閑位置。參見MEMORY。
如果沒有指定的內(nèi)存區(qū)域,或者沒有匹配段的,則輸出地址將會基于當前位置計數(shù)器的值
4.2輸入段
輸入段存在于輸出段的內(nèi)容中,用來指定不同輸入段在輸出段中的位置,常見的有.text .data .rodat .bss COMMOM等,一個輸入段描述由跟隨在段名稱后面括號包含的一個可選的文件名稱列表構(gòu)成。也可以使用通配符,例如
*main.o(.text)或者直接*(.text)
前一個代表main.o 文件中所有.text段,后一個代表所有參與鏈接文件中的.text段,當然也可以排除一些文件
EXCLUDE_FILE (*文件名.o) *(.text)
5. 一些內(nèi)建函數(shù)
ABSOLUTE(exp)
返回表達式exp的絕對(非可重分配的,而不是非負)值。主要用來在段定義內(nèi)為符號分配一個絕對值,通常段定義內(nèi)的符號值都是相對段地址的。
ADDR(section)
返回名為’section’的段的地址(VMA)。你的腳本必須事先未該段定義了位置。在下面的例子里,start_of_output_1, symbol_1, symbol_2分配了同樣的值,除了symbol_1為與段.output1相關(guān)的值而其他兩個為絕對值:
SECTIONS { ...
.output1 :
{
start_of_output_1 = ABSOLUTE(.);
...
}
.output :
{
symbol_1 =ADDR(.output1);
symbol_2 =start_of_output_1;
}
... }
LENGTH(memory)
返回名為memory的內(nèi)存的長度。
MAX(exp1, exp2)
返回exp1和exp2最大的
MIN(exp1, exp2)
返回exp1和exp2最小的。
ORIGIN(memory)
返回名為memory的內(nèi)存區(qū)域的起始地址。
SIZEOF(section)
返回名為section段的字節(jié)數(shù)。如果段還沒被分配就是用函數(shù)求值,將會產(chǎn)生錯誤。
|
|