链接程序和库指南

第 9 章 Mapfile 选项

链接编辑器会自动且智能地将可重定位目标文件中的输入节映射到正在创建的输出文件中的段。通过结合使用 -M 选项和关联的 mapfile,可以更改链接编辑器提供的缺省映射。 此外,还可以使用 mapfile 创建新段、修改属性,以及提供符号版本控制信息。


注 –

使用 mapfile 选项时,可以轻松创建不会执行的输出文件。链接编辑器可以在不使用 mapfile 选项的情况下生成正确的输出文件。


系统提供的 mapfiles 样例驻留在 /usr/lib/ld 目录中。

Mapfile 结构和语法

可以将以下基本类型的指令输入 mapfile

每个指令可以跨越多行,并且可以包含任意数量的空格(包括换行符),但空格后面要跟随一个分号。

通常,段声明后面跟有映射指令。您可以声明段,然后定义节成为段的一部分所依据的标准。如果未先声明要映射到的段(内置段除外),便输入映射指令或大小符号声明,则会为该段赋予缺省属性。此类段为隐式声明的段。

大小符号声明和文件控制指令可以出现在 mapfile 中的任何位置。

以下各节将针对每种指令类型进行介绍。对于所有语法讨论,以下约定都适用:

段声明

段声明可在输出文件中创建新段,或者更改现有段的属性值。现有段可以是指先前定义的段,也可以是随后即将介绍的四个内置段之一。

段声明的语法如下:

        segment_name = {segment_attribute_value}*;

对于每个 segment_name,都可以按任意顺序指定任何数量的 segment_attribute_values,但每个值都要由空格进行分隔。每个段属性只能有一个属性值。下表列出了段属性及其有效值。

表 9–1 Mapfile 段属性

属性 

值 

segment_type

LOAD | NOTE | STACK

segment_flags

? [E] [N] [O] [R] [W] [X]

virtual_address

Vnumber

physical_address

Pnumber

length

Lnumber

rounding

Rnumber

alignment

Anumber

有四个内置段,其缺省属性值如下所示:

缺省情况下,禁用 bss 段。任何类型为 SHT_NOBITS(此类型为节的唯一输入)的节都是在 data 段中捕获的。有关 SHT_NOBITS 节的完整说明,请参见表 7–5。最简单的 bss 声明:

        bss =;

便足以创建 bss 段。任何 SHT_NOBITS 节都是由此段(而不是 data 段)捕获的。此段采用最简单的形式,并且使用与应用于任何其他段相同的缺省值对齐。还可以声明其他既可创建段又可为指定属性赋值的段属性。

链接编辑器的行为方式就好像在读入 mapfile 之前已经声明了这些段。请参见Mapfile 缺省选项

输入段声明时,请注意以下事项:


注 –

如果指定了 virtual_address 值,则将段放置在该虚拟地址处。对于系统内核,此方法可生成正确的结果。对于通过 exec(2) 启动的文件,此方法将生成错误的输出文件,因为段与其页边界的相对偏移量是错误的。


可以使用 ?E 标志创建空段。此空段没有关联的节。此段只能为可执行文件指定,并且其类型必须为具有指定大小和对齐值的 LOAD。允许具有多个此类型的段定义。

可以使用 ?N 标志控制是否将 ELF 头和任何程序头作为第一个可装入段的一部分包括在内。缺省情况下,ELF 头和程序头包括在第一个段内。这些头中的信息通常由运行时链接程序用在映射的映像中。可以使用 ?N 选项从第一个段的第一个节开始计算映像的虚拟地址。

可以使用 ?O 标志控制输出文件中各节的顺序。此标志用于与编译器的 -xF 选项一起使用。使用 -xF 选项编译文件时,将该文件中的每个函数都放置在与 .text 节具有相同属性的单独节中。这些节称为 .text%function_name

