OK6410---链接脚本

每个链接都由链接脚本控制。此脚本使用链接器命令语言编写。

链接脚本的主要目的是描述输入文件中的各部分应如何映射到输出文件,以及如何控制输出文件的内存布局。大多数链接器脚本只会执行此操作。但是,必要时,链接脚本还可以使用下面描述的命令指示链接器执行许多其他操作。

链接器始终使用链接脚本。如果您自己不提供,则链接器将使用自带的默认脚本。您可以使用'--verbose'命令行选项显示默认链接脚本。某些命令行选项(例如“-r”或“-N”)将影响默认链接脚本。

您可以使用'-T'命令行选项提供自己的链接脚本。执行此操作时,自己编写的链接脚本将替换默认链接脚本。您也可以隐式使用链接脚本,将它们命名为链接器的输入文件,就好像它们是被链接的文件一样。

1 基本链接器脚本概念

我们需要定义一些基本概念和词汇来描述链接器脚本语言。链接器将多个输入文件合并为单个输出文件。输出文件和每个输入文件采用称为目标文件格式的特殊数据格式。每个文件都称为目标文件。输出文件通常称为可执行文件,但出于我们的目的,我们也将其称为目标文件。除了别的以外,每个目标文件都有一个sections列表。我们有时将输入文件中的一个section称为输入段;类似地,输出文件中的section是输出段。

目标文件中的每个section都有名称和大小。大多数section还有一个相关的数据块,称为段内容。一个section可能被标记为可加载,这意味着在运行输出文件时应将内容加载到内存中。没有内容的section可以是可分配的,这意味着应该留出内存中的区域,但是不应该在那里加载任何内容(在某些情况下,该内存必须被清零)。既不可加载也不可分配的section通常包含某种调试信息。

每个可加载或可分配的输出section都有两个地址。第一个是VMA(虚拟内存地址)。这是运行输出文件时该section的地址。第二个是LMA(加载内存地址)。这是加载到内存时该section的地址。在大多数情况下,两个地址将是相同的。它们可能不同的一个例子是当数据部分加载到ROM中,然后在程序启动时复制到RAM中(这种技术通常用于在基于ROM的系统中初始化全局变量)。在这种情况下,ROM地址将是LMA,RAM地址将是VMA。

您可以使用带有'-h'选项的objdump程序查看目标文件中的部分。每个目标文件还有一个符号列表,称为符号表。符号可以定义或未定义。每个符号都有一个名称,每个定义的符号都有一个地址,以及其他信息。如果将C或C ++程序编译为目标文件,则将为每个已定义的函数和全局或静态变量获取已定义的符号。输入文件中引用的每个未定义函数或全局变量都将成为未定义的符号。您可以使用nm程序或使用带有'-t'选项的objdump程序查看目标文件中的符号。

2 链接器脚本格式

链接器脚本是文本文件。链接脚本由一系列命令组成。每个命令都是关键字,可能后跟参数,或者是符号的赋值。您可以使用分号分隔命令。空格通常被忽略。

通常可以直接输入文件或格式名称等字符串。如果文件名包含逗号,则用于分隔文件名,也可以用双引号括起来。在文件名中不能使用双引号字符。您可以在链接器脚本中包含注释,就像在C中一样,由“/*”和“*/”分隔。在C语言中,注释在语法上等同于空白。

3 简单链接器脚本示例

许多链接器脚本相当简单。最简单的链接器脚本只有一个命令:“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”节之间创建一个小间隙。这是一个简单而完整的链接器脚本。

4 简单链接器脚本命令

4.1 设置入口点

在程序中执行的第一条指令称为入口点。您可以使用ENTRY链接脚本命令设置入口点。参数是符号名称:

          ENTRY(symbol)

有几种方法可以设置入口点。链接器将按顺序尝试以下每个方法来设置入口点,并在其中一个成功时停止:

  • '-e'命令行选项
  • 链接脚本中的ENTRY(symbol)命令
  • 目标特定符号的值,如果已定义;对于许多目标,这是start,但是基于PE和BeOS的系统例如检查可能的条目符号列表,匹配找到的第一个符号。
  • '.text'部分的第一个字节的地址(如果存在)
  • 地址0

4.2 文件处理的命令

