亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

Chinaunix

標題: GNU-ld連接腳本 參考 [打印本頁]

作者: zleil    時間: 2005-11-08 20:01
標題: GNU-ld連接腳本 參考
GNU-ld連接腳本   Linker Scripts
------------------------------------
E-mail: zhanglei@sict.ac.cn
2005.11.5-2005.11.8
注:以下信息直接來源于info ld文檔和作者的實踐
由于本人也只是個初學者,歡迎讀者指出錯誤之處。非常感謝。
1. 概論
2. 基本概念
3. 腳本格式
4. 簡單例子
5. 簡單腳本命令
6. 對符號的賦值
7. SECTIONS命令
8. MEMORY命令
9. PHDRS命令
10. VERSION命令
11. 腳本內(nèi)的表達式
12. 暗含的連接腳本
GNU-ld連接腳本 Linker Scripts
-----------------------------
E-mail: zhanglei@sict.ac.cn
2005.11.5-2005.11.8
注:以下信息直接來源于info ld文檔和作者的實踐
由于本人也只是個初學者,歡迎讀者指出錯誤之處。非常感謝。
1. 概論
2. 基本概念
3. 腳本格式
4. 簡單例子
5. 簡單腳本命令
6. 對符號的賦值
7. SECTIONS命令
8. MEMORY命令
9. PHDRS命令
10. VERSION命令
11. 腳本內(nèi)的表達式
12. 暗含的連接腳本
1. 概論
-------
    每一個連接過程都由連接腳本控制。
    連接腳本主要用于,怎樣把輸入文件內(nèi)的section放入輸出文件內(nèi),并且控制輸出文件內(nèi)各部分在程序地址空間內(nèi)的布局。但你也可以用連接命令做一些其他事情。
    連接器有個默認的內(nèi)置連接腳本,可以用命令ld --verbose查看。連接選項-r和-N可以影響默認的連接腳本(如何影響?)。
    -T選項可以指定自己的連接腳本,它將**代替**默認的連接腳本。你也可以使用以**增加**自己的連接命令。
    以下沒有特殊說明,連接器指的是*靜態(tài)連接器*。
