gnu-linker 譯文一

本文純屬自己翻譯,不能保證其準確性,歡迎拍磚;如有轉載,註明出處。

3.鏈接器腳本

每個鏈接都由鏈接器腳本控制,而鏈接器腳本又由鏈接器命令語言書寫。

鏈接器腳本的主要目的在於描述輸入文件的各個段如何向輸出文件映射,並且控制輸出文件如何在內存中存放。大多數連接器腳本無非只做這些事情。 但是,必要的時候,鏈接器腳本通過下面的命令直接操作連接器進行一些其他的操作。鏈接器總是要用到鏈接器腳本。如果你自己沒有提供該腳本,那麼連接器將使用默認腳本,這個腳本已經編譯到了鏈接器的可執行文件中。你可以通過‘--verbose'命令行選項顯示默認鏈接腳本,一些命令行選項,如‘-r’‘-N’,將會影響鏈接器腳本。

注: 在終端中輸入命令:arm-linux-ld --verbose

會出現默認腳本如下,我們這裏不做分析:

GNU ld version 2.15.90.0.3 20040415

Supported emulations:

armelf_linux

armelf

using internal linker script:

==================================================

/* Script for -z combreloc: combine andsort reloc sections */

OUTPUT_FORMAT("elf32-littlearm","elf32-bigarm",

"elf32-littlearm")

OUTPUT_ARCH(arm)

ENTRY(_start)

SEARCH_DIR("=/usr/local/lib");SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");

/* Do we need any of these for elf?

__DYNAMIC = 0; */

SECTIONS