INCLUDE filename

     此时包括链接器脚本文件名。将在当前目录和使用“-l”选项指定的任何目录中搜索该文件。您可以嵌套调用以包含最多10个级别的深度。您可以将include指令放在顶层、内存或SECTIONS命令中,或者放在输出节描述中。

INPUT(file, file, ...)
INPUT(file file ...)

     INPUT命令指示链接器在链接中包含命名文件,就好像它们是在命令行上命名的一样。例如,如果您总是希望在任何时候进行链接时包含“subr.o”,但您不想费心将其放在每个链接命令行上,那么您可以将“input(subr.o)”放在链接器脚本中。实际上,如果您愿意,可以在链接器脚本中列出所有输入文件,然后只使用“-t”选项调用链接器。如果配置了sysroot前缀,并且文件名以“/”字符开头,并且正在处理的脚本位于sysroot前缀内,则将在sysroot前缀中查找文件名。否则,链接器将尝试在当前目录中打开该文件。如果未找到,链接器将搜索存档库搜索路径。也可以通过指定=作为文件名路径中的第一个字符,或者使用$ SYSROOT为文件名路径添加前缀来强制sysroot前缀。如果使用'INPUT(-lfile)',ld会将名称转换为libfile.a,与命令行参数'-l'一样。在隐式链接器脚本中使用输入命令时,这些文件将包含在链接器脚本文件所在的链接中。这可能会影响存档搜索。

GROUP(file, file, ...)
GROUP(file file ...)

     GROUP命令类似于INPUT,除了命名文件都应该是归档文件,并且重复搜索它们,直到没有创建新的未定义引用。

AS_NEEDED(file, file, ...)
AS_NEEDED(file file ...)

     此构造只能出现在INPUT或GROUP命令中,以及其他文件名中。列出的文件将被视为直接出现在INPUT或GROUP命令中,但ELF共享库除外,它们仅在实际需要时才会添加。这个结构基本上为其中列出的所有文件启用了“--as-needed”选项,并恢复了之前的'--as-needed'响应。 ‘--no-as-needed’ 之后设置。

OUTPUT(filename)

     OUTPUT命令命名输出文件。在链接器脚本中使用OUTPUT(filename)与在命令行中使用“-o filename”效果相同。如果两者都使用,则命令行选项优先。您可以使用OUTPUT命令为输出文件定义默认名称,而不是通常的默认值'a.out'。

SEARCH_DIR(path)

     SEARCH_DIR命令添加路径到ld查找归档库的路径列表。使用SEARCH_DIR(path)就像在命令行上使用'-L path'一样。如果两者都使用,则链接器将搜索两个路径。首先搜索使用命令行选项指定的路径。

STARTUP(filename)

     STARTUP命令就像INPUT命令一样,除了文件名将成为要链接的第一个输入文件,就好像它是在命令行中首先指定的那样。

4.3 处理目标文件格式的命令

OUTPUT_FORMAT(bfdname)
OUTPUT_FORMAT(default, big, little)

     OUTPUT_FORMAT命令将BFD格式命名为输出文件。使用OUTPUT_FORMAT(bfdname)就像在命令行上使用'--oformat bfdname'。如果两者都使用,则命令行选项优先。您可以将OUTPUT_FORMAT与三个参数一起使用,以根据'-EB'和'-EL'命令行选项使用不同的格式。这允许链接器脚本根据所需的endianness设置输出格式。如果既不使用'-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命令也用于设置输出文件的格式。

4.4 将别名分配给内存区域

REGION_ALIAS(alias, region)

     每个名称最多对应一个存储区域。 REGION_ALIAS函数为内存区域区域创建别名name别名。这允许灵活地将输出部分映射到存储器区域。一个例子如下。

假设我们有一个嵌入式系统的应用程序,它带有各种内存存储设备。它们都具有通用的易失性存储器RAM,允许代码执行或数据存储。一些可能具有只读,非易失性存储器ROM,其允许代码执行和只读数据访问。最后一个变体是只读,非易失性存储器ROM2,具有只读数据访问和无代码执行能力。我们有四个输出部分:

  • .text程序代码;
  • .rodata只读数据;
  • .data读写初始化数据;
  • .bss读写未初始化数据。

