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段之间创造小的空隙以满足一些对齐要求。


  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值