链接文件的编写语法

ld命令语言是一组语句;有些是设置特定选项的简单关键字,有些用于选择和分组输入文件或命名输出文件;两种语句类型对链接过程有着根本而普遍的影响。

ld命令语言最基本的命令是SECTIONS命令。每个有意义的命令脚本都必须有一个SECTIONS命令:它以不同的细节程度指定输出文件布局的“图片”。在任何情况下都不需要其他命令。
MEMORY命令通过描述目标体系结构中的可用内存来补充SECTIONS。此命令是可选的;如果不使用MEMORY命令,ld假设在一个连续块中有足够的内存用于所有输出。请参阅内存布局一节。

您可以在链接器脚本中包括注释,就像在C:中一样:由“/”和“/”分隔。与C语言一样,注释在语法上等同于空白。

表达式
许多有用的命令都涉及算术表达式。命令语言中表达式的语法与C表达式的语法相同,具有以下特点:
所有计算结果为整数且为“long”或“unsigned long”类型的表达式。
所有常数都是整数。
提供了所有的C算术运算符。
您可以引用、定义和创建全局变量。
您可以调用特殊用途的内置函数。
Integers

An octal integer is 0' followed by zero or more of the octal digits (01234567’).

_as_octal = 0157255;

A decimal integer starts with a non-zero digit followed by zero or more digits (`0123456789’).

_as_decimal = 57005;

A hexadecimal integer is 0x' or 0X’ followed by one or more hexadecimal digits chosen from `0123456789abcdefABCDEF’.

_as_hex = 0xdead;

To write a negative integer, use the prefix operator `-’ (see section Operators).

_as_neg = -57005;

Additionally the suffixes K and M may be used to scale a constant by respectively. For example, the following all refer to the same quantity:

    _fourk_1 = 4K;
    _fourk_2 = 4096;
    _fourk_3 = 0x1000;

符号名

除非引用,否则符号名称以字母、下划线或点开头,并可能包括任何字母、下划线、数字、点和连字符。未引用的符号名称不得与任何关键字冲突。您可以指定一个包含奇数字符或与关键字名称相同的符号,方法是将符号名称用双引号括起来:

    "SECTION" = 9;
    "with a space" = "also with a space" + 10;

Since symbols can contain many non-alphabetic characters, it is safest to delimit symbols with spaces. For example, A-B' is one symbol, whereas A - B’ is an expression involving subtraction.

位置计数器

特殊链接器变量点“.”始终包含当前输出位置计数器。“.”始终引用输出节中的某个位置,它必须始终出现在SECTIONS命令中的表达式中。这个符号可以出现在表达式中允许使用普通符号的任何位置,但其赋值会产生副作用。为指定值。符号将导致位置计数器移动。这可以用于在输出部分中创建孔。位置计数器可能永远不会向后移动。
SECTIONS
{
output :
{
file1(.text)
. = . + 1000;
file2(.text)
. += 1000;
file3(.text)
} = 0x1234;
}

在前面的例子中,file1位于输出部分的开头,然后有一个1000字节的间隙。然后会出现file2,在加载file3之前还会出现1000字节的间隙。符号“=0x1234”指定要在间隙中写入的数据。

评估

链接器对表达式使用“惰性求值”;它只在绝对必要的时候计算一个表达式。链接器需要起始地址的值和内存区域的长度,以便进行任何链接;当链接器读取命令文件时,会尽快计算这些值。然而,直到存储分配之后,才知道或需要其他值(例如符号值)。当其他信息(例如输出部分的大小)可用于符号分配表达式时,稍后将评估这些值。

指定:定义符号

您可以使用任何C赋值运算符创建全局符号,并为全局符号赋值(地址):
symbol = expression ;
symbol &= expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;

在ld表达式中,赋值和其他运算符有两个区别。

赋值只能在表达式的根处使用a=b+3;'是允许的,但a+b=3;'是一个错误。

必须在赋值语句的末尾加一个尾随分号(“;”)。

分配语句可能出现:

作为ld脚本中的命令;或

作为SECTIONS命令中的独立语句;或

作为SECTIONS命令中节定义内容的一部分。

前两种情况实际上是等效的——都定义了一个具有绝对地址的符号。最后一种情况定义了一个符号,该符号的地址与特定节相关(请参见“指定输出节”一节)。

当对链接器表达式进行求值并将其分配给变量时,将为其提供绝对类型或可重定位类型。绝对表达式类型是指符号包含其在输出文件中的值的类型;可重定位表达式类型是这样一种类型,其中的值表示为距节底的固定偏移量。

表达式的类型由其在脚本文件中的位置控制。在截面定义中指定的符号是相对于截面的底部创建的;指定在任何其他位置的符号都将被创建为绝对符号。由于在节定义中创建的符号是相对于节的基的,因此如果请求可重定位输出,它将保持可重定位。即使通过使用绝对赋值函数absolute在区间定义内赋值,也可以创建具有绝对值的符号。例如,要创建一个绝对符号,其地址是名为.data的输出节的最后一个字节:
SECTIONS{ …
.data :
{
*(.data)
_edata = ABSOLUTE(.) ;
}
… }

链接器试图推迟对赋值的求值,直到知道源表达式中的所有项为止(请参见求值一节)。例如,分区的大小在分配之后才能知道,因此依赖于这些分区的分配在分配之后才会执行。一些表达式,例如那些依赖于位置计数器的表达式点“.”必须在分配期间进行评估。如果需要表达式的结果,但该值不可用,则会导致错误。例如,如下所示的脚本
SECTIONS { …
text 9+this_isnt_constant :
{ …
}
… }

将导致错误消息“初始地址的非常量表达式”。
在某些情况下,链接器脚本最好仅在符号被引用时定义符号,并且仅在链接中包含的任何对象都未定义符号时定义符号。例如,传统的链接器定义了符号“etext”。但是,ANSI C要求用户能够使用“etext”作为函数名而不会遇到错误。PROVIDE关键字可用于定义一个符号,例如“etext”,仅当它被引用但未定义时。语法为PROVIDE(符号=表达式)。

算术函数

命令语言包括许多用于链接脚本表达式的内置函数。
ABSOLUTE(exp)
返回表达式expr的绝对值(不可重定位,与非负值相反)。主要用于为节定义中的符号分配绝对值,其中符号值通常是相对于节的。
ADDR(section)
返回命名节的绝对地址。您的脚本之前必须定义了该节的位置。在以下示例中,symbol_1和symbol_2被指定了相同的值:

SECTIONS{ ...
  .output1 :
    { 
    start_of_output_1 = ABSOLUTE(.);
    ...
    }
  .output :
    {
    symbol_1 = ADDR(.output1);
    symbol_2 = start_of_output_1;
    }
... }

LOADADDR(区段)

返回命名节的绝对加载地址。这通常与ADDR相同,但如果在节定义中使用AT关键字,则可能有所不同(请参阅可选节属性一节)
ALIGN(exp)
返回与下一个exp边界对齐的当前位置计数器(.)的结果。exp必须是一个值为2的幂的表达式。这相当于
(. + exp - 1) & ~(exp - 1)
ALIGN不会更改位置计数器的值,它只是对其进行算术运算。例如,将output.data节与前一节之后的下一个0x2000字节边界对齐,并将该节中的变量设置为输入节之后的上一个0x8000边界:
SECTIONS{ …
.data ALIGN(0x2000): {
*(.data)
variable = ALIGN(0x8000);
}
… }
本例中首次使用ALIGN指定节的位置,因为它被用作节定义的可选起始属性(请参见可选节属性一节)。第二种用法只是定义变量的值。内置NEXT与ALIGN密切相关。

DEFINED(symbol)

如果符号在链接器全局符号表中并且已定义,则返回1,否则返回0。可以使用此函数为符号提供默认值。例如,以下命令文件片段显示了如何将全局符号begin设置为.text部分中的第一个位置,但如果名为begin的符号已经存在,则会保留其值:
SECTIONS{ …
.text : {
begin = DEFINED(begin) ? begin : . ;

}
… }
NEXT(exp)
返回下一个未分配的地址,它是exp的倍数。此函数与ALIGN(exp)密切相关;除非使用MEMORY命令为输出文件定义不连续内存,否则这两个函数是等效的。

SIZEOF(section)

如果已分配命名节,则返回该节的大小(以字节为单位)。在以下示例中,symbol_1和symbol_2被指定了相同的值:
SECTIONS{ …
.output {
.start = . ;

.end = . ;
}
symbol_1 = .end - .start ;
symbol_2 = SIZEOF(.output);
… }

SIZEOF_HEADERS
sizeof_headers
返回输出文件头的大小(以字节为单位)。如果您选择,您可以使用此数字作为第一部分的起始地址,以便于分页。
MAX(exp1, exp2)
Returns the maximum of exp1 and exp2.
MIN(exp1, exp2)
Returns the minimum of exp1 and exp2.

分号

以下位置需要使用分号(“;”)。在所有其他地方,它们可能出于美学原因而出现,但在其他方面却被忽视了。

分配
Assignment
分号必须出现在赋值表达式的末尾。
PHDRS
分号必须出现在PHDRS语句的末尾。

Memory Layout

链接器的默认配置允许分配所有可用内存。您可以使用MEMORY命令来覆盖此配置。MEMORY命令描述目标中内存块的位置和大小。通过仔细使用它,您可以描述链接器可能使用的内存区域,以及必须避免的内存区域。链接器不会打乱节以适应可用区域,但会将请求的节移动到正确的区域,并在区域过满时发出错误。
一个命令文件最多可以包含MEMORY命令的一个使用;但是,您可以在其中定义任意数量的内存块。语法为:
MEMORY
{
name (attr) : ORIGIN = origin, LENGTH = len

}
name
是链接器内部用来引用区域的名称。可以使用任何符号名称。区域名称存储在单独的名称空间中,不会与符号、文件名或节名冲突。使用不同的名称可以指定多个区域。
(attr)
是一个可选的属性列表,用于指定是否使用特定内存来放置链接器脚本中未列出的节。有效的属性列表必须由与节属性匹配的字符“ALIRWX”组成。如果省略属性列表,也可以省略其周围的括号。当前支持的属性包括:

Letter' Section Attribute R’
Read-only sections.
W' Read/write sections. X’
Sections containing executable code.
A' Allocated sections. I’
Initialized sections.
L' Same as I. !’
Invert the sense of any of the following attributes.

origin
是物理内存中区域的起始地址。在执行内存分配之前,它是一个必须计算为常量的表达式。关键词ORIGIN可以缩写为org或o(但不能缩写为“org”)。
len
是区域(表达式)的大小(以字节为单位)。关键字LENGTH可以缩写为len或l。
例如,指定内存有两个可供分配的区域——一个从0开始表示256千字节,另一个从0x40000000开始表示4兆字节。rom内存区域将获得所有没有显式内存寄存器的只读或包含代码的部分,而ram内存区域将获取这些部分。

MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0x40000000, l = 4M
}

一旦定义了一个名为mem的内存区域,就可以通过在sections命令中使用以“>mem”结尾的命令将特定的输出部分定向到那里(请参阅可选部分属性一节)。如果指向某个区域的组合输出部分对于该区域来说太大,则链接器将发出错误消息。

Specifying Output Sections

SECTIONS命令精确地控制输入部分在输出部分中的放置位置、它们在输出文件中的顺序以及它们被分配给哪些输出部分。

在一个脚本文件中最多可以使用一个SECTIONS命令,但其中可以有任意多的语句。SECTIONS命令中的语句可以执行以下三种操作之一:

定义切入点;

为符号指定一个值;

描述命名输出部分的位置,以及哪些输入部分进入其中。

您也可以在SECTIONS命令之外使用前两个操作——定义入口点和定义符号:请参阅“入口点”一节和“分配:定义符号”一节。为了方便您阅读脚本,这里也允许使用它们,以便在输出文件布局中的有意义的点上定义符号和入口点。

如果不使用SECTIONS命令,链接器会将每个输入节按照输入文件中第一次遇到这些节的顺序放入一个名称相同的输出节中。例如,如果所有输入部分都存在于第一个文件中,则输出文件中部分的顺序将与第一个输入文件中的顺序匹配。

Section Definitions

SECTIONS命令中最常用的语句是节定义,它指定输出节的属性:其位置、对齐方式、内容、填充模式和目标内存区域。这些规格大多是可选的;截面定义的最简单形式是
SECTIONS { …
secname : {
contents
}
… }
secname是输出部分的名称,其中包含一个指定的内容——例如,输入文件或输入文件部分的列表(请参阅section Placement一节)。secname周围的空格是必需的,这样节名就不含糊了。显示的其他空白是可选的。但是,您确实需要冒号“:”和大括号“{}”。

secname必须满足输出格式的限制。在只支持有限数量的节的格式中,如a.out,名称必须是该格式支持的名称之一(例如,a.out只允许.text、.data或.bss)。如果输出格式支持任何数量的节,但支持数字而不是名称(如Oasys的情况),则名称应以带引号的数字字符串提供。节名称可以由任何字符序列组成,但必须引用任何不符合标准ld符号名称语法的名称。请参见“符号名称”一节。

特殊的secname“/DISCARD/”可用于丢弃输入部分。分配给名为“/DISCARD/”的输出部分的任何部分都不包括在最终链接输出中。

链接器不会创建没有任何内容的输出节。这是为了在参考可能存在或可能不存在的输入部分时的方便。例如
.foo { *(.foo) }
只有在至少一个输入文件中有“.foo”节的情况下,才会在输出文件中创建“.foo”节。

Section Placement

在节定义中,可以通过列出特定的输入文件、列出特定的输出文件节或两者的组合来指定输出节的内容。也可以在剖面中放置任意数据,并定义相对于剖面开头的符号。

节定义的内容可以包括以下任何类型的语句。您可以在一个单独的节定义中包含任意多个,并用空格隔开。
filename
您可以简单地命名要放置在当前输出部分中的特定输入文件;该文件中的所有剖面都放置在当前剖面定义中。如果文件名已在另一个节定义中提及,并带有明确的节名称列表,则仅使用尚未分配的节。要按名称指定特定文件的列表,请执行以下操作:
.data : { afile.o bfile.o cfile.o }
该示例还说明了节定义的内容中可以包含多个语句,因为每个文件名都是一个单独的语句。
filename( section )
filename( section , section, … )
filename( section section … )

您可以命名输入文件中的一个或多个节,以便插入到当前输出节中。如果希望在括号内指定输入文件节的列表,请用空格分隔节名。

  • (section)
  • (section, section, …)
  • (section section …)

您可以从ld命令行引用所有文件,而不是在链接控制脚本中显式命名特定的输入文件:在带括号的输入文件节列表之前使用“”而不是特定的文件名。如果您已经按名称显式包含了一些文件,那么“”指的是所有剩余的文件——那些在输出文件中的位置尚未定义的文件。例如,要将Oasys文件中的第1节到第4节复制到.out文件的.text节,并将第13节和第14节复制到.data节:
SECTIONS {
.text :{
*(“1” “2” “3” “4”)
}

.data :{
*(“13” “14”)
}
}
`[ section … ]’ 用于从所有未分配的输入文件中指定命名节的替代方式。由于某些操作系统(VMS)允许在文件名中使用方括号,因此不再支持这种表示法。
filename( COMMON )
*( COMMON )

使用此表示法指定在输出文件中放置未初始化数据的位置*(COMMON)本身指的是来自所有输入文件的所有未初始化数据(只要尚未分配);filename(COMMON)是指特定文件中未初始化的数据。两者都是用于指定将输入文件节放置在何处的通用机制的特殊情况:ld允许您引用未初始化的数据,就像它在名为COMMON的输入文件节中一样,而不管输入文件的格式如何。
在任何可以使用特定文件或节名的地方,也可以使用通配符模式。链接器处理通配符的方式与Unix外壳程序处理通配符的方法类似。“”字符与任意数量的字符匹配。A“?”字符匹配任何单个字符。序列“[chars]”将匹配任何字符的单个实例;“-”字符可用于指定字符范围,如在“[a-z]”中,以匹配任何小写字母。“\”字符可用于引用以下字符。
当文件名与通配符匹配时,通配符将与“/”字符(在Unix上用于分隔目录名)不匹配。由单个“
”字符组成的模式是一个例外;它将始终与任何文件名匹配。在节名称中,通配符将与“/”字符匹配。
通配符只匹配在命令行上明确指定的文件。链接器不搜索目录以展开通配符。但是,如果在链接器脚本中指定了一个简单的文件名(一个没有通配符的名称),并且没有在命令行上指定该文件名,则链接器将尝试打开该文件,就像它出现在命令行一样。

在以下示例中,命令脚本将输出文件排列为三个连续的部分,分别命名为.text、.data和.bss,从所有输入文件的相应命名部分中获取每个部分的输入:
SECTIONS {
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) *(COMMON) }
}