目标是提供一个链接器命令文件,其中包含定义输出节的系统独立部分和系统相关部分,将输出节映射到系统上可用的内存区。我们的嵌入式系统有三种不同的内存设置A,B和C:

符号RAM / ROM或RAM / ROM2表示该部分分别加载到区域ROM或ROM2中。请注意,.data部分的加载地址从.rodata部分末尾的所有三个Variant开始。下面是处理输出部分的基本链接器脚本。它包括描述内存布局的系统相关linkcmds.memory文件:

INCLUDE linkcmds.memory

SECTIONS
{
    .text :
    {
        *(.text)
    } > REGION_TEXT
    .rodata :
    {
        *(.rodata)
    rodata_end = .;
    } > REGION_RODATA
    .data : AT (rodata_end)
    {
        data_start = .;
        *(.data)
    } > REGION_DATA
    data_size = SIZEOF(.data);
    data_load_start = LOADADDR(.data);
    .bss :
    {
        *(.bss)
    } > REGION_BSS
}

 现在我们需要三个不同的linkcmds.memory文件来定义内存区域和别名。linkcmds.memory的内容为三个变体A,B和C:

A               这里的一切都进入了RAM。

MEMORY
{
    RAM : ORIGIN = 0, LENGTH = 4M
}
REGION_ALIAS("REGION_TEXT", RAM);
REGION_ALIAS("REGION_RODATA", RAM);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);

B               程序代码和只读数据进入ROM。读写数据进入RAM。初始化数据的映像被加载到ROM中,并在系统启动期间被复制到RAM中。

MEMORY
{
    ROM : ORIGIN = 0, LENGTH = 3M
    RAM : ORIGIN = 0x10000000, LENGTH = 1M
}
REGION_ALIAS("REGION_TEXT", ROM);
REGION_ALIAS("REGION_RODATA", ROM);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);

C               程序代码进入ROM。只读数据进入ROM2。读写数据进入RAM。初始化数据的映像被加载到ROM2中,并在系统启动期间被复制到RAM中。

MEMORY
{
    ROM : ORIGIN = 0, LENGTH = 2M
    ROM2 : ORIGIN = 0x10000000, LENGTH = 1M
    RAM : ORIGIN = 0x20000000, LENGTH = 1M
}
REGION_ALIAS("REGION_TEXT", ROM);
REGION_ALIAS("REGION_RODATA", ROM2);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);

如有必要,可以编写一个通用系统初始化例程,将.data部分从ROM或ROM2复制到RAM中:

#include <string.h>

extern char data_start [];
extern char data_size [];
extern char data_load_start [];

void copy_data(void)
{
    if (data_start != data_load_start)
    {
        memcpy(data_start, data_load_start, (size_t) data_size);
    }
}

 4.5 其他链接器脚本命令

ASSERT(exp, message)

     确保exp不为零。如果为零,则使用错误代码退出链接器,然后打印消息。请注意,在进行链接的最后阶段之前检查断言。这意味着如果用户没有为这些符号设置值,则涉及节定义内部符号PROVIDEd的表达式将失败。此规则的唯一例外是仅引用点的PROVIDEd符号。因此这样的断言:

.stack :
{
    PROVIDE (__stack = .);
    PROVIDE (__stack_size = 0x100);
    ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
}

如果__stack_size未在其他地方定义,则会失败。在部分定义之外提供的符号会在之前进行评估,因此可以在ASSERTions中使用它们。由此:

PROVIDE (__stack_size = 0x100);

.stack :
{
    PROVIDE (__stack = .);
    ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
}

将工作。

EXTERN(symbol symbol ...)

     强制符号作为未定义的符号输入到输出文件中。例如,这样做可以触发标准库中其他模块的链接。您可以为每个EXTERN列出几个符号,并且可以多次使用EXTERN。此命令与'-u'命令行选项具有相同的效果。 

FORCE_COMMON_ALLOCATION

     此命令与'-d'命令行选项具有相同的效果:即使指定了可重定位的输出文件(' -  r'),也要使ld为常用符号分配空间。 

INHIBIT_COMMON_ALLOCATION

     此命令与'--no-define-common'命令行选项具有相同的效果:使ld省略了对公共符号的地址分配,即使对于不可重定位的输出文件也是如此。 