{

/* Read-only sections, merged intotext segment: */

PROVIDE (__executable_start =0x00008000); . = 0x00008000 + SIZEOF_HEADERS;

.interp : { *(.interp) }

.hash : { *(.hash) }

.dynsym : { *(.dynsym) }

.dynstr : { *(.dynstr) }

.gnu.version : { *(.gnu.version)}

.gnu.version_d : { *(.gnu.version_d)}

.gnu.version_r : { *(.gnu.version_r)}

.rel.dyn :

{

*(.rel.init)

*(.rel.text .rel.text.*.rel.gnu.linkonce.t.*)

*(.rel.fini)

*(.rel.rodata .rel.rodata.*.rel.gnu.linkonce.r.*)

*(.rel.data .rel.data.*.rel.gnu.linkonce.d.*)

*(.rel.tdata .rel.tdata.*.rel.gnu.linkonce.td.*)

*(.rel.tbss .rel.tbss.*.rel.gnu.linkonce.tb.*)

*(.rel.ctors)

*(.rel.dtors)

*(.rel.got)

*(.rel.bss .rel.bss.*.rel.gnu.linkonce.b.*)

}

.rela.dyn :

{

*(.rela.init)

*(.rela.text .rela.text.*.rela.gnu.linkonce.t.*)

*(.rela.fini)

*(.rela.rodata .rela.rodata.*.rela.gnu.linkonce.r.*)

*(.rela.data .rela.data.*.rela.gnu.linkonce.d.*)

*(.rela.tdata .rela.tdata.*.rela.gnu.linkonce.td.*)

*(.rela.tbss .rela.tbss.*.rela.gnu.linkonce.tb.*)

*(.rela.ctors)

*(.rela.dtors)

*(.rela.got)

*(.rela.bss .rela.bss.*.rela.gnu.linkonce.b.*)

}

.rel.plt : { *(.rel.plt) }

.rela.plt : { *(.rela.plt) }

.init :

{

KEEP (*(.init))

} =0

.plt : { *(.plt) }

.text :

{

*(.text .stub .text.*.gnu.linkonce.t.*)

/* .gnu.warning sections arehandled specially by elf32.em. */

*(.gnu.warning)

*(.glue_7t) *(.glue_7)

} =0

.fini :

{

KEEP (*(.fini))

} =0

PROVIDE (__etext = .);

PROVIDE (_etext = .);

PROVIDE (etext = .);

.rodata : { *(.rodata.rodata.* .gnu.linkonce.r.*) }

.rodata1 : { *(.rodata1) }

.eh_frame_hdr : { *(.eh_frame_hdr) }

/* Adjust the address for the datasegment. We want to adjust up to

the same address within the pageon the next page up. */

. = ALIGN (0x8000) - ((0x8000 - .) &(0x8000 - 1)); . = DATA_SEGMENT_ALIGN (0x8000, 0x1000);

/* Ensure the __preinit_array_startlabel is properly aligned. We

could instead move the labeldefinition inside the section, but

the linker would then create thesection even if it turns out to

be empty, which isn't pretty. */

. = ALIGN(32 / 8);

PROVIDE (__preinit_array_start = .);

.preinit_array : {*(.preinit_array) }

PROVIDE (__preinit_array_end = .);

PROVIDE (__init_array_start = .);

.init_array : { *(.init_array) }

PROVIDE (__init_array_end = .);

PROVIDE (__fini_array_start = .);

.fini_array : { *(.fini_array) }

PROVIDE (__fini_array_end = .);

.data :

{

__data_start = . ;

*(.data .data.* .gnu.linkonce.d.*)

SORT(CONSTRUCTORS)

}

.data1 : { *(.data1) }

.tdata : { *(.tdata .tdata.*.gnu.linkonce.td.*) }

.tbss : { *(.tbss .tbss.*.gnu.linkonce.tb.*) *(.tcommon) }

.eh_frame : { KEEP(*(.eh_frame)) }

.gcc_except_table : {*(.gcc_except_table) }

.dynamic : { *(.dynamic) }

.ctors :

{

/* gcc uses crtbegin.o to find thestart of

the constructors, so we makesure it is

first. Because this is awildcard, it

doesn't matter if the user doesnot

actually link againstcrtbegin.o; the

linker won't look for a file tomatch a

wildcard. The wildcard alsomeans that it

doesn't matter which directorycrtbegin.o

is in. */

KEEP (*crtbegin*.o(.ctors))

/* We don't want to include the.ctor section from

from the crtend.o file untilafter the sorted ctors.

The .ctor section from thecrtend file contains the

end of ctors marker and it mustbe last */

KEEP (*(EXCLUDE_FILE (*crtend*.o ).ctors))

KEEP (*(SORT(.ctors.*)))

KEEP (*(.ctors))

}

.dtors :

{

KEEP (*crtbegin*.o(.dtors))

KEEP (*(EXCLUDE_FILE (*crtend*.o ).dtors))

KEEP (*(SORT(.dtors.*)))

KEEP (*(.dtors))

}

.jcr : { KEEP (*(.jcr)) }

.got : { *(.got.plt)*(.got) }

_edata = .;

PROVIDE (edata = .);

__bss_start = .;

__bss_start__ = .;

.bss :

{

*(.dynbss)

*(.bss .bss.* .gnu.linkonce.b.*)

*(COMMON)

/* Align here to ensure that the.bss section occupies space up to

_end. Align after .bss to ensurecorrect alignment even if the

.bss section disappears becausethere are no input sections. */

. = ALIGN(32 / 8);

}

. = ALIGN(32 / 8);

_end = .;

_bss_end__ = . ; __bss_end__ = . ;__end__ = . ;

PROVIDE (end = .);

. = DATA_SEGMENT_END (.);

/* Stabs debugging sections. */

.stab 0 : { *(.stab) }

.stabstr 0 : { *(.stabstr) }

.stab.excl 0 : { *(.stab.excl) }

.stab.exclstr 0 : { *(.stab.exclstr)}

.stab.index 0 : { *(.stab.index)}

.stab.indexstr 0 : {*(.stab.indexstr) }

.comment 0 : { *(.comment) }

/* DWARF debug sections.

Symbols in the DWARF debuggingsections are relative to the beginning

of the section so we begin them at0. */

/* DWARF 1 */

.debug 0 : { *(.debug) }

.line 0 : { *(.line) }

/* GNU DWARF 1 extensions */

.debug_srcinfo 0 : {*(.debug_srcinfo) }

.debug_sfnames 0 : {*(.debug_sfnames) }

/* DWARF 1.1 and DWARF 2 */

.debug_aranges 0 : {*(.debug_aranges) }

.debug_pubnames 0 : {*(.debug_pubnames) }

/* DWARF 2 */

.debug_info 0 : { *(.debug_info.gnu.linkonce.wi.*) }

.debug_abbrev 0 : {*(.debug_abbrev) }

.debug_line 0 : { *(.debug_line)}

.debug_frame 0 : { *(.debug_frame)}

.debug_str 0 : { *(.debug_str)}

.debug_loc 0 : { *(.debug_loc)}

.debug_macinfo 0 : {*(.debug_macinfo) }

/* SGI/MIPS DWARF 2 extensions */

.debug_weaknames 0 : {*(.debug_weaknames) }

.debug_funcnames 0 : {*(.debug_funcnames) }

.debug_typenames 0 : {*(.debug_typenames) }

.debug_varnames 0 : {*(.debug_varnames) }

.note.gnu.arm.ident 0 : { KEEP(*(.note.gnu.arm.ident)) }

/DISCARD/ : { *(.note.GNU-stack) }

}