以下示例从文件all.o中读取所有节,并将它们放在输出节outputa的开头,该输出节从位置0x10000开始。文件foo.o中的所有.input1节都紧跟在同一个输出节中。foo.o中的所有.input2节进入输出节outputb,然后是foo1.o中的.input1节。任何文件中的所有剩余.input1和.input2部分都写入输出节oututc。

SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}

此示例显示了如何使用通配符模式对文件进行分区。所有.text节都放在.text中,所有.bss节都放放在.bss中。对于所有以大写字符开头的文件,.data节都放进.data;对于所有其他文件,.data部分都放在.data中。

SECTIONS {
.text : { (.text) }
.DATA : { [A-Z]
(.data) }
.data : { *(.data) }
.bss : { *(.bss) }
}

Section Data Expressions
上述语句在输出文件中排列来自输入文件的数据。也可以将数据直接放置在链接命令脚本的输出部分中。这些附加语句大多涉及表达式(请参见“表达式”一节)。尽管为了便于演示,这里单独显示了这些语句,但在SECTIONS命令中的节定义中不需要这样的分离;你可以随意地把它们与我们刚才描述的任何语句混合在一起。
CREATE_OBJECT_SYMBOLS
为当前节中的每个输入文件创建一个符号,设置为从该输入文件写入的第一个数据字节的地址。例如,对于.out文件,通常每个输入文件都有一个符号。您可以通过如下定义output.text部分来实现这一点:
SECTIONS {
.text 0x2020 :
{
CREATE_OBJECT_SYMBOLS
*(.text)
_etext = ALIGN(0x2000);
}

}