FORCE_GROUP_ALLOCATION

     此命令与'--force-group-allocation'命令行选项具有相同的效果:使ld将section部分成员组成正常的输入部分,并删除部分组,即使指定了可重定位的输出文件('-r')。 

INSERT [ AFTER | BEFORE ] output_section

     此命令通常用于由'-T'指定的脚本中,以使用例如叠加来扩充默认的SECTIONS。它在输出节之后(或之前)插入所有以前的链接器脚本语句,并导致“-t”不重写默认链接器脚本。插入点与孤立部分相同。在链接器将输入节映射到输出节之后发生插入。在插入之前,由于在默认链接器脚本之前对“-T”脚本进行了分析,因此“-T”脚本中的语句出现在脚本的内部链接器表示形式中的默认链接器脚本语句之前。 特别是,输入节分配将在默认脚本中的输出节之前进行。-T'输出节。以下是使用INSERT的'-T'脚本如何显示的示例:

SECTIONS
{
    OVERLAY :
    {
        .ov1 { ov1*(.text) }
        .ov2 { ov2*(.text) }
    }
}
INSERT AFTER .text;

 

 NOCROSSREFS(section section ...)

     此命令可用于告诉ld发出有关某些输出节之间的任何引用的错误。在某些类型的程序中,特别是在使用叠加层时的嵌入式系统上,当一个部分加载到内存中时,另一个部分不会。这两个部分之间的任何直接引用都是错误。例如,如果一个部分中的代码调用了另一个部分中定义的函数,这将是一个错误。

NOCROSSREFS命令采用输出节名称列表。如果ld检测到部分之间的任何交叉引用,它将报告错误并返回非零退出状态。请注意,NOCROSSREFS命令使用输出节名称,而不是输入节名称。

NOCROSSREFS_TO(tosection fromsection ...)

     此命令可用于告诉ld从其他节的列表中发出关于一个节的任何引用的错误。当确保两个或多个输出节完全独立但有些情况需要单向依赖时,NOCROSSREFS命令很有用。例如,在多核应用程序中,可能存在可以从每个核心调用的共享代码,但为了安全,必须永远不回调。NOCROSSREFS_TO命令获取输出节名称列表。第一部分不能从任何其他部分引用。如果ld检测到任何其他部分对第一部分的任何引用,它将报告错误并返回非零退出状态。请注意,NOCROSSREFS_TO命令使用输出节名称,而不是输入节名称。 

OUTPUT_ARCH(bfdarch)

      指定特定的输出机器体系结构。参数是BFD库使用的名称之一。您可以使用带有'-f'选项的objdump程序来查看目标文件的体系结构。 

LD_FEATURE(string)

     此命令可用于修改ld行为。如果字符串是“SANE_EXPR”,那么脚本中的绝对符号和数字在任何地方都被简单地视为数字。 

5 将值分配给符号

可以将值赋给链接器脚本中的符号。这将定义符号并将其放入具有全局范围的符号表中。

5.1 简单分配

您可以使用任何C赋值运算符分配符号:

symbol = expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;
symbol <<= expression ;
symbol >>= expression ;
symbol &= expression ;
symbol |= expression ;

第一种情况将符号定义为表达式的值。在其他情况下,必须已经定义符号,并相应地调整值。特殊符号名称“.”表示位置计数器。您只能在SECTIONS命令中使用它。表达式后需要分号。 

您可以将符号赋值作为命令单独编写,也可以作为SECTIONS命令中的语句编写,或者作为SECTIONS命令中输出节描述的一部分编写。以下示例显示了可以使用符号分配的三个不同位置:

floating_point = 0;
SECTIONS
{
    .text :
    {
        *(.text)
        _etext = .;
    }
    _bdata = (. + 3) & ~ 3;
    .data : { *(.data) }
}

在此示例中,符号“floating_point”将定义为零。符号'_etext'将被定义为最后'.text'输入部分后面的地址。符号'_bdata'将被定义为'.text'输出部分后面的地址,向上对齐到4字节边界。

5.2 HIDDEN

对于以ELF为目标的端口,定义一个将隐藏且不会导出的符号。语法是HIDDEN(symbol = expression)。 以下是第5.1节[简单分配中的示例,重写为使用HIDDEN:

HIDDEN(floating_point = 0);
SECTIONS
{
    .text :
    {
        *(.text)
        HIDDEN(_etext = .);
    }
    HIDDEN(_bdata = (. + 3) & ~ 3);
    .data : { *(.data) }
}

在这种情况下,三个符号中没有一个在该模块外可见。

5.3 PROVIDE

在某些情况下,链接器脚本只有在引用符号时才需要定义符号,并且链接中包含的任何对象都不定义符号。例如,传统的链接器定义了符号“etext”。但是,ANSI C要求用户能够使用“etext”作为函数名称而不会遇到错误。PROVIDE关键字只能在被引用但未定义的情况下用于定义符号,如“etext”。语法是PROVIDE(symbol = expression)。以下是使用PROVIDE定义'etext'的示例:

SECTIONS
{
    .text :
    {
        *(.text)
        _etext = .;
        PROVIDE(etext = .);
    }
}

在此示例中,如果程序定义'_etext'(带有前导下划线),则链接器将给出多重定义错误。另一方面,如果程序定义'etext'(没有前导下划线),链接器将默默使用程序中的定义。如果程序引用了“etext”,但没有定义它,则链接器将使用链接器脚本中的定义。注 -  PROVIDE指令考虑定义一个公共符号,即使这样的符号可以与PROVIDE创建的符号组合。在考虑构造函数和析构函数列表符号(例如'__CTOR_LIST__')时,这一点尤其重要,因为这些符号通常被定义为公共符号。

5.4 PROVIDE HIDDEN

与PROVIDE类似。对于ELF目标端口,该符号将被隐藏,不会被导出。

5.5 源代码引用

从源代码访问链接器脚本定义的变量是不直观的。尤其是链接器脚本符号不等同于高级语言中的变量声明,而是没有值的符号。

在进一步说明之前,请务必注意,编译器在将源代码中的名称存储在符号表中时,通常会将其转换为不同的名称。例如,FORTRAN编译器通常准备或追加下划线,C++执行广泛的“名称修改”。因此,源代码中使用的变量名与链接器脚本中定义的相同变量名之间可能存在差异。例如,在C中,链接器脚本变量可能被称为:

extern int foo; 

但在链接器脚本中,它可能被定义为:

_foo = 1000; 

然而,在其余示例中,假设没有发生名称变换。当用C等高级语言声明符号时,会发生两件事。第一个是编译器在程序的内存中保留足够的空间来保存符号的值。第二个是编译器在程序的符号表中创建一个条目,该表保存符号的地址。即符号表包含保存符号值的存储块的地址。例如,在文件范围内的以下C声明:

int foo = 1000; 

在符号表中创建一个名为“foo”的条目。此条目保存“int”大小的内存块的地址,其中最初存储数字1000。当程序引用符号时,编译器生成的代码首先访问符号表以查找符号的内存块的地址,然后编写代码以从该内存块中读取值。所以:

 foo = 1;

在符号表中查找符号'foo',获取与该符号关联的地址,然后将值1写入该地址。然而:

int * a = & foo; 

在符号表中查找符号'foo',获取其地址,然后将此地址复制到与变量'a'关联的内存块中。

相反,链接器脚本符号声明在符号表中创建一个条目,但不为它们分配任何内存。因此,它们是没有价值的地址。 例如,链接器脚本定义:

foo = 1000;

在名为'foo'的符号表中创建一个条目,该条目保存内存位置1000的地址,但在地址1000处不存储任何特殊内容。这意味着您无法访问链接器脚本定义的符号值 - 它没有值 - 您所能做的就是访问链接器脚本定义的符号的地址。

因此,当您在源代码中使用链接器脚本定义符号时,应始终使用符号的地址,而不要尝试使用其值。 例如,假设您要将名为.ROM的内存部分的内容复制到名为.FLASH的部分中,并且链接脚本包含以下声明:

start_of_ROM = .ROM;
end_of_ROM = .ROM + sizeof (.ROM);
start_of_FLASH = .FLASH;

然后执行复制的C源代码将是:

extern char start_of_ROM, end_of_ROM, start_of_FLASH;
memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM); 

