1. 程式人生 > >linux 下的連結檔案詳解

linux 下的連結檔案詳解

轉載來自: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.ofoo.ofoo1.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 ] 前面我們介紹了SECTIONADDRESSOUTPUT-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的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);   八、 記憶體區域命令 在預設情形下,聯結器可以為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關鍵字,表示