你可以通過‘-T’命令行選項提供自己的鏈接器腳本,當你這樣做了,你的鏈接器腳本就會取代默認鏈接器腳本。

你也可以通過將需要鏈接的文件作爲鏈接器的輸入文件而隱含的使用鏈接器腳本。

3.1基本的鏈接器腳本概念

我們需要定義一些基本的概念和詞彙來描述鏈接器腳本語言。

鏈接器將數個輸入文件整合爲一個輸出文件。輸出文件和每個輸入文件均已特殊的數據格式-----目標文件格式呈現。每個文件都叫做目標文件。每個文件都有一系列分段。我們有時稱呼輸入文件中的段爲輸入段;同樣的,我們將輸出文件中的段稱爲輸出段。

目標文件中的每個段都有名字的大小。絕大多數段都含有相關的數據塊,稱之爲段內容。一個段可能

標記爲裝載段,意思是當輸出文件運行時該段內容應該被裝載進內存。一個沒有內容的段也許是可分配的,

這意味着內存中的一部分區域應該被保留,但是沒有任何東西可以被裝載到哪裏(有些情況下這些必須被清零)。一些段既不是裝載段,又不是可分配段,通常包含的是一些調試信息。

每個裝載或者可分配輸出段有兩個地址,第一個是VMAvirtualmemory address,虛擬內存地址,

這個地址是輸出文件運行時段所擁有的地址。第二個是LMAloadmemoryaddress),裝載地址,這是段將要被加載到的地址。大多數情況下,兩個地址是相同的。當一個段被裝載進了ROM 然後當程序啓動時又被複制到內存中時,VMALMA是不同的。這個時候,ROM的地址就是LMARAM的地址就是VMA

使用objdump程序時加‘-h’選項,你可以查看目標文件中的段。每個目標文件均有一系列符號,

稱之爲符號表。一個符號可能被定義了,也可能沒有被定義。每個符號有一個名字,每個已定義的符合都有一個地址和一些其他信息。如果你將一個CC++程序編譯爲目標文件,每個以定義的函數、全局變量、靜態變量都會獲得一個定義的符號。在輸入文件中所有未定義的符號或者全局變量在目標文件中將會變爲未定義的符號。

注: 終端輸入命令arm-linux-objdump -h leds_elf

可以看到:

leds_elf: file formatelf32-littlearm

//我的註釋,arm-linux-objdump只支持格式爲ELF小端的目標文件

Sections:

Idx Name Size VMA LMA File off Algn//有虛擬地址,裝載地址,段大小等信息

0 .text 00000088 00000000 00000000 00008000 2**2

CONTENTS, ALLOC,LOAD, READONLY, CODE

1 .data 00000000 00008088 00008088 00008088 2**0

CONTENTS, ALLOC,LOAD, DATA

2 .bss 00000000 00008088 00008088 00008088 2**0

ALLOC

3 .comment 00000012 00000000 00000000 00008088 2**0

CONTENTS, READONLY

你可以查看目標文件中的符號,通過兩種方法,一個是使用nm程序;另一個使用objdump和它的

‘-t’ 選項。

注:終端輸入命令:arm-linux-objdump -t leds_elf 會輸出符號表:

leds_elf: file formatelf32-littlearm

SYMBOL TABLE:

符號表入口 符號名 //其他不知道

00000000 l d .text 00000000

