寫一個ld文件

OUTPUT_FORMAT("elf32-tradlittlemips") OUTPUT_ARCH(mips) ENTRY(_start) SECTIONS { . = 0x80100000; .text : { _ftext = . ; *(.text) *(.rodata) *(.rodata1) *(.reginfo) *(.init) *(.stub) *(.gnu.warning) } =0 _etext = .; PROVIDE (etext = .); .fini : { *(.fini) } =0 .data : { _fdata = . ; *(.data) CONSTRUCTORS } .data1 : { *(.data1) } .ctors : { __CTOR_LIST__ = .; LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2) *(.ctors) LONG(0) __CTOR_END__ = .; } .dtors : { __DTOR_LIST__ = .; LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2) *(.dtors) LONG(0) __DTOR_END__ = .; } _gp = ALIGN(16) + 0x7ff0; .got : { *(.got.plt) *(.got) } .sdata : { *(.sdata) } .lit8 : { *(.lit8) } .lit4 : { *(.lit4) } _edata = .; PROVIDE (edata = .); __bss_start = .; _fbss = .; .sbss : { *(.sbss) *(.scommon) } .bss : { *(.dynbss) *(.bss) *(COMMON) } . = ALIGN(16); __bss_end = .; _end = .;__end = .; end = .; PROVIDE (end = .); .stab 0 : { *(.stab) } .stabstr 0 : { *(.stabstr) } .debug 0 : { *(.debug) } .debug_srcinfo 0 : { *(.debug_srcinfo) } .debug_aranges 0 : { *(.debug_aranges) } .debug_pubnames 0 : { *(.debug_pubnames) } .debug_sfnames 0 : { *(.debug_sfnames) } .line 0 : { *(.line) } .gptab.sdata : { *(.gptab.data) *(.gptab.sdata) } .gptab.sbss : { *(.gptab.bss) *(.gptab.sbss) } }

下面逐句解釋。

OUTPUT_FORMAT("elf32-tradlittlemips") OUTPUT_ARCH(mips)


OUTPUT_FORMAT 和 OUTPUT_ARCH 都是 ld 腳本的保留字命令。OUTPUT_FORMAT 說明輸出二進制文件的格式。OUTPUT_ARCH 說明輸出文件在平臺。

ENTRY(_start)


ENTRY 命令的作用是,將後面括號中的符號值設置成入口地址。入口地址(entry point)的定義是這樣的──進程執行的第一條用戶空間的指令在進程地址空間中的地址。

ld 有多種方法設置進程入口地址,通常它按以下順序:(編號越前, 優先級越高)
1, ld 命令行的-e選項
2, 連接腳本的 ENTRY(SYMBOL) 命令
3, 如果定義了 start 符號, 使用 start 符號值
4, 如果存在 .text section, 使用 .text section 的第一字節的位置值
5, 使用值 0

SECTIONS {


然後,接下來是一大段的 SECTIONS,對應的右大括號直到腳本的末尾。
SECTIONS 命令告訴 ld 如何把輸入文件的 sections 映射到輸出文件的各個 section:即是如何將輸入 section 合爲輸出 section;如何把輸出 section 放入程序地址空間 (VMA) 和進程地址空間 (LMA) 。該命令格式如下:
SECTIONS
{
….
}

. = 0x80100000;


這句把定位器符號置爲 0x80100000 (若不指定,則該符號的初始值爲 0)。
. 是一個特殊的符號,它是定位器,一個位置指針,指向程序地址空間內的某位置(或某section內的偏移,如果它在SECTIONS命令內的某section描述內),該符號只能在SECTIONS命令內使用。

.text : { _ftext = . ; *(.text) *(.rodata) *(.rodata1) *(.reginfo) *(.init) *(.stub) *(.gnu.warning) } =0


.text : 表示text段開始。
_ftext
*(.text) 將所有(*符號代表任意輸入文件)輸入文件的.text section合併成一個.text section, 該section的地址由定位器符號的值指定, 即0x80100000.
*(.rodata)
*(.rodata1)
*(.reginfo)
*(.init)
*(.stub)
*(.gnu.warning)
} =0 表示合併時留下的空隙用 0 填充;

_etext = .; PROVIDE (etext = .);


_etext = .; 我們看到,很多變量都定義成等於這個 . 符,實際上這個符號所代表的值是在變化的,隨着越往後走,值越增加,根據前面填充的多少自動往後加。
PROVIDE關鍵字用於定義這類符號:在目標文件內被引用,但沒有在任何目標文件內被定義的符號。
這裏就定義了一個 etext 符號,當目標文件內引用了 etext 符號,卻沒有定義它時,etext 符號對應的地址被定義爲 .text section 之後的第一個字節的地址。

.fini : { *(.fini) } =0


意思與前面一樣,但 fini 這名字是哪個段,我還搞不太清楚(???)。

.data : { _fdata = . ; *(.data) CONSTRUCTORS } .data1 : { *(.data1) }


數據段終於來到了,意思很容易理解的了。
CONSTRUCTORS 是一個保留字命令。與 c++ 內的(全局對象的)構造函數和(全局對像的)析構函數相關。

.ctors : { __CTOR_LIST__ = .; LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2) *(.ctors) LONG(0) __CTOR_END__ = .; } .dtors : { __DTOR_LIST__ = .; LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2) *(.dtors) LONG(0) __DTOR_END__ = .; }


