linux学习中
分类: C/C++
2010-06-10 14:48:27
这里给出三种类型的全局符号的解释(用C语言):
`int i = 1;'
一个定义, 它会存在于输出文件中的已初始化数据节.
`extern int i;'
一个未定义符号,它不占用空间. 必须在另外某一处对它有一个定义,或一个普通符号
`int i;'
一个普通符号.如果对于一个变量只有(一个或多个)普通符号, 它进入输出文件的未初始化数据域. 连
接器会把同一变量的多个普通符号合并成一个单一的符号. 如果他们有不同的大小, 它采用最大的一
个. 如果是对同一变量的定义,连接器把一个普通符号转化为一个声明.
'--warn-common'选项可以产生五种类型的警告. 每种警告由两行组成: 第一行描述遇到的符号, 第二
行描述遇到的前一个具有相同名字的符号. 一个或两个都可能成为普通符号.
1. 把一个普通符号转化为一个引用, 因为这个符号已经有一个定义了.
FILE(SECTION): warning: common of `SYMBOL'
overridden by definition
FILE(SECTION): warning: defined here
2. 把一个普通符号转化为一个引用,因为遇到了第二个关于符号的定义. 这跟前一种情况相同,除了符
号遇到的顺序相反.
FILE(SECTION): warning: definition of `SYMBOL'
overriding common
FILE(SECTION): warning: common is here
3. 把一个普通符号跟前一个相同大小的普通符号合并.
FILE(SECTION): warning: multiple common
of `SYMBOL'
FILE(SECTION): warning: previous common is here
4. 把一个普通符号跟前一个更大的普通符号合并.
FILE(SECTION): warning: common of `SYMBOL'
overridden by larger common
FILE(SECTION): warning: larger common is here
5. 把一个普通符号跟前一个更小的普通符号合并. 这跟前一种情况相同, 除了遇到的符号的顺序不同.
FILE(SECTION): warning: common of `SYMBOL'
overriding smaller common
FILE(SECTION): warning: smaller common is here
`--warn-constructors'
如果有全局结构体被使用到了,警告. 这只对很少的一些目标文件格式有用. 对于COFF或ELF格式, 连
接器不同探测到全局结构体的使用.
`--warn-multiple-gp'
如果在输出文件中,需要多个全局指针值,警告. 这只对特定的处理器有意义, 比如Alpha. 特别的,有
些处理器在特定的节中放入很大的常数值. 一个特殊的寄存器(全局指针)指向这个节的中间部分, 所
以通过一个基地址寄存器相关的地址模式,这个常数可以很容易地被载入. 因为这个基寄存器相关模式
的偏移值是固定的而且很小(比如,16位), 这会限制常量池的最大尺寸. 所以,一个很大的问题是,为了
能够定位所有可能的常数,经常需要使用多个全局指针值. 这个选项在这种情况发生时产生一条警告.
`--warn-once'
对于每一个未定义符号只警告一次, 而不是在每一个用到它的模块中警告一次.
`--warn-section-align'
如果输出节的地址因为对齐被改变了,警告. 通常, 对齐会被输入节设置. 如果'SECTION'命令没有指
定节的起始地址, 地址就会被隐式改变.
`--whole-archive'
对于每一个在命令行中'--whole-archive'选项后面出现的档案文件, 在连接中包含档案文件中的所有
目标文件, 而不是为需要的目标文件搜索档案文件. 这在把一个档案文件转化为一个共享库时使用, 把
所有的目标放到最终的共享库中. 这个选项可以被多次使用.
在GCC中使用这个选项需要注意两点: 首先,GCC不知道这个选项, 所以,你必须使用'-Wl, -whole-archive'.
第二, 不要忘了在你的档案文件列表的后面使用'-Wl, -no-whole-archive',因为GCC会把它自己的档
案列表加到你的连接后面, 而这可能并不是你所预期的.
`--wrap SYMBOL'
对SYMBOL符号使用包装函数. 任何未定义的对SYMBOL符号的引用会被解析成'_wrap_SYMBOL'. 而任何
未定义的对'_real_SYMBOL'的引用会被解析成SYMBOL.
这可以用来为系统函灵敏提供一个包装. 包装函灵敏应当被叫做'__wrap_SYMBOL'. 如果需要调用这个
函数, 那就应该调用'__real_SYMBOL'
这里是一个没什么实用价值的例子:
void *
__wrap_malloc (int c)
{
printf ("malloc called with %ld\n", c);
return __real_malloc (c);
}
如果你使用'--wrap malloc'把这节代码跟其他的代码连接, 那所有的对'malloc'的调用都会调用
'__wrap_malloc'函数. 而在'__wrap_malloc'中的'__real_malloc'会调用真正的'malloc'函数.
你有可能也希望提供一个'__real_malloc'函数, 这样,不带有'--wrap'的连接器也会成功连接.如果
你这样做了, 你不能把'__real_malloc'的定义跟'__wrap_malloc'放到同一个文件中;如果放在一起
汇编器会在连接器之前把调用解析成真正的'malloc'.
`--enable-new-dtags'
`--disable-new-dtags'
连接器可以在ELF中创建一个新的动态标签. 但是旧的ELF系统可能不理解这个. 如果你指定了
'--enable-new-dtags',动态标签会按需要被创建. 如果你指定了'--disable-new-dtags',那不会有
新的动态标签被创建. 缺省地,新的动态标签不会被创建. 注意这些选项只在ELF系统中有效.
i386 PE平台的特定选项.
-----------------------------------
i386 PE连接器支持'-shared'选项, 它使输出文件为一个动态链接库(DLL),而不是一个普通的可执行文件. 在
使用这个选项的时候,你应当为输出文件取名'*.dll',另外, 连接器完全支持标准的'*.def'文件, 这类文件可
以在连接器命令行上象一个目标文件一样被指定(实际上, 它应当被放在它从中导出符号的那个档案文件前面,
以保证它们象一个普通的目标文件一样被连接进去.)
除了对所有平台通用的那些选项外,i386 PE连接器支持一些只对i386平台专有的命令行选面. 带有值的选项应
当用空格或等号把它跟值分隔开.
`--add-stdcall-alias'
如果给出这个选项, 带有标准调用后缀(@NN)的符号会被剥掉后缀后导出.
`--base-file FILE'
使用FILE作为文件名,该文件是存放用'dlltool'产生 DLL文件时所需的所有重定位符的基地址的.(这
个选面是i386 PE平台所专有的]
`--dll'
创建一个DLL文件而不是一个常规可执行文件. 你可能在一个给出的'.def'文件中使用'-shared'或指
定'LIBRARY'.
`--enable-stdcall-fixup'
`--disable-stdcall-fixup'
如果连接器发现有符号不能解析, 它会试图进行'失真连接',即寻找另一个定义的符号,它们只是在
符号名的格式上不同(cdecl vs stdcall),并把符号解析为找到的这个符号. 比如, 一个未定义的符
号'_foo'可能被连接到函数'_foo@12', 或者一个未定义的符号'_bar@16'可能被连接到函数'_bar'.
如果连接器这么做了, 它会打印出一条警告信息, 因为在正常情况下,这会连接失败, 但有时,由第三
方库产生的导入库可能需要这个特性. 如果你指定了'--enable-stdcall-fixup', 这个特性会被完全
开启,警告信息也不会打印出来. 如果你指定了'--disable-stdcall-fixup',这个特性被关闭,而且这
样的错误匹配会被认为是个错误.
`--export-all-symbols'
如果给出这个选项,目标中所有由DLL建立的全局符号会被DLL导出. 注意这是缺省情况,否则没有任何
符号被导出. 如果符号由DEF文件显式地导出,或由函数本身的属性隐式地导出, 缺省情况是除非选项
给出,否则不导出任何其他的符号. 注意符号`DllMain@12',`DllEntryPoint@0',
`DllMainCRTStartup@12'和`impure_ptr'不会自动被导出.而且,由其他的DLL导入的符号也不会被再
次导出, 还有指定DLL内部布局的符号,比如那些以'_head_'开头,或者以'_iname'结尾的符号也不会
被导出.还有,'libgcc','libstd++','libmingw32'或'crtX.o'中的符号也不会被导出. ......
环境变量
=====================
你可以通过环境变量`GNUTARGET', `LDEMULATION'和`COLLECT_NO_DEMANGLE'改变'ld'的行为.
`GNUTARGET'在你没有使用'-b'(或者它的同义词'--format')的时候,决定输入文件的格式. 它的值应当是BFD
中关于输入格式的一个名字. 如果环境中没有'GNUTARGET'变量, 'ld'使用目标平台的缺省格式. 如果
'GNUTARGET'被设为'default', 那BFD就会通过检查二进制的输入文件来找到输入格式; 这个方法通常会成功,
但会有潜在的不明确性, 因为没有办法保证指定一个目标文件格式的魔数总是唯一的. 但是, 在每一个系统上
的BFD配置程序会把这个系统的常规格式放在搜索列表的首位, 所以不明确性可以通过这种惯列来解决.
`LDEMULATION'在你没有使用'-m'选项的时候决定缺省的模拟器. 模拟器可以影响到连接器行为的很多方面,
特别是连接器的缺省连接脚本. 你可以通过'--verbose'或'-V'选项列出所有可用的模拟器. 如果'-m'选项没
有使用, 而且`LDEMULATION'环境变量没有定义, 缺省的模拟器跟连接器如何被配置有关.
一般地,连接器缺省状况下会重构符号.但是,如果在环境中设置了`COLLECT_NO_DEMANGLE', 那缺省状态下就不
会重构符号.这个环境变量在GCC的连接包装程序中会以相似的方式被使用. 这个缺省的行为可以被'--demangle'
或'--no-demangle'选项覆盖.
连接脚本
**************
每个连接都被一个'连接脚本'所控制. 这个脚本是用连接命令语言书写的.
连接脚本的一个主要目的是描述输入文件中的节如何被映射到输出文件中,并控制输出文件的内存排布. 几乎
所有的连接脚本只做这两件事情. 但是,在需要的时候,连接器脚本还可以指示连接器执行很多其他的操作.这
通过下面描述的命令实现.
连接器总是使用连接器脚本的.如果你自己不提供, 连接器会使用一个缺省的脚本,这个脚本是被编译进连接器
可执行文件的. 你可以使用'--verbose'命令行选项来显示缺省的连接器脚本的内容. 某些命令行选项,比如
'-r'或'-N', 会影响缺省的连接脚本.
你可以过使用'-T'命令行选项来提供你自己的连接脚本. 当你这么做的时候, 你的连接脚本会替换缺省的连
接脚本.
你也可以通过把连接脚本作为一个连接器的输入文件来隐式地使用它,就象它们是一个被连接的文件一样.
基本的连接脚本的概念
============================
我们需要定义一些基本的概念与词汇以描述连接脚本语言.
连接器把多个输入文件合并成单个输出文件. 输出文件和输入文件都以一种叫做'目标文件格式'的数据格式形
式存在. 每一个文件被叫做'目标文件'. 输出文件经常被叫做'可执行文件',但是由于需要,我们也把它叫做目
标文件. 每一个目标文件中,在其它东西之间,有一个节列表.我们有时把输入文件的节叫做输入节; 相似的,输
出文件中的一个节经常被叫做输出节.
一个目标文件中的每一个节都有一个名字和一个大小尺寸. 大多数节还有一个相关的数据块, 称为节内容. 某
一个节可能被标式讵'loadable',含义是在输出文件被执行时,这个节应当被载入到内存中去. 一个没有内容的
节可能是'allocatable', 含义是内存中必须为这个节开辟一块空间,但是没有实际的内容载入到这里(在某些
情况下,这块内存必须被标式讵零). 一个既不是loadable也不是allocatable的节一般含有一些调试信息.
每一个loadable或allocatable的输出节有两个地址. 第一个是'VMA'或称为虚拟内存地址. 这是当输出文件运
行时节所拥有的地址. 第二个是"LMA', 或称为载入内存地址. 这个节即将要载入的内存地址. 这大多数情况下
这两个地址是相同的. 它们两个有可能不同的一个例子是当一个数据节在ROM中时, 当程序启动时,被拷贝到RAM
中(这个技术经常被用在基于ROM的系统中进行全局变量的初始化). 在这种情况下, ROM地址就是LMA, 而RAM地
址就是VMA.
你可以通过使用带有'-h'选项的'objdump'来察看目标文件中的节.
每一个目标文件还有一个关于符号的列表, 被称为'符号表'. 一个符号可能是定义过了的,也可能是未定义的.
每一个符号有一个名字, 而且每一个定义的符号有一个地址. 如果你把一个C/C++程序编译为一个目标文件,对
于每一个定义的函数和全局或静态变量,你为得到一个定义的符号. 每一个在输入文件中只是一个引用而未定义
的函数或全局变量会变成一个未定义的符号.
你可以使用'nm'程序来看一个目标文件中的符号, 或者使用'objdump'程序带有'-t'选项.
连接脚本的格式
====================
连接脚本是文本文件.
你写了一系列的命令作为一个连接脚本. 每一个命令是一个带有参数的关键字,或者是一个对符号的赋值. 你可
以用分号分隔命令. 空格一般被忽略.
文件名或格式名之类的字符串一般可以被直接键入. 如果文件名含有特殊字符,比如一般作为分隔文件名用的逗
号, 你可以把文件名放到双引号中. 文件名中间无法使用双引号.
你可以象在C语言中一样,在连接脚本中使用注释, 用'/*'和'*/'隔开. 就像在C中,注释在语法上等同于空格.
简单的连接脚本示例
============================
许多脚本是相当的简单的.
可能的最简单的脚本只含有一个命令: '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'节和'.bss'节. 连接器会把'.data'输出节放到地址'0x8000000'处. 连接
器放好'.data'输出节之后, 定位计数器的值是'0x8000000'加上'.data'输出节的长度. 得到的结果是连接器会
把'.bss'输出节放到紧接'.data'节后面的位置.
连接器会通过在必要时增加定位计数器的值来保证每一个输出节具有它所需的对齐. 在这个例子中, 为'.text'
和'.data'节指定的地址会满足对齐约束, 但是连接器可能会需要在'.data'和'.bss'节之间创建一个小的缺口.
就这样,这是一个简单但完整的连接脚本.
简单的连接脚本命令.
=============================
在本章中,我们会描述一些简单的脚本命令.
设置入口点.
-----------------------
在运行一个程序时第一个被执行到的指令称为"入口点". 你可以使用'ENTRY'连接脚本命令来设置入口点.参数
是一个符号名:
ENTRY(SYMBOL)
有多种不同的方法来设置入口点.连接器会通过按顺序尝试以下的方法来设置入口点, 如果成功了,就会停止.
* `-e'入口命令行选项;
* 连接脚本中的`ENTRY(SYMBOL)'命令;
* 如果定义了start, 就使用start的值;
* 如果存在,就使用'.text'节的首地址;
* 地址`0'.
处理文件的命令.
---------------------------
有几个处理文件的连接脚本命令.
`INCLUDE FILENAME'
在当前点包含连接脚本文件FILENAME. 在当前路径下或用'-L'选项指定的所有路径下搜索这个文件,
你可以嵌套使用'INCLUDE'达10层.
`INPUT(FILE, FILE, ...)'
`INPUT(FILE FILE ...)'
'INPUT'命令指示连接器在连接时包含文件, 就像它们是在命令行上指定的一样.
比如,如果你在连接的时候总是要包含文件'subr.o',但是你对每次连接时要在命令行上输入感到厌烦
, 你就可以在你的连接脚本中输入'INPUT (subr.o).
事实上,如果你喜欢,你可以把你所有的输入文件列在连接脚本中, 然后在连接的时候什么也不需要,
只要一个'-T'选项就够了.
在一个'系统根前缀'被配置的情况下, 一个文件名如果以'/'字符打头, 并且脚本也存放在系统根
前缀的某个子目录下, 文件名就会被在系统根前缀下搜索. 否则连接器就会企图打开当前目录下的文
件. 如果没有发现, 连接器会通过档案库搜索路径进行搜索.
如果你使用了'INPUT (-lFILE)', 'ld'会把文件名转换为'libFILE.a', 就象命令行参数'-l'一样.
当你在一个隐式连接脚本中使用'INPUT'命令的时候, 文件就会在连接时连接脚本文件被包含的点上
被包含进来. 这会影响到档案搜索.
`GROUP(FILE, FILE, ...)'
`GROUP(FILE FILE ...)'
除了文件必须全是档案文件之外, 'GROUP'命令跟'INPUT'相似, 它们会被反复搜索,直至没有未定义
的引用被创建.
`OUTPUT(FILENAME)'
'OUTPUT'命令命名输出文件. 在连接脚本中使用'OUTPUT(FILENAME)'命令跟在命令行中使用'-o
FILENAME'命令是完全等效的. 如果两个都使用了, 那命令行选项优先.
你可以使用'OUTPUT'命令为输出文件创建一个缺省的文件名,而不是常用的'a.out'.
`SEARCH_DIR(PATH)'
`SEARCH_DIR'命令给'ld'用于搜索档案文件的路径中再增加新的路径. 使用`SEARCH_DIR(PATH)'跟在
命令行上使用'-L PATH'选项是完全等效的. 如果两个都使用了, 那连接器会两个路径都搜索. 用命
令行选项指定的路径首先被搜索.
`STARTUP(FILENAME)'
除了FILENAME会成为第一个被连接的输入文件, 'STARTUP'命令跟'INPUT'命令完全相似, 就象这个文
件是在命令行上第一个被指定的文件一样. 如果在一个系统中, 入口点总是存在于第一个文件中,那
这个就很有用.
处理目标文件格式的命令.
-----------------------------------------
有两个处理目标文件格式的连接脚本命令.
`OUTPUT_formAT(BFDNAME)'
`OUTPUT_formAT(DEFAULT, BIG, LITTLE)'
`OUTPUT_formAT'命令为输出文件使用的BFD格式命名. 使用`OUTPUT_formAT(BFDNAME)'跟在命令行上
使用'-oformat BFDNAME'是完全等效的. 如果两个都使用了, 命令行选项优先.
你可在使用`OUTPUT_formAT'时带有三个参数以使用不同的基于'-EB'和'-EL'的命令行选项的格式.
如果'-EB'和'-EL'都没有使用, 那输出格式会是第一个参数DEFAULT, 如果使用了'-EB',输出格式会是
第二个参数BIG, 如果使用了'-EL', 输出格式会是第三个参数, LITTLE.
比如, 缺省的基于MIPS ELF平台连接脚本使用如下命令:
OUTPUT_formAT(elf32-bigmips, elf32-bigmips, elf32-littlemips)
这表示缺省的输出文件格式是'elf32-bigmips', 但是当用户使用'-EL'命令行选项的时候, 输出文件就会
被以`elf32-littlemips'格式创建.
`TARGET(BFDNAME)'
'TARGET'命令在读取输入文件时命名BFD格式. 它会影响到后来的'INPUT'和'GROUP'命令. 这个命令跟
在命令行上使用`-b BFDNAME'相似. 如果使用了'TARGET'命令但`OUTPUT_formAT'没有指定, 最后的
'TARGET'命令也被用来设置输出文件的格式.
其它的连接脚本命令.
----------------------------
还有一些其它的连接脚本命令.
`ASSERT(EXP, MESSAGE)'
确保EXP不等于零,如果等于零, 连接器就会返回一个错误码退出,并打印出MESSAGE.
`EXTERN(SYMBOL SYMBOL ...)'
强制SYMBOL作为一个无定义的符号输入到输出文件中去. 这样做了,可能会引发从标准库中连接一些
节外的库. 你可以为每一个EXTERN'列出几个符号, 而且你可以多次使用'EXTERN'. 这个命令跟'-u'
命令行选项具有相同的效果.
`FORCE_COMMON_ALLOCATION'
这个命令跟命令行选项'-d'具有相同的效果: 就算指定了一个可重定位的输出文件('-r'),也让'ld'
为普通符号分配空间.
`INHIBIT_COMMON_ALLOCATION'
这个命令跟命令行选项`--no-define-common'具有相同的效果: 就算是一个不可重位输出文件, 也让
'ld'忽略为普通符号分配的空间.
`NOCROSSREFS(SECTION SECTION ...)'
这个命令在遇到在某些特定的节之间引用的时候会产生一条错误信息.
在某些特定的程序中, 特别是在使用覆盖技术的嵌入式系统中, 当一个节被载入内存时,另外一个节
就不会在内存中. 任何在两个节之间的直接引用都会是一个错误. 比如, 如果节1中的代码调用了另
一个节中的一个函数,这就会产生一个错误.
`NOCROSSREFS'命令带有一个输出节名字的列表. 如果'ld'遇到任何在这些节之间的交叉引用, 它就
会报告一个错误,并返回一个非零退出码. 注意, `NOCROSSREFS'命令使用输出节名,而不是输入节名.
`OUTPUT_ARCH(BFDARCH)'
指定一个特定的输出机器架构. 这个参数是BFD库中使用的一个名字. 你可以通过使用带有'-f'选项
的'objdump'程序来查看一个目标文件的架构.
为符号赋值.
===========================
你可以在一个连接脚本中为一个符号赋一个值. 这会把一个符号定义为一个全局符号.
简单的赋值.
------------------
你可以使用所有的C赋值符号为一个符号赋值.
`SYMBOL = EXPRESSION ;'
`SYMBOL += EXPRESSION ;'
`SYMBOL -= EXPRESSION ;'
`SYMBOL *= EXPRESSION ;'
`SYMBOL /= EXPRESSION ;'
`SYMBOL <<= EXPRESSION ;'
`SYMBOL >>= EXPRESSION ;'
`SYMBOL &= EXPRESSION ;'
`SYMBOL = EXPRESSION ;'
第一个情况会把SYMBOL定义为值EXPRESSION. 其它情况下, SYMBOL必须是已经定义了的, 而值会作出相应的调
整.
特殊符号名'.'表示定位计数器. 你只可以在'SECTIONS'命令中使用它.
EXPRESSION后面的分号是必须的.
表达式下面会定义.
你在写表达式赋值的时候,可以把它们作为单独的部分,也可以作为'SECTIONS'命令中的一个语句,或者作为
'SECTIONS'命令中输出节描述的一个部分.
符号所在的节会被设置成表达式所在的节.
下面是一个关于在三处地方使用符号赋值的例子:
floating_point = 0;
SECTIONS
{
.text :
{
*(.text)
_etext = .;
}
_bdata = (. + 3) & ~ 3;
.data : { *(.data) }
}
在这个例子中, 符号`floating_point'被定义为零. 符号'-etext'会被定义为前面一个'.text'节尾部的地址.
而符号'_bdata'会被定义为'.text'输出节后面的一个向上对齐到4字节边界的一个地址值.
PROVIDE
-------
在某些情况下, 一个符号被引用到的时候只在连接脚本中定义,而不在任何一个被连接进来的目标文件中定
义. 这种做法是比较明智的. 比如, 传统的连接器定义了一个符号'etext'. 但是, ANSI C需要用户能够把
'etext'作为一个函数使用而不会产生错误. 'PROVIDE'关键字可以被用来定义一个符号, 比如'etext', 这个
定义只在它被引用到的时候有效,而在它被定义的时候无效.语法是 `PROVIDE(SYMBOL = EXPRESSION)'.
下面是一个关于使用'PROVIDE'定义'etext'的例子:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
在这个例子中, 如果程序定义了一个'_etext'(带有一个前导下划线), 连接器会给出一个重定义错误. 如果,
程序定义了一个'etext'(不带前导下划线), 连接器会默认使用程序中的定义. 如果程序引用了'etext'但不
定义它, 连接器会使用连接脚本中的定义.
SECTIONS命令
================
'SECTIONS'命令告诉连接器如何把输入节映射到输出节, 并如何把输出节放入到内存中.
'SECTIONS'命令的格式如下:
SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND
...
}
每一个SECTIONS-COMMAND可能是如下的一种:
* 一个'ENTRY'命令.
* 一个符号赋值.
* 一个输出节描述.
* 一个重叠描述.
'ENTRY'命令和符号赋值在'SECTIONS'命令中是允许的, 这是为了方便在这些命令中使用定位计数器. 这也可
以让连接脚本更容易理解, 因为你可以在更有意义的地方使用这些命令来控制输出文件的布局.
输出节描述和重叠描述在下面描述.
如果你在连接脚本中不使用'SECTIONS'命令, 连接器会按在输入文件中遇到的节的顺序把每一个输入节放到同
名的输出节中. 如果所有的输入节都在第一个文件中存在,那输出文件中的节的顺序会匹配第一个输入文件中
的节的顺序. 第一个节会在地址零处.
输出节描述
--------------------------
一个完整的输出节的描述应该是这个样子的:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]
大多数输出节不使用这里的可选节属性.
SECTION边上的空格是必须的, 所以节名是明确的. 冒号跟花括号也是必须的. 断行和其他的空格是可选的.
每一个OUTPUT-SECTION-COMMAND可能是如下的情况:
* 一个符号赋值.
* 一个输入节描述.
* 直接包含的数据值.
* 一个特定的输出节关键字.
输出节名.
-------------------
输出节的名字是SECTION. SECTION必须满足你的输出格式的约束. 在一个只支持限制数量的节的格式中,比如
'a.out',这个名字必须是格式支持的节名中的一个(比如, 'a.out'只允许'.text', '.data'或'.bss').如果
输出格式支持任意数量的节, 但是只支持数字,而没有名字(就像Oasys中的情况), 名字应当以一个双引号中的
数值串的形式提供.一个节名可以由任意数量的字符组成,但是一个含有任意非常用字符(比如逗号)的字句必须
用双引号引起来.
输出节描述
--------------------------
ADDRESS是关于输出节中VMS的一个表达式. 如果你不提供ADDRESS, 连接器会基于REGION(如果存在)设置它,或
者基于定位计数器的当前值.
如果你提供了ADDRESS, 那输出节的地址会被精确地设为这个值. 如果你既不提供ADDRESS也不提供REGION, 那
输出节的地址会被设为当前的定位计数器向上对齐到输出节需要的对齐边界的值. 输出节的对齐要求是所有输
入节中含有的对齐要求中最严格的一个.
比如:
.text . : { *(.text) }
和
.text : { *(.text) }
有细微的不同. 第一个会把'.text'输出节的地址设为当前定位计数器的值. 第二个会把它设为定位计数器的
当前值向上对齐到'.text'输入节中对齐要求最严格的一个边界.
ADDRESS可以是任意表达式; 比如,如果你需要把节对齐对0x10字节边界,这样就可以让低四字节的节地址值为
零, 你可以这样做:
.text ALIGN(0x10) : { *(.text) }
这个语句可以正常工作,因为'ALIGN'返回当前的定位计数器,并向上对齐到指定的值.
指定一个节的地址会改变定位计数器的值.
输入节描述
-------------------------
最常用的输出节命令是输入节描述.
输入节描述是最基本的连接脚本操作. 你使用输出节来告诉连接器在内存中如何布局你的程序. 你使用输入节
来告诉连接器如何把输入文件映射到你的内存中.
输入节基础
---------------------------
一个输入节描述由一个文件名后跟有可选的括号中的节名列表组成.
文件名和节名可以通配符形式出现, 这个我们以后再介绍.
最常用的输入节描述是包含在输出节中的所有具有特定名字的输入节. 比如, 包含所有输入'.text'节,你可以
这样写:
*(.text)
这里,'*'是一个通配符,匹配所有的文件名. 为把一部分文件排除在匹配的名字通配符之外, EXCLUDE_FILE可
以用来匹配所有的除了在EXCLUDE_FILE列表中指定的文件.比如:
(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors))
会让除了`crtend.o'文件和`otherfile.o'文件之外的所有的文件中的所有的.ctors节被包含进来.
有两种方法包含多于一个的节:
*(.text .rdata)
*(.text) *(.rdata)
上面两句的区别在于'.text'和'.rdata'输入节的输出节中出现的顺序不同. 在第一个例子中, 两种节会交替
出现,并以连接器的输入顺序排布. 在第二个例子中,所有的'.text'输入节会先出现,然后是所有的'.rdata'节.
你可以指定文件名,以从一个特定的文件中包含节. 如果一个或多个你的文件含有特殊的数据在内存中需要特
殊的定位,你可以这样做. 比如:
data.o(.data)
如果你使用一个不带有节列表的文件名, 那输入文件中的所有的节会被包含到输出节中. 通常不会这样做, 但
是在某些场合下这个可能非常有用. 比如:
data.o
当你使用一个不含有任何通配符的文件名时, 连接器首先会查看你是否在连接命令行上指定了文件名或者在
'INPUT'命令中. 如果你没有, 连接器会试图把这个文件作为一个输入文件打开, 就像它在命令行上出现一样.
注意这跟'INPUT'命令不一样, 因为连接器会在档案搜索路径中搜索文件.
输入节通配符
---------------------------------
在一个输入节描述中, 文件名或者节名,或者两者同时都可以是通配符形式.
文件名通配符'*'在很多例子中都可以看到,这是一个简单的文件名通配符形式.
通配符形式跟Unix Shell中使用的一样.
`*'
匹配任意数量的字符.
`?'
匹配单个字符.
`[CHARS]'
匹配CHARS中的任意单个字符; 字符'-'可以被用来指定字符的方讧, 比如[a-z]匹配任意小字字符.
`\'
转义其后的字符.
当一个文件名跟一个通配符匹配时, 通配符字符不会匹配一个'/'字符(在UNIX系统中用来分隔目录名), 一个
含有单个'*'字符的形式是个例外; 它总是匹配任意文件名, 不管它是否含有'/'. 在一个节名中, 通配符字
符会匹配'/'字符.
文件名通配符只匹配那些在命令行或在'INPUT'命令上显式指定的文件. 连接器不会通过搜索目录来展开通配
符.
如果一个文件名匹配多于一个通配符, 或者如果一个文件名显式出现同时又匹配了一个通配符, 连接器会使用
第一次匹配到的连接脚本. 比如, 下面的输入节描述序列很可能就是错误的,因为'data.o'规则没有被使用:
.data : { *(.data) }
.data1 : { data.o(.data) }
通常, 连接器会把匹配通配符的文件和节按在连接中被看到的顺序放置. 你可以通过'SORT'关键字改变它, 它
出现在括号中的通配符之前(比如, 'SORT(.text*)'). 当'SORT'关键字被使用时, 连接器会在把文件和节放到
输出文件中之前按名字顺序重新排列它们.
如果你对于输入节被放置到哪里去了感到很困惑, 那可以使用'-M'连接选项来产生一个位图文件. 位图文件会
精确显示输入节是如何被映射到输出节中的.
这个例子显示了通配符是如何被用来区分文件的. 这个连接脚本指示连接器把所有的'.text'节放到'.text'中, 把所有的'.bss'节放到'.bss'. 连接器会把所有的来自文件名以一个大写字母开始的文件中的'.data'节放进'.DATA'节中; 对于所有其他文件, 连接器会把'.data'节放进'.data'节中.
SECTIONS {
.text : { *(.text) }
.DATA : { [A-Z]*(.data) }
.data : { *(.data) }
.bss : { *(.bss) }
}
输入节中的普通符号.
-----------------------------------
对于普通符号,需要一个特殊的标识, 因为在很多目标格式中, 普通符号没有一个特定的输入节. 连接器会把
普通符号处理成好像它们在一个叫做'COMMON'的节中.
你可能像使用带有其他输入节的文件名一样使用带有'COMMON'节的文件名。你可以通过这个把来自一个特定输
入文件的普通符号放入一个节中,同时把来自其它输入文件的普通符号放入另一个节中。
在大多数情况下,输入文件中的普通符号会被放到输出文件的'.bss'节中。比如:
.bss { *(.bss) *(COMMON) }
有些目标文件格式具有多于一个的普通符号。比如,MIPS ELF目标文件格式区分标准普通符号和小普通符号。
在这种情况下,连接器会为其他类型的普通符号使用一个不同的特殊节名。 在MIPS ELF的情况中, 连接器
为标准普通符号使用'COMMON',并且为小普通符号使用'.common'。这就允许你把不同类型的普通符号映射到
内存的不同位置。
在一些老的连接脚本上,你有时会看到'[COMMON]'。这个符号现在已经过时了, 它等效于'*(COMMON)'。
输入节和垃圾收集
---------------------------------------
当连接时垃圾收集正在使用中时('--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] [: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 }
覆盖描述
-------------------
一个覆盖描述提供一个简单的描述办法,以描述那些要被作为一个单独内存映像的一部分载入内存,但是却要
在同一内存地址运行的节。在运行时,一些覆盖管理机制会把要被覆盖的节按需要拷入或拷出运行时内存地址,
并且多半是通过简单地处理内存位。 这个方法可能非常有用,比如在一个特定的内存区域比另一个快时。
覆盖是通过‘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代码看上去可能会像下面这样:
extern char __load_start_text1, __load_stop_text1;
memcpy ((char *) 0x1000, &__load_start_text1,
&__load_stop_text1 - &__load_start_text1);
注意'OVERLAY'命令只是为了语法上的便利,因为它所做的所有事情都可以用更加基本的命令加以代替。上面
的例子可以用下面的完全特效的写法:
.text0 0x1000 : AT (0x4000) { o1/*.o(.text) }
__load_start_text0 = LOADADDR (.text0);
__load_stop_text0 = LOADADDR (.text0) + SIZEOF (.text0);
.text1 0x1000 : AT (0x4000 + SIZEOF (.text0)) { o2/*.o(.text) }
__load_start_text1 = LOADADDR (.text1);
__load_stop_text1 = LOADADDR (.text1) + SIZEOF (.text1);
. = 0x1000 + MAX (SIZEOF (.text0), SIZEOF (.text1));
MEMORY命令
==============
连接器在缺省状态下被配置为允许分配所有可用的内存块。你可以使用‘MEMORY’命令重新配置这个设置。
‘MEMORY’命令描述目标平台上内存块的位置与长度。你可以用它来描述哪些内存区域可以被连接器使用,
哪些内存区域是要避免使用的。然后你就可以把节分配到特定的内存区域中。连接器会基于内存区域设置节
的地址,对于太满的区域,会提示警告信息。连接器不会为了适应可用的区域而搅乱节。
一个连接脚本最多可以包含一次'MEMORY'命令。但是,你可以在命令中随心所欲定义任意多的内存块,语法
如下:
MEMORY
{
NAME [(ATTR)] : ORIGIN = ORIGIN, LENGTH = LEN
...
}
NAME是用在连接脚本中引用内存区域的名字。出了连接脚本,区域名就没有任何实际意义。区域名存储在一个
单独的名字空间中,它不会和符号名,文件名,节名产生冲突,每一块内存区域必须有一个唯一的名字。
ATTR字符串是一个可选的属性列表,它指出是否为一个没有在连接脚本中进行显式映射地输入段使用一个特定
的内存区域。如果你没有为某些输入段指定一个输出段,连接器会创建一个跟输入段同名的输出段。如果你定
义了区域属性,连接器会使用它们来为它创建的输出段选择内存区域。
ATTR字符串必须包含下面字符中的一个,且必须只包含一个:
`R'
只读节。
`W'
可读写节。
`X'
可执行节。
`A'
可分配节。
`I'
已初始化节。
`L'
同‘I’
`!'
对前一个属性值取反。
如果一个未映射节匹配了上面除'!'之外的一个属性,它就会被放入该内存区域。'!'属性对该测试取反,, 所以
只有当它不匹配上面列出的行何属性时,一个未映射节才会被放入到内存区域。
ORIGIN是一个关于内存区域地始地址的表达式。在内存分配执行之前,这个表达式必须被求值产生一个常数,
这意味着你不可以使用任何节相关的符号。关键字'ORIGIN'可以被缩写为'org'或'o'(但是,不可以写为,比
如‘ORG’)
LEN是一个关于内存区域长充(以字节为单位)的表达式。就像ORIGIN表达式,这个表达式在分配执行前也
必须被求得为一个常数值。关键字'LENGTH'可以被简写为‘len'或'l'。
在下面的例子中,我们指定两个可用于分配的内存区域:一个从0开始,有256kb长度,另一个从0x4000000
开始,有4mb长度。连接器会把那些没有进行显式映射且是只读或可执行的节放到'rom'内存区域。并会把另
外的没有被显式映射地节放入到'ram'内存区域。
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0x40000000, l = 4M
}
一旦你定义了一个内存区域,你也可以指示连接器把指定的输出段放入到这个内存区域中,这可以通过使用
'>REGION'输出段属性。比如,如果你有一个名为'mem'的内存区域,你可以在输出段定义中使用'>mem'。如
果没有为输出段指定地址,连接器就会把地址设置为内存区域中的下一个可用的地址。如果总共的映射到一
个内存区域的输出段对于区域来说太大了,连接器会提示一条错误信息。
PHDRS命令
=============
ELF目标文件格式使用“程序头”,它也就是人们熟知的“节”。程序头描述了程序应当如何被载入到内存中。
你可以通过使用带有'-p'选项的‘objdump’命令来打印出这个程序头。
当你在一个纯ELF系统上运行ELF程序时,系统的载入程序通过读取文件头来计算得到如何来