00008088 l d .data 00000000

00008088 l d .bss 00000000

00000000 l d .comment 00000000

00000000 l d *ABS* 00000000

00000000 l d *ABS* 00000000

00000000 l d *ABS* 00000000

00000000 l df *ABS* 00000000 crt0.S

00000000 l df *ABS* 00000000<command line>

00000000 l df *ABS* 00000000<built-in>

00000000 l df *ABS* 00000000 crt0.S

00000000 l F .text 00000000 $a

00000014 l .text 00000000halt_loop

00000000 l df *ABS* 00000000 leds.c

00000018 l F .text 00000000 $a

00008088 g *ABS* 00000000_bss_end__

00008088 g *ABS* 00000000__bss_start__

00008088 g *ABS* 00000000__bss_end__

00000018 g F .text 00000034 wait

00000000 g .text 00000000 _start

00008088 g *ABS* 00000000__bss_start

0000004c g F .text 0000003c main

00008088 g *ABS* 00000000__end__

00008088 g *ABS* 00000000 _edata

00008088 g *ABS* 00000000 _end

00008088 g .data 00000000__data_start

3.2鏈接器腳本格式

鏈接器腳本是文本文件(textfile)。

編寫一個鏈接器腳本就是就是書寫一系列命令,每個命令要麼是一個關鍵字,可能會跟隨一些參數,要麼是對一個符號的分配。你可以使用分號分割命令,空格通常被忽略。

字符串比如文件或者格式名字通常可以直接鍵入(直接書寫出來)。如果文件名包含字符,比如逗號,這可能導致文件名分開,你可以將文件名放在雙引號之間。文件名中是無法包含雙引號字符的。

你可以在鏈接器腳本中加入註釋(就像給C程序進行註釋一樣),以‘/*’和‘*/‘爲界,將註釋放在二者之間,就像在C中,註釋在語法上等同於空格。

3.3簡單的鏈接腳本例子

許多鏈接器腳本都相當簡單。

最簡單的鏈接器腳本只有一個命令:‘SECTIONS’。你用SECTIONS命令描述輸出文件在內存的分配。

‘SECTIONS’命令是一個強有力的命令。這裏我們簡單描述如何使用它。我們假設你的程序中僅僅包含代碼,初始化數據,和未初始化數據,這些將會被分別放置在'.text'、‘.data’、‘.bss’段中。我們進一步假設,你的輸入文件中只有這些段。

這個例子中,我們認爲待應該被裝載到地址0x10000,數據應該放在0x8000000地址處。鏈接器腳本就應該這麼寫:

SECTIONS

{

. = 0x10000;

.text : {*(.text)}

. = 0x8000000

.data : {*(.data)}

.bss :{*(.bss)}

}

SECTIONS命令(SECTIONS是一個關鍵字),後面花括號內存放了一系列的符號分配和輸出段描述。

SECTIONS命令的第一行就是設置特殊符號‘. ’的值,特殊符號‘.’是一個位置計數器。如果你不用其他方式指定輸出段的地址,地址就會設置爲位置計數器的當前值。位置計數器的值會 根據輸出段的大小遞增,

SECTIONS命令命令的開始,位置計數器的地址爲0.

第二行定義一個輸出段,.text,其後的冒號是語法需要,在輸出段名後面的花括號內,你列出輸入段的名字,它們會被放到這個輸出段中。‘*’是通配符,可以匹配任何文件名。 *(.text)代表所有輸入文件的全部.text輸入段。

因爲當.text段定義的時候,位置計數器是0x10000,鏈接器就會設置.text段在輸出文件中的地址爲0x10000

接下來在輸出文件中定義了.data.text段,鏈接器將放置.data段到地址0x8000000,在鏈接器放置.data段之後,位置計數器的值是0x8000000加上.data段的大小,鏈接器在內存中將會放置.bss輸出段到

.data輸出段之後。

通過增加位置計數器的值,鏈接器將會確保相應的輸出段有必要的對齊。這個例子中,爲.text段和.data段定義的地址可能滿足任何對齊限制,但是鏈接器可能會在.data段和.bss段之間創造小的空隙以滿足一些對齊要求。


發佈了47 篇原創文章 · 獲贊 8 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章