Linux下的lds連接腳本簡介(二)

7、 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.一、輸出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.一、輸出section名字
輸出section名字 SECTION-NAME 必須符合輸出文件格式要求,好比: a.out格式的文件只容許存在.text、.data和.bss section名而有的格式只容許存在 數字名字 ,那麼此時應該用引號將全部名字內的數字組合在一塊兒;另外,還有一些格式容許任何序列的字符存在於section名字內,此時若是名字內包含特殊字符(好比空格、逗號等),那麼須要用引號將其組合在一塊兒。
7.1.二、輸出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.三、輸出section描述
輸出section描述OUTPUT-SECTION-COMMAND爲如下四種之一:
(1). 符號賦值語句
(2). 輸入section描述
(3). 直接包含的數據值
(4). 一些特殊的輸出section關鍵字
7.1.3.一、符號賦值語
符號賦值語句 已經在《 Linux下的lds連接腳本基礎(一) 》前文介紹過,這裏就不累述。
7.1.3.二、輸入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依次放入輸出 outputa section內,該section的VMA是 0×10000;將 foo.o文件的全部 .input2 section和 foo1.o文件的全部 .input1 section依次放入輸出 outputb section內,該section的VMA是當前定位器符號的修調值(對齊後);將其餘文件(非 all.ofoo.ofoo1.o)文件的 . input1 section和 .input2 section放入輸出 outputc section內。
7.1.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.四、特殊的輸出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.二、輸出section描述(進階)
咱們再回顧如下輸出section描述的文法:
SECTION-NAME [ ADDRESS ] [( TYPE )] : [ AT ( LMA )]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
} [>REGION] [AT>LMA_REGION] [ : PHDR HDR ... ] [= FILLEXP ]
前面咱們介紹了 SECTIONADDRESSOUTPUT-SECTION-COMMAND相關信息,下面咱們將介紹其餘屬性。
7.2.一、輸出section的類型
能夠經過 [( TYPE )] 設置輸出section的類型若是沒有指定TYPE類型,那麼鏈接器根據輸出section引用的輸入section的類型設置該輸出section的類型。它能夠爲如下五種值,
NOLOAD :該section在程序運行時,不被載入內存。
DSECT,COPY,INFO,OVERLAY :這些類型不多被使用,爲了向後兼容才被保留下來。這種類型的section必須被標記爲「 不可加載的」,以便在程序運行不爲它們分配內存。
默認值是多少呢?Puzzle!
7.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.三、設置輸出section所在的程序段
能夠經過 [ : PHDR HDR ... ] 項將輸出section放入預先定義的程序段(program segment)內。若是某個輸出section設置了它所在的一個或多個程序段,那麼接下來定義的輸出section的默認程序段與該輸出 section的相同。除非再次顯示地指定。例子,
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } : text }
能夠經過 :NONE指定鏈接器不把該section放入任何程序段內。詳情請查看PHDRS命令
7.2.四、設置輸出section的填充模版
這個在前面提到過,任何輸出section描述內的未指定的內存區域,鏈接器用該模版填充該區域。咱們能夠經過 [= FILLEXP ] 項設置填充值。用法: =FILEEXP ,前兩字節有效,當區域大於兩字節時,重複使用這兩字節以將其填滿。例子,
SECTIONS { .text : { *(.text) } = 0×9090 }
7.三、覆蓋圖(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的 LMALDADDR決定,若是它沒有被指定,那麼由 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) ;
相關文章
相關標籤/搜索