例如,使用 -xF 选项编译包含 main()foo()bar() 这三个函数的文件时,会生成可重定位的目标文件,并会将三个函数的文本放置在名为 .text%main.text%foo.text%bar 的节中。由于 -xF 选项强制实行每节一个函数,因此使用 ?O 标志控制各节的顺序实际上是控制函数的顺序。

请考虑以下用户定义的 mapfile

        text = LOAD ?RXO;

        text: .text%foo;

        text: .text%bar;

        text: .text%main;

第一个声明将 ?O 标志与缺省文本段进行关联。

如果源文件中函数定义的顺序为 mainfoobar,则最终的可执行文件所包含的函数顺序为 foobarmain

对于具有相同名称的静态函数,还必须使用文件名。?O 标志强制按 mapfile 中的要求对节进行排序。例如,如果静态函数 bar() 位于文件 a.ob.o 中,并且要将文件 a.o 中的函数 bar() 放置在文件 b.o 中的函数 bar() 之前,则 mapfile 项将显示为:

        text: .text%bar: a.o;

        text: .text%bar: b.o;

虽然此语法允许具有项:

        text: .text%bar: a.o b.o;

但此项不能保证将文件 a.o 中的函数 bar() 放置在 b.o 中的函数 bar() 之前。由于结果不可靠,因此建议不要使用第二种格式。

映射指令

映射指令指示链接编辑器如何将输入节映射到输出段。本质上,就是指定要映射到的段,并指明节为了映射到指定的段而必须具备的属性。某个节为映射到特定段而必须具备的 section_attribute_values 集称为此段的入口条件。节必须完全满足段的入口条件,才能置于输出文件的指定段中。

映射指令的语法如下:

        segment_name : {section_attribute_value}* [: {file_name}+];

对于 segment_name,可以按任意顺序指定任何数量的 section_attribute_values,其中每个值都由空格进行分隔。每个节属性最多允许具有一个节属性值。还可以通过 file_name 声明指定节必须来自某个特定的 .o 文件。下表列出了节属性及其有效值。

表 9–2 节属性

节属性 

值 

section_name

任何有效的节名 

section_type

$PROGBITS

$SYMTAB

$STRTAB

$REL

$RELA

$NOTE

$NOBITS

section_flags

? [[!]A] [[!]W] [[!]X]

输入映射指令时,请注意以下几点:

段内节的排序

使用以下表示法可以指定段中放置节的顺序:

        segment_name | section_name1;

        segment_name | section_name2;

        segment_name | section_name3;

将以上述形式命名的节按照它们在 mapfile 中列出的顺序放置在任何未命名的节之前。

大小符号声明

使用大小符号声明,可以定义新的全局绝对符号,以表示指定段的大小(以字节为单位)。可以在目标文件中引用此符号。大小符号声明的语法如下:

        segment_name @ symbol_name;

symbol_name 可以是任何合法的 C 标识符。链接编辑器不会检查 symbol_name 的语法。

文件控制指令

使用文件控制指令,可以指定共享库中有哪些版本定义在链接编辑期间可用。文件控制定义的语法如下:

        shared_object_name - version_name [ version_name ... ];

version_name 是指定 shared_object_name 中包含的版本定义名称。

映射示例

以下示例是用户定义的 mapfile。示例中左边的号码用于教学演示。实际上只有号码右边的信息会出现在 mapfile 中。


示例 9–1 用户定义的 Mapfile

    1.  elephant : .data : peanuts.o *popcorn.o; 

    2.  monkey : $PROGBITS ?AX; 

    3.  monkey : .data; 

    4.  monkey = LOAD V0x80000000 L0x4000; 

    5.  donkey : .data; 

    6.  donkey = ?RX A0x1000; 

    7.  text = V0x80008000;