對於支持任意section名的目標文件格式,比如COFF、ELF格式,GNU C++將全局構造和全局析構信息分別放入 .ctors section 和 .dtors section 內
當 連接器生成的目標文件格式不支持任意section名字時,比如說ECOFF、XCOFF格式,連接器將通過名字來識別全局構造和全局析構,對於這些文件 格式,連接器把與全局構造和全局析構的相關信息放入出現 CONSTRUCTORS 關鍵字的輸出section內。

符號__CTORS_LIST__表示全局構造信息的的開始處,__CTORS_END__表示全局構造信息的結束處。
符號__DTORS_LIST__表示全局構造信息的的開始處,__DTORS_END__表示全局構造信息的結束處。
這兩塊信息的開始處是一字長的信息,表示該塊信息有多少項數據,然後以值爲零的一字長數據結束。
一般來說,GNU C++在函數__main內安排全局構造代碼的運行,而__main函數被初始化代碼(在main函數調用之前執行)調用。

_gp = ALIGN(16) + 0x7ff0;


_gp是一個重要的全局變量,好像是用作全局引用的一個指針。

.got : { *(.got.plt) *(.got) } .sdata : { *(.sdata) } .lit8 : { *(.lit8) } .lit4 : { *(.lit4) }


意義類似。

_edata = .; PROVIDE (edata = .);


意義與前面的 etext 類似。edata 符號也較爲重要。

__bss_start = .; _fbss = .; .sbss : { *(.sbss) *(.scommon) } .bss : { *(.dynbss) *(.bss) *(COMMON) } . = ALIGN(16); __bss_end = .; _end = .;__end = .; end = .; PROVIDE (end = .);


BSS段開始了。
COMMON 這個保留字的意義:
通用符號(common symbol)的輸入section:
在許多目標文件格式中,通用符號並沒有佔用一個section。連接器認爲:輸入文件的所有通用符號在名爲COMMON的section內。
上例中將所有輸入文件的所有通用符號放入輸出.bss section內。

這裏,定義了幾個重要的符號
__bss_start = .;
__bss_end = .;
_end = .;
__end = .;
end = .;
在代碼中可能會用到的。

.stab 0 : { *(.stab) } .stabstr 0 : { *(.stabstr) } .debug 0 : { *(.debug) } .debug_srcinfo 0 : { *(.debug_srcinfo) } .debug_aranges 0 : { *(.debug_aranges) } .debug_pubnames 0 : { *(.debug_pubnames) } .debug_sfnames 0 : { *(.debug_sfnames) } .line 0 : { *(.line) } .gptab.sdata : { *(.gptab.data) *(.gptab.sdata) } .gptab.sbss : { *(.gptab.bss) *(.gptab.sbss) } }


餘下的這幾個意義也類似,看英文註釋應該能明白。

對初步編譯出來的一個二進制文件進行 nm 解析,得到如下內容

80100200 D __CTOR_END__ 801001f8 D __CTOR_LIST__ 80100208 D __DTOR_END__ 80100200 D __DTOR_LIST__ 80100220 A __bss_end 80100208 A __bss_start 80100220 A __end 80100208 A _edata 80100220 A _end 801001f8 A _etext 80100208 A _fbss 80100200 A _fdata 80100000 T _ftext 80108200 A _gp 80100000 T _start 80100038 t cleanpipe 80100220 A end 80100210 b flag_initialized.1263 80100158 T inbFrCom 801000b4 T initBss 80100060 T initConstructor 80100100 T initMips 801001a4 T outbToCom 80100140 T readComReg 801000f8 T showVersion 800fc000 T stack 80100000 T start 80100120 T writeComReg

可以看到,所有的地址全是從 0x80100000 開始的。

三個起始符號(T表示在text段中)
80100000 T _start
80100000 T start
80100000 T _ftext

幾個函數都在代碼段內
80100038 t cleanpipe
80100060 T initConstructor
801000b4 T initBss
801000f8 T showVersion
80100100 T initMips
80100120 T writeComReg
80100140 T readComReg
80100158 T inbFrCom
801001a4 T outbToCom

棧底地址果然是在 start 下方 0x4000 處
800fc000 T stack

(A 表示絕對不變)
801001f8 A _etext
80100200 A _fdata
80100208 A _edata
80100208 A _fbss
80100208 A __bss_start
80100220 A __bss_end
80100220 A __end
80100220 A _end
80100220 A end

全局構造和析構變量段(D表示在已初始化過的數據段中)
801001f8 D __CTOR_LIST__
80100200 D __CTOR_END__
80100200 D __DTOR_LIST__
80100208 D __DTOR_END__

還有一個是用 B 標記的,表示在未初始化的數據段中
80100210 b flag_initialized.1263
對應的代碼是
static int flag_initialized = 0;
可以看出,這是個局部靜態變量。

令其按地址排序

nm -n bamboo 800fc000 T stack 80100000 T _ftext 80100000 T _start 80100000 T start 80100038 t cleanpipe 80100060 T initConstructor 801000b4 T initBss 801000f8 T showVersion 80100100 T initMips 80100120 T writeComReg 80100140 T readComReg 80100158 T inbFrCom 801001a4 T outbToCom 801001f8 D __CTOR_LIST__ 801001f8 A _etext 80100200 D __CTOR_END__ 80100200 D __DTOR_LIST__ 80100200 A _fdata 80100208 D __DTOR_END__ 80100208 A __bss_start 80100208 A _edata 80100208 A _fbss 80100210 b flag_initialized.1263 80100220 A __bss_end 80100220 A __end 80100220 A _end 80100220 A end 80108200 A _gp
結合這些數據,去理解前面的 ld.script 的講解,會有一個清晰的印象
發佈了16 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章