2. 基本概念
-----------
    連接器把一個或多個輸入文件合成一個輸出文件。
    輸入文件:為目標文件或連接腳本文件,目標文件有固定的文件格式,linux下常見的是elf文件格式
    輸出文件:為目標文件或可執(zhí)行文件,linux下常見的是elf文件格式
    有時把輸入文件內(nèi)的section稱為,輸入section(input section)
    有時把輸出文件內(nèi)的section稱為,輸出section(output sectin)
    目標文件的每個section至少包含兩個信息:名字和大小。大部分section還包含與它相關(guān)連的一塊數(shù)據(jù),稱為section contents(section內(nèi)容)。一個section可以被標記為“l(fā)odable(可加載的)”和“allocatable(可分配的)”。如果某section被標記為“可加載的”,那么在輸出文件運行時,相應(yīng)的section內(nèi)容將被載入進程地址空間中。沒有section內(nèi)容的section可以被標記為“可分配的”,在輸出文件運行時,部分進程地址空間(大小為該section指定的大小)應(yīng)該被空出來,某些情況下,這塊內(nèi)存必須被置零。如果一個section不是“可加載的”或“可分配的”,那么該section內(nèi)容通常是調(diào)試信息?捎胦bjdump -h命令查看到相關(guān)信息。
    每個“可加載的”或“可分配的”輸出section通常包含兩個地址,VMA(virtual memory address虛擬內(nèi)存地址或程序地址空間地址)和LMA(load memory address加載內(nèi)存地址或進程地址空間地址)。通常VMA和LMA是相同的。
    例子,讀者可這樣來理解VMA和LMA。假設(shè).data section對應(yīng)的VMA地址是0x08050000
    .data section內(nèi)包含了兩個全局變量,變量名分別是i、j和k,并假設(shè)都是32位的,其值分別為1,2,3。
    .text section內(nèi)包含由如下c程序片段產(chǎn)生的代碼
    printf( "j=%d
", j );
    連接時指定.data section的VMA為0x08050000,產(chǎn)生的printf指令是將地址為0x08050004處的4字節(jié)內(nèi)容作為一個整數(shù)打印出來。
    如果.data section的LMA為0x08050000,顯然結(jié)果是j=2
    如果.data section的LMA為0x08050004,顯然結(jié)果是j=1
    例子,讀者可這樣理解LMA。
    .text section內(nèi)容的開始處包含如下兩條指令(intel i386指令是10字節(jié),每行對應(yīng)5字節(jié)),
    jmp  0x08048285
    movl $0x1,%eax
    如果.text section的LMA為0x08048280,那么在進程地址空間內(nèi)0x08048280處為“jmp 0x08048285”指令,0x08048285處為movl $0x1,%eax指令。假設(shè)某指令跳轉(zhuǎn)到地址0x08048280,顯然它的執(zhí)行將導致%eax寄存器被賦值為1。
    如果.text section的LMA為0x08048285,那么在進程地址空間內(nèi)0x08048285處為“jmp 0x08048285”指令,0x0804828a處為movl $0x1,%eax指令。假設(shè)某指令跳轉(zhuǎn)到地址0x08048285,顯然它的執(zhí)行又跳轉(zhuǎn)到進程地址空間內(nèi)0x08048285處,造成死循環(huán)。
    符號:每個目標文件都有符號表,包含已定義的符號(對應(yīng)全局變量和static變量和定義的函數(shù)的名字)和未定義符號(未定義的函數(shù)的名字和引用但沒定義的符號)信息。
    符號值:每個符號對應(yīng)一個地址,即符號值(這與c程序內(nèi)變量的值不一樣,某種情況下可以把它看成變量的地址)?捎胣m命令查看他們。
3. 腳本格式
-----------
   連接腳本由一系列命令組成,每個命令由一個關(guān)鍵字(一般情況后面緊跟相關(guān)參數(shù))或一條對符號的賦值語句。命令由分號‘;’分隔開。
   文件名或格式名內(nèi)如果包分號‘;’或其他分割符,那么請用引號‘"’將名字全稱包含在一起。無法處理含引號的文件名。
   /* */之間的是注釋。
4. 簡單例子
-----------
    假設(shè)輸出程序僅包含.text section,.data section,.bss section。輸入文件也是。
    以下腳本將輸出文件的代碼section定位在0x10000,數(shù)據(jù)section定位在0x8000000:
    SECTIONS
    {
      . = 0x10000;
      .text : { *(.text) }
      . = 0x8000000;
      .data : { *(.data) }
      .bss : { *(.bss) }
    }
    . = 0x10000  :把定位器符號置為0x10000,如果不指定,該符號的初始值是0
    .text : { *(.text) } :將所有(*符號代表任意輸入文件)輸入文件的.text section合并成一個.text section,該section的地址由定位器符號的值指定,即0x10000.
    . = 0x8000000 :把定位器符號置為0x8000000
    .data : { *(.data) } :將所有輸入文件的.text section合并成一個.data section,該section的地址被置為0x8000000
    .bss : { *(.bss) } :將所有輸入文件的.bss section合并成一個.bss section,該section的地址被置為0x8000000+.data section的大小。
    連接器每讀完一個section描述后,將定位器符號的值*增加*該section的大小。
    注意:此處沒有考慮  對齊約束。
    到此為止,讀者應(yīng)該對連接腳本有一個感性的認識了吧。
5. 簡單腳本命令
---------------
設(shè)置進程入口地址(即進程執(zhí)行的第一條用戶空間的指令在進程地址空間的地址):
    ENTRY(SYMBOL)
    此命令將符號SYMBOL的值設(shè)置成入口地址。
    連接器按如下順序設(shè)置進程入口地址:
    ld命令行的-e選項
    連接腳本的ENTRY(SYMBOL)命令
    如果定義了start符號,使用start符號值
    如果存在.text section,使用.text section的第一字節(jié)的位置值
    使用值0
文件命令:
    INCLUDE FILENAME:相當于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(FILE, FILE, ...):
    INPUT(FILE FILE ...):將括號內(nèi)的文件做為連接過程的輸入文件,連接器首先在當前目錄下尋找該文件,如果沒找到,連接器會在由-L指定的搜索路徑下找。FILE可以為-lFILE形式,就象命令行的-l選項一樣。如果該命令出現(xiàn)在暗含的腳本內(nèi),那么該命令內(nèi)的FILE在連接過程中的順序由該暗含的腳本在命令行內(nèi)的順序決定。
    GROUP(FILE, FILE, ...):
    GROUP(FILE FILE ...):其中,F(xiàn)ILE必須是庫文件,并且FILE內(nèi)的文件作為一組被連接器重復掃描,直到不在有新的未定義的引用出現(xiàn)。
    OUTPUT(FILENAME):定義輸出文件的名字,同連接器的-o選項一樣,不過-o選項的優(yōu)先級更高。所以它可以用來定義默認的輸出文件名,如:a.out
    SEARCH_DIR(PATH):同連接器的-L選項,定義搜索路徑,不過由-L指定的路徑要比它定義的優(yōu)先被搜索。
    STARTUP(FILENAME):在連接過程中,每個輸入文件是有順序的。此命令設(shè)置文件FILENAME為第一個輸入文件。
處理目標文件格式的命令:
    OUTPUT_FORMAT(BFDNAME):設(shè)置輸出文件使用的BFD格式,如同連接器選項-oformat BFDNAME,不過連接器選項優(yōu)先級跟高
    OUTPUT_FORMAT(DEFAULT,BIG,LITTLE):設(shè)置三個BFD格式,如果有命令行選項-EB,則使用第一個BFD格式,如果有命令行選項-EL,則使用第二個BFD格式,如果兩者都沒有,則選第一個BFD格式。
    TARGET(BFDNAME):設(shè)置輸入文件的BFD格式,如同連接器選項-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查看。
6. 對符號的賦值
---------------
    在目標文件內(nèi)定義的符號可以在連接腳本內(nèi)被賦值。此時該符號被定義為全局的。
    每個符號都對應(yīng)了一個地址,此處的賦值是更改這個符號對應(yīng)的地址。
例子:
$ cat a.c
/* a.c */
int i = 100;
int main()
{
    printf( "&i=0x%x
", &i );
    return 0;
}
$ cat a.lds
/* a.lds */
i = 3;
$ gcc a.lds a.c
$ ./a.out
&i=0x3
$
簡單的賦值語句
    能使用任何c語言內(nèi)的賦值操作,如下
    SYMBOL = EXPRESSION ;
    SYMBOL += EXPRESSION ;
    SYMBOL -= EXPRESSION ;
    SYMBOL *= EXPRESSION ;
    SYMBOL /= EXPRESSION ;
    SYMBOL >= EXPRESSION ;
    SYMBOL &= EXPRESSION ;
    SYMBOL |= EXPRESSION ;
    除了第一類表達式外,使用其他表達式需要SYMBOL被定義于某目標文件。
    . 是一個特殊的符號,它是定位器,一個位置指針,指向程序地址空間內(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)和全局位置;如下,
    floating_point = 0; /* 全局位置 */
    SECTIONS
    {
      .text :
      {
        *(.text)
        _etext = .; /* section描述內(nèi) */
      }
      _bdata = (. + 3) & ~ 4; /* SECTIONS命令內(nèi) */
      .data : { *(.data) }
    }
PROVIDE關(guān)鍵字
    該關(guān)鍵字用于定義這類符號:在目標文件內(nèi)被引用,但沒有在任何目標文件內(nèi)被定義的符號。
    例子:
     SECTIONS
     {
       .text :
       {
         *(.text)
         _etext = .;
         PROVIDE(etext = .);
       }
     }
     當目標文件內(nèi)引用了etext符號,確沒有定義它時,etext符號對應(yīng)的地址被定義為.text section之后的第一個字節(jié)的地址。
7. SECTIONS命令
---------------
    SECTIONS命令告訴連接器如何把輸入section合為輸出section,如何把輸出section放入程序地址空間(VMA)和進程地址空間(LMA)。該命令格式如下:
    SECTIONS
    {
      SECTIONS-COMMAND
      SECTIONS-COMMAND
      ...
    }
    SECTION-COMMAND有四種:
        ENTRY命令
        符號賦值語句
        一個輸出section的描述
        一個section疊加描述
    如果整個連接腳本內(nèi)沒有SECTIONS命令,那么連接器將所有同名輸入section合成為一個輸出section內(nèi),各輸入section的順序為它們被連接器發(fā)現(xiàn)的順序。
    如果某輸入section沒有在SECTIONS命令中提到,那么該section將被直接拷貝成輸出section。
輸出section描述:
    輸出section描述具有如下格式,
    SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
    {
      OUTPUT-SECTION-COMMAND
      OUTPUT-SECTION-COMMAND
      ...
    } [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]
    [ ]內(nèi)的內(nèi)容為可選選項,大部分描述沒有用到它們。
    SECTION:section名字
    SECTION左右的空白、圓括號、冒號是必須的,換行符和其他空格是可選的。
    每個OUTPUT-SECTION-COMMAND為以下四種之一,
        符號賦值語句
        一個輸入section描述
        直接包含的數(shù)據(jù)值
        一個特殊的輸出section關(guān)鍵字
輸出section名字(SECTION):
    輸出section名字必須符合輸出文件格式要求,比如:a.out格式的文件只允許存在.text、.data和.bss section名。而有的格式只允許存在數(shù)字名字,那么此時應(yīng)該用引號將所有名字內(nèi)的數(shù)字組合在一起;另外,還有一些格式允許任何序列的字符存在于section名字內(nèi),此時如果名字內(nèi)包含特殊字符(比如空格、逗號等),那么需要用引號將其組合在一起。
輸出section地址(ADDRESS):
    ADDRESS是一個表達式,它的值用于設(shè)置VMA。如果沒有該選項且有REGION選項,那么連接器將根據(jù)REGION設(shè)置VMA;如果也沒有REGION選項,那么連接器將根據(jù)定位符號‘.’的值設(shè)置該section的VMA,將定位符號的值調(diào)整到滿足輸出section對齊要求后的值,輸出section的對齊要求為:該輸出section描述內(nèi)用到的所有輸入section的對齊要求中最嚴格的。
    例子:
    .text . : { *(.text) }
    和
    .text : { *(.text) }
    這兩個描述是截然不同的,第一個將.text section的VMA設(shè)置為定位符號的值,而第二個則是設(shè)置成定位符號的修調(diào)值,滿足對齊要求后的。
    ADDRESS可以是一個任意表達式,比如ALIGN(0x10)這將把該section的VMA設(shè)置成定位符號的修調(diào)值,滿足16字節(jié)對齊后的。
    注意:設(shè)置ADDRESS值,將更改定位符號的值。
輸入section描述:
    最常見的輸出section描述命令是輸入section描述。
    輸入section描述是最基本的連接腳本描述。
輸入section描述基礎(chǔ):
    基本語法: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)的文件的。
    當FILENAME是一個特定的文件名時,連接器會查看它是否在連接命令行內(nèi)出現(xiàn)或在INPUT命令中出現(xiàn)。
    當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),通配符不匹配文件夾分隔符/,但當字符串模式僅包含通配符*時除外。
    任何一個文件的任意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:
    在許多目標文件格式中,通用符號并沒有占用一個section。連接器認為:輸入文件的所有通用符號在名為COMMON的section內(nèi)。
    例子,
    .bss { *(.bss) *(COMMON) }
    這個例子中將所有輸入文件的所有通用符號放入輸出.bss section內(nèi)?梢钥吹紺OMMOM section的使用方法跟其他section的使用方法是一樣的。
    有些目標文件格式把通用符號分成幾類。例如,在MIPS elf目標文件格式中,把通用符號分成standard common symbols(標準通用符號)和small common symbols(微通用符號,不知道這么譯對不對?),此時連接器認為所有standard common symbols在COMMON section內(nèi),而small common symbols在.scommon section內(nèi)。
    在一些以前的連接腳本內(nèi)可以看見[COMMON],相當于*(COMMON),不建議繼續(xù)使用這種陳舊的方式。