在此示例中处理四个单独的段。隐式声明的段 elephant(第 1 行)从文件 peanuts.opopcorn.o 中接收所有 .data 节。请注意,*popcorn.o 与可以提供给链接编辑的任何 popcorn.o 文件相匹配。该文件无需位于当前目录中。另一方面,如果将 /var/tmp/peanuts.o 提供给链接编辑,则不会与 peanuts.o 相匹配,因为它没有前缀 *

隐式声明的段 monkey(第 2 行)接收既有 $PROGBITS 属性又有可分配且可执行属性 (?AX) 的节,同时还接收所有尚未存在于段 elephant 中且名为 .data(第 3 行)的节。进入 monkey 段的 .data 节无需是 $PROGBITS 节或可分配且可执行的节,因为输入 section_typesection_flags 值的行与 section_name 值所在的行不是同一个行。

如第 2 行的 $PROGBITS“与” ?AX 所示,相同行中的属性之间存在“与”关系。如第 2 行的 $PROGBITS ?AX“或”第 3 行的 .data 所示,相同段不同行的属性之间存在“或”关系。

monkey 段在第 2 行中隐式声明为:segment_type 值为 LOADsegment_flags 值为 RWX,未指定 virtual_addressphysical_addresslengthalignment 值(使用缺省值)。在第 4 行中,monkeysegment_type 值设置为 LOAD。由于 segment_type 属性值未发生更改,因此不会发出任何警告。virtual_address 值设置为 0x80000000 并且最大 length 值设置为 0x4000

第 5 行隐式声明 donkey 段。入口条件指定为将所有 .data 节路由到此段。实际上,没有任何节进入此段,因为第 3 行中 monkey 的入口条件会捕获所有这些节。在第 6 行中,segment_flags 值设置为 ?RX 并且 alignment 值设置为 0x1000。由于这两个属性值都发生了更改,因此会发出警告。

第 7 行将文本段的 virtual_address 值设置为 0x80008000

为了进行说明,用户定义的 mapfile 示例设计为会发出警告。如果要更改指令的顺序以避免发出警告,请使用以下示例:

    1.  elephant : .data : peanuts.o *popcorn.o; 

    4.  monkey = LOAD V0x80000000 L0x4000; 

    2.  monkey : $PROGBITS ?AX; 

    3.  monkey : .data; 

    6.  donkey = ?RX A0x1000; 

    5.  donkey : .data; 

    7.  text = V0x80008000;

以下 mapfile 示例使用段内节的排序:

    1.  text = LOAD ?RXN V0xf0004000; 

    2.  text | .text; 

    3.  text | .rodata; 

    4.  text : $PROGBITS ?A!W; 

    5.  data = LOAD ?RWX R0x1000;

此示例中处理了 textdata 段。第 1 行声明 text 段的 virtual_address0xf0004000,并且没有将 ELF 头或任何程序头作为此段的地址计算的一部分包括在内。第 2 行和第 3 行启用段内节排序,并指定 .text.rodata 节是此段中的前两个节。结果是 .text 节的虚拟地址为 0xf0004000,并且 .rodata 节紧跟该地址之后。

构成 text 段的任何其他 $PROGBITS 节位于 .rodata 节之后。第 5 行声明 data 段并指定其虚拟地址必须从 0x1000 字节边界开始。构成 data 段的第一个节还驻留在文件映像内的 0x1000 字节边界上。

Mapfile 缺省选项

链接编辑器使用缺省的 segment_attribute_values 和相应的缺省映射指令定义四个内置段(textdatabssnote)。虽然链接编辑器不使用实际的 mapfile 提供缺省值,但是缺省 mapfile 的模型可以帮助说明链接编辑器遇到 mapfile 时出现的情况。

以下示例说明 mapfile 在使用链接编辑器的缺省值时的表现。链接编辑器开始执行操作时的行为方式就好像已经读入了 mapfile。随后链接编辑器会读取 mapfile 并增大或更改缺省值。

        text = LOAD ?RX; 

        text : ?A!W; 

        data = LOAD ?RWX; 

        data : ?AW; 

        note = NOTE; 

        note : $NOTE;

