色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          "); //-->

          博客專欄

          EEPW首頁 > 博客 > Linux下的lds鏈接腳本詳解

          Linux下的lds鏈接腳本詳解

          發(fā)布人:電子禪石 時間:2022-10-12 來源:工程師 發(fā)布文章
          [轉(zhuǎn)]Linux下的lds鏈接腳本詳解

          轉(zhuǎn)載自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml


          一、 概論
          每一個鏈接過程都由鏈接腳本(linker script, 一般以lds作為文件的后綴名)控制. 鏈接腳本主要用于規(guī)定如何把輸入文件內(nèi)的section放入輸出文件內(nèi), 并控制輸出文件內(nèi)各部分在程序地址空間內(nèi)的布局. 但你也可以用連接命令做一些其他事情.
          連接器有個默認(rèn)的內(nèi)置連接腳本, 可用ld –verbose查看. 連接選項-r和-N可以影響默認(rèn)的連接腳本(如何影響?).
          -T選項用以指定自己的鏈接腳本, 它將代替默認(rèn)的連接腳本。你也可以使用以增加自定義的鏈接命令.
          以下沒有特殊說明,連接器指的是靜態(tài)連接器.
           
          二、基本概念
          鏈接器把一個或多個輸入文件合成一個輸出文件.
          輸入文件: 目標(biāo)文件或鏈接腳本文件.
          輸出文件: 目標(biāo)文件或可執(zhí)行文件.
          目標(biāo)文件(包括可執(zhí)行文件)具有固定的格式, 在UNIX或GNU/Linux平臺下, 一般為ELF格式
          有時把輸入文件內(nèi)的section稱為輸入section(input section), 把輸出文件內(nèi)的section稱為輸出section(output sectin).
          目標(biāo)文件的每個section至少包含兩個信息: 名字大小. 大部分section還包含與它相關(guān)聯(lián)的一塊數(shù)據(jù), 稱為section contents(section內(nèi)容). 一個section可被標(biāo)記為“l(fā)oadable(可加載的)”或“allocatable(可分配的)”.
          loadable section在輸出文件運行時, 相應(yīng)的section內(nèi)容將被載入進程地址空間中.
          allocatable section內(nèi)容為空的section可被標(biāo)記為“可分配的”. 在輸出文件運行時, 在進程地址空間中空出大小同section指定大小的部分. 某些情況下, 這塊內(nèi)存必須被置零.
          如果一個section不是“可加載的”或“可分配的”, 那么該section通常包含了調(diào)試信息. 可用objdump -h命令查看相關(guān)信息.
          每個“可加載的”或“可分配的”輸出section通常包含兩個地址VMA(virtual memory address虛擬內(nèi)存地址或程序地址空間地址)和LMA(load memory address加載內(nèi)存地址或進程地址空間地址). 通常VMA和LMA是相同的.
          在目標(biāo)文件中, loadable或allocatable的輸出section有兩種地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是執(zhí)行輸出文件時section所在的地址, 而LMA是加載輸出文件時section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系統(tǒng)中, 經(jīng)常存在加載地址和執(zhí)行地址不同的情況: 比如將輸出文件加載到開發(fā)板的flash中(由LMA指定), 而在運行時將位于flash中的輸出文件復(fù)制到SDRAM中(由VMA指定).
          可這樣來理解VMA和LMA, 假設(shè):
          (1) .data section對應(yīng)的VMA地址是0×08050000, 該section內(nèi)包含了3個32位全局變量, i、j和k, 分別為1,2,3.
          (2) .text section內(nèi)包含由”printf( “j=%d “, j );”程序片段產(chǎn)生的代碼.
          連接時指定.data section的VMA為0×08050000, 產(chǎn)生的printf指令是將地址為0×08050004處的4字節(jié)內(nèi)容作為一個整數(shù)打印出來。
          如果.data section的LMA為0×08050000,顯然結(jié)果是j=2
          如果.data section的LMA為0×08050004,顯然結(jié)果是j=1
          還可這樣理解LMA:
          .text section內(nèi)容的開始處包含如下兩條指令(intel i386指令是10字節(jié),每行對應(yīng)5字節(jié)):
          jmp 0×08048285
          movl $0×1,%eax
          如果.text section的LMA為0×08048280, 那么在進程地址空間內(nèi)0×08048280處為“jmp 0×08048285”指令, 0×08048285處為movl $0×1,%eax指令. 假設(shè)某指令跳轉(zhuǎn)到地址0×08048280, 顯然它的執(zhí)行將導(dǎo)致%eax寄存器被賦值為1.
          如果.text section的LMA為0×08048285, 那么在進程地址空間內(nèi)0×08048285處為“jmp 0×08048285”指令, 0×0804828a處為movl $0×1,%eax指令. 假設(shè)某指令跳轉(zhuǎn)到地址0×08048285, 顯然它的執(zhí)行又跳轉(zhuǎn)到進程地址空間內(nèi)0×08048285處, 造成死循環(huán).
          符號(symbol): 每個目標(biāo)文件都有符號表(SYMBOL TABLE), 包含已定義的符號(對應(yīng)全局變量和static變量和定義的函數(shù)的名字)和未定義符號(未定義的函數(shù)的名字和引用但沒定義的符號)信息.
          符號值: 每個符號對應(yīng)一個地址, 即符號值(這與c程序內(nèi)變量的值不一樣, 某種情況下可以把它看成變量的地址). 可用nm命令查看它們. (nm的使用方法可參考本blog的GNU binutils筆記)
           
          三、 腳本格式
          鏈接腳本由一系列命令組成, 每個命令由一個關(guān)鍵字(一般在其后緊跟相關(guān)參數(shù))或一條對符號的賦值語句組成. 命令由分號‘;’分隔開.
          文件名或格式名內(nèi)如果包含分號’;'或其他分隔符, 則要用引號‘’將名字全稱引用起來. 無法處理含引號的文件名.
          /* */之間的是注釋。
           
          四、 簡單例子
          在介紹鏈接描述文件的命令之前, 先看看下述的簡單例子:
          以下腳本將輸出文件的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的值設(shè)置成入口地址。
          入口地址(entry point)是指進程執(zhí)行的第一條用戶空間的指令在進程地址空間的地址
          ld有多種方法設(shè)置進程入口地址, 按一下順序: (編號越前, 優(yōu)先級越高)
          1, ld命令行的-e選項
          2, 連接腳本的ENTRY(SYMBOL)命令
          3, 如果定義了start符號, 使用start符號值
          4, 如果存在.text section, 使用.text section的第一字節(jié)的位置值
          5, 使用值0
          INCLUDE filename : 包含其他名為filename的鏈接腳本
          相當(dāng)于c程序內(nèi)的的#include指令, 用以包含另一個鏈接腳本.
          腳本搜索路徑由-L選項指定. INCLUDE指令可以嵌套使用, 最大深度為10. 即: 文件1內(nèi)INCLUDE文件2, 文件2內(nèi)INCLUDE文件3… , 文件10內(nèi)INCLUDE文件11. 那么文件11內(nèi)不能再出現(xiàn) INCLUDE指令了.
          INPUT(files): 將括號內(nèi)的文件做為鏈接過程的輸入文件
          ld首先在當(dāng)前目錄下尋找該文件, 如果沒找到, 則在由-L指定的搜索路徑下搜索. file可以為 -lfile形式,就象命令行的-l選項一樣. 如果該命令出現(xiàn)在暗含的腳本內(nèi), 則該命令內(nèi)的file在鏈接過程中的順序由該暗含的腳本在命令行內(nèi)的順序決定.
          GROUP(files: 指定需要重復(fù)搜索符號定義的多個輸入文件
          file必須是庫文件, 且file文件作為一組被ld重復(fù)掃描,直到不在有新的未定義的引用出現(xiàn)。
          OUTPUT(FILENAME: 定義輸出文件的名字
          同ld的-o選項, 不過-o選項的優(yōu)先級更高. 所以它可以用來定義默認(rèn)的輸出文件名. 如a.out
          SEARCH_DIR(PATH:定義搜索路徑,
          同ld的-L選項, 不過由-L指定的路徑要比它定義的優(yōu)先被搜索。
          STARTUP(filename) : 指定filename為第一個輸入文件
          在鏈接過程中, 每個輸入文件是有順序的. 此命令設(shè)置文件filename為第一個輸入文件。
          OUTPUT_FORMAT(BFDNAME) : 設(shè)置輸出文件使用的BFD格式
          同ld選項-o format BFDNAME, 不過ld選項優(yōu)先級更高.
          OUTPUT_FORMAT(DEFAULT,BIG,LITTLE: 定義三種輸出文件的格式(大小端)
          若有命令行選項-EB, 則使用第2個BFD格式; 若有命令行選項-EL,則使用第3個BFD格式.否則默認(rèn)選第一個BFD格式.
          TARGET(BFDNAME):設(shè)置輸入文件的BFD格式
          同ld選項-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 則最用一個TARGET命令設(shè)置的BFD格式將被作為輸出文件的BFD格式.
          ASSERT(EXP, MESSAGE):如果EXP不為真,終止連接過程
          EXTERN(SYMBOL SYMBOL …):在輸出文件中增加未定義的符號,如同連接器選項-u
          FORCE_COMMON_ALLOCATION:為common symbol(通用符號)分配空間,即使用了-r連接選項也為其分配
          NOCROSSREFS(SECTION SECTION …):檢查列出的輸出section,如果發(fā)現(xiàn)他們之間有相互引用,則報錯。對于某些系統(tǒng),特別是內(nèi)存較緊張的嵌入式系統(tǒng),某些section是不能同時存在內(nèi)存中的,所以他們之間不能相互引用。
          OUTPUT_ARCH(BFDARCH):設(shè)置輸出文件的machine architecture(體系結(jié)構(gòu)),BFDARCH為被BFD庫使用的名字之一??梢杂妹頾bjdump -f查看。
          可通過 man -S 1 ld查看ld的聯(lián)機幫助, 里面也包括了對這些命令的介紹.
           
          六、 對符號的賦值
          在目標(biāo)文件內(nèi)定義的符號可以在鏈接腳本內(nèi)被賦值. (注意和C語言中賦值的不同!) 此時該符號被定義為全局的. 每個符號都對應(yīng)了一個地址, 此處的賦值是更改這個符號對應(yīng)的地址.
          舉例. 通過下面的程序查看變量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
          運行結(jié)果:
          &a = 0×601020
          編譯命令:
          $ gcc -Wall -o a-with-lds.exe a.c a.lds
          運行結(jié)果:
          &a = 0×3
          注意: 對符號的賦值只對全局變量起作用!
          對于一些簡單的賦值語句,我們可以使用任何c語言語法的賦值操作:
          SYMBOL = EXPRESSION ;
          SYMBOL += EXPRESSION ;
          SYMBOL -= EXPRESSION ;
          SYMBOL *= EXPRESSION ;
          SYMBOL /= EXPRESSION ;
          SYMBOL >= EXPRESSION ;
          SYMBOL &= EXPRESSION ;
          SYMBOL |= EXPRESSION ;
          除了第一類表達式外, 使用其他表達式需要SYMBOL已經(jīng)被在某目標(biāo)文件的源碼中被定義。
          是一個特殊的符號,它是定位器,一個位置指針,指向程序地址空間內(nèi)的某位置(或某section內(nèi)的偏移,如果它在SECTIONS命令內(nèi)的某section描述內(nèi)),該符號只能在SECTIONS命令內(nèi)使用。
          注意:賦值語句包含4個語法元素:符號名、操作符、表達式、分號;一個也不能少。
          被賦值后,符號所屬的section被設(shè)值為表達式EXPRESSION所屬的SECTION(參看11. 腳本內(nèi)的表達式)
          賦值語句可以出現(xiàn)在連接腳本的三處地方:SECTIONS命令內(nèi),SECTIONS命令內(nèi)的section描述內(nèi)和全局位置。
          示例1
          floating_point = 0; /* 全局位置 */
          SECTIONS
          {
          .text :
          {
          *(.text)
          _etext = .; /* section描述內(nèi) */
          }
          _bdata = (. + 3) & ~ 4; /* SECTIONS命令內(nèi) */
          .data : { *(.data) }
          }
          PROVIDE關(guān)鍵字
          該關(guān)鍵字用于定義這類符號:在目標(biāo)文件內(nèi)被引用,但沒有在任何目標(biāo)文件內(nèi)被定義的符號。
          示例2
          SECTIONS
          {
          .text :
          {
          *(.text)
          _etext = .;
          PROVIDE(etext = .);
          }
          }
          這里,當(dāng)目標(biāo)文件內(nèi)引用了etext符號,卻沒有定義它時,etext符號對應(yīng)的地址被定義為.text section之后的第一個字節(jié)的地址。
           
          七、 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)
          如果整個連接腳本內(nèi)沒有SECTIONS命令, 那么ld將所有同名輸入section合成為一個輸出section內(nèi), 各輸入section的順序為它們被連接器發(fā)現(xiàn)的順序.如果某輸入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]
          [ ]內(nèi)的內(nèi)容為可選選項, 一般不需要.
          SECTION-NAME:section名字.SECTION-NAME左右的空白、圓括號、冒號是必須的,換行符和其他空格是可選的。
           
          7.1.1、輸出section名字
          輸出section名字SECTION-NAME必須符合輸出文件格式要求,比如:a.out格式的文件只允許存在.text、.data和.bss section名。而有的格式只允許存在數(shù)字名字,那么此時應(yīng)該用引號將所有名字內(nèi)的數(shù)字組合在一起;另外,還有一些格式允許任何序列的字符存在于section名字內(nèi),此時如果名字內(nèi)包含特殊字符(比如空格、逗號等),那么需要用引號將其組合在一起。
           
          7.1.2、輸出section地址
          輸出section地址[ADDRESS]是一個表達式,它的值用于設(shè)置VMA。如果沒有該選項且有REGION選項,那么連接器將根據(jù)REGION設(shè)置VMA;如果也沒有REGION選項,那么連接器將根據(jù)定位符號‘.’的值設(shè)置該section的VMA,將定位符號的值調(diào)整到滿足輸出section對齊要求后的值,這時輸出 section的對齊要求為:該輸出section描述內(nèi)用到的所有輸入section的對齊要求中最嚴(yán)格的對齊要求。
          例子
          .text : { *(.text) }.text : { *(.text) }
          這兩個描述是截然不同的,第一個將.text section的VMA設(shè)置為定位符號的值,而第二個則是設(shè)置成定位符號的修調(diào)值,滿足對齊要求后的。
          ADDRESS可以是一個任意表達式,比如,ALIGN(0×10)這將把該section的VMA設(shè)置成定位符號的修調(diào)值,滿足16字節(jié)對齊后的。
          注意:設(shè)置ADDRESS值,將更改定位符號的值。
           
          7.1.3、輸出section描述
          輸出section描述OUTPUT-SECTION-COMMAND為以下四種之一:
          (1).符號賦值語句
          (2).輸入section描述
          (3).直接包含的數(shù)據(jù)值
          (4).一些特殊的輸出section關(guān)鍵字
           
          7.1.3.1、符號賦值語
          符號賦值語句已經(jīng)在《Linux下的lds鏈接腳本基礎(chǔ)(一)》前文介紹過,這里就不累述。
           
          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
          下面看連接器是如何找到對應(yīng)的文件的。
          當(dāng)FILENAME是一個特定的文件名時,連接器會查看它是否在連接命令行內(nèi)出現(xiàn)或在INPUT命令中出現(xiàn)。
          當(dāng)FILENAME是一個字符串模式時,連接器僅僅只查看它是否在連接命令行內(nèi)出現(xiàn)。
          注意:如果連接器發(fā)現(xiàn)某文件在INPUT命令內(nèi)出現(xiàn),那么它會在-L指定的路徑內(nèi)搜尋該文件。
          字符串模式內(nèi)可存在以下通配符:
          :表示任意多個字符
          :表示任意一個字符
          [CHARS] :表示任意一個CHARS內(nèi)的字符,可用-號表示范圍,如:a-z
          表示引用下一個緊跟的字符
          在文件名內(nèi),通配符不匹配文件夾分隔符/,但當(dāng)字符串模式僅包含通配符*時除外。
          任何一個文件的任意section只能在SECTIONS命令內(nèi)出現(xiàn)一次。
          看如下例子
          SECTIONS {
          .data : { *(.data) }
          .data1 : { data.o(.data) }
          }
          data.o文件的.data section在第一個OUTPUT-SECTION-COMMAND命令內(nèi)被使用了,那么在第二個OUTPUT-SECTION-COMMAND命令內(nèi)將不會再被使用,也就是說即使連接器不報錯,輸出文件的.data1 section的內(nèi)容也是空的。
          再次強調(diào):連接器依次掃描每個OUTPUT-SECTION-COMMAND命令內(nèi)的文件名,任何一個文件的任何一個section都只能使用一次。
          讀者可以用-M連接命令選項來產(chǎn)生一個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()關(guān)鍵字對滿足字符串模式的所有名字進行遞增排序,如SORT(.text*)。
           
          通用符號(common symbol)的輸入section
          在許多目標(biāo)文件格式中,通用符號并沒有占用一個section。連接器認(rèn)為:輸入文件的所有通用符號在名為COMMON的section內(nèi)。
          例子,
          .bss { *(.bss) *(COMMON) }
          這個例子中將所有輸入文件的所有通用符號放入輸出.bss section內(nèi)??梢钥吹?span style="margin: 0px; padding: 0px; color: rgb(0, 0, 128);">COMMOM section的使用方法跟其他section的使用方法是一樣的。
          有些目標(biāo)文件格式把通用符號分成幾類。例如,在MIPS elf目標(biāo)文件格式中,把通用符號分成standard common symbols(標(biāo)準(zhǔn)通用符號)和small common symbols(微通用符號,不知道這么譯對不對?),此時連接器認(rèn)為所有standard common symbols在COMMON section內(nèi),而small common symbols在.scommon section內(nèi)。
          在一些以前的連接腳本內(nèi)可以看見[COMMON],相當(dāng)于*(COMMON),不建議繼續(xù)使用這種陳舊的方式。
           
          輸入section和垃圾回收
          在連接命令行內(nèi)使用了選項–gc-sections后,連接器可能將某些它認(rèn)為沒用的section過濾掉,此時就有必要強制連接器保留一些特定的 section,可用KEEP()關(guān)鍵字達此目的。如KEEP(*(.text))或KEEP(SORT(*)(.text))
          最后我們看個簡單的輸入section相關(guān)例子:
          SECTIONS {
          outputa 0×10000 :
          {
          all.o
          foo.o (.input1)
          }
          outputb :
          {
          foo.o (.input2)
          foo1.o (.input1)
          }
          outputc :
          {
          *(.input1)
          *(.input2)
          }
          }
          本例中,將all.o文件的所有section和foo.o文件的所有(一個文件內(nèi)可以有多個同名section).input1 section依次放入輸出outputasection內(nèi),該section的VMA是0×10000;將foo.o文件的所有.input2 section和foo1.o文件的所有.input1 section依次放入輸出outputb section內(nèi),該section的VMA是當(dāng)前定位器符號的修調(diào)值(對齊后);將其他文件(非all.o、foo.o、foo1.o)文件的. input1section和.input2 section放入輸出outputc section內(nèi)。
           
          7.1.3.3、直接包含數(shù)據(jù)值
          可以顯示地在輸出section內(nèi)填入你想要填入的信息(這樣是不是可以自己通過連接腳本寫程序?當(dāng)然是簡單的程序)。
          BYTE(EXPRESSION) 1 字節(jié)
          SHORT(EXPRESSION) 2 字節(jié)
          LOGN(EXPRESSION) 4 字節(jié)
          QUAD(EXPRESSION) 8 字節(jié)
          SQUAD(EXPRESSION) 64位處理器的代碼時,8 字節(jié)
          輸出文件的字節(jié)順序big endianness 或little endianness,可以由輸出目標(biāo)文件的格式?jīng)Q定;如果輸出目標(biāo)文件的格式不能決定字節(jié)順序,那么字節(jié)順序與第一個輸入文件的字節(jié)順序相同。
          BYTE(1)、LANG(addr)
          注意,這些命令只能放在輸出section描述內(nèi),其他地方不行。
          錯誤SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
          正確SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }
          在當(dāng)前輸出section內(nèi)可能存在未描述的存儲區(qū)域(比如由于對齊造成的空隙),可以用FILL(EXPRESSION)命令決定這些存儲區(qū)域的內(nèi)容, EXPRESSION的前兩字節(jié)有效,這兩字節(jié)在必要時可以重復(fù)被使用以填充這類存儲區(qū)域。如FILE(0×9090)。在輸出section描述中可以有=FILEEXP屬性,它的作用如同F(xiàn)ILE()命令,但是FILE命令只作用于該FILE指令之后的section區(qū)域,而=FILEEXP屬性作用于整個輸出section區(qū)域,且FILE命令的優(yōu)先級更高!?。?/span>
           
          7.1.3.4、特殊的輸出section關(guān)鍵字
          在輸出section描述OUTPUT-SECTION-COMMAND中還可以使用一些特殊的輸出section關(guān)鍵字。
          CREATE_OBJECT_SYMBOLS :為每個輸入文件建立一個符號,符號名為輸入文件的名字。每個符號所在的section是出現(xiàn)該關(guān)鍵字的section。
          CONSTRUCTORS :與c++內(nèi)的(全局對象的)構(gòu)造函數(shù)和(全局對像的)析構(gòu)函數(shù)相關(guān),下面將它們簡稱為全局構(gòu)造全局析構(gòu)。
          對于a.out目標(biāo)文件格式,連接器用一些不尋常的方法實現(xiàn)c++的全局構(gòu)造和全局析構(gòu)。
          當(dāng)連接器生成的目標(biāo)文件格式不支持任意section名字時,比如說ECOFF、XCOFF格式,連接器將通過名字來識別全局構(gòu)造和全局析構(gòu),對于這些文件格式,連接器把與全局構(gòu)造和全局析構(gòu)的相關(guān)信息放入出現(xiàn) CONSTRUCTORS關(guān)鍵字的輸出section內(nèi)。
          符號__CTORS_LIST__表示全局構(gòu)造信息的的開始處,__CTORS_END__表示全局構(gòu)造信息的結(jié)束處。
          符號__DTORS_LIST__表示全局構(gòu)造信息的的開始處,__DTORS_END__表示全局構(gòu)造信息的結(jié)束處。
          這兩塊信息的開始處是一字長的信息,表示該塊信息有多少項數(shù)據(jù),然后以值為零的一字長數(shù)據(jù)結(jié)束。
          一般來說,GNU C++在函數(shù)__main內(nèi)安排全局構(gòu)造代碼的運行,而__main函數(shù)被初始化代碼(在main函數(shù)調(diào)用之前執(zhí)行)調(diào)用。是不是對于某些目標(biāo)文件格式才這樣???
          對于支持任意section名的目標(biāo)文件格式,比如COFF、ELF格式,GNU C++將全局構(gòu)造和全局析構(gòu)信息分別放入.ctors section和.dtors section內(nèi),然后在連接腳本內(nèi)加入如下,
          __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++提供的初始化優(yōu)先級支持(它能控制每個全局構(gòu)造函數(shù)調(diào)用的先后順序),那么請在連接腳本內(nèi)把CONSTRUCTORS替換成SORT (CONSTRUCTS),把*(.ctors)換成*(SORT(.ctors)),把*(.dtors)換成*(SORT(.dtors))。一般來說,默認(rèn)的連接腳本已作好的這些工作。
          修改定位器
          我們可以對定位器符合。進行賦值來修改定位器的值。
          示例
          SECTIONS
          {
          = SIZEOF_HEADERS;
          .text : { *(.text) }
          = 0×10000;
          .data : { *(.data) }
          = 0×8000000;
          .bss : { *(.bss) }
          }
          輸出section的丟棄
          對于.foo: { *(.foo) },如果沒有任何一個輸入文件包含.foo section,那么連接器將不會創(chuàng)建.foo輸出section。但是如果在這些輸出section描述內(nèi)包含了非輸入section描述命令(如符號賦值語句),那么連接器將總是創(chuàng)建該輸出section。
          另外,有一個特殊的輸出section,名為/DISCARD/被該section引用的任何輸入section將不會出現(xiàn)在輸出文件內(nèi),這就是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]
          前面我們介紹了SECTION、ADDRESS、OUTPUT-SECTION-COMMAND相關(guān)信息,下面我們將介紹其他屬性。
           
          7.2.1、輸出section的類型
          可以通過[(TYPE)]設(shè)置輸出section的類型。如果沒有指定TYPE類型,那么連接器根據(jù)輸出section引用的輸入section的類型設(shè)置該輸出section的類型。它可以為以下五種值,
          NOLOAD :該section在程序運行時,不被載入內(nèi)存。
          DSECT,COPY,INFO,OVERLAY :這些類型很少被使用,為了向后兼容才被保留下來。這種類型的section必須被標(biāo)記為“不可加載的”,以便在程序運行不為它們分配內(nèi)存。
          默認(rèn)值是多少呢?Puzzle!
           
          7.2.2、輸出section的LMA 
          默認(rèn)情況下,LMA等于VMA,但可以通過[AT(LMA)]項,即關(guān)鍵字AT()指定LMA。
          用關(guān)鍵字AT()指定,括號內(nèi)包含表達式,表達式的值用于設(shè)置LMA。如果不用AT()關(guān)鍵字,那么可用AT>LMA_REGION達式設(shè)置指定該section加載地址的范圍。這個屬性主要用于構(gòu)件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、設(shè)置輸出section所在的程序段
          可以通過[:PHDR HDR ...]項將輸出section放入預(yù)先定義的程序段(program segment)內(nèi)。如果某個輸出section設(shè)置了它所在的一個或多個程序段,那么接下來定義的輸出section的默認(rèn)程序段與該輸出 section的相同。除非再次顯示地指定。例子,
          PHDRS { text PT_LOAD ; }
          SECTIONS { .text : { *(.text) } :text }
          可以通過:NONE指定連接器不把該section放入任何程序段內(nèi)。詳情請查看PHDRS命令
           
          7.2.4、設(shè)置輸出section的填充模版
          這個在前面提到過,任何輸出section描述內(nèi)的未指定的內(nèi)存區(qū)域,連接器用該模版填充該區(qū)域。我們可以通過[=FILLEXP]項設(shè)置填充值。用法:=FILEEXP,前兩字節(jié)有效,當(dāng)區(qū)域大于兩字節(jié)時,重復(fù)使用這兩字節(jié)以將其填滿。例子,
          SECTIONS { .text : { *(.text) } =0×9090 }
           
          7.3、覆蓋圖(overlay)描述
          覆蓋圖描述使兩個或多個不同的section占用同一塊程序地址空間。覆蓋圖管理代碼負(fù)責(zé)將section的拷入和拷出??紤]這種情況,當(dāng)某存儲塊的訪問速度比其他存儲塊要快時,那么如果將section拷到該存儲塊來執(zhí)行或訪問,那么速度將會有所提高,覆蓋圖描述就很適合這種情形文法如下,
          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]
          }
          由以上文法可以看出,同一覆蓋圖內(nèi)的section具有相同的VMA。這里VMA由[START決定。SECNAME2的LMA為SECTNAME1的LMA加上SECNAME1的大小,同理計算SECNAME2,3,4…的LMA。SECNAME1的LMALDADDR決定,如果它沒有被指定,那么由START決定,如果它也沒有被指定,那么由當(dāng)前定位符號的值決定。
          NOCROSSREFS關(guān)鍵字說明各section之間不能交叉引用,否則報錯。
          對于OVERLAY描述的每個section,連接器將定義兩個符號__load_start_SECNAME__load_stop_SECNAME,這兩個符號的值分別代表SECNAME section的LMA地址的開始結(jié)束
          連接器處理完OVERLAY描述語句后,將定位符號的值加上所有覆蓋圖內(nèi)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);
           
          八、 內(nèi)存區(qū)域命令
          在默認(rèn)情形下,連接器可以為section在程序地址空間內(nèi)分配任意位置的存儲區(qū)域。并通過輸出section描述的REGION屬性顯示地將該輸出section限定于在程序地址空間內(nèi)的某塊存儲區(qū)域,當(dāng)存儲區(qū)域大小不能滿足要求時,連接器會報告該錯誤。
          你也可以用MEMORY命令在SECTIONS命令內(nèi)*未*引用selection分配在程序地址空間內(nèi)的某個存儲區(qū)域內(nèi)。
          注意:以下存儲區(qū)域指的是在程序地址空間內(nèi)的。
          MEMORY命令的文法如下,
          MEMORY {
          NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN1
          NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
          }
          NAME :存儲區(qū)域的名字,這個名字可以與符號名、文件名、section名重復(fù),因為它處于一個獨立的名字空間。
          ATTR :定義該存儲區(qū)域的屬性,在講述SECTIONS命令時提到,當(dāng)某輸入section沒有在SECTIONS命令內(nèi)引用時,連接器會把該輸入 section直接拷貝成輸出section,然后將該輸出section放入內(nèi)存區(qū)域內(nèi)。如果設(shè)置了內(nèi)存區(qū)域設(shè)置了ATTR屬性,那么該區(qū)域只接受滿足該屬性的section(怎么判斷該section是否滿足?輸出section描述內(nèi)好象沒有記錄該section的讀寫執(zhí)行屬性)。
          ATTR屬性內(nèi)可以出現(xiàn)以下7個字符,
          R 只讀section
          W 讀/寫section
          X 可執(zhí)行section
          A ‘可分配的’section
          I 初始化了的section
          L 同I
          ! 不滿足該字符之后的任何一個屬性的section
          ORIGIN :關(guān)鍵字,區(qū)域的開始地址,可簡寫成org或o
          LENGTH :關(guān)鍵字,區(qū)域的大小,可簡寫成len或l
          示例
          MEMORY
          {
          rom (rx) : ORIGIN = 0, LENGTH = 256K
          ram (!rx) : org = 0×40000000, l = 4M
          }
          此例中,把在SECTIONS命令內(nèi)*未*引用的且具有讀屬性或?qū)憣傩缘妮斎雜ection放入rom區(qū)域內(nèi),把其他未引用的輸入section放入 ram。如果某輸出section要被放入某內(nèi)存區(qū)域內(nèi),而該輸出section又沒有指明ADDRESS屬性,那么連接器將該輸出section放在該區(qū)域內(nèi)下一個能使用位置。
           
          九、 PHDRS命令
          該命令僅在產(chǎn)生ELF目標(biāo)文件時有效。
          ELF目標(biāo)文件格式用program headers程序頭(程序頭內(nèi)包含一個或多個segment程序段描述)來描述程序如何被載入內(nèi)存??梢杂胦bjdump -p命令查看。
          當(dāng)在本地ELF系統(tǒng)運行ELF目標(biāo)文件格式的程序時,系統(tǒng)加載器通過讀取程序頭信息以知道如何將程序加載到內(nèi)存。要了解系統(tǒng)加載器如何解析程序頭,請參考ELF ABI文檔。
          在連接腳本內(nèi)不指定PHDRS命令時,連接器能夠很好的創(chuàng)建程序頭,但是有時需要更精確的描述程序頭,那么PAHDRS命令就派上用場了。
          注意:一旦在連接腳本內(nèi)使用了PHDRS命令,那么連接器**僅會**創(chuàng)建PHDRS命令指定的信息,所以使用時須謹(jǐn)慎。
          PHDRS命令文法如下,
          PHDRS
          {
          NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]
          [ FLAGS ( FLAGS ) ] ;
          }
          其中FILEHDR、PHDRS、AT、FLAGS為關(guān)鍵字。
          NAME :為程序段名,此名字可以與符號名、section名、文件名重復(fù),因為它在一個獨立的名字空間內(nèi)。此名字只能在SECTIONS命令內(nèi)使用。
          一個程序段可以由多個‘可加載’的section組成。通過輸出section描述的屬性:PHDRS可以將輸出section加入一個程序段,: PHDRS中的PHDRS為程序段名。在一個輸出section描述內(nèi)可以多次使用:PHDRS命令,也即可以將一個section加入多個程序段。
          如果在一個輸出section描述內(nèi)指定了:PHDRS屬性,那么其后的輸出section描述將默認(rèn)使用該屬性,除非它也定義了:PHDRS屬性。顯然當(dāng)多個輸出section屬于同一程序段時可簡化書寫。
          TYPE可以是以下八種形式,
          PT_NULL 0
          表示未被使用的程序段
          PT_LOAD 1
          表示該程序段在程序運行時應(yīng)該被加載
          PT_DYNAMIC 
          表示該程序段包含動態(tài)連接信息
          PT_INTERP 3
          表示該程序段內(nèi)包含程序加載器的名字,在linux下常見的程序加載器是ld-linux.so.2
          PT_NOTE 4
          表示該程序段內(nèi)包含程序的說明信息
          PT_SHLIB 5
          一個保留的程序頭類型,沒有在ELF ABI文檔內(nèi)定義
          PT_PHDR 6
          表示該程序段包含程序頭信息。
          EXPRESSION 表達式值
          以上每個類型都對應(yīng)一個數(shù)字,該表達式定義一個用戶自定的程序頭。
          在TYPE屬性后存在FILEHDR關(guān)鍵字,表示該段包含ELF文件頭信息;存在PHDRS關(guān)鍵字,表示該段包含ELF程序頭信息。
          AT(ADDRESS)屬性定義該程序段的加載位置(LMA),該屬性將**覆蓋**該程序段內(nèi)的section的AT()屬性。
          默認(rèn)情況下,連接器會根據(jù)該程序段包含的section的屬性(什么屬性?好象在輸出section描述內(nèi)沒有看到)設(shè)置FLAGS標(biāo)志,該標(biāo)志用于設(shè)置程序段描述的p_flags域。
          下面看一個典型的PHDRS設(shè)置
          示例
          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 */
          . = . + 0×1000; /* move to a new page in memory */
          .data : { *(.data) } :data
          .dynamic : { *(.dynamic) } :data :dynamic
          }
           
          十、版本號命令
          當(dāng)使用ELF目標(biāo)文件格式時,連接器支持帶版本號的符號。版本號也只限于ELF文件格式。
          讀者可以發(fā)現(xiàn)僅僅在共享庫中,符號的版本號屬性才有意義。動態(tài)加載器使用符號的版本號為應(yīng)用程序選擇共享庫內(nèi)的一個函數(shù)的特定實現(xiàn)版本。
          可以在連接腳本內(nèi)直接使用版本號命令,也可以將版本號命令實現(xiàn)于一個特定版本號描述文件(用連接選項–version-script指定該文件)。
          該命令的文法如下,
          VERSION { version-script-commands }
           以下討論用gcc
           
          10.1. 帶版本號的符號的定義(共享庫內(nèi))
          文件b.c內(nèi)容如下,
          int getVersion()
          {
          return 1;
          }
          寫連接器的版本控制腳本,本例中為b.lds,內(nèi)容如下
          VER1.0{
          getVersion;
          };
          VER2.0{
          };
          $gcc -c b.c
          $gcc -shared -Wl,--version-script=b.lds -o libb.so b.o
          可以在{}內(nèi)填入要綁定的符號,本例中getVersion符號就與VER1.0綁定了。
          那么如果有一個應(yīng)用程序連接到該庫的getVersion符號,那么它連接的就是VER1.0版本的getVersion符號
          如果我們對b.c文件進行了升級,更改如下:
          int getVersion()
          {
          return 101;
          }
          這里我對getVersion()進行了更改,其返回值的意義也進行改變,也就是它和前不兼容:
          為了程序的安全,我們把b.lds更改為,
          VER1.0{
          };
          VER2.0{
          getVersion;
          };
          然后生成新的libb.so文件。
          這時如果我們運行app.exe(它已經(jīng)連接到VER1.0版本的getVersion()),就會發(fā)現(xiàn)該應(yīng)用程序不能運行了。
          提示信息如下:
          ./app.exe: relocation error: ./app.exe: symbol getVersion, version VER1.0 not defined in file libb.so with link time reference
          因為庫內(nèi)沒有VER1.0版本的getVersion(),只有VER2.0版本的getVersion()。
           
          10.2、參看連接的符號的版本
          對上面生成的app.exe執(zhí)行以下命令:
          nm app.exe | grep getVersion
          結(jié)果
          U new_true@@VER1.0
          用nm命令發(fā)現(xiàn)app連接到VER1.0版本的getVersion
           
          10.3、 GNU的擴充
          在GNU中,允許在程序文件內(nèi)綁定 *符號* 到 *帶版本號的別名符號*
          文件b.c內(nèi)容如下,
          int old_getVersion()
          {
          return 1;
          }
          int new_getVersion()
          {
          return 101;
          }
          __asm__(".symver old_getVersion,getVersion@VER1.0");
          __asm__(".symver new_getVersion,getVersion@@VER2.0");
          其中,對于VER1.0版本號的getVersion別名符號是old_getVersion
          對于VER2.0版本號的getVersion別名符號是new_getVersion,
          在連接時,默認(rèn)的版本號為VER2.0
          供連接器用的版本控制腳本b.lds內(nèi)容如下,
          VER1.0{
          };
          VER2.0{
          };
          版本控制文件內(nèi)必須包含版本VER1.0和版本VER2.0的定義,因為在b.c文件內(nèi)有對他們的引用
          再次執(zhí)行以下命令編譯連接b.c和app.c
          gcc -c src/b.c
          gcc -shared -Wl,--version-script=./lds/b.lds -o libb.so b.o
          gcc -o app.exe ./src/app.c libb.so
          運行:
          ./app.exe
          結(jié)果:
          Version=0x65
          說明app.exe的確是連接的VER2.0的getVersion,即new_getVersion()
           
          我們再對app.c進行修改,以使它連接的VER1.0的getVersion,即old_getVersion()
          app.c文件:
          #include <stdio.h>
          __asm__(".symver getVersion,getVersion@VER1.0");
          extern int getVersion();
          int main()
          {
          printf("Version=%p\n", getVersion());
          return 0;
          }
          再次編譯連接b.c和app.c
          運行:
          ./app.exe
          結(jié)果:
          Version=0x1
          說明此次app.exe的確是連接的VER1.0的getVersion,即old_getVersion()
           
          十一、 表達式
          lds中表達式的文法與C語言的表達式文法一致,表達式的值都是整型,如果ld的運行主機和生成文件的目標(biāo)機都是32位,則表達式是32位數(shù)據(jù),否則是64位數(shù)據(jù)
          以下是一些常用的表達式:
          _fourk_1 = 4K; /* K、M單位 */
          _fourk_2 = 4096; /* 整數(shù) */
          _fourk_3 = 0×1000; /* 16 進位 */
          _fourk_4 = 01000; /* 8 進位 */
          注意:1K=1024 1M=1024*1024
           
          11.1、符號名
          沒有被引號”"包圍的符號,以字母、下劃線或’.'開頭,可包含字母、下劃線、’.'和’-'。當(dāng)符號名被引號包圍時,符號名可以與關(guān)鍵字相同。如,
          “SECTION”=9;
          “with a space” = “also with a space” + 10;
           
          11.2、定位符號’.'
          只在SECTIONS命令內(nèi)有效,代表一個程序地址空間內(nèi)的地址。
          注意:在連接時,當(dāng)定位符用在SECTIONS命令的輸出section描述內(nèi)時,它代表的是該section的當(dāng)前**偏移**,而不是程序地址空間的絕對地址。當(dāng)然當(dāng)程序載入后,符號最后的地址還是程序地址空間的絕對地址。
          示例11.2_1:
          SECTIONS
          {
          output :
          {
          file1(.text)
          . . + 1000;
          file2(.text)
          += 1000;
          file3(.text)
          } = 0×1234;
          }
          其中由于對定位符的賦值而產(chǎn)生的空隙由0×1234填充。其他的內(nèi)容應(yīng)該容易理解吧。
          示例11.2_2:
          SECTIONS
          {
          . = 0×100
          .text: {
          *(.text)
          = 0×200
          }
          = 0×500
          .data: {
          *(.data)
          . += 0×600
          }
          .text section在程序地址空間的開始位置是0x100
          示例11.2_3
          文件src\a.c
          #include <stdio.h>
          int a = 100;
          int b=0;
          int c=0;
          int d=1;
          int main()
          {
          printf( "&a=%p\n", &a );
          printf( "&b=%p\n", &b );
          printf( "&c=%p\n", &c );
          printf( "&d=%p\n", &d );
          return 0;
          }
          文件lds\a.lds
          a = 10; /* 全局位置 */
          SECTIONS
          {
          b = 11;
          .text :
          {
          *(.text)
          c = .; /* section描述內(nèi) */
          . = 10000;
          d = .;
          }
          _bdata = (. + 3) & ~ 4; /* SECTIONS命令內(nèi) */
          .data : { *(.data) }
          }
          在沒有使用a.lds情況下編譯
          gcc -Wall -o a-without-lds.exe ./src/a.c
          運行./a-without-lds.exe
          結(jié)果:
          &a=0x601020
          &b=0x601038
          &c=0x60103c
          &d=0x601024
          在使用a.lds情況下編譯
          gcc -Wall -o a-with-lds.exe ./src/a.c ./lds/a.lds
          運行./a-with-lds.exe
          結(jié)果:
          &a=0xa
          &b=0xb
          &c=0x400638
          &d=0x402b20
           
          10.3、表達式的操作符
          在lds中,表達式的操作符與C語言一致。
          優(yōu)先級 結(jié)合順序 操作符
          1 left ! – ~ (1)
          2 left * / %
          3 left + -
          4 left >>  =
          5 left &
          6 left |
          7 left &&
          8 left ||
          9 right ? :
          10 right &= += -= *= /= (2)
          (1)表示前綴符,(2)表示賦值符。
           
          10.4、表達式的計算
          連接器延遲計算大部分表達式的值。
          但是,對待與連接過程緊密相關(guān)的表達式,連接器會立即計算表達式,如果不能計算則報錯。比如,對于section的VMA地址、內(nèi)存區(qū)域塊的開始地址和大小,與其相關(guān)的表達式應(yīng)該立即被計算。
          例子,
          SECTIONS
          {
          .text 9+this_isnt_constant :
          { *(.text) }
          }
          這個例子中,9+this_isnt_constant表達式的值用于設(shè)置.text section的VMA地址,因此需要立即運算,但是由于this_isnt_constant變量的值不確定,所以此時連接器無法確立表達式的值,此時連接器會報錯。
           
          10.5、相對值與絕對值
          在輸出section描述內(nèi)的表達式,連接器取其相對值,相對與該section的開始位置的偏移
          SECTIONS命令內(nèi)且非輸出section描述內(nèi)的表達式,連接器取其絕對值
          通過ABSOLUTE關(guān)鍵字可以將相對值轉(zhuǎn)化成絕對值,即在原來值的基礎(chǔ)上加上表達式所在section的VMA值。
          示例
          SECTIONS
          {
          .data : { *(.data) ;_edata = ABSOLUTE(.); }
          }
          該例子中,_edata符號的值是.data section的末尾位置(絕對值,在程序地址空間內(nèi))。
           
          10.6、內(nèi)建函數(shù)
          lds中有以下一些內(nèi)建函數(shù):
          ABSOLUTE(EXP) :轉(zhuǎn)換成絕對值
          ADDR(SECTION) :返回某section的VMA值。
          ALIGN(EXP) :返回定位符’.'的按照EXP進行對齊后的修調(diào)值,對齊后的修調(diào)值算法為:(. + EXP – 1) & ~(EXP – 1)
          BLOCK(EXP) :如同ALIGN(EXP),為了向前兼容。
          DEFINED(SYMBOL) :如果符號SYMBOL在全局符號表內(nèi),且被定義了,那么返回1,否則返回0。
          示例
          SECTIONS { …
          .text : {
          begin = DEFINED(begin) ? begin : . ;
          }
          }
          LOADADDR(SECTION) :返回三SECTION的LMA
          MAX(EXP1,EXP2) :返回大者
          MIN(EXP1,EXP2) :返回小者
          NEXT(EXP) :返回下一個能被使用的地址,該地址是EXP的倍數(shù),類似于ALIGN(EXP)。除非使用了MEMORY命令定義了一些非連續(xù)的內(nèi)存塊,否則NEXT(EXP)與ALIGH(EXP)一定相同。
          SIZEOF(SECTION) :返回SECTION的大小。當(dāng)SECTION沒有被分配時,即此時SECTION的大小還不能確定時,連接器會報錯。
          SIZEOF_HEADERS :返回輸出文件頭部的字節(jié)數(shù)。這些信息出現(xiàn)在輸出文件的開始處。當(dāng)設(shè)置第一個段的開始地址時,你可以使用這個數(shù)字。如果你選擇了加速分頁,當(dāng)產(chǎn)生一個ELF輸出文件時,如果鏈接器腳本使用SIZEOF_HEADERS內(nèi)建函數(shù),連接器必須在它
          算出所有段地址和長度之前計算程序頭部的數(shù)值。如果連接器后來發(fā)現(xiàn)它需要附加程序頭,它將報告一個“not enough room for 
          program headers”錯誤。為了避免這樣的錯誤,你必須避免使用SIZEOF_HEADERS函數(shù),或者你必須修改你的連接器腳本去避免強制
          連接器去使用附加程序頭,或者你必須使用PHDRS命令去定義你自己的程序頭
           
          十二、 暗含的連接腳本
          輸入文件可以是目標(biāo)文件,也可以是連接腳本,此時的連接腳本被稱為 暗含的連接腳本
          如果連接器不認(rèn)識某個輸入文件,那么該文件被當(dāng)作連接腳本被解析。更進一步,如果發(fā)現(xiàn)它的格式又不是連接腳本的格式,那么連接器報錯。
          一個暗含的連接腳本不會替換默認(rèn)的連接腳本,僅僅是增加新的連接而已。
          一般來說,暗含的連接腳本符號分配命令,或INPUT、GROUP、VERSION命令。
          在連接命令行中,每個輸入文件的順序都被固定好了,暗含的連接腳本在連接命令行內(nèi)占住一個位置,這個位置決定了由該連接腳本指定的輸入文件在連接過程中的順序。



          *博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點,如有侵權(quán)請聯(lián)系工作人員刪除。



          關(guān)鍵詞: lds

          技術(shù)專區(qū)

          關(guān)閉