注意使用'&'运算符。这些是正确的。或者,可以将符号视为向量或数组的名称,然后代码将再次按预期工作:

extern char start_of_ROM[], end_of_ROM[], start_of_FLASH[];
memcpy (start_of_FLASH, start_of_ROM, end_of_ROM - start_of_ROM); 

请注意,使用此方法不需要使用'&'运算符。 

6 SECTIONS命令 

SECTIONS命令告诉链接器如何将输入节映射到输出节,以及如何将输出节放在内存中。SECTIONS命令的格式为:

SECTIONS
{
    sections-command
    sections-command
    ...

每个sections-command可以是以下之一:

  • 一个ENTRY命令
  • 符号赋值
  • 输出部分描述
  • 重叠描述

SECTIONS命令中允许使用ENTRY命令和符号赋值,以方便在这些命令中使用位置计数器。这也可以使链接器脚本更容易理解,因为您可以在输出文件布局中有意义的点上使用这些命令。

输出部分描述和覆盖描述如下所述。 如果在链接脚本中未使用SECTIONS命令,则链接器将按输入文件中首次遇到这些节的顺序将每个输入节放入同名的输出节中。例如,如果第一个文件中存在所有输入节,则输出文件中节的顺序将与第一个输入文件中的顺序相匹配。第一个section将在零地址处。

6.1 输出部分说明

输出部分的完整描述如下所示:

section [address] [(type)] :
 [AT(lma)]
 [ALIGN(section_align) | ALIGN_WITH_INPUT]
 [SUBALIGN(subsection_align)]
 [constraint]
{
    output-section-command
    output-section-command
    ...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] [,]

大多数输出节不使用大多数可选节属性。section周围的空白是必需的,因此节名是明确的。 冒号和花括号也是必需的。如果使用fillexp并且下一条sections-command看起来像表达式的延续,则可能需要最后的逗号。换行符和其他空格是可选的。每个output-section-command可以是以下之一:

  • 符号赋值
  • 输入部分描述
  • 要直接包含的数据值
  • 一个特殊的输出节关键字

 6.2 输出部分名称

输出节的名称是section。section必须满足输出格式的约束。在仅支持有限数量的部分的格式中,例如a.out,名称必须是格式支持的名称之一(例如,a.out仅允许'.text','.data'或'的.bss')。如果输出格式支持任意数量的节,但是带有数字而不是名称(如Oasys的情况),则该名称应作为带引号的数字字符串提供。节名称可以包含任何字符序列,但必须引用包含任何不常见字符(如逗号)的名称。

6.3 输出部分地址

地址是输出部分的VMA(虚拟存储器地址)的表达式。此地址是可选的,但如果提供,则输出地址将完全按照指定的方式设置。如果没有指定输出地址,那么将根据下面的启发式方法为该部分选择一个地址。将调整此地址以适应输出部分的对齐要求。对齐要求是输出部分中包含的任何输入部分的最严格对齐。输出段地址启发式如下:

  • 如果为该部分设置了输出存储区域,则将其添加到该区域,并且其地址将是该区域中的下一个空闲地址。
  • 如果已使用MEMORY命令创建存储区域列表,则选择具有与该部分兼容的属性的第一个区域来包含它。该部分的输出地址将是该区域的下一个可用地址。
  • 如果未指定任何内存区域,或者没有匹配该部分,则输出地址将基于位置计数器的当前值。

例如:

.text . : { *(.text) }

.text : { *(.text) }

略有不同。第一个将'.text'输出部分的地址设置为位置计数器的当前值。第二个选项将其设置为位置计数器的当前值,该值与任何“.text”输入节的最严格对齐方式对齐。地址可以是任意表达式。例如,如果要在0x10字节边界上对齐节,使节地址的最低四位为零,可以执行如下操作:

.text ALIGN(0x10) : { *(.text) } 

这是有效的,因为ALIGN返回向上对齐到指定值的当前位置计数器。指定节的地址将更改位置计数器的值,前提是该节是非空的。 (空的section被忽略)。 

6.4 输入部分描述

最常见的输出部分命令是输入部分描述。输入部分描述是最基本的链接器脚本操作。使用输出部分告诉链接器如何在内存中布局程序。您可以使用输入部分描述来告诉链接器如何将输入文件映射到内存布局中。

6.4.1 输入部分基础知识

 输入节描述由可选的文件名和括号中的节名列表组成。最常见的输入节描述是在输出节中包含具有特定名称的所有输入节。例如,要包含所有输入'.text'部分,您可以编写:

*(.text)

这里'*'是一个匹配任何文件名的通配符。要排除文件列表与文件名通配符匹配,可以使用EXCLUDE FILE匹配除EXCLUDE FILE列表中指定的文件之外的所有文件。例如:

EXCLUDE_FILE (*crtend.o *otherfile.o) *(.ctors) 

将导致除“crtend.o”和“otherfile.o”之外的所有文件中的所有.ctors部分都包含在内。EXCLUDE FILE也可以放在部分列表中,例如:

*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors) 