如果sample.ld是包含此脚本的文件,而a.o、b.o、c.o和d.o是四个输入文件,其内容如下—
/* a.c */

afunction() { }
int adata=1;
int abss;

`ld -M -T sample.ld a.o b.o c.o d.o’ would create a map like this, containing symbols matching the object file names:

00000000 A __DYNAMIC
00004020 B _abss
00004000 D _adata
00002020 T _afunction
00004024 B _bbss
00004008 D _bdata
00002038 T _bfunction
00004028 B _cbss
00004010 D _cdata
00002050 T _cfunction
0000402c B _dbss
00004018 D _ddata
00002068 T _dfunction
00004020 D _edata
00004030 B _end
00004000 T _etext
00002020 t a.o
00002038 t b.o
00002050 t c.o
00002068 t d.o

symbol = expression ;
symbol f= expression ;
symbol是任何符号名称(请参见“符号名称”一节)。“f=”指的是任意一个运算符&=+=-=*=/=,它们结合了算术和赋值。将值指定给特定截面定义中的符号时,该值相对于截面的开头(请参见“指定:定义符号”一节)。如果你写
SECTIONS {
abs = 14 ;

.data : { … rel = 14 ; … }
abs2 = 14 + ADDR(.data);

}
abs和rel不具有相同的值;rel具有与abs2相同的值。

BYTE(expression)
SHORT(expression)
LONG(expression)
QUAD(expression)
SQUAD(expression)
通过在节定义中包含这四条语句中的一条,可以显式地将一个、两个、四个、八个无符号字节或八个有符号字节(分别)放置在该节的当前地址。当使用64位主机或目标时,QUAD和SQUAD是相同的。当主机和目标都是32位时,QUAD使用无符号的32位值,而SQUAD符号扩展该值。在写入值时,两者都将使用正确的端序。多字节数量以适合输出文件格式的任何字节顺序表示(见BFD部分)。
FILL(expression)

指定当前剖面的“填充样式”。节中任何未指定的内存区域(例如,通过向位置计数器“.”分配新值跳过的区域)都将用表达式参数中的两个最低有效字节填充。FILL语句覆盖在节定义中出现的点之后的内存位置;通过包含多个FILL语句,可以在输出部分的不同部分使用不同的填充模式。

Optional Section Attributes

以下是节定义的完整语法,包括所有可选部分:
SECTIONS {

secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill

}
secname和contents是必需的。有关内容的详细信息,请参见章节定义和章节放置。其余元素–start、BLOCK(align)、(NOLOAD)、AT(ldadr)、>region、:phdr和=fill–都是可选的。

start

您可以通过在节名称后立即指定start,强制将输出节加载到指定的地址。start可以表示为任何表达式。以下示例在位置生成部分输出
0x40000000:

SECTIONS {

output 0x40000000: {

}

}

BLOCK(align)
您可以包含BLOCK()规范来推进位置计数器。在截面开始之前,以便截面将从指定的路线开始。align是一个表达式。
(NOLOAD)
“(NOLOAD)”指令将标记在运行时不加载的节。链接器将正常处理该节,但会对其进行标记,以便程序加载程序不会将其加载到内存中。例如,在下面的脚本示例中,ROM部分被寻址在存储器位置“0”,并且在程序运行时不需要加载。ROM部分的内容将像往常一样出现在链接器输出文件中。
SECTIONS {
ROM 0 (NOLOAD) : { … }

}
AT ( ldadr )
AT关键字后面的表达式ldadr指定节的加载地址。默认情况(如果不使用AT关键字)是使加载地址与重新定位地址相同。此功能旨在使构建ROM映像变得容易。例如,此SECTIONS定义创建了两个输出部分:一个名为“.text”,从0x1000开始,另一个称为“.mdata”,即使其重新定位地址为0x2000,也会加载在“.text’部分的末尾。符号_data定义为值0x2000:
SECTIONS
{
.text 0x1000 : { *(.text) _etext = . ; }
.mdata 0x2000 :
AT ( ADDR(.text) + SIZEOF ( .text ) )
{ _data = . ; *(.data); _edata = . ; }
.bss 0x3000 :
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
与以这种方式生成的ROM一起使用的运行时初始化代码(对于C程序,通常为crt0)必须包括以下内容,以将初始化的数据从ROM映像复制到其运行时地址:
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

将此分区分配给以前定义的内存区域。请参阅内存布局一节。
:phdr
将此节分配给由程序标题描述的段。请参阅ELF程序标题一节。如果一个节被分配给一个或多个节,那么所有后续分配的节也将被分配给这些节,除非它们使用显式:phdr修饰符。若要防止将截面指定给通常默认为一个分段的分段,请使用:NONE。
=fill
包含=填充剖面定义指定该剖面的初始填充值。您可以使用任何表达式来指定填充。当写入输出文件时,当前输出部分中任何未分配的孔都将填充该值的两个最低有效字节,并根据需要重复。您也可以在节定义的内容中使用fill语句更改填充值

Overlays

OVERLAY命令提供了一种简单的方法来描述要作为单个内存映像的一部分加载但要在同一内存地址运行的部分。在运行时,某种覆盖管理器会根据需要将覆盖的部分复制到运行时内存地址中或从中复制出来,可能只需操作寻址位即可。例如,当内存的某个区域比另一个区域快时,这种方法可能很有用。

OVERLAY命令用于SECTIONS命令中。其显示如下:
OVERLAY start : [ NOCROSSREFS ] AT ( ldaddr )
{
secname1 { contents } :phdr =fill
secname2 { contents } :phdr =fill

} >region :phdr =fill

除了OVERLAY(关键字)之外,所有内容都是可选的,并且每个部分都必须有一个名称(上面的secname1和secname2)。OVERLAY构造中的节定义与一般SECTIONS构造中的定义相同(请参阅“指定输出节”一节),只是OVERLAY中的节不能定义地址和内存区域。

这些节都是用相同的起始地址定义的。各节的加载地址的排列方式使其在内存中连续,从OVERLAY整体使用的加载地址开始(与正常节定义一样,加载地址是可选的,默认为起始地址;起始地址也是可选的,并且默认为。)。

如果使用了NOCROSSREFS关键字,并且节之间有任何引用,则链接器将报告错误。由于节都在同一地址运行,因此一个节直接引用另一个节通常没有意义。请参见选项命令一节。

For each section within the OVERLAY, the linker automatically defines two symbols. The symbol __load_start_secname is defined as the starting load address of the section. The symbol __load_stop_secname is defined as the final load address of the section. Any characters within secname which are not legal within C identifiers are removed. C (or assembler) code may use these symbols to move the overlaid sections around as necessary.

At the end of the overlay, the value of . is set to the start address of the overlay plus the size of the largest section.

Here is an example. Remember that this would appear inside a SECTIONS construct.

对于OVERLAY中的每个部分,链接器会自动定义两个符号。符号__load_start_secname被定义为节的起始加载地址。符号__load_stop_secname被定义为节的最终加载地址。secname中任何在C标识符中不合法的字符都将被删除。C(或汇编程序)代码可以根据需要使用这些符号来移动覆盖的部分。

在覆盖的末尾,为的值。被设置为覆盖的起始地址加上最大部分的大小。

下面是一个例子。请记住,这将出现在SECTIONS构造内部。

OVERLAY 0x1000 : AT (0x4000)
{
.text0 { o1/.o(.text) }
.text1 { o2/
.o(.text) }
}

This will define both .text0 and .text1 to start at address 0x1000. .text0 will be loaded at address 0x4000, and .text1 will be loaded immediately after .text0. The following symbols will be defined: __load_start_text0, __load_stop_text0, __load_start_text1, __load_stop_text1.

这将定义.text0和.text1从地址0x1000开始。text0将在地址0x4000处加载,.text1将在.text0之后立即加载。将定义以下符号:__load_start_text0、__load_stop_text0,__load_start _text1、__load_stop_text1。
将overlay.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));

ELF Program Headers

ELF对象文件格式使用程序头,程序头由系统加载程序读取,并描述如何将程序加载到内存中。必须正确设置这些程序头,才能在本机ELF系统上运行程序。默认情况下,链接器将创建合理的程序头。然而,在某些情况下,希望更精确地指定程序头;PHDRS命令可以用于该目的。使用PHDRS命令时,链接器本身不会生成任何程序头。

PHDRS命令只有在生成ELF输出文件时才有意义。在其他情况下会忽略它。本手册未详细说明系统加载程序如何解释程序头;有关更多信息,请参阅ELF ABI。ELF文件的程序头可以使用objdump命令的“-p”选项显示。

这是PHDRS命令的语法。单词PHDRS、FILEHDR、AT和FLAGS是关键字。
PHDRS
{
name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ]
[ FLAGS ( flags ) ] ;
}
该名称仅用于链接器脚本的SECTIONS命令中的引用。它不会被放入输出文件中。

某些程序头类型描述了由系统加载程序从文件加载的内存段。在链接器脚本中,这些段的内容是通过将分配的输出节放置在段中来指定的。为此,描述SECTIONS命令中输出部分的命令应使用“:name”,其中name是PHDRS命令中出现的程序头的名称。请参见可选截面属性一节。

某些部分出现在多个线段中是正常的。这仅仅意味着一段内存包含另一段内存。这是通过重复“:name”来指定的,对要出现该节的每个程序头使用一次。

如果使用“:name”将一个节放置在一个或多个节中,则所有未指定“:name“的后续分配节都放置在相同的节中。这是为了方便,因为通常将一整套连续的部分放置在一个片段中。若要防止将截面指定给通常默认为一个分段的分段,请使用:NONE。

可能出现在节目标题类型之后的FILEHDR和PHDRS关键字也指示存储器段的内容。FILEHDR关键字表示该段应包括ELF文件头。PHDRS关键字意味着该段应包括ELF程序标题本身。

类型可以是以下类型之一。数字表示关键字的值。

PT_NULL (0)
Indicates an unused program header.
PT_LOAD (1)
Indicates that this program header describes a segment to be loaded from the file.
PT_DYNAMIC (2)
Indicates a segment where dynamic linking information can be found.
PT_INTERP (3)
Indicates a segment where the name of the program interpreter may be found.
PT_NOTE (4)
Indicates a segment holding note information.
PT_SHLIB (5)
A reserved program header type, defined but not specified by the ELF ABI.
PT_PHDR (6)
Indicates a segment where the program headers may be found.
expression
An expression giving the numeric type of the program header. This may be used for types not defined above.

可以指定段应加载到内存中的特定地址。这是使用AT表达式完成的。这与SECTIONS命令中使用的AT命令相同(请参见可选截面属性一节)。对程序头使用AT命令会覆盖SECTIONS命令中的任何信息。

通常,分段标志是根据分段设置的。FLAGS关键字可以用于显式地指定分段标志。标志的值必须是整数。用于设置程序头的p_flags字段。

以下是PHDRS的使用示例。这显示了在本机ELF系统上使用的一组典型的程序头。
PHDRS
{
headers PT_PHDR PHDRS ;
interp PT_INTERP ;
text PT_LOAD FILEHDR PHDRS ;
data PT_LOAD ;
dynamic PT_DYNAMIC ;
}

SECTIONS
{
. = SIZEOF_HEADERS;
.interp : { *(.interp) } :text :interp
.text : { *(.text) } :text
.rodata : { (.rodata) } / defaults to :text /

. = . + 0x1000; /
move to a new page in memory */
.data : { *(.data) } :data
.dynamic : { *(.dynamic) } :data :dynamic

}

The Entry Point

链接器命令语言包括专门用于定义输出文件(其入口点)中的第一可执行指令的命令。其参数是一个符号名称:

ENTRY(symbol)
与符号指定一样,ENTRY命令可以作为一个独立的命令放置在命令文件中,也可以放置在SECTIONS命令中的截面定义中——任何对布局最有意义的命令。

ENTRY只是选择入口点的几种方法之一。您可以通过以下任何方式指示它(按优先级降序显示:列表中较高的方法覆盖较低的方法)。

“-e”条目命令行选项;

链接器控制脚本中的ENTRY(符号)命令;

符号起始的值(如果存在);

.text部分的第一个字节的地址(如果存在);

地址0。

例如,您可以使用这些规则生成带有赋值语句的入口点:如果在输入文件中没有定义符号开始,您可以简单地定义它,并为其分配适当的值—

start=0x2020;

该示例显示了一个绝对地址,但您可以使用任何表达式。例如,如果输入对象文件使用其他一些符号名称约定作为入口点,则只需指定包含要开始的起始地址的任何符号的值:

start=其他符号;

Version Script

链接器命令脚本包括一个专门用于指定版本脚本的命令,并且仅对支持共享库的ELF平台有意义。版本脚本可以直接构建到您正在使用的链接器脚本中,也可以在链接时将版本脚本作为另一个输入文件提供给链接器。命令脚本语法为:
VERSION { version script contents }
版本脚本也可以通过“–version script”链接器命令行选项指定给链接器。版本脚本只有在创建共享库时才有意义。

版本脚本本身的格式与Solaris2.5中Sun链接器使用的格式相同。版本控制是通过定义具有版本脚本中指定的名称和相互依存关系的版本节点树来完成的。版本脚本可以指定哪些符号绑定到哪些版本节点,还可以将指定的一组符号缩小到本地范围,使它们在共享库之外不全局可见。

演示版本脚本语言的最简单方法是使用几个示例。
VERS_1.1 {
global:
foo1;
local:
old*;
original*;
new*;
};

VERS_1.2 {
foo2;
} VERS_1.1;

VERS_2.0 {
bar1; bar2;
} VERS_1.2;

在本例中,定义了三个版本节点`VERS_1.1’是定义的第一个版本节点,没有其他依赖项。符号“foo1”绑定到此版本节点,并且出现在各种对象文件中的许多符号在范围上被缩减为本地符号,因此它们在共享库之外不可见。

接下来,定义节点“VERS_1.2”。它依赖于“VERS_1.1”。符号“foo2”绑定到此版本节点。

最后,定义了节点“VERS_2.0”。它依赖于“VERS_1.2”。符号“bar1”和“bar2”绑定到此版本节点。

在库中定义的未特定绑定到版本节点的符号实际上绑定到库的未指定基本版本。可以在版本脚本中的某个位置使用“global:*”将所有未指定的符号绑定到给定的版本节点。

从词汇上讲,版本节点的名称除了可能向阅读它们的人建议之外,没有其他特定的含义。“2.0”版本本可以出现在“1.1”和“1.2”之间。然而,这将是编写版本脚本的一种令人困惑的方式。

当您将应用程序链接到具有版本化符号的共享库时,应用程序本身知道它需要每个符号的哪个版本,也知道它需要链接到的每个共享库的哪个版本节点。因此,在运行时,动态加载程序可以进行快速检查,以确保所链接的库实际上提供了应用程序解析所有动态符号所需的所有版本节点。通过这种方式,动态链接器可以确定地知道它所需要的所有外部符号将是可解析的,而不必搜索每个符号引用。

符号版本控制实际上是一种比SunOS更复杂的小版本检查方式。这里要解决的基本问题是,对外部函数的引用通常是根据需要绑定的,而不是在应用程序启动时全部绑定。如果共享库已过期,则可能缺少所需的接口;当应用程序尝试使用该接口时,它可能会突然意外地失败。使用符号版本控制,如果与应用程序一起使用的库太旧,则用户在启动程序时会收到警告。
Sun的版本控制方法有几个GNU扩展。其中第一个功能是将符号绑定到源文件中的版本节点,而不是在版本控制脚本中定义符号。这样做主要是为了减轻库维护人员的负担。这可以通过放置以下内容来实现:
asm(“.symver original_foo,foo@VERS_1.1”);
在C源文件中。这将函数“original_foo”重命名为绑定到版本节点“VERS_1.1”的“foo”的别名。“local:”指令可用于防止导出符号“original_foo”。