读入 mapfile 中的每个段声明时,都会将其与现有的段声明列表进行如下比较:

  1. 如果 mapfile 中尚未存在此段,但是存在具有相同段类型值的其他段,则在具有相同 segment_type 的所有现有段之前添加此段。

  2. 如果现有 mapfile 中没有一个段与刚读入的段的 segment_type 值相同,则按 segment_type 值添加段以保持以下顺序:

    INTERP

    LOAD

    DYNAMIC

    NOTE

  3. 如果段的 segment_type 值为 LOAD,并且已经为此可装入 (LOAD) 的段定义了 virtual_address 值,则将此段放置在任何没有定义 virtual_address 值或 virtual_address 值较大的可装入 (LOAD) 的段之前,但要放置在任何 virtual_address 值较小的段之后。

读入 mapfile 中的每个映射指令时,将该指令添加在已经为同一个段指定的所有其他映射指令之后,但要在此段的缺省映射指令之前。

内部映射结构

映射结构是基于 ELF 的链接编辑器中最重要的数据结构之一。对应于模型缺省 mapfile 的缺省映射结构由链接编辑器使用。任何用户 mapfile 都可以增大或覆盖缺省映射结构中的特定值。

图 9–1 展示了一个典型但某种程度上已简化的映射结构。“入口条件”框对应于缺省映射指令中的信息。“段属性描述符”框对应于缺省段声明中的信息。“输出节描述符”框提供了每段下的各个节的详细属性。循环显示节本身。

图 9–1 简单的映射结构

简化的映射结构。

将节映射到段时,链接编辑器执行以下步骤:

  1. 读入节时,链接编辑器会检查入口条件列表以查看是否匹配。必须与所有指定的条件相匹配。

    图 9–1 中,text 段下节的 section_type 值必须为 $PROGBITSsection_flags 值必须为 ?A!W。此节的名称无需为 .text,因为入口条件中未指定任何名称。节的 section_flags 值可以是 X,也可以是 !X,因为入口条件中未指定任何执行位。

    如果不与任何入口条件匹配,则将节放置在输出文件的末尾(位于所有其他段之后)。不会为此信息创建任何程序头项。

  2. 当节位于段下时,链接编辑器会检查此段中现有输出节描述符的列表,方式如下:

    如果节属性值与现有输出节描述符的属性值完全匹配,则将此节放置在与该输出节描述符关联的节列表的末尾。

    例如,section_name 值为 .data1section_type 值为 $PROGBITSsection_flags 值为 ?AWX 的节将进入图 9–1 中第二个“入口条件”框,可以将其放置在 data 段中。此节与第二个“输出节描述符”框(.data1$PROGBITS?AWX)完全匹配,并将被添加到与该框关联的列表的末尾。fido.orover.osam.o 中的 .data1 节说明了这一点。

    如果未找到匹配的输出节描述符,但是存在其他具有相同 section_type 的输出节描述符,则使用与节相同的属性值创建新的输出节描述符,并且此节与新的输出节描述符相关联。输出节描述符和节放置在相同节类型的最后一个输出节描述符之后。图 9–1 中的 .data2 节便是按照此方式放置的。

    如果不存在其他具有所示节类型的输出节描述符,则创建新的输出节描述符,并将节放置在此节中。


    注 –

    如果输入节的用户定义类型值介于 SHT_LOUSERSHT_HIUSER 之间,则将其视为 $PROGBITS 节。在 mapfile 中没有命名此 section_type 值的方法,但是可以使用入口条件中的其他属性值规范(section_flagssection_name)重定向这些节。


  3. 如果在读取所有命令行目标文件和库之后,段中没有任何节,则不会为此段生成任何程序头项。


注 –

类型为 $SYMTAB$STRTAB$REL$RELA 的输入节由链接编辑器在内部使用。引用这些节类型的指令只能将链接编辑器生成的输出节映射到段。