结果与前面的例子完全相同。如果节列表包含多个节,则支持EXCLUDE FILE的两种语法很有用,如下所述。

有两种方法可以包含多个部分:

*(.text .rdata)
*(.text) *(.rdata) 

它们之间的区别是'.text'和'.rdata'输入节将出现在输出节中的顺序。在第一个示例中,它们将混合在一起,出现的顺序与链接器输入中的顺序相同。在第二个示例中,所有'.text'输入节将首先出现,然后是所有'.rdata'输入节。当使用包含多个部分的EXCLUDE FILE时,如果排除在部分列表中,则排除仅适用于紧接的后续部分,例如:

*(EXCLUDE_FILE (*somefile.o) .text .rdata) 

 将包括除'somefile.o'以外的所有文件中的所有'.text'部分,而包含所有文件的所有'.rdata'部分,包括'somefile.o'。要从'somefile.o'中排除'.rdata'部分,可以将示例修改为:

*(EXCLUDE_FILE (*somefile.o) .text EXCLUDE_FILE (*somefile.o) .rdata)

或者,在输入文件选择之前将EXCLUDE FILE放在节列表之外,将导致排除适用于所有节。因此,前面的示例可以重写为:

EXCLUDE_FILE (*somefile.o) *(.text .rdata) 

您可以指定文件名以包含特定文件中的部分。如果一个或多个文件包含需要位于内存中特定位置的特殊数据,则可以执行此操作。例如:

data.o(.data) 

要根据输入节的节标记细化包含的节,可以使用INPUT SECTION FLAGS。以下是使用ELF部分的Section头标志的简单示例:

SECTIONS {
    .text : { INPUT_SECTION_FLAGS (SHF_MERGE & SHF_STRINGS) *(.text) }
    .text2 : { INPUT_SECTION_FLAGS (!SHF_WRITE) *(.text) }

 在此示例中,输出节'.text'将包含与名称*(.text)匹配的任何输入节,其中节标志SHF_MERGE和SHF_STRINGS已设置。输出节'.text2'将包含与名称*(.text)匹配的任何输入节,其节标题SHF_WRITE是清除的。您还可以通过编写与存档匹配的模式,冒号,然后匹配文件的模式,在冒号周围没有空格来指定存档中的文件。

‘archive:file’

    匹配存档中的文件

‘archive:’

    匹配整个档案

‘:file’    匹配文件,但不是存档中的文件 

'archive'和'file'中的一个或两个都可以包含shell通配符。在基于DOS的文件系统上,链接器将假定后面跟冒号的单个字母是驱动器说明符,因此“c:myfile.o”是一个简单的文件规范,而不是“c”存档中的“myfile.o”。'archive:file'filespecs也可以在EXCLUDE_FILE列表中使用,但可能不会出现在其他链接脚本上下文中。 例如,您无法使用INPUT命令中的“archive:file”从存档中提取文件。如果使用不带部分列表的文件名,则输入文件中的所有部分都将包含在输出部分中。这种情况并不常见,但有时可能会有用。例如:

data.o

当您使用的文件名不是“archive:file”说明符且不包含任何通配符时,链接器将首先查看您是否还在链接器命令行或INPUT命令中指定了文件名。如果没有,链接器将尝试将文件作为输入文件打开,就好像它出现在命令行中一样。请注意,这与INPUT命令不同,因为链接器不会在归档搜索路径中搜索该文件。 

-------------------------------------------------------2019.07.16--------------------------------------------------------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值