輸入section和垃圾回收:
    在連接命令行內(nèi)使用了選項--gc-sections后,連接器可能將某些它認為沒用的section過濾掉,此時就有必要強制連接器保留一些特定的section,可用KEEP()關(guān)鍵字達此目的。如KEEP(*(.text))或KEEP(SORT(*)(.text))
最后看個簡單的輸入section相關(guān)例子:
    SECTIONS {
      outputa 0x10000 :
      {
        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依次放入輸出outputa section內(nèi),該section的VMA是0x10000;將foo.o文件的所有.input2 section和foo1.o文件的所有.input1 section依次放入輸出outputb section內(nèi),該section的VMA是當前定位器符號的修調(diào)值(對齊后);將其他文件(非all.o、foo.o、foo1.o)文件的.input1 section和.input2 section放入輸出outputc section內(nèi)。
在輸出section存放數(shù)據(jù)命令:
    能夠顯示地在輸出section內(nèi)填入你想要填入的信息(這樣是不是可以自己通過連接腳本寫程序?當然是簡單的程序)。
    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,可以由輸出目標文件的格式?jīng)Q定;如果輸出目標文件的格式不能決定字節(jié)順序,那么字節(jié)順序與第一個輸入文件的字節(jié)順序相同。
    如:BYTE(1)、LANG(addr)。
    注意,這些命令只能放在輸出section描述內(nèi),其他地方不行。
    錯誤:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
    正確:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }
    在當前輸出section內(nèi)可能存在未描述的存儲區(qū)域(比如由于對齊造成的空隙),可以用FILL(EXPRESSION)命令決定這些存儲區(qū)域的內(nèi)容,EXPRESSION的前兩字節(jié)有效,這兩字節(jié)在必要時可以重復被使用以填充這類存儲區(qū)域。如FILE(0x9090)。在輸出section描述中可以有=FILEEXP屬性,它的作用如同F(xiàn)ILE()命令,但是FILE命令只作用于該FILE指令之后的section區(qū)域,而=FILEEXP屬性作用于整個輸出section區(qū)域,且FILE命令的優(yōu)先級更高。!