第二个GNU扩展是允许同一函数的多个版本出现在给定的共享库中。通过这种方式,可以在不增加共享库的主要版本号的情况下对接口进行不兼容的更改,同时仍然允许针对旧接口链接的应用程序继续运行。

这只能通过在汇编程序中使用多个“.symver”指令来实现。例如:
asm(“.symver original_foo,foo@”);
asm(“.symver old_foo,foo@VERS_1.1”);
asm(“.symver old_foo1,foo@VERS_1.2”);
asm(“.symver new_foo,foo@@VERS_2.0”);

在本例中,“foo@”表示绑定到符号的未指定基础版本的符号“foo”。包含此示例的源文件将定义4个C函数:“original_foo”、“old_foo”、“old_foo1”和“new_foo”。

当您对一个给定符号有多个定义时,需要有某种方法来指定一个默认版本,该版本将绑定对此符号的外部引用。这可以通过“.symver”指令的“foo@@VERS_2.0”类型来实现。一个符号的一个版本只能以这种方式声明为“默认”,否则同一个符号实际上会有多个定义。

如果希望将引用绑定到共享库中符号的特定版本,可以使用方便的别名(即“old_foo”),也可以使用“.symver”指令专门绑定到所讨论函数的外部版本。
附参考资料官网
gnu链接文件

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值