发信人: tellme (菰城浪子*苕霅流长*难舍浙大), 信区: AsmDos
标 题: GLD中文手册--(七)
发信站: 飘渺水云间 (Mon Feb 2 21:13:19 2004), 转信
输入节和垃圾收集
---------------------------------------
当连接时垃圾收集正在使用中时('--gc-sections'),这在标识那些不应该被排除在外
的节时非常有用。这是通过在输入节的通配符入口外面加上'KEEP()'实现的,比如
'KEEP(*(.init))'或者'KEEP(SORT(*)(.sorts))
'。
输入节示例
---------------------
接下来的例子是一个完整的连接脚本。它告诉连接器去读取文件'all.o'中的所有节,并
把它们放到输出节'outputa'的开始位置处, 该输出节是从位置'0x10000'处开始的。
从文件'foo.o'中来的所有节'.input1'在同一个输出节中紧密排列。 从文件'foo.o'
中来的所有节'.input2'全部放入到输出节'outputb'中,后面跟上从'foo1.o'中来的
节'.input1'。来自所有文件的所有余下的'.input1'和'.input2'节被写入到输出节
'outputc'中。
SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}
输出节数据
-------------------
你可以通过使用输出节命令'BYTE','SHORT','LONG','QUAD',或者'SQUAD'在输出节中
显式包含几个字节的数据每一个关键字后面都跟上一个圆括号中的要存入的值。表达式
的值被存在当前的定位计数器的值处。
‘BYTE’,‘SHORT’,‘LONG’‘QUAD’命令分别存储一个,两个,四个,八个字节。
存入字节后,定位计数器的值加上被存入的字节数。
比如,下面的命令会存入一字节的内容1,后面跟上四字节,其内容是符号'addr'的值。
BYTE(1)
LONG(addr)
当使用64位系统时,‘QUAD’和‘SQUAD’是相同的;它们都会存储8字节,或者说是64
位的值。而如果软硬件系统都是32位的,一个表达式就会被作为32位计算。在这种情况
下,‘QUAD’存储一个32位值,并把它零扩展到64位, 而‘SQUAD’会把32位值符号扩
展到64位。
如果输出文件的目标文件格式有一个显式的endianness,它在正常的情况下,值就会被
以这种endianness存储当一个目标文件格式没有一个显式的endianness时, 值就会被以
第一个输入目标文件的endianness存储。
注意, 这些命令只在一个节描述内部才有效,而不是在它们之间, 所以,下面的代码会
使连接器产生一个错误信息:
SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
而这个才是有效的:
SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }
你可能使用‘FILL’命令来为当前节设置填充样式。它后面跟有一个括号中的表达式。
任何未指定的节内内存区域(比如,因为输入节的对齐要求而造成的裂缝)会以这个表达
式的值进行填充。一个'FILL'语句会覆盖到它本身在节定义中出现的位置后面的所有内
存区域;通过引入多个‘FILL’语句,你可以在输出节的不同位置拥有不同的填充样式。
这个例子显示如何在未被指定的内存区域填充'0x90':
FILL(0x90909090)
‘FILL’命令跟输出节的‘=FILLEXP’属性相似,但它只影响到节内跟在‘FILL’命令
后面的部分,而不是整个节。如果两个都用到了,那‘FILL’命令优先。
输出节关键字
-----------------------
有两个关键字作为输出节命令的形式出现。
`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] [

HDR

HDR ...] [=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
...................
你可以通过使用`

HDR'把一个节赋给前面已定义的一个程序段。如果一个节被赋给一个
或多个段,那后来分配的节都会被赋给这些段,除非它们显式使用了':PHDR'修饰符。你
可以使用':NONE'来告诉连接器不要把节放到任何一个段中。
这儿有一个简单的例子:
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }
输出段填充
...................
你可以通过使用'=FILLEXP'为整个节设置填充样式。FILLEXP是一个表达式。任何没有
指定的输出段内的内存区域(比如,因为输入段的对齐要求而产生的裂缝)会被填入这
个值。如果填充表达式是一个简单的十六进制值,比如,一个以'0x'开始的十六进制数
字组成的字符串,并且尾部不是'k'或'M',那一个任意的十六进制数字长序列可以被用
来指定填充样式;前导零也变为样式的一部分。对于所有其他的情况,包含一个附加的
括号或一元操作符'+',那填充样式是表达式的最低四字节的值。在所有的情况下,数值
是big-endian.
你还可以通过在输出节命令中使用'FILL'命令来改变填充值。
这里是一个简单的例子:
SECTIONS { .text : { *(.text) } =0x90909090 }
--
※ 来源:·飘渺水云间 freecity.cnzju.net·[FROM: tellme]