輸出section內(nèi)命令的關(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目標文件格式,連接器用一些不尋常的方法實現(xiàn)c++的全局構(gòu)造和全局析構(gòu)。當連接器生成的目標文件格式不支持任意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)用。是不是對于某些目標文件格式才這樣???
    對于支持任意section名的目標文件格式,比如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))。一般來說,默認的連接腳本已作好的這些工作。
   
輸出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被它自己引用呢?想想看。
輸出section屬性:
    終于講到這里了,呵呵。
    我們再回顧以下輸出section描述的文法:
    SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
    {
       OUTPUT-SECTION-COMMAND
       OUTPUT-SECTION-COMMAND
       ...
    } [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]
    前面我們?yōu)g覽了SECTION、ADDRESS、OUTPUT-SECTION-COMMAND相關(guān)信息,下面我們將瀏覽其他屬性。
   
    TYPE :每個輸出section都有一個類型,如果沒有指定TYPE類型,那么連接器根據(jù)輸出section引用的輸入section的類型設(shè)置該輸出section的類型。它可以為以下五種值,
           NOLOAD :該section在程序運行時,不被載入內(nèi)存。
           DSECT,COPY,INFO,OVERLAY :這些類型很少被使用,為了向后兼容才被保留下來。這種類型的section必須被標記為“不可加載的”,以便在程序運行不為它們分配內(nèi)存。
    輸出section的LMA :默認情況下,LMA等于VMA,但可以通過關(guān)鍵字AT()指定LMA。
    用關(guān)鍵字AT()指定,括號內(nèi)包含表達式,表達式的值用于設(shè)置LMA。如果不用AT()關(guān)鍵字,那么可用AT>LMA_REGION表達式設(shè)置指定該section加載地址的范圍。
    這個屬性主要用于構(gòu)件ROM境象。
    例子,
    SECTIONS
    {
      .text 0x1000 : { *(.text) _etext = . ; }
      .mdata 0x2000 :
        AT ( ADDR (.text) + SIZEOF (.text) )
        { _data = . ; *(.data); _edata = . ;  }
      .bss 0x3000 :
        { _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 }
    輸出section所在的程序段:可以將輸出section放入預先定義的程序段(program segment)內(nèi)。如果某個輸出section設(shè)置了它所在的一個或多個程序段,那么接下來定義的輸出section的默認程序段與該輸出section的相同。除非再次顯示地指定。例子,
       PHDRS { text PT_LOAD ; }
       SECTIONS { .text : { *(.text) } :text }
       可以通過:NONE指定連接器不把該section放入任何程序段內(nèi)。詳情請查看PHDRS命令
    輸出section的填充模版:這個在前面提到過,任何輸出section描述內(nèi)的未指定的內(nèi)存區(qū)域,連接器用該模版填充該區(qū)域。用法:=FILEEXP,前兩字節(jié)有效,當區(qū)域大于兩字節(jié)時,重復使用這兩字節(jié)以將其填滿。例子,
        SECTIONS { .text : { *(.text) } =0x9090 }
覆蓋圖(overlay)描述:
    覆蓋圖描述使兩個或多個不同的section占用同一塊程序地址空間。覆蓋圖管理代碼負責將section的拷入和拷出?紤]這種情況,當某存儲塊的訪問速度比其他存儲塊要快時,那么如果將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。SECNAME2的LMA為SECTNAME1的LMA加上SECNAME1的大小,同理計算SECNAME2,3,4...的LMA。SECNAME1的LMA由LDADDR決定,如果它沒有被指定,那么由START決定,如果它也沒有被指定,那么由當前定位符號的值決定。
    NOCROSSREFS關(guān)鍵字指定各section之間不能交叉引用,否則報錯。
    對于OVERLAY描述的每個section,連接器將定義兩個符號__load_start_SECNAME和__load_stop_SECNAME,這兩個符號的值分別代表SECNAME section的LMA地址的開始和結(jié)束。
    連接器處理完OVERLAY描述語句后,將定位符號的值加上所有覆蓋圖內(nèi)section大小的最大值。
    看個例子吧,
    SECTIONS{
      ...
      OVERLAY 0x1000 : AT (0x4000)
      {
        .text0 { o1/*.o(.text) }
        .text1 { o2/*.o(.text) }
      }
      ...
    }
    .text0 section和.text1 section的VMA地址是0x1000,.text0 section加載于地址0x4000,.text1 section緊跟在其后。
    程序代碼,拷貝.text1 section代碼,
    extern char __load_start_text1, __load_stop_text1;
    memcpy ((char *) 0x1000, &__load_start_text1,
             &__load_stop_text1 - &__load_start_text1);
8. 內(nèi)存區(qū)域命令
---------------
    注意:以下存儲區(qū)域指的是在程序地址空間內(nèi)的。
    在默認情形下,連接器可以為section分配任意位置的存儲區(qū)域。你也可以用MEMORY命令定義存儲區(qū)域,并通過輸出section描述的>REGION屬性顯示地將該輸出section限定于某塊存儲區(qū)域,當存儲區(qū)域大小不能滿足要求時,連接器會報告該錯誤。
    MEMORY命令的文法如下,
    MEMORY {
      NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2
      NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
      ...
    }
    NAME :存儲區(qū)域的名字,這個名字可以與符號名、文件名、section名重復,因為它處于一個獨立的名字空間。
    ATTR :定義該存儲區(qū)域的屬性,在講述SECTIONS命令時提到,當某輸入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 = 0x40000000, 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)下一個能使用位置。
9. PHDRS命令
------------
    該命令僅在產(chǎn)生ELF目標文件時有效。
    ELF目標文件格式用program headers程序頭(程序頭內(nèi)包含一個或多個segment程序段描述)來描述程序如何被載入內(nèi)存?梢杂胦bjdump -p命令查看。
    當在本地ELF系統(tǒng)運行ELF目標文件格式的程序時,系統(tǒng)加載器通過讀取程序頭信息以知道如何將程序加載到內(nèi)存。要了解系統(tǒng)加載器如何解析程序頭,請參考ELF ABI文檔。
    在連接腳本內(nèi)不指定PHDRS命令時,連接器能夠很好的創(chuàng)建程序頭,但是有時需要更精確的描述程序頭,那么PAHDRS命令就派上用場了。
    注意:一旦在連接腳本內(nèi)使用了PHDRS命令,那么連接器**僅會**創(chuàng)建PHDRS命令指定的信息,所以使用時須謹慎。
    PHDRS命令文法如下,
    PHDRS
    {
      NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]
            [ FLAGS ( FLAGS ) ] ;
    }
    其中FILEHDR、PHDRS、AT、FLAGS為關(guān)鍵字。
    NAME :為程序段名,此名字可以與符號名、section名、文件名重復,因為它在一個獨立的名字空間內(nèi)。此名字只能在SECTIONS命令內(nèi)使用。
    一個程序段可以由多個‘可加載’的section組成。通過輸出section描述的屬性:PHDRS可以將輸出section加入一個程序段,:PHDRS中的PHDRS為程序段名。在一個輸出section描述內(nèi)可以多次使用:PHDRS命令,也即可以將一個section加入多個程序段。
    如果在一個輸出section描述內(nèi)指定了:PHDRS屬性,那么其后的輸出section描述將默認使用該屬性,除非它也定義了:PHDRS屬性。顯然當多個輸出section屬于同一程序段時可簡化書寫。
    在TYPE屬性后存在FILEHDR關(guān)鍵字,表示該段包含ELF文件頭信息;存在PHDRS關(guān)鍵字,表示該段包含ELF程序頭信息。
    TYPE可以是以下八種形式,
        PT_NULL         0
            表示未被使用的程序段
        PT_LOAD         1
            表示該程序段在程序運行時應(yīng)該被加載
        PT_DYNAMIC      2
            表示該程序段包含動態(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ù)字,該表達式定義一個用戶自定的程序頭。
    AT(ADDRESS)屬性定義該程序段的加載位置(LMA),該屬性將**覆蓋**該程序段內(nèi)的section的AT()屬性。
    默認情況下,連接器會根據(jù)該程序段包含的section的屬性(什么屬性?好象在輸出section描述內(nèi)沒有看到)設(shè)置FLAGS標志,該標志用于設(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 */
       ...
       . = . + 0x1000; /* move to a new page in memory */
       .data : { *(.data) } :data
       .dynamic : { *(.dynamic) } :data :dynamic
       ...
     }
10. 版本號命令
--------------
    當使用ELF目標文件格式時,連接器支持帶版本號的符號。
    讀者可以發(fā)現(xiàn)僅僅在共享庫中,符號的版本號屬性才有意義。
    動態(tài)加載器使用符號的版本號為應(yīng)用程序選擇共享庫內(nèi)的一個函數(shù)的特定實現(xiàn)版本。
    可以在連接腳本內(nèi)直接使用版本號命令,也可以將版本號命令實現(xiàn)于一個特定版本號描述文件(用連接選項--version-script指定該文件)。
    該命令的文法如下,
    VERSION { version-script-commands }
    以下內(nèi)容直接拷貝于以前的文檔,
=====================   開始   ==================================
內(nèi)容簡介
---------
0 前提
1 帶版本號的符號的定義
2 連接到帶版本的符號
3 GNU擴充
4 我的疑問
5 英文搜索關(guān)鍵字
6 我的參考
0. 前提
-- 只限于ELF文件格式
-- 以下討論用gcc
1. 帶版本號的符號的定義(共享庫內(nèi))
文件b.c內(nèi)容如下,
int old_true()
{
        return 1;
}
int new_true()
{
        return 2;
}
寫連接器的版本控制腳本,本例中為b.lds,內(nèi)容如下
VER1.0{
    new_true;
};
VER2.0{
};
$gcc -c b.c
$gcc -shared -Wl,--version-script=b.lds -o libb.so b.o
可以在{}內(nèi)填入要綁定的符號,本例中new_true符號就與VER1.0綁定了。
那么如果有一個應(yīng)用程序連接到該庫的new_true符號,那么它連接的就是VER1.0版本的new_true符號
如果把b.lds更改為,
VER1.0{
};
VER2.0{
    new_true;
};
然后在生成libb.so文件,在運行那個連接到VER1.0版本的new_true符號的應(yīng)用程序,可以發(fā)現(xiàn)該應(yīng)用程序不能運行了,
因為庫內(nèi)沒有VER1.0版本的new_true,只有VER2.0版本的new_true。
2. 連接到帶版本的符號
寫一個簡單的應(yīng)用(名為app)連接到libb.so,應(yīng)用符號new_true
假設(shè)libb.so的版本控制文件為,
VER1.0{
};
VER2.0{
    new_true;
};
$ nm app | grep new_true
         U new_true@@VER1.0
$
用nm命令發(fā)現(xiàn)app連接到VER1.0版本的new_true
3. GNU的擴充
它允許在程序文件內(nèi)綁定 *符號* 到 *帶版本號的別名符號*
文件b.c內(nèi)容如下,
int old_true()
{
        return 1;
}
int new_true()
{
        return 2;
}
__asm__( ".symver old_true,true@VER1.0" );
__asm__( ".symver new_true,true@@VER2.0" );
其中,帶版本號的別名符號是true,其默認的版本號為VER2.0
供連接器用的版本控制腳本b.lds內(nèi)容如下,
VER1.0{
};
VER2.0{
};
版本控制文件內(nèi)必須包含版本VER1.0和版本VER2.0的定義,因為在b.c文件內(nèi)有對他們的引用
****** 假定libb.so與app.c在同一目錄下 ********
以下應(yīng)用程序app.c連接到該庫,
int true();
int main()
{
    printf( "%d
", true );
}
$ gcc app.c libb.so
$ LD_LIBRARY_PATH=. ./app
2
$ nm app | grep true
         U true@@VER2.0
$
很明顯,程序app使用的是VER2.0版本的別名符號true,如果在b.c內(nèi)沒有指明別名符號true的默認版本,
那么gcc app.c libb.so將出現(xiàn)連接錯誤,提示true沒有定義。
也可以在程序內(nèi)指定特定版本的別名符號true,程序如下,
__asm__( ".symver true,true@VER1.0" );
int true();
int main()
{
    printf( "%d
", true );
}
$ gcc app.c libb.so
$ LD_LIBRARY_PATH=. ./app
1
$ nm app | grep true
         U true@VER1.0
$
顯然,連接到了版本號為VER1.0的別名符號true。其中只有一個@表示,該版本不是默認的版本
我的疑問:
    版本控制腳本文件中,各版本號節(jié)點之間的依賴關(guān)系
英文搜索關(guān)鍵字:
    .symver
    versioned symbol
    version a shared library
參考:
    info ld, Scripts node
=====================   結(jié)束   ==================================
11. 表達式
----------
    表達式的文法與C語言的表達式文法一致,表達式的值都是整型,如果ld的運行主機和生成文件的目標機都是32位,則表達式是32位數(shù)據(jù),否則是64位數(shù)據(jù)。
    能夠在表達式內(nèi)使用符號的值,設(shè)置符號的值。
    下面看六項表達式相關(guān)內(nèi)容,
常表達式:
    _fourk_1 = 4K; /* K、M單位 */
    _fourk_2 = 4096; /* 整數(shù) */
    _fourk_3 = 0x1000; /* 16 進位 */
    _fourk_4 = 01000; /* 8 進位 */
    1K=1024 1M=1024*1024
符號名:
    沒有被引號""包圍的符號,以字母、下劃線或'.'開頭,可包含字母、下劃線、'.'和'-'。當符號名被引號包圍時,符號名可以與關(guān)鍵字相同。如,
    "SECTION"=9
    "with a space" = "also with a space" + 10;
定位符號'.':
    只在SECTIONS命令內(nèi)有效,代表一個程序地址空間內(nèi)的地址。
    注意:當定位符用在SECTIONS命令的輸出section描述內(nèi)時,它代表的是該section的當前**偏移**,而不是程序地址空間的絕對地址。
    先看個例子,
    SECTIONS
    {
      output :
      {
        file1(.text)
        . = . + 1000;
        file2(.text)
        . += 1000;
        file3(.text)
      } = 0x1234;
    }
    其中由于對定位符的賦值而產(chǎn)生的空隙由0x1234填充。其他的內(nèi)容應(yīng)該容易理解吧。
    再看個例子,
    SECTIONS
    {
      . = 0x100
      .text: {
        *(.text)
        . = 0x200
      }
      . = 0x500
      .data: {
        *(.data)
        . += 0x600
      }
    } .text section在程序地址空間的開始位置是0x
表達式的操作符:
    與C語言一致。
    優(yōu)先級          結(jié)合順序        操作符                  
    1               left            !  -  ~                  (1)
    2               left            *  /  %
    3               left            +  -
    4               left            >>    =
    6               left            &
    7               left            |
    8               left            &&
    9               left            ||
    10              right           ? :
    11              right           &=  +=  -=  *=  /=       (2)
    (1)表示前綴符,(2)表示賦值符。
表達式的計算:
    連接器延遲計算大部分表達式的值。
    但是,對待與連接過程緊密相關(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變量的值不確定,所以此時連接器無法確立表達式的值,此時連接器會報錯。
相對值與絕對值:
    在輸出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))。
內(nèi)建函數(shù):
    ABSOLUTE(EXP) :轉(zhuǎn)換成絕對值
    ADDR(SECTION) :返回某section的VMA值。
    ALIGN(EXP) :返回定位符'.'的修調(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的大小。當SECTION沒有被分配時,即此時SECTION的大小還不能確定時,連接器會報錯。
    SIZEOF_HEADERS :
    sizeof_headers :返回輸出文件的文件頭大小(還是程序頭大小),用以確定第一個section的開始地址(在文件內(nèi))。???
12. 暗含的連接腳本
------------------
    輸入文件可以是目標文件,也可以是連接腳本,此時的連接腳本被稱為   暗含的連接腳本
    如果連接器不認識某個輸入文件,那么該文件被當作連接腳本被解析。更進一步,如果發(fā)現(xiàn)它的格式又不是連接腳本的格式,那么連接器報錯。
    一個暗含的連接腳本不會替換默認的連接腳本,僅僅是增加新的連接而已。
    一般來說,暗含的連接腳本符號分配命令,或INPUT、GROUP、VERSION命令。
    在連接命令行中,每個輸入文件的順序都被固定好了,暗含的連接腳本在連接命令行內(nèi)占住一個位置,這個位置決定了由該連接腳本指定的輸入文件在連接過程中的順序。
    典型的暗含的連接腳本是libc.so文件,在GNU/linux內(nèi)一般存在/usr/lib目錄下。
2005年11月6日,悲慘啊。21天,3個星期。
在極度郁悶的情況下,居然能把這東西弄完,呵呵。我不會是天才吧
看樣子我是真的蠢到家了,早就有人翻譯了全部info ld文檔。蠢才一個。


本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u/7038/showart_55906.html




歡迎光臨 Chinaunix (http://www.72891.cn/) Powered by Discuz! X3.2