输出节关键字
-----------------------
有两个关键字作为输出节命令的形式出现。 `CREATE_OBJECT_SYMBOLS'
这个命令告诉连接器为每一个输入文件创建一个符号。而符号的名字正好就是 相关输入文件的名字。而每一个符号的节就是`CREATE_OBJECT_SYMBOLS'命令 出现的那个节。
这个命令一直是a.out目标文件格式特有的。 它一般不为其它的目标文件格式 所使用。
`CONSTRUCTORS'
当使用a.out目标文件格式进行连接的时候, 连接器使用一组不常用的结构以支持C++的全局构造函数和析构函数。当连接不支持专有节的目标文件格式时,比如ECOFF和XCOFF,连接器会自动辩识C++全局构造函数和析构函数的名字。
对于这些目标文件格式,‘CONSTRUCTORS’命令告诉连接器把构造函数信息放到‘CONSTRUCTORS’命令出现的那个输出节中。对于其它目标文件格式,‘CONSTRUCTORS’命令被忽略。
符号`__CTOR_LIST__'标识全局构造函数的开始,而符号`__DTOR_LIST'标识结束。这个列表的第一个WORD是入口的数量,紧跟在后面的是每一个构造函数 和析构函数的地址,再然后是一个零WORD。编译器必须安排如何实际运行代码。 对于这些目标文件格式,GNU C++通常从一个`__main'子程序中调用构造函数, 而对`__main'的调用自动被插入到`main'的启动代码中。GNU C++通常使用'atexit'运行析构函数,或者直接从函数'exit'中运行。
对于像‘COFF’或‘ELF’这样支持专有节名的目标文件格式,GNU C++通常会把全局构造函数与析构函数的地址值放到'.ctors'和'.dtors'节中。把下面的代码序列放到你的连接脚本中去,这样会构建出GNU C++运行时代码希望见到的表类型。
__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++支持来进行优先初始化,那它提供一些可以控制全局构造函数运行顺序的功能,你必须在连接时给构造函数排好序以保证它们以正确 的顺序被执行。当使用'CONSTRUCTORS'命令时,替代为`SORT(CONSTRUCTORS)'。 当使用'.ctors'和'dtors'节时,使用`*(SORT(.ctors))'和`*(SORT(.dtors))' 而不是`*(.ctors)'和`*(.dtors)'。
通常,编译器和连接器会自动处理这些事情,并且你不必亲自关心这些事情。 但是,当你正在使用C++,并自己编写连接脚本时,你可能就要考虑这些事情了。
输出节的丢弃。
-------------------------
连接器不会创建那些不含有任何内容的输出节。这是为了引用那些可能出现或不出现在任何输入文件中的输出节时方便。比如: .foo { *(.foo) }
如果至少在一个输入文件中有'.foo'节,它才会在输出文件中创建一个'.foo'节如果你使用了其它的而不是一个输入节描述作为一个输出节命令,比如一个符号赋值,那这个输出节总是被创建,即使没有匹配的输入节也会被创建。 一个特殊的输出节名`/DISCARD/'可以被用来丢弃输入节。任何被分配到名为`/DISCARD/'的输出节中的输入节不包含在输出文件中。
输出节属性
-------------------------
上面,我们已经展示了一个完整的输出节描述,看下去就象这样: SECTION [ADDRESS] [(TYPE)] : [AT(LMA)] {
OUTPUT-SECTION-COMMAND OUTPUT-SECTION-COMMAND ...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP] 我们已经介绍了SECTION, ADDRESS, 和OUTPUT-SECTION-COMMAND. 在这一节中,我们将介绍余下的节属性。
输出节类型
...................
每一个输出节可以有一个类型。类型是一个放在括号中的关键字,已定义的类型如下所示: `NOLOAD'
这个节应当被标识为不可载入,所以当程序运行时,它不会被载入到内存中。
`DSECT' `COPY' `INFO' `OVERLAY'
支持这些类型名只是为了向下兼容,它们很少使用。它们都具有相同的效果:这个节应当被标识为不可分配,所以当程序运行时,没有内存为这个节分配。
连接器通常基于映射到输出节的输入节来设置输出节的属性。你可以通过使用节类型来重设这个属性,比如,在下面的脚本例子中,‘ROM’节被定址在内存地址零处,并且在程序运行时不需要被载入。‘ROM’节的内容会正常出现在连接输出文件中。
SECTIONS {
ROM 0 (NOLOAD) : { ... } ... }
输出节LMA
..................
每一个节有一个虚地址(VMA)和一个载入地址(LMA);出现在输出节描述中的地址表式设置VMS连接器通常把LMA跟VMA设成相等。你可以通过使用‘AT’关键字改变这个。跟在关键字‘AT’后面的表达式LMA指定节的载入地址。或者,通过`AT>LMA_REGION'表达式, 你可以为节的载入地址指定一个内存区域。 这个特性是为了便于建立ROM映像而设计的。比如,下面的连接脚本创建了三个输出节:一个叫做‘.text’从地址‘0x1000’处开始,一个叫‘.mdata’,尽管它的VMA是'0x2000',它会被载入到'.text'节的后面,最后一个叫做‘.bss’是用来放置未初始化的数据的,其地址从'0x3000'处开始。符号'_data'被定义为值'0x2000', 它表示定位计数器的值是VMA的值,而不是LMA。 SECTIONS {
.text 0x1000 : { *(.text) _etext = . ; } .mdata 0x2000 :
AT ( ADDR (.text) + SIZEOF (.text) ) { _data = . ; *(.data); _edata = . ; } .bss 0x3000 :
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;} }
这个连接脚本产生的程序使用的运行时初始化代码会包含象下面所示的一些东西,以把初始化后的数据从ROM映像中拷贝到它的运行时地址中去。注意这节代码是如何利用好连接脚本定义的符号的。
extern char _etext, _data, _edata, _bstart, _bend; char *src = &_etext; char *dst = &_data;
/* ROM has data at end of text; copy it. */ while (dst < &_edata) { *dst++ = *src++; }
/* Zero bss */
for (dst = &_bstart; dst< &_bend; dst++) *dst = 0; 输出节区域
.....................
你可以通过使用`>REGION'把一个节赋给前面已经定义的一个内存区域。
这里有一个简单的例子:
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 } SECTIONS { ROM : { *(.text) } >rom } 输出节Phdr
...................
你可以通过使用`:PHDR'把一个节赋给前面已定义的一个程序段。如果一个节被赋给一个或多个段,那后来分配的节都会被赋给这些段,除非它们显式使用了':PHDR'修饰符。你可以使用':NONE'来告诉连接器不要把节放到任何一个段中。 这儿有一个简单的例子:
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text } 输出段填充
...................
你可以通过使用'=FILLEXP'为整个节设置填充样式。FILLEXP是一个表达式。任何没有指定的输出段内的内存区域(比如,因为输入段的对齐要求而产生的裂缝)会被填入这个值。如果填充表达式是一个简单的十六进制值,比如,一个以'0x'开始的十六进制数字组成的字符串,并且尾部不是'k'或'M',那一个任意的十六进制数字长序列可以被用来指定填充样式;前导零也变为样式的一部分。对于所有其他的情况,包含一个附加的括号或一元操作符'+',那填充样式是表达式的最低四字节的值。在所有的情况下,数值是big-endian.
你还可以通过在输出节命令中使用'FILL'命令来改变填充值。 这里是一个简单的例子:
SECTIONS { .text : { *(.text) } =0x90909090 } --
标 题: GLD中文手册--(八)
覆盖描述
-------------------
一个覆盖描述提供一个简单的描述办法,以描述那些要被作为一个单独内存映像的一部分载入内存,但是却要在同一内存地址运行的节。在运行时,一些覆盖管理机制会把要被覆盖的节按需要拷入或拷出运行时内存地址,并且多半是通过简单地处理内存位。 这个方法可能非常有用,比如在一个特定的内存区域比另一个快时。
覆盖是通过‘OVERLAY’命令进行描述。‘OVERLAY’命令在‘SECTIONS’命令中使用,就像输出段描述一样。‘OVERLAY’命令的完整语法如下: 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]
除了‘OVERLAY’关键字,所有的都是可选的,每一个节必须有一个名字(上面的SECNAME1和SECNAME2)。在‘OVERLAY’结构中的节定义跟通常的SECTIONS’结构中的节定义是完全相同的,除了一点,就是在‘OVERLAY’中没有地址跟内存区域的定义。
节都被定义为同一个开始地址。所有节的载入地址都被排布,使它们在内存中从整个'OVERLAY'的载入地址开始都是连续的(就像普通的节定义,载入地址是可选的,缺省的就是开始地址;开始地址也是可选的,缺省的是当前的定位计数器的值。)
如果使用了关键字`NOCROSSREFS', 并且在节之间存在引用,连接器就会报告一个错误。因为节都运行在同一个地址上,所以一个节直接引用另一个节中的内容是错误的。
对于'OVERLAY'中的每一个节,连接器自动定义两个符号。符号`__load_start_SECNAME'被定义为节的开始载入地址。符号
`__load_stop_SECNAME'被定义为节的最后载入地址。SECNAME中的不符合C规定的任何字符都将被删除。C(或者汇编语言)代码可能使用这些符号在必要的时间搬移覆盖代码。
在覆盖区域的最后,定位计数器的值被设为覆盖区域的开始地址加上最大的节的长度。
这里是一个例子。记住这只会出现在‘SECTIONS’结构的内部。 OVERLAY 0x1000 : AT (0x4000) {
.text0 { o1/*.o(.text) } .text1 { o2/*.o(.text) } }
这段代码会定义'.text0'和'.text1',它们都从地址0x1000开始。‘.text0'会被载入到地址0x4000处,而'.text1'会被载入到紧随'.text0'后的位置。下面的几个符号会被定义:`__load_start_text0', `__load_stop_text0', `__load_start_text1', `__load_stop_text1'.
拷贝'.text1'到覆盖区域的C代码看上去可能会像下面这样: