系列文章目錄
官方 Linker Scripts 語法和規則解析(1)
官方 Linker Scripts 語法和規則解析(2)
官方 Linker Scripts 語法和規則解析(3)
鏈接腳本(Linker Scripts)語法和規則解析(自官方手冊)
7.9. 鏈接腳本
鏈接腳本的作用及格式
程序員的自我修養:鏈接、裝載與庫閱讀
Linux Section
Gcc 鏈接文件
文章目錄
- 系列文章目錄
- 3 Linker Scripts
- 3.6 SECTIONS Command
- 3.6.1 Output Section Description
- 3.6.2 Output Section Name
- 3.6.3 Output Section Address
- 3.6.4 Input Section Description
- 3.6.4.1 Input Section Basics
- 3.6.4.2 Input Section Wildcard Patterns
- 3.6.4.3 Input Section for Common Symbols
- 3.6.4.4 Input Section and Garbage Collection
- 3.6.4.5 Input Section Example
- 3.6.5 Output Section Data
- 3.6.6 Output Section Keywords
- 3.6.7 Output Section Discarding
- 3.6.8 Output Section Attributes
- 3.6.8.1 Output Section Type
- 3.6.8.2 Output Section LMA
- 3.6.8.3 Forced Output Alignment
- 3.6.8.4 Forced Input Alignment
- 3.6.8.5 Output Section Constraint
- 3.6.8.6 Output Section Region
- 3.6.8.7 Output Section Phdr
- 3.6.8.8 Output Section Fill
- 3.6.9 Overlay Description
? ? 為了便于與英文原文對照學習與理解(部分翻譯可能不準確),本文中的每個子章節標題和引用使用的都是官方手冊英文原稱。命令及命令行選項統一使用斜體書寫。高頻小節會用藍色字體標出。
3 Linker Scripts
Basic Script Concepts
: 鏈接器腳本的基本概念Script Format
: 鏈接器腳本的格式Simple Example
: 簡單的鏈接器腳本例子Simple Commands
: 簡單的鏈接器腳本命令Assignments
: 為符號指定數值SECTIONS
: 段命令MEMORY
: 內存命令PHDRS
: PHDRS 命令VERSION
: 版本命令Expressions
: 鏈接腳本的表達式Implicit Linker Scripts
: 隱式鏈接腳本
3.6 SECTIONS Command
? ?SECTIONS
命令告訴鏈接器如何將輸入段映射到輸出段,以及如何將輸出段放在內存中。
SECTIONS 命令的格式為:
SECTIONS
{sections-commandsections-command…
}
? ?每個 sections-command
命令可能是下面之一:
- ENTRY 命令(請參閱 Entry command)
- 符號賦值(請參閱 Assigning Values to Symbols)
- 輸出段的描述
- overlay 描述
? ?為了方便在這些命令中使用位置計數器,在 SECTIONS
命令中允許使用 ENTRY
命令和符號賦值。 這也可以使鏈接描述文件更容易理解,因為你可以在更有意義的地方使用這些命令來控制輸出文件的布局。
輸出段的描述
和 覆蓋描述
在下面將會分析。
? ?如果在鏈接腳本中未使用 SECTIONS
命令,則鏈接器將會照輸入文本的順序,將每個輸入部段放置到名稱相同的輸出段中。例如,如果所有輸入段出現在第一個文件中,輸出文件的段的順序將會與第一個輸入文件保持一致。第一個段被放在地址 0 。
Output Section Description
輸出段描述Output Section Name
輸出段名稱Output Section Address
輸出段地址Input Section
輸入段描述Output Section Data
輸出段數據Output Section Keywords
輸出段關鍵字Output Section Discarding
輸出段忽略的內容Output Section Attributes
輸出段屬性Overlay Description
Overlay description
3.6.1 Output Section Description
輸出段的完整描述如下所示:
section [address] [(type)] :[AT(lma)][ALIGN(section_align) | ALIGN_WITH_INPUT][SUBALIGN(subsection_align)][constraint]{output-section-commandoutput-section-command...} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] [,]
大部分的可選段屬性在多數輸出段不需要使用。
? ?SECTION
邊上的空格是必須的,這樣段名就沒有歧義了。冒號和花括號也是必需的。如果使用了 fillexp
,并且下一個 section-command 看起來像是表達式的延續,則可能需要在末尾使用逗號。換行符和其他空格是可選的。
? ?當 fillexp 使用且接下來的 sections-command 看起來像是表達式的延續的時候,可能需要在后面加上逗號。
? ?每個 output-section-command
可以是下列命令之一:
- 符號賦的值(參見 Assigning Values to Symbols)
- 輸入段描述(參見 Input Section Description)
- 直接包引用的數據值(參見 Output Section Data)
- 特殊的輸出段關鍵字(參見 Output Section Keywords)
3.6.2 Output Section Name
? ?輸出段的名字是 section
。section 必須滿足輸出格式的規定。在只支持有限段數目的格式中,例如 a.out ,名稱必須是該格式所支持的名稱之一(例如 a.out ,只允許 ‘.text’,‘.data’,‘.bss’)。如果輸出格式支持任意數量的段,但是只有數字而不是名稱(Oasys 就是這種情況),則名稱應該以帶引號的數字字符串的形式提供。一個段的名字可以由任意字符序列組成,但一個含有許多特殊字符(如逗號)的名稱必須用引號括起來。
? ?名稱為 ‘/DISCARD/’ 的輸出段 ,有特殊含義; 參考 Output Section Discarding 。
3.6.3 Output Section Address
? ?address
是輸出段 VMA
(虛擬內存地址)的表達式。此地址是可選參數,但如果提供了該地址,則輸出地址就會被精確的設置為指定的值。
? ?如果沒有指定輸出地址,那么則依照下面的幾種方式嘗試選擇一個地址。此地址將被調整以適應輸出段的對齊要求。輸出段的對齊要求是所有輸入節中含有的對齊要求中最嚴格的一個。
輸出段地址探索如下:
-
如果為該段設置了一個輸出內存區域,那么它將被添加到該區域中,其地址將是該區域中的下一個空閑地址。
-
如果使用
MEMORY
命令創建內存區域列表,那么將選擇具有與該段兼容屬性的第一個區域來包含該區域。該部分的輸出地址將是該區域中的下一個空閑地址;MEMORY Command 。 -
如果沒有指定內存區域,或者沒有與段匹配的內存區域,則輸出地址將基于位置計數器的當前值。
例如:
.text . : { *(.text) }
和
.text : { *(.text) }
? ?有著細微的不同。 第一個將 ‘.text’ 輸出段的地址設置為位置計數器的當前值。 第二個參數會將其設置為位置計數器的當前值,但是該值與所有 ‘.text’ 輸入段中最嚴格的對齊方式對齊。
? ?address
可以是任意表達式; 例如,如果要在 0x10 字節(16字節)邊界上對齊段,以使節地址的最低四位為零,則可以執行以下操作:
.text ALIGN(0x10) : { *(.text) }
? ?之所以這樣做,是因為 ALIGN
返回的當前位置計數器向上對齊到指定的值。
? ?為段指定地址將會改變位置計數器的值,前提是該段是非空的(空的段被忽略)。
3.6.4 Input Section Description
? ?最常見的輸出段命令(output-section-command)是輸入段描述(input section description
)。
? ?輸入段描述是鏈接腳本最基本的操作。 您可以使用輸出段來告訴鏈接器如何在內存中布置程序。 您可以使用輸入段描述來告訴鏈接器如何將輸入文件映射到您的內存布局中。
Input Section Basics
基本的輸入段Input Section Wildcards Patterns
輸入段通配符模板Input Section Common Symbols
普通符號的輸入段Input Section and Garbage Collection
輸入段與垃圾回收Input Section Example
輸入段例子
3.6.4.1 Input Section Basics
輸入段說明由一個文件名和一個括號中的段名列表(可選)組成。
文件名和段名可以是通配符,我們將在下面進一步描述(請參閱 Input Section Wildcards)。
最常見的輸入段描述是在輸出段中包括所有具有特定名稱的輸入段。 例如,把所有輸入段放入 ‘.text’ 段,可以這么寫:
*(.text)
? ?這里的 '*'
是一個通配符,它可以用來匹配任何文件名。要排除與文件名通配符匹配的文件列表,可以使用 EXCLUDE_FILE
來匹配除 EXCLUDE_FILE 列表中指定的文件以外的所有文件。例如:
EXCLUDE_FILE (*crtend.o *otherfile.o) *(.ctors)
? ?將導致包括除 crtend.o
和 otherfile.o
以外的所有文件的所有 .ctors
段。EXCLUDE_FILE 也可以放在段的列表中,例如:
*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)
? ?其結果與前面的示例相同。如果段列表包含多個段,則支持 EXCLUDE_FILE 的兩個語法非常有用,如下所述。
有兩種方法可以包含多個段:
*(.text .rdata)
*(.text) *(.rdata)
? ?兩種方法的區別是輸入段的 '.text'
和 '.rdata'
段出現在輸出段中的順序。第一個例子里,他們將被混合在一起,按照鏈接器找到它們的順序存放。另一個例子中,所有 ‘.text’ 輸入段將會先出現,后面是 ‘.rdata’ 輸入段。
? ?將 EXCLUDE_FILE 與多個段一起使用時,這個排除命令僅僅對緊隨其后的段有效,例如:
*(EXCLUDE_FILE (*somefile.o) .text .rdata)
? ?將導致包含除 somefile.o 以外的所有文件的所有 ‘.text’ 段,而包括 somefile.o 在內的所有文件的所有 ‘.rdata’ 段都將被包含。要從 somefile.o 中排除 ‘.rdata’ 段部分,可以將示例修改為:
*(EXCLUDE_FILE (*somefile.o) .text EXCLUDE_FILE (*somefile.o) .rdata)
? ?或者,將 EXCLUDE_FILE
放在段列表之外(在選擇輸入文件之前),將導致排除操作對所有段有效。因此,前一示例可以重寫為:
EXCLUDE_FILE (*somefile.o) *(.text .rdata)
? ?你可以指定一個文件名來包含特定文件的段。如果一個或者多個你的文件需要被放在內存中的特定位置,你可能需要這么做。例如:
data.o(.data)
如果想使用段標志來選擇輸入文件的段,可以使用 INPUT_SECTION_FLAGS
。
下面是一個為 ELF 段使用段頭標志的簡單示例:
SECTIONS {.text : { INPUT_SECTION_FLAGS (SHF_MERGE & SHF_STRINGS) *(.text) }.text2 : { INPUT_SECTION_FLAGS (!SHF_WRITE) *(.text) }
}
? ?在本例中,輸出段 ‘.text’ 將被由那些與 *(.text)
能匹配的段(名字)且段頭部標志設置了SHF_MERGE 和 SHF_STRINGS 的段構成。輸出段 ‘.text2’ 由那些與 *(.text)
能匹配的段(名字)且段頭部標志未設置 SHF_WRITE 的段構成。
? ?你也可以指出特別的關聯庫名稱的文件,命令是[ 庫匹配模板:與文件匹配的模式 ]
,冒號兩邊不能有空格。
'archive:file'
在庫中尋找能夠匹配的文件'archive:'
匹配整個庫':file'
匹配文件但不匹配庫
? ?‘archive’ 和 ‘file’ 中的一個或兩個都可以包含 shell 通配符。在基于 DOS 的文件系統上,鏈接器會假定一個單字跟著一個冒號是一個特殊的驅動符,因此 ‘c:myfile.o’ 是一個文件的特殊使用,而不是關聯庫 ‘c’ 的 ‘myfile.o’ 文件。‘archive:file’:可以使用在 EXCLUDE_FILE 列表中,但不能出現在其他鏈接腳本內部。例如,你不能使用 ‘archive:file’ 從 INPUT 命令中取出一個庫相關的文件。
? ?如果你使用一個文件名而不指出段列表,則所有的輸入文件的段將被放入輸出段。通常不會這么做,但有些場合比較有用,例如:
data.o
? ?當你使用一個文件名且不是 ‘archive:file’ 特殊命令,并且不含任何通配符,鏈接器將先查看你是否在命令行上或者在 INPUT 命令里指定了該文件。如果沒有這么做,鏈接器嘗試將文件當作輸入文件打開,就像文件出現在了命令行一樣。注意與 INPUT 命令有區別,因為鏈接器不會在庫文件路徑搜索文件。
3.6.4.2 Input Section Wildcard Patterns
在輸入段描述中,文件名和段名都可以使用通配符模式。
在許多示例中看到的文件名 ‘*’
是一個簡單的文件名通配符模式。
通配符模式類似于 Unix shell 使用的那些模式。
'*'
匹配任意數量字符'?'
匹配任意單字'[chars]'
匹配任何字符的單個實例;'-'
字符可被用來指出一個字符的范圍,例如 ‘[a-z]’ 可以用來匹配所有小寫字母'\'
引用后面的字符
? ?當文件名與通配符匹配時,通配符將不匹配 '/'
字符(在 Unix 上用于分隔目錄名)。由單個 '*'
字符組成的模式是除外;它將始終匹配任何文件名,無論它是否包含 ‘/’ 。在段名稱中,通配符將匹配 ‘/’ 字符。
? ?文件名通配符模式只匹配在命令行或輸入命令中顯式指定的文件。鏈接器不會搜索目錄以擴展通配符。
? ?如果一個文件名匹配多個通配符,或者一個文件名被顯示指定了,且又被通配符匹配了,則鏈接器將使用鏈接器腳本中的第一個匹配項。例如,下面的輸入段描述可能有錯誤,因為 data.o 的規則不會被應用:
.data : { *(.data) }
.data1 : { data.o(.data) }
? ?通常情況下,鏈接器將按照鏈接過程中出現通配符的順序放置文件和段。您可以通過使用 SORT_BY_NAME
關鍵字來更改此行為,該關鍵字出現在括號中的通配符模式之前(例如,SORT_BY_NAME(.text*))。當使用 SORT_BY_NAME 關鍵字時,鏈接器將按名稱按升序對文件或段進行排序,然后將它們放入輸出文件中。
? ?SORT_BY_ALIGNMENT
對齊方式類似于 SORT_BY_NAME 。SORT_BY_ALIGNMENT 將在將段放入輸出文件之前,按對齊方式的降序對段進行排序。大的對齊被放在小的對齊前面可以減少所需的填充量。
? ?SORT_BY_INIT_PRIORITY
與 SORT_BY_NAME 相似,區別是 SORT_BY_INIT_PRIORITY 把段按照 GCC 的嵌入在段名稱的 init_priority 數字屬性值升序排列后放入輸出文件。.init_array.NNNNN 和 .fini_array.NNNNN, NNNNN 是 init_priority 。 .ctors.NNNNN 和 .dtors.NNNNN, NNNNN 是65535減去 init_priority 。
? ?SORT
是 SORT_BY_NAME
的別名。
? ?當鏈接器腳本中有嵌套的段排序命令時,段排序命令最多可以有1個嵌套級別。
SORT_BY_NAME (SORT_BY_ALIGNMENT (wildcard section pattern))
。它將首先按名稱對輸入部分進行排序,如果兩個部分同名,則按對齊方式排序。SORT_BY_ALIGNMENT (SORT_BY_NAME (wildcard section pattern))
。它將首先按對齊方式對輸入段進行排序,如果兩個段具有相同的對齊方式,則按名稱排序。SORT_BY_NAME (SORT_BY_NAME (wildcard section pattern))
與SORT_BY_NAME (wildcard section pattern)
相同。SORT_BY_ALIGNMENT (SORT_BY_ALIGNMENT (wildcard section pattern))
與SORT_BY_ALIGNMENT (wildcard section pattern)
相同。- 除此之外,其它所有嵌套段排序命令都是無效的。
? ?當同時使用命令行段排序選項和鏈接器腳本段排序命令時,段排序命令總是優先于命令行選項。
? ?如果鏈接器腳本中的段排序命令不是嵌套的,那么命令行選項將使段排序命令被視為嵌套的排序命令。
SORT_BY_NAME (wildcard section pattern )
與–sort-sections alignment
連用等價于SORT_BY_NAME (SORT_BY_ALIGNMENT (wildcard section pattern))
。SORT_BY_ALIGNMENT (wildcard section pattern)
與–sort-section name
連用等價于
SORT_BY_ALIGNMENT (SORT_BY_NAME (wildcard section pattern))
。
如果鏈接器腳本中的段排序命令是嵌套的,那么命令行選項將被忽略。
SORT_NONE
通過忽略命令行部段排序選項來禁用段排序。
? ?如果您對輸入段的去向感到困惑, 可以使用 ‘-M’ 鏈接器選項來生成映射文件 。映射文件精確地顯示了如何將輸入段映射到輸出段。
? ?下面這個示例展示了通配符如何被用來分隔文件。這個鏈接腳本指引鏈接器把所有 ‘.text’ 段放在 '.text'
里,以及所有 ‘.bss’ 段放到 '.bss'
中。鏈接器將會把所有以大寫字母開頭的文件的 ‘.data’ 段放入 '.DATA'
,其他文件的 ‘.data’ 段放入 '.data'
。
SECTIONS {.text : { *(.text) }.DATA : { [A-Z]*(.data) }.data : { *(.data) }.bss : { *(.bss) }
}
3.6.4.3 Input Section for Common Symbols
? ?普通符號需要一個特別的標記,因為很多目標文件格式中沒有特定的普通符號輸入段。鏈接器把普通符號當作位于一個名為 'COMMON'
的輸入段中。
? ?像使用其它文件名與段一樣,你也可以使用文件名與 ‘COMMON’ 段的組合。通過這種方法把一個特定文件的普通符號放入一個段內,同時把其它輸入文件的普通符號放入另一個段內。
? ?大多數情況下,輸入文件的普通符號會被放到輸出文件的 ‘.bss’ 段里面。例如:
.bss { *(.bss) *(COMMON) }
? ?有些目標文件格式含有多種普通符號的類型。例如,MIPS EL F目標文件把標準普通符號和小型普通符號區分開來。在這種情況下,鏈接器會為另一個類型的普通符號使用其它的特殊段名稱。在 MIPS ELF 中,鏈接器為普通符號使用 ‘COMMON’ 以及為小型普通符號使用 ‘.scommon’ 。這樣就可以把不同類型的普通符號映射到內存中的不同位置。
? ?有時在老的鏈接腳本中能看見 ‘[COMMON]’ 。這個標記現在已廢棄。它等價于 ‘*(COMMON)’ 。
3.6.4.4 Input Section and Garbage Collection
? ?使用了鏈接時垃圾收集('–gc-sections'
)的功能,在把段標記為不應被消除非常常用。此功能通過把一個輸入段的通配符入口使用 KEEP() 實現,類似于 KEEP((.init))
或 KEEP(SORT_BY_NAME()(.ctors))
。
3.6.4.5 Input Section Example
? ?下面是一個完整的鏈接腳本的例子。它告訴鏈接器從 all.o 讀取所有段,把它們放到輸出段 'outputa'
的開頭位置,‘outputa’ 的起始地址為 ‘0x10000’ 。所有文件 foo.o 中的 ‘.input1’ 段緊跟其后。所有文件 foo.o 中的 ‘input2’ 段放入輸出文件的 'outputb'
中,跟著是 foo1.o 中的 ‘input1’ 段。所有其它的 '.input1” 和 ‘.input2’ 段被放入輸出段 'outputc'
。
SECTIONS {outputa 0x10000 :{all.ofoo.o (.input1)}outputb :{foo.o (.input2)foo1.o (.input1)}outputc :{*(.input1)*(.input2)}
}
? ?如果輸出段的名稱與輸入段的名稱相同,并且可以表示為 C 標識符,那么鏈接器將自動看到 PROVIDE
兩個符號:余下的 __start_SECNAME
和 __stop_SECNAME
,其中 SECNAME
是段的名稱。它們分別指示輸出段的開始地址和結束地址。注意:大多數段名不能表示為 C 標識符,因為它們包含 '.'
字符。
3.6.5 Output Section Data
? ?你可以通過使用輸出段命令 BYTE
, SHORT
, LONG
, QUAD
, 或者 SQUAD
在輸出段顯式的包含幾個字節的數據。每個關鍵字后面跟著一個括號包裹的表達式指出需要存儲的數值(參照 Expressions in Linker Scripts)。表達式的值被存儲在當前位置計數器值的地方。
? ?BYTE
, SHORT
, LONG
, QUAD
命令分別存儲 1,2,4,8 字節。在存儲字節后,位置計數器會按照存儲的字節數增加。
例如,下面將會存儲一個單字節數據 1,然后存儲一個符號為 ‘addr’ 四字節數據的值:
BYTE(1)
LONG(addr)
? ?當使用 64 位主機或目標時,QUAD
和 SQUAD
是相同的; 它們都存儲一個 8 字節或 64 位的值。主機和目標都是 32 位時,表達式被當作 32 位計算。在這種情況下 QUAD
存儲一個 32 位的值,并使用 0 擴展到 64 位,SQUAD
保存 32 位值并使用符號位擴展到 64 位。
? ?如果輸出文件的目標文件格式顯式的指定 endiannes,在正常的情況下,值將按照大小端存儲。當對象文件格式沒有顯式的指定 endianness,例如,S-records,值將被按照第一個輸入目標文件的大小端存儲。
? ?注意 —— 這些命令僅在段描述內部工作,因此下面的例子會使鏈接器產生錯誤:
SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
而下面這是可行的:
SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }
? ?您可以使用 FILL
命令設置當前段的填充模式。該命令后面跟著一個括號包裹的表達式。所有其它沒有被特別指定段的內存區域(例如因為對齊需要而留出來的縫隙)按照表達式的值填充,如果有必要可以重復填充。一個 FILL 語句僅會覆蓋它本身在段定義中出現的位置后面的所有內存區域;通過使用不同的 FILL 聲明,你可以在一個輸出段中使用不同的填充模板。
? ?這個例子顯示了如何使用 ‘0x90’ 填充未定義內存區域:
FILL(0x90909090)
? ?FILL 命令類似 '=fillexp'
輸出段屬性,但其僅影響 FILL 命令后面的段,而不是整個段。如果同時使用,FILL 命令為高優先級。參考 Output Section Fill 獲取更多填充細節。
3.6.6 Output Section Keywords
這里有兩個關鍵字可以作為輸出段的命令:
-
CREATE_OBJECT_SYMBOLS
? ?
? ?此命令告訴鏈接器為每個輸入文件創建一個符號。每個符號的名字為對應輸入文件的名字。每個符號出現的位置位于包含CREATE_OBJECT_SYMBOLS
命令的輸出段中。
? ?
這個命令常常是 a.out 目標文件格式特有的。 它一般不為其它的目標文件格式所使用。 -
CONSTRUCTORS
? ?
? ?當鏈接時使用 a.out 目標文件的格式,鏈接器使用一個特殊構造集來支持 C++ 全局構造函數和析構函數。在鏈接不支持任意段的文件格式時,例如 ECOFF 和 XCOFF ,鏈接器將會通過名字自動識別 C++ 全局構造函數和析構函數。對于這些格式的目標文件,CONSTRUCTORS
命令告訴鏈接器把構造函數信息放到出現CONSTRUCTORS
命令的輸出段中。其它文件格式中 CONSTRUCTORS 命令被忽略。
? ?
? ?符號__CTOR_LIST__
標記全局構造函數的開始,符號__CTOR_END__
標記結束。同樣的,__DTOR_LIST__
和__DTOR_END__
分別標記全局析構函數的開始和結束。第一個列表中的字是入口的數量,后面是每個構造函數或者析構函數的地址,最后是一個全零的字。編譯器必須安排實際運行代碼。對于這些目標文件格式,GNU C++ 通常從一個__main
子程序中調用構造函數,而對__main
的調用自動被插入到 main 的啟動代碼中。GNU C++ 通常使用atexit
運行析構函數,或者直接從函數 exit 中退出。
? ?
? ?對于 COFF 或者 ELF 等支持任意段名字的目標文件格式,GNU C++ 通常把全局構造函數和析構函數放入.ctors
和.dtors
段。把下面的代碼放入你的鏈接腳本,將會創建 GUN C++ 運行時期望的表。__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__ = .;
? ?如果你正在使用 GUN C++ 支持的初始化優先級,初始化優先級提供了一些對全局構造函數運行順序的控制,則你必須在鏈接時對構造函數排序以保證它們以正確的順序執行。當你使用
CONSTRUCTORS
命令,使用 ‘SORT_BY_NAME(CONSTRUCTORS)’ 替換它。當使用.ctors
和.dtors
段,使用 ‘(SORT_BY_NAME(.ctors))’ 和’ (SORT_BY_NAME(.dtors))’ 取代 ‘(.ctors)’ 和’ ‘(.dtors)’ 。
? ?通常編譯器和鏈接器會自動處理這些問題,您不需要關心它們。但是,在你自己寫鏈接腳本且正在使用 C++ 的時候,你可能需要考慮這些。
3.6.7 Output Section Discarding
? ?鏈接器通常不會創建沒有內容的輸出段。這是為了方便引用那些有可能出現或者不出現任何輸入文件中的段。例如:
.foo : { *(.foo) }
? ?只有在至少有一個輸入文件含有 '.foo'
段且 ‘.foo’ 段不為空的時候才會在輸出文件創建一個 '.foo'
段。其它鏈接腳本指出在一個段中間分配空間也會創建輸出段。賦值也一樣即使賦值沒有創建空間,除了 '. = 0'
, '. = . + 0'
, '. = sym'
, '. = . + sym'
和 '. = ALIGN (. != 0, expr, 1)'
其中 ‘sym’ 是一個值為 0 的已定義絕對符號。因此你可以強制一個空的輸出段使用 '. = .'
。
? ?鏈接器將忽略為丟棄的輸出段進行地址賦值(請參見 Output Section Address),除非鏈接器腳本在輸出段中定義符號。在這種情況下,鏈接器將遵守地址賦值,有可能更新 ‘.’ 的值,即便段被拋棄了。
? ?特殊輸出段名稱 '/DISCARD/'
可被用來拋棄輸入段。一個被分派到名為 ‘/DISCARD/’ 的輸出段的輸入段將不會被包含在輸出文件中。
3.6.8 Output Section Attributes
我們在前面展示了輸出部分的完整描述如下:
section [address] [(type)] :[AT(lma)][ALIGN(section_align) | ALIGN_WITH_INPUT][SUBALIGN(subsection_align)][constraint]{output-section-commandoutput-section-command...} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
我們已經描述了section, address, and output-section-command 命令。在本節中,我們將描述其余的段屬性。
Output Section Type
: 輸出段類型Output Section LMA
: 輸出段LMA —— 加載地址Forced Output Alignment
: 強制輸出對齊Forced Input Alignment
: 強制輸入對齊Output Section Constraint
: 輸出段限制Output Section Region
: 輸出段區域Output Section Phdr
: 輸出段 phdrOutput Section Fill
: 輸出段填充
3.6.8.1 Output Section Type
每個輸出段可以有一個類型。類型是圓括號中的關鍵字。定義了以下類型:
-
NOLOAD
此段應標記為不可加載,以便在程序運行時不會將其加載到內存中。 -
DSECT
/COPY
/INFO
/OVERLAY
支持這些類型名稱是為了向后兼容,而且很少使用。它們都具有相同的效果: 該段應該標記為不可分配,以便在程序運行時不會為該段分配內存。
? ?鏈接器通常根據映射到輸出段的輸入段設置輸出段的屬性。您可以使用 section 類型來覆蓋它。例如,在下面的腳本示例中,’ ROM’ 部分位于內存位置 ’ 0 ',在程序運行時不需要加載它。
SECTIONS {ROM 0 (NOLOAD) : { ... }...
}
3.6.8.2 Output Section LMA
? ?每個段有一個虛擬地址 (VMA
) 和一個加載地址 (LMA
) ; 參見 Basic Linker Script Concepts 。虛擬地址由前面描述的 Output Section Address 指定。加載地址由 AT
或 AT>
關鍵字指定。指定加載地址是可選的。
? ?AT
關鍵字把一個表達式當作自己的參數。這將指定段的實際加載地址 。關鍵字 AT>
使用內存區域的名字作為參數。參考 MEMORY Command 。段的加載地址被設置為該區域的當前空閑位置,并且按照段對齊要求對齊。
? ?如果沒有為可分配段使用 AT 和 AT>,鏈接器會使用下面的方式嘗試來決定加載地址:
- 如果段有一個特定的 VMA 地址,則 LMA 也使用該地址。
- 如果段為不可分配的則 LMA 被設置為它的 VMA 。
- 否則如果可以找到符合當前段的一個內存區域,且此區域至少包含了一個段,則設置 LMA 在那里。如此 VMA 和 LMA 的區別類似于 VMA 和 LMA 在該區域的上一個段的區別。
- 如果沒有聲明內存區域且默認區域覆蓋了整個地址空間,則采用前面的步驟。
- 如果找不到合適的區域或者沒有前面存在的段,則 LMA 被設置為等于 VMA 。
? ? 這些功能旨在使構建 ROM 映像變得容易。例如,以下鏈接器腳本創建三個輸出段:一個名為 “.text”
,從 0x1000 開始;一個名為 “.mdata”
,即使其 VMA 為 0x2000,也加載在 “.text” 節的末尾;另一個名為 “.bss”
,用于在地址 0x3000 保存未初始化的數據。符號 ‘_data’ 被定義為值 0x2000,這表明位置計數器保存 VMA 值,而不是 LMA 值。
SECTIONS
{.text 0x1000 : { *(.text) _etext = . ; }.mdata 0x2000 :AT ( ADDR (.text) + SIZEOF (.text) ){ _data = . ; *(.data); _edata = . ; }.bss 0x3000 :{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
? ? 此鏈接腳本的運行時初始化代碼應該類似于下面的形式,把初始化數據從 ROM 鏡像復制到運行時地址。注意這些代碼是如何利用鏈接器腳本定義的符號的。
extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;/* ROM has data at end of text; copy it. */
while (dst < &_edata)*dst++ = *src++;/* Zero bss. */
for (dst = &_bstart; dst< &_bend; dst++)*dst = 0;
3.6.8.3 Forced Output Alignment
? ? 你可以使用 ALIGN
增加輸出段的對齊。作為替換,你可以通過 ALIGN_WITH_INPUT
屬性強制 VMA 與 LMA 自始至終保持它們之間的區別。
? ? 您可以使用 ALIGN
來增加輸出段的對齊方式。作為一種替代方法,您可以使用 ALIGN_WITH_INPUT
屬性在整個輸出段保持 VMA 和 LMA 之間的差異。
3.6.8.4 Forced Input Alignment
? ? 您可以使用 SUBALIGN
來強制輸出段中的輸入段對齊。指定的值將覆蓋輸入段提供的任何對齊方式,無論比原來大還是小。
3.6.8.5 Output Section Constraint
? ? 通過分別使用關鍵字 ONLY_IF_RO
和 ONLY_IF_RW
,可以指定只有在所有輸入段都是只讀或所有輸入段都是讀寫的情況下才創建輸出段。
3.6.8.6 Output Section Region
? ? 可以使用 '>region'
把一個段指定到此前設置的內存區域內。參見 MEMORY Command 。
下面是一個例子:
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }
3.6.8.7 Output Section Phdr
? ? 您可以使用 ':phdr'
將一個段分配給先前定義的程序段。參見 PHDRS Command 。如果一個段被分配給一個或多個段,那么所有后續分配的段也將被分配給這些段,除非它們顯式地使用 :phdr
修飾符。您可以使用 :NONE
來告訴鏈接器根本不要將該段放在任何段中。
這里有一個簡單的例子:
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }
3.6.8.8 Output Section Fill
? ? 你可以使用 '=fillexp'
為整個段設置填充模板。fillexp
是一個表達式(參考 Expressions in Linker Scripts)。任何其它的未被特殊指定的輸出段的內存區域(例如,因為對其輸入段產生的縫隙)將會被用 fillexp 的值填充,如果有需要可以重復填充。如果表達式是一個簡單的 hex 數字,例如一個十六進制數字由 ‘0x’ 開頭且結尾沒有 ‘k’ 或 ‘M’ ,則一個任意長的十六進制數字可以被用來給填充模板賦值,前面的 0 同樣成為模板的一部分。在其它情況中,包含額外的括號或者一個一元 + ,填充模板為表達式值的最低 4 個有意義的字節。在所有情況中,數字總是大端的。
? ? 你也可以使用 FILL
命令設置填充值(參考 Output Section Data)。
這里有一個簡單的例子:
SECTIONS { .text : { *(.text) } =0x90909090 }
3.6.9 Overlay Description
? ? 覆蓋描述提供了一種簡單的方法來描述將作為單個內存映像的一部分加載但將在相同內存地址上運行的段。在運行時,某種類型的覆蓋管理器將根據需要從運行時內存地址復制覆蓋的段,可能通過簡單地操作尋址位來實現。這種方法可能很有用,例如,當某個內存區域比另一個區域更快時。
? ? 覆蓋描述使用 OVERLAY
命令。OVERLAY 命令和 SECTIONS 命令一起使用,就像一個輸出段描述符。完整的 OVERLAY 命令的語義如下:
OVERLAY [start] : [NOCROSSREFS] [AT ( ldaddr )]{secname1{output-section-commandoutput-section-command...} [:phdr...] [=fill]secname2{output-section-commandoutput-section-command...} [:phdr...] [=fill]...} [>region] [:phdr...] [=fill] [,]
? ? 除了 OVERLAY
(關鍵字),以及每個段都必須有一個名字(上面的 secname1 和 secname2 ),所有的部分都是可選的。除了 OVERLAY 中不能為段定義地址和內存區域,使用 OVERLAY 結構定義的段類似于那些普通的 SECTIONS 中的結構(參考 SECTIONS)。
? ? 結尾的逗號可能會被使用,如果使用了 fill 且下一個 sections-command 看起來像是表達式的延續。
? ? 所有的段都使用同樣的開始地址定義。所有段的載入地址都被排布,使它們在內存中從整個 ‘OVERLAY’ 的載入地址開始都是連續的(就像普通的段定義,載入地址是可選的,缺省的就是開始地址;開始地址也是可選的,缺省是當前的位置計數器的值)。
? ? 如果使用了關鍵字 NOCROSSREFS
,并且在任何段間有互相引用,鏈接器將會產生一個錯誤報告。因為所有的段運行在同樣的地址,直接引用其它的段通常沒有任何意義。參考 NOCROSSREFS 。
? ? 每個伴隨 OVERLAY 的段,鏈接器自動提供兩個符號。符號 __load_start_secname
被定義為段的起始地址。符號 __load_stop_secname
被定義為段結束地址。任何不符合 C 定義的伴隨 secname 的字符都將被移除。C(或者匯編)代碼可以使用這些符號在需要時搬移復蓋代碼。
? ? 覆蓋之后,位置計數器的值設置為覆蓋的起始值加上最大段的長度。
下面是例子,請記住這應該放在 SECTIONS 結構內。
OVERLAY 0x1000 : AT (0x4000){.text0 { o1/*.o(.text) }.text1 { o2/*.o(.text) }}
? ? 這將把 '.text0'
和 '.text1'
的起始地址設置為地址 0x1000
。'.text0'
的加載地址為 0x4000
,‘.text1’ 會加載到 ‘.text0’ 后面。下面的符號如果被引用則會被定義: __load_start_text0
, __load_stop_text0
, __load_start_text1
, __load_stop_text1
。
C 代碼拷貝覆蓋 .text1 到覆蓋區域可能像下面的形式。
extern char __load_start_text1, __load_stop_text1;memcpy ((char *) 0x1000, &__load_start_text1,&__load_stop_text1 - &__load_start_text1);
? ? 注意 ‘OVERLAY’ 命令只是為了語法上的便利,因為它所做的所有事情都可以用更加基本的命令加以代替。上面的例子可以用下面的寫法:
.text0 0x1000 : AT (0x4000) { o1/*.o(.text) }PROVIDE (__load_start_text0 = LOADADDR (.text0));PROVIDE (__load_stop_text0 = LOADADDR (.text0) + SIZEOF (.text0));.text1 0x1000 : AT (0x4000 + SIZEOF (.text0)) { o2/*.o(.text) }PROVIDE (__load_start_text1 = LOADADDR (.text1));PROVIDE (__load_stop_text1 = LOADADDR (.text1) + SIZEOF (.text1));. = 0x1000 + MAX (SIZEOF (.text0), SIZEOF (.text1));