链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)

Posted Nerazzur

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)相关的知识,希望对你有一定的参考价值。

原链接:链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)_BSP-路人甲的博客-CSDN博客_链接脚本语法 

为了便于与英文原文对照学习与理解(部分翻译可能不准确),本文中的每个子章节标题和引用使用的都是官方手册英文原称。命令及命令行选项统一使用斜体书写。高频小节会用蓝色字体标出。


3 Linker Scripts
每个链接都由一个链接脚本控制。该脚本使用链接器命令语言编写。

链接脚本的主要目的是描述如何将输入文件中的各个部分映射到输出文件中,并控制输出文件的内存布局。大多数链接脚本仅此而已。但是,必要时,链接器脚本也可以使用下面描述的命令来指导链接器执行更多的其它操作。

链接器通常使用一个链接脚本。如果没有为其提供,链接器将会使用默认的编译在链接器执行文件内部的脚本。可以使用命令 ’– verbose ’ 显示默认的链接脚本。某些命令行选项,例如 ’-r ’,’-N ’ 会影响默认的链接脚本。

你可以通过在命令行使用 ’-T ’ 命令使用自己的脚本。如果使用此命令,你的链接脚本将会替代默认链接脚本。

也可以通过将脚本作为链接器输入文件隐式的使用链接脚本,参考Implicit Linker Scripts。

Basic Script Concepts: 链接器脚本的基本概念
Script Format: 链接器脚本的格式
Simple Example: 简单的链接器脚本例子
Simple Commands: 简单的链接器脚本命令
Assignments: 为符号指定数值
SECTIONS: 段命令
MEMORY: 内存命令
PHDRS: PHDRS命令
VERSION: 版本命令
Expressions: 链接脚本的表达式
Implicit Linker Scripts: 隐式链接脚本
3.1 Basic Linker Script Concepts
为了描述链接脚本语言,我们需要定义一些基本概念和词汇。

链接器将输入文件(一个或多个)合并为一个输出文件。输出文件和每个输入文件都采用一种特殊的数据格式,称为目标文件格式。每个文件称为目标文件。输出文件通常称为可执行文件,但出于我们的目的,我们也将其称为目标文件。每个目标文件都有一个段(section)列表。有时把输入文件的段称作输入段,类似的,输出文件的段称作输出段。

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

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

您可以将 objdump程序与 ’ -h '选项一起使用,以查看目标文件中的各个部分。

每个目标文件还具有一个符号列表,称为符号表。符号可以是定义的也可以是未定义的。每个符号都有一个名称,每个定义的符号都有一个地址,以及其他信息。如果将C或C ++程序编译到目标文件中,则将会将所有定义过的函数和全局变量以及静态变量作为已定义符号。输入文件中引用的每个未定义函数或全局变量都将成为未定义符号。

您可以使用 nm 程序或带有 ‘-t’ 选项的 objdump 程序在目标文件中查看符号。

3.2 Linker Script Format
链接脚本是文本文件。

一个链接器脚本是一系列的命令。每个命令都是一个关键字,可能后面还跟有一个参数,或者一个符号的赋值。使用分号分割命令,空格通常被忽略。

类似于文件名或者格式名的字串可以直接输入。如果文件名含有一个字符例如逗号(逗号被用来分割文件名),你可以将文件名放在双引号内部。 但是禁止在文件名内使用双引号字符 。

你可以像C语言一样在链接脚本内包含注释,由’/’和’/’划分。和C一样,注释在句法上被当作空格。

3.3 Simple Linker Script Example
大多数的链接脚本非常简单。

最简单的链接脚本只有一个命令:’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 ’ 段之间创建一个小的间隙。

如上,这就是一个简单完整的链接脚本。

3.4 Simple Linker Script Commands
在本节中,我们将介绍一些简单的链接脚本命令。

Entry Point : 设置入口点
File Commands : 处理文件的命令
Format Commands : 处理目标文件格式的命令
REGION_ALIAS : 为内存区域分配别名
Miscellaneous Commands : 其它链接脚本命令
3.4.1 Setting the Entry Point
在程序中执行的第一条指令称为入口点。 您可以使用 ENTRY 链接器脚本命令来设置入口点。 参数是符号名称:

ENTRY(symbol)

有几种设置入口点的方法。 链接器将通过依次尝试以下每种方法来设置入口点,并在其中一种成功后停止:

‘-e ’输入命令行选项;
链接描脚本中的 ENTRY(symbol) 命令;
目标专用符号值(如果已定义); 对于许多目标来说是 start 符号,但是例如基于PE和BeOS的系统检查可能的输入符号列表,并与找到的第一个符号匹配。
‘.text ’ 部段的第一个字节的地址(如果存在);
地址0。
3.4.2 Commands Dealing with Files
以下是链接器脚本处理文件的几个常用命令:
(1)INCLUDE filename
在命令处包含链接脚本文件 filename ,将在当前目录以及 -L 选项指定的任何目录中搜索文件。INCLUDE 调用嵌套最多10个级别。

可以直接把 INCLUDE 放到顶层、 MEMORY 或者 SECTIONS 命令中,或者在输出段的描述中。

(2)INPUT(file, file, …) / INPUT(file file …)
INPUT 命令指示链接程序在链接中包含指定的文件,就好像它们是在命令行上命名的一样。

例如,如果您始终希望在每次执行链接时都包含 subr.o,但又不想将其放在每个链接命令行中,则可以在链接脚本中放置 ‘INPUT (subr.o) ’。

实际上,您可以在链接描述文件中列出所有输入文件,然后仅用‘-T ’选项调用链接脚本。

如果配置了sysroot 前缀,且文件名以‘/ ’符开头,并且正在处理的脚本位于sysroot 前缀内,则将在sysroot 前缀中查找文件名。也可以通过指定 = 作为文件名路径中的第一个字符,或在文件名路径前加上 $ SYSROOT 来强制使用sysroot 前缀。另请参阅命令行选项中对‘-L ’ 的描述(Command-line Options)。

如果未使用 sysroot 前缀,则链接器将尝试打开包含链接器脚本的目录中的文件。如果没有找到,链接器将搜索当前目录。如果仍未找到,链接器将搜索库的搜索路径。

如果您使用 ‘INPUT (-lfile) ’ ,则 ld 会将名称转换为 libfile.a,就像命令行参数‘-l ’一样。

当您在隐式链接脚本中使用 INPUT 命令时,文件在链接脚本文件被包含的时刻才会被加入。这可能会影响库的搜索。

(3)GROUP(file, file, …) / GROUP(file file …)
GROUP 命令类似于 INPUT,不同之处在于,所有file指出的名字都应该为库,并且所有库将会被重复搜索直到没有新的未定义引用被创建。 请参阅命令行选项中 ‘-(’ 的说明(Command-line Options)。

(4)AS_NEEDED(file, file, …) / AS_NEEDED(file file …)
此构造只能出现在 INPUT 或 GROUP 命令以及其他文件名中。命令中的文件将会以类似于直接出现在 INPUT 或 GROUP 命令中的文件一样处理,除了ELF共享库,ELF共享库仅在真正需要使用时才被添加。这个构造实质上为其中列出的所有文件启用了 -as-needed 选项,为了恢复以前编译环境,之后需设置 --no-as-needed。

(5)OUTPUT(filename)
OUTPUT 命令为输出文件命名。 在链接脚本中使用 OUTPUT(filename)与在命令行中使用 ‘-o filename’ 一样(请参阅Command-line Options)。 如果两者都使用,则命令行选项优先。

您可以使用 OUTPUT 命令为输出文件定义默认名称,以此替代默认名称a.out。

(6)SEARCH_DIR(path)
SEARCH_DIR 命令添加一个 ld 搜索库的路径。使用 SEARCH_DIR(path) 与在命令行上使用 ’ -L path ’ 完全一样(参见Command-line Options)。如果同时使用了这两条路径,那么链接器将会搜索所有路径。首先搜索使用命令行选项指定的路径。

(7)STARTUP(filename)
STARTUP 命令与 INPUT 命令类似,除了filename将成为要链接的第一个输入文件,就像它是在命令行中首先指定的一样。在一些把第一个文件当作入口点的系统上这个命令非常有效。

3.4.3 Commands Dealing with Object File Formats
有两个链接器脚本命令可以用来处理对象文件格式:

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

OUTPUT_FORMAT 命令使用BFD格式的命名方式(请参见BFD)。使用 OUTPUT_FORMAT(bfdname) 与在命令行上使用 ‘–oformat bfdname ’ 完全相同(请参见Command-line Options)。如果两者都使用,则命令行选项优先。

您可以将OUTPUT_格式与三个参数一起使用,以根据 ’ -EB ’ 和 ‘-EL’ 命令行选项使用不同的格式。这允许链接器脚本根据所需的endianness设置输出格式。

如果未使用 ’ -EB ’ 和 ‘-EL’ ',那么输出格式将会使用第一个参数作为默认值。如果使用 ’ -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’ (参见Command-line Options)。如果使用了TARGET命令,但OUTPUT_FORMAT命令没使用,则最后的TARGET命令还被用来设置输出文件的格式(参见BFD)。

3.4.4 Assign alias names to memory regions
可以为MEMORY命令创建的内存区域提供别名。 每个名称最多对应一个存储区域

REGION_ALIAS(alias, region)

REGION_ALIAS 函数为 内存区域创建别名 。这允许灵活地将输出部分映射到内存指定区域。下面有一个例子。

假设我们有一个用于嵌入式系统的应用程序,它带有各种内存存储设备。它们都有一个通用的,易失性内存RAM,允许代码执行或数据存储。一些可能有一个只读的、非易失性内存ROM,允许代码执行和只读数据访问。最后一个是只读、非易失性存储器ROM2,允许对只读数据段读取,不允许代码执行。现在有四个输出段:

.text :程序代码
.rodata :只读数据
.data :可读写且需要初始化数据
.bss :可读写的置零初始化数据
目标是提供一个链接器脚本文件,该文件包含定义系统无关的输出段的部分,和将输出段映射到系统上可用内存区域的系统相关部分。我们的嵌入式系统有三种不同的内存设置A、B和C:
Section    Variant A    Variant B    Variant C
.text    RAM             ROM        ROM
.rodata    RAM             ROM        ROM2
.data    RAM             RAM/ROM    RAM/ROM2
.bss    RAM             RAM        RAM

RAM/ROM或RAM/ROM2表示将此段分别加载到区域ROM或ROM2中。请注意,三个设置的.data段的起始地址都位于.rodata段的末尾。

接下来是处理输出段的基本链接脚本。 它包含描述内存布局的系统相关链接 cmds.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 来定义内存区域以及别名。下面是A,B,C不同的 linkcmds.memory :

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);
    


3.4.5 Other Linker Script Commands
还有一些其他链接器脚本命令:

ASSERT(exp, message)
注意此断言会在最终链接阶段之前进行检查。这表示,在段内使用PROVIDE的定义如果用户没有为其设置值,此表达式将无法通过检测。唯一的例外是PROVIDE的符号刚刚引用了’.’。因此,一个如下断言:
确保 exp 不为零。 如果为零,则退出链接并显示错误代码,并打印一些相关的信息。
请注意,在链接的最后阶段发生之前会检查断言。 这意味着,如果用户没有为这些符号设置值,则涉及段定义中提供的符号的表达式将失败。 该规则的唯一例外是仅引用点的提供的符号。 因此,这样的断言:

.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 place 段组成员像普通的输入段一样,并且即使指定了可重定位的输出文件(’ -r ')也可以删除段组。

INSERT [ AFTER | BEFORE ] output_section
此命令通常在‘-T ’ 指定的脚本中使用,用来增强默认的SECTIONS。例如,重复占位程序段。它将把所有此前的链接脚本的声明插入output_section的后面(或者前面),并且使 ’-T ’不要覆盖默认链接脚本。实际插入点类似于孤儿段。参见Location Counter。插入发生在链接器把输入段映射到输出段后。在插入前,因为’-T ’的脚本在默认脚本之前被解析,在’-T’脚本中的声明会先于默认内部脚本的声明而执行。特别是,将对默认脚本中的’-T ’输出段进行输入段分配。下例为’-T ’脚本使用INSERT可能的情况:

SECTIONS

  OVERLAY :
  
    .ov1  ov1*(.text) 
    .ov2  ov2*(.text) 
  

INSERT AFTER .text;


NOCROSSREFS(section section …)
此命令可能被用来告诉 ld,如果引用了section的参数就报错。
在特定的程序类型中,比如使用覆盖技术的嵌入式系统,当一个段被加载到内存中,另一个段不会被加载。任何两个段之间直接的引用都会带来错误。例如,如果一个段中的代码调用另一个段中的函数,将会产生错误。

NOCROSSREFS 命令列出了一系列输出段的名字。如果 ld 检测到任何段间交叉引用,将会报告错误并返回非零退出码。注意NOCROSSREFS使用输出段名称,而不是输入段名称。

NOCROSSREFS_TO(tosection fromsection …)
此命令可能被用来告诉 ld,从其他段列表中对某个段的任何引用就会引发错误。
当需要确保两个或多个输出段完全独立,但是在某些情况下需要单向依赖时,NOCROSSREFS 命令很有用。 例如,在多核应用程序中,可能存在可以从每个核调用的共享代码,但是出于安全考虑,绝不能回调。

NOCROSSREFS_TO 命令携带(给出)输出段名称的列表。 其他任何部分都不能引用第一部分。 如果 ld 从其他任何部分中检测到对第一部分的任何引用,它将报告错误并返回非零退出状态。 请注意,NOCROSSREFS_TO 命令使用输出段名称,而不是输入段名称。

OUTPUT_ARCH(bfdarch)
指定一个特定的输出机器架构。该参数是BFD库使用的名称之一(请参阅BFD)。通过使用带有 ’ -f ’ 选项的objdump程序,您可以看到目标文件的体系结构。

LD_FEATURE(string)
此命令可用于修改 ld 行为。如果字符串是“SANE_EXPR”,那么脚本中的绝对符号和数字将被在任何地方当作数字对待。请参考 Expression Section。

3.5 Assigning Values to Symbols
可以给链接器脚本中的符号赋值。这会定义符号并将其放入具有全局作用域的符号表中。

Simple Assignments 简单赋值
HIDDEN 隐藏
PROVIDE PROVIDE
PROVIDE_HIDDEN PROVIDE_HIDDEN
Source Code Reference 如何在源代码中使用一个链接脚本定义的符号
3.5.1 Simple Assignments
您可以使用任何C赋值操作符来赋值符号:

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

第一种情况将表达式的值赋值给符号。 在其他情况下,必须先定义符号,并相应地调整符号的值。

特殊符号名称 ‘. ’ 表示位置计数器。 您只能在 SECTIONS 命令中使用它。 请参阅 Location Counter。

表达式后面的分号不能省略。

表达式定义如下; 请参阅Expressions。

你在写表达式赋值语句时,可以把它们作为单独的部分,也可以作为 ’SECTIONS’ 命令中的一个语句,或者作为 ’SECTIONS’ 命令中输出段描述的一个部分。

符号的有效作用区域由表达式所在的段决定,Expression Section。

下面是是三个不同位置为符号赋值的示例:

floating_point = 0;
SECTIONS

  .text :
   
      *(.text)
      _etext = .;
   
  _bdata = (. + 3) & ~ 3;
  .data : *(.data)


在本例中,符号 ‘floating_point’ 将被定义为零。符号 ’ _etext ’ 将被设置为紧随 ’.text’ 最后一个输入段后面的地址。符号’ _bdata '将被定义为在 ’.text’ 输出段后面的一个4字节向上对齐的地址。

3.5.2 HIDDEN
语法HIDDEN(symbol = expression)为ELF目标的端口定义一个符号,符号将被隐藏并且不会被导出。
下面是Simple Assignments的例子,使用HIDDEN重写:

HIDDEN(floating_point = 0);
SECTIONS

  .text :
   
      *(.text)
      HIDDEN(_etext = .);
   
  HIDDEN(_bdata = (. + 3) & ~ 3);
  .data : *(.data)


在本例中,这三个符号在此模块之外都不可见

3.5.3 PROVIDE
在某些情况下,仅当一个符号被引用了却没有定义在任何链接目标中,才需要为链接脚本定义一个符号。例如,传统链接器定义了符号‘etext’。然而,ANSI C要求用户能够使用’ etext '作为函数名而不会引发错误。PROVIDE关键字可以用来定义一个符号,比如‘etext’ ,只有当它被引用但没有被定义时才使用。语法是 PROVIDE(symbol = expression)。

下面是一个使用提供定义‘etext’的例子:

SECTIONS

  .text :
   
      *(.text)
      _etext = .;
      PROVIDE(etext = .);
   


在本例中,如果程序定义了’ _etext ‘(带有前导下划线),链接器将给出重复定义错误。另一方面,如果程序定义了’ etext ‘(没有前导下划线),链接器会默认使用程序中的定义。如果程序引用了’ etext '但没有定义它,链接器将使用链接器脚本中的定义。

注意 -PROVIDE指令将考虑定义一个普通符号,即使这样的符号可以与PROVIDE将创建的符号组合在一起。当考虑构造函数和析构函数列表符号时,这一点尤其重要,因为它们通常被定义为普通符号。

3.5.4 PROVIDE_HIDDEN
与 PROVIDE 类似。对于ELF目标的端口,符号将被隐藏且不会被输出。

3.5.5 Source Code Reference
从源代码获取链接器脚本定义的变量并不直观。 特别是,特别是链接脚本中的符号与高级语言定义的变量声明不同的时候,将使用一个没有值的变量替代它。

在进一步讨论之前,必须注意,当编译器将源代码中的名称存储在符号表中时,它们通常会将它们转换为不同的名称。 例如,Fortran编译器通常在前面或后面加上下划线,而C ++则执行大量的 ‘name mangling ’。 因此,在源代码中使用的变量名称与在链接脚本中定义的相同变量的名称之间可能会有差异。 例如,在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);

注意此时不需要操作符 ’&’ 了。

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

SECTIONS

  sections-command
  sections-command
  …


每个 sections-command 命令可能是下面之一:

ENTRY 命令(请参阅Entry command)
符号赋值(请参阅Assignments)
输出段的描述
overlay描述
为了方便在这些命令中使用位置计数器,在SECTIONS 命令中允许使用 ENTRY 命令和符号赋值。 这也可以使链接描述文件更容易理解,因为你可以在更有意义的地方使用这些命令来控制输出文件的布局。

输出段描述和覆盖在下面将会分析。

如果在链接脚本中未使用 SECTIONS 命令,则链接器将会照输入文本的顺序,将每个输入部段放置到名称相同的输出段中。例如,如果所有输入段出现在第一个文件中,输出文件的段的顺序将会与第一个输入文件保持一致。第一个段被放在地址0。

Output Section Description 输出段描述
Output Section Name 输出段名称
Output Section Address 输出段地址
Input Section 输入段描述
Output Section Data 输出段数据
Output Section Keywords 输出段关键字
Output Section Discarding 输出段忽略的内容
Output Section Attributes 输出段属性
Overlay Description Overlay description

3.6.1 Output Section Description
输出段的完整描述如下所示:

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,并且下一个section -命令看起来像是表达式的延续,则可能需要在末尾使用逗号。换行符和其他空格是可选的。

当 fillexp 使用且接下来的 sections-command 看起来像是表达式的延续的时候,可能需要在后面加上逗号。

每个 output-section-command 可以是下列命令之一:
符号赋的值(参见Assignments)
输入段描述(参见Input Section)
直接包引用的数据值(参见Output Section Data)
特殊的输出段关键字(参见Output Section Keywords))

3.6.2 Output Section Name
输出段的名字是 section 。section必须满足输出格式的规定。在只支持有限段数目的格式中,例如 a.out ,名称必须是该格式所支持的名称之一(例如a.out ,只允许’.text’,’.data’,’.bss’)。如果输出格式支持任意数量的段,但是只有数字而不是名称(Oasys 就是这种情况),则名称应该以带引号的数字字符串的形式提供。一个段的名字可以由任意字符序列组成,但一个含有许多特殊字符(如逗号)的名称必须用引号括起来。

名称为 ‘/DISCARD/’ 的输出段 ,有特殊含义; 参考Output Section Discarding.

3.6.3 Output Section Address
address 是输出段VMA(虚拟内存地址)的表达式。此地址是可选参数,但如果提供了该地址,则输出地址就会被精确的设置为指定的值。

如果没有指定输出地址,那么则依照下面的几种方式尝试选择一个地址。此地址将被调整以适应输出段的对齐要求。输出段的对齐要求是所有输入节中含有的对齐要求中最严格的一个。

输出段地址探索如下:

如果为该段设置了一个输出内存区域,那么它将被添加到该区域中,其地址将是该区域中的下一个空闲地址。

如果使用 MEMORY 命令创建内存区域列表,那么将选择具有与该段兼容属性的第一个区域来包含该区域。该部分的输出地址将是该区域中的下一个空闲地址;MEMORY 。

如果没有指定内存区域,或者没有与段匹配的内存区域,则输出地址将基于位置计数器的当前值。

例如:

.text . : *(.text)

.text : *(.text)

有着细微的不同。 第一个将‘.text’ 输出段的地址设置为位置计数器的当前值。 第二个参数会将其设置为位置计数器的当前值,但是该值与所有‘.text’ 输入段中最严格的对齐方式对齐。

address 可以是任意表达式; 例如,如果要在0x10字节(16字节)边界上对齐段,以使节地址的最低四位为零,则可以执行以下操作:

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

之所以这样做,是因为 ALIGN 返回的当前位置计数器向上对齐到指定的值。
为段指定地址将会改变位置计数器的值,前提是该段是非空的(空的段被忽略)。

3.6.4 Input Section Description
最常见的输出段命令(output-section-command)是输入段描述。

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

Input Section Basics 基本的输入段
Input Section Wildcards 输入段通配符模板
Input Section Common 普通符号的输入段
Input Section Keep 输入段与垃圾回收
Input Section Example 输入段例子

3.6.4.1 Input Section Basics
输入段说明由一个文件名和一个括号中的段名列表(可选)组成。

文件名和段名可以是通配符,我们将在下面进一步描述(请参阅Input Section Wildcards)。

最常见的输入段描述是在输出段中包括所有具有特定名称的输入段。 例如,把所有输入段放入’.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’ 和 ’.rata’ 段出现在输出段中的顺序。第一个例子里,他们将被混合在一起,按照链接器找到它们的顺序存放。另一个例子中,所有 ’.text’ 输入段将会先出现,后面是 ’.rdata’ 输入段。

将EXCLUDE_FILE与多个段一起使用时,这个排除命令仅仅对紧随其后的段有效,例如:

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

将导致包含除 somefile.o 以外的所有文件的所有‘.text’段,而包括somefile.o在内的所有文件的所有‘.rdata’ 段都将被包含。要从somefile.o中排除‘.rdata’ 段部分,可以将示例修改为:

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

或者,将EXCLUDE_FILE放在段列表之外(在选择输入文件之前),将导致排除操作对所有段有效。因此,前一示例可以重写为:

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

你可以指定一个文件名来包含特定文件的段。如果一个或者多个你的文件需要被放在内存中的特定位置,你可能需要这么做。例如:

data.o(.data)
1
如果想使用段标志来选择输入文件的段,可以使用INPUT_SECTION_FLAGS。

下面是一个为ELF段使用段头标志的简单示例:

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’:可以使用在EXCLUDE_FILE列表中,但不能出现在其他链接脚本内部。例如,你不能使用 ‘archive:file’从 INPUT命令中取出一个库相关的文件。
如果你使用一个文件名而不指出段列表,则所有的输入文件的段将被放入输出段。通常不会这么做,但有些场合比较有用,例如:

data.o

当你使用一个文件名且不是 ‘archive:file’特殊命令,并且不含任何通配符,链接器将先查看你是否在命令行上或者在INPUT命令里指定了该文件。如果没有这么做,链接器尝试将文件当作输入文件打开,就像文件出现在了命令行一样。注意与INPUT命令有区别,因为链接器不会在库文件路径搜索文件。

3.6.4.2 Input Section Wildcard Patterns
在输入段描述中,文件名和段名都可以使用通配符模式。
在许多示例中看到的文件名’ * '是一个简单的文件名通配符模式。
通配符模式类似于Unix shell使用的那些模式。

‘*’ 匹配任意数量字符
‘?’ 匹配任意单字
‘[chars]’ 匹配任何字符的单个实例;‘-’ 字符可被用来指出一个字符的范围,例如 ‘[a-z]’ 可以用来匹配所有小写字母
‘\\’ 引用后面的字符
当文件名与通配符匹配时,通配符将不匹配 ‘/’ 字符(在Unix上用于分隔目录名)。由单个 ‘*’ 字符组成的模式是除外;它将始终匹配任何文件名,无论它是否包含 ‘/’ 。在段名称中,通配符将匹配 ‘/’ 字符。

文件名通配符模式只匹配在命令行或输入命令中显式指定的文件。链接器不会搜索目录以扩展通配符。

如果一个文件名匹配多个通配符,或者一个文件名被显示指定了,且又被通配符匹配了,则链接器将使用链接器脚本中的第一个匹配项。例如,例如,下面的输入段描述可能有错误,因为 data.o 的规则不会被应用:

.data : *(.data)
.data1 : data.o(.data)
1
2
通常情况下,链接器将按照链接过程中出现通配符的顺序放置文件和段。您可以通过使用SORT_BY_NAME 关键字来更改此行为,该关键字出现在括号中的通配符模式之前(例如,SORT_BY_NAME(.text*))。当使用 SORT_BY_NAME 关键字时,链接器将按名称按升序对文件或段进行排序,然后将它们放入输出文件中。

SORT_BY_ALIGNMENT 对齐方式类似于 SORT_BY_NAME. SORT_BY_ALIGNMENT 将在将段放入输出文件之前,按对齐方式的降序对段进行排序。大的对齐被放在小的对齐前面可以减少所需的填充量。

SORT_BY_INIT_PRIORITY 与 SORT_BY_NAME 相似,区别是 SORT_BY_INIT_PRIORITY把段按照GCC的嵌入在段名称的 init_priority 数字属性值升序排列后放入输出文件。.init_array.NNNNN 和 .fini_array.NNNNN, NNNNN 是init_priority 。 .ctors.NNNNN 和 .dtors.NNNNN, NNNNN 是65535减去 init_priority 。

SORT 是 SORT_BY_NAME 的别名。

当链接器脚本中有嵌套的段排序命令时,段排序命令最多可以有1个嵌套级别。
(1)SORT_BY_NAME (SORT_BY_ALIGNMENT (wildcard section pattern)) 。它将首先按名称对输入部分进行排序,如果两个部分同名,则按对齐方式排序。
(2)SORT_BY_ALIGNMENT (SORT_BY_NAME (wildcard section pattern))。它将首先按对齐方式对输入段进行排序,如果两个段具有相同的对齐方式,则按名称排序。
(3)*SORT_BY_NAME (SORT_BY_NAME (wildcard section pattern))*与 SORT_BY_NAME (wildcard section pattern) 相同。
(4)SORT_BY_ALIGNMENT (SORT_BY_ALIGNMENT (wildcard section pattern)) 与 SORT_BY_ALIGNMENT (wildcard section pattern) 相同。
(5)除此之外,其它所有嵌套段排序命令都是无效的。

当同时使用命令行段排序选项和链接器脚本段排序命令时,段排序命令总是优先于命令行选项。

如果链接器脚本中的段排序命令不是嵌套的,那么命令行选项将使段排序命令被视为嵌套的排序命令。
(1)SORT_BY_NAME (wildcard section pattern ) 与 –sort-sections alignment 连用等价于SORT_BY_NAME (SORT_BY_ALIGNMENT (wildcard section pattern)) 。
(2)SORT_BY_ALIGNMENT (wildcard section pattern) 与 –sort-section name 连用等价于
SORT_BY_ALIGNMENT (SORT_BY_NAME (wildcard section pattern))。

如果链接器脚本中的段排序命令是嵌套的,那么命令行选项将被忽略。

SORT_NONE 通过忽略命令行部段排序选项来禁用段排序。

如果您对输入段的去向感到困惑, 可以使用 ’ -M ’ 链接器选项来生成映射文件 。映射文件精确地显示了如何将输入段映射到输出段。

下面这个示例展示了通配符如何被用来分隔文件。这个链接脚本指引链接器把所有 ‘.text’ 段放在’ ‘.text’ 里,以及所有 ’.bss’ 段放到 ’.bss’ 中。链接器将会把所有以大写字母开头的文件的 ’.data’ 段放入 ’.DATA’ ,其他文件的 ’.data’ 段放入 ’.data’ 。

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


3.6.4.3 Input Section for Common Symbols
普通符号需要一个特别的标记,因为很多目标文件格式中没有特定的普通符号输入段。链接器把普通符号当作位于一个名为 ’COMMON’ 的输入段中。

像使用其它文件名与段一样,你也可以使用文件名与 ’COMMON’ 段的组合。通过这种方法把一个特定文件的普通符号放入一个段内,同时把其它输入文件的普通符号放入另一个段内。

大多数情况下,输入文件的普通符号会被放到输出文件的 ’.bss’ 段里面。例如:

.bss *(.bss) *(COMMON)

有些目标文件格式含有多种普通符号的类型。例如,MIPS ELF目标文件把标准普通符号和小型普通符号区分开来。在这种情况下,链接器会为另一个类型的普通符号使用其它的特殊段名称。在MIPS ELF中,链接器为普通符号使用 ’COMMON’ 以及为小型普通符号使用 ’.scommon’ 。这样就可以把不同类型的普通符号映射到内存中的不同位置。

有时在老的链接脚本中能看见 ’[COMMON]’ 。这个标记现在已废弃。它等价于’*(COMMON)’ 。

3.6.4.4 Input Section and Garbage Collection
使用了链接时垃圾收集(‘–gc-sections’)的功能,在把段标记为不应被消除非常常用。此功能通过把一个输入段的通配符入口使用 KEEP() 实现,类似于 KEEP((.init)) 或KEEP(SORT_BY_NAME()(.ctors))。

3.6.4.5 Input Section Example
下面是一个完整的链接脚本的例子。它告诉链接器从 all.o 读取所有段,把它们放到输出段 ’outputa’ 的开头位置,’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)
   


如果输出段的名称与输入段的名称相同,并且可以表示为C标识符,那么链接器将自动看到 PROVIDE两个符号:余下的*__start_SECNAME* 和 _stop_SECNAME,其中SECNAME是段的名称。它们分别指示输出段的开始地址和结束地址。注意:大多数段名不能表示为C标识符,因为它们包含 ‘.’ 字符。

3.6.5 Output Section Data
你可以通过使用输出段命令BYTE, SHORT, LONG, QUAD, 或者 SQUAD在输出段显式的包含几个字节的数据。每个关键字后面跟着一个括号包裹的表达式指出需要存储的数值(参照Expressions)。表达式的值被存储在当前位置计数器值的地方。

BYTE, SHORT, LONG, QUAD命令分别存储1,2,4,8字节。在存储字节后,位置计数器会按照存储的字节数增加。

例如,下面将会存储一个单字节数据1,然后存储一个符号为 ’addr’ 四字节数据的值:

BYTE(1)
LONG(addr)

当使用64位主机或目标时,QUAD 和SQUAD是相同的;它们都存储一个8字节或64位的值。主机和目标都是32位时,表达式被当作32位计算。在这种情况下QUAD存储一个32位的值,并使用0扩展到64位,SQUAD保存32位值并使用符号位扩展到64位。

如果输出文件的目标文件格式显式的指定 endiannes,在正常的情况下,值将按照大小端存储。当对象文件格式没有显式的指定 endianness,例如,S-records,值将被按照第一个输入目标文件的大小端存储。
注意 - 这些命令仅在段描述内部工作,因此下面的例子会使链接器产生错误:

SECTIONS .text : *(.text) LONG(1) .data : *(.data)  

而下面这是可行的:

SECTIONS .text : *(.text) ; LONG(1) .data : *(.data)  

您可以使用 FILL 命令设置当前段的填充模式。该命令后面跟着一个括号包裹的表达式。所有其它没有被特别指定段的内存区域(例如因为对齐需要而留出来的缝隙)按照表达式的值填充,如果有必要可以重复填充。一个FILL语句仅会覆盖它本身在段定义中出现的位置后面的所有内存区域;通过使用不同的FILL声明,你可以在一个输出段中使用不同的填充模板。
这个例子显示了如何使用 ’0x90’ 填充未定义内存区域:

FILL(0x90909090)

FILL命令类似 ’=fillexp’ 输出段属性,但其仅影响FILL命令后面的段,而不是整个段。如果同时使用,FILL命令为高优先级。参考 Output Section Fill获取更多填充细节。

3.6.6 Output Section Keywords
这里有两个关键字可以作为输出段的命令:

CREATE_OBJECT_SYMBOLS

此命令告诉链接器为每个输入文件创建一个符号。每个符号的名字为对应输入文件的名字。每个符号出现的位置位于包含CREATE_OBJECT_SYMBOLS命令的输出段中。

这个命令常常是 a.out 目标文件格式特有的。 它一般不为其它的目标文件格式所使用。

CONSTRUCTORS

当链接时使用 a.out 目标文件的格式,链接器使用一个特殊构造集来支持C++ 全局构造函数和析构函数。在链接不支持任意段的文件格式时,例如 ECOFF 和 XCOFF ,链接器将会通过名字自动识别C++全局构造函数和析构函数。对于这些格式的目标文件,CONSTRUCTORS命令告诉链接器把构造函数信息放到出现 CONSTRUCTORS 命令的输出段中。其它文件格式中CONSTRUCTORS命令被忽略。

符号__CTOR_LIST__ 标记全局构造函数的开始,符号__CTOR_END__标记结束。同样的,__DTOR_LIST__和__DTOR_END__分别标记全局析构函数的开始和结束。第一个列表中的字是入口的数量,后面是每个构造函数或者析构函数的地址,最后是一个全零的字。编译器必须安排实际运行代码。对于这些目标文件格式,GNU C++通常从一个 __main 子程序中调用构造函数,而对 __main 的调用自动被插入到 main 的启动代码中。GNU C++通常使用 atexit 运行析构函数,或者直接从函数 exit 中退出。

对于COFF或者ELF等支持任意段名字的目标文件格式,GNU C++通常把全局构造函数和析构函数放入 .ctors 和 .dtors 段。把下面的代码放入你的链接脚本,将会创建GUN 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__ = .;

如果你正在使用GUN C++支持的初始化优先级,初始化优先级提供了一些对全局构造函数运行顺序的控制,则你必须在链接时对构造函数排序以保证它们以正确的顺序执行。当你使用CONSTRUCTORS 命令,使用 ‘SORT_BY_NAME(CONSTRUCTORS)’ 替换它。当使用 .ctors 和 .dtors 段,使用 ‘(SORT_BY_NAME(.ctors))’ 和’ (SORT_BY_NAME(.dtors))’ 取代 ‘(.ctors)’ 和’ ‘(.dtors)’ 。

通常编译器和链接器会自动处理这些问题,您不需要关心它们。但是,在你自己写链接脚本且正在使用C++的时候,你可能需要考虑这些。

3.6.7 Output Section Discarding
链接器通常不会创建没有内容的输出段。这是为了方便引用那些有可能出现或者不出现任何输入文件中的段。例如:

.foo : *(.foo)

只有在至少有一个输入文件含有 ’.foo’ 段且 ’.foo’ 段不为空的时候才会在输出文件创建一个 ’.foo’ 段。其它链接脚本指出在一个段中间分配空间也会创建输出段。赋值也一样即使赋值没有创建空间,除了‘. = 0’, ‘. = . + 0’, ‘. = sym’, ‘. = . + sym’ 和‘. = ALIGN (. != 0, expr, 1)’ 其中 ’sym’ 是一个值为0的已定义绝对符号。因此你可以强制一个空的输出段使用 ‘. = .’。

链接器将忽略为丢弃的输出段进行地址赋值(请参见Output Section Address),除非链接器脚本在输出段中定义符号。在这种情况下,链接器将遵守地址赋值,有可能更新 ’.’ 的值,即便段被抛弃了。

特殊输出段名称 ’/DISCARD/’ 可被用来抛弃输入段。一个被分派到名为 ’/DISCARD/’ 的输出段的输入段将不会被包含在输出文件中。

3.6.8 Output Section Discarding
我们在前面展示了输出部分的完整描述如下:

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, address, and output-section-command命令。在本节中,我们将描述其余的段属性。

Output Section Type: 输出段类型
Output Section LMA: 输出段LMA —加载地址
Forced Output Alignment: 强制输出对齐
Forced Input Alignment: 强制输入对齐
Output Section Constraint: 输出段限制
Output Section Region: 输出段区域
Output Section Phdr: 输出段phdr
Output Section Fill: 输出段填充

3.6.8.1 Output Section Type
每个输出段可以有一个类型。类型是圆括号中的关键字。定义了以下类型:

NOLOAD

此段应标记为不可加载,以便在程序运行时不会将其加载到内存中。

DSECT
COPY
INFO
OVERLAY

支持这些类型名称是为了向后兼容,而且很少使用。它们都具有相同的效果:该段应该标记为不可分配,以便在程序运行时不会为该段分配内存。

链接器通常根据映射到输出段的输入段设置输出段的属性。您可以使用 section 类型来覆盖它。例如,在下面的脚本示例中,’ ROM ’ 部分位于内存位置 ’ 0 ',在程序运行时不需要加载它。

SECTIONS
  ROM 0 (NOLOAD) : …
  …


3.6.8.2 Output Section LMA
每个段有一个虚拟地址(VMA)和一个加载地址(LMA);参见 Basic Script Concepts。虚拟地址由前面描述的 Output Section Address指定。加载地址由 AT 或 AT> 关键字指定。指定加载地址是可选的。

AT 关键字把一个表达式当作自己的参数。这将指定段的实际加载地址 。关键字 AT> 使用内存区域的名字作为参数。参考MEMORY。段的加载地址被设置为该区域的当前空闲位置,并且按照段对齐要求对齐。

如果没有为可分配段使用 AT 和 AT>,链接器会使用下面的方式尝试来决定加载地址:

如果段有一个特定的VMA地址,则LMA也使用该地址。
如果段为不可分配的则LMA被设置为它的VMA。
否则如果可以找到符合当前段的一个内存区域,且此区域至少包含了一个段,则设置LMA在那里。如此VMA和LMA的区别类似于VMA和LMA在该区域的上一个段的区别。
如果没有声明内存区域且默认区域覆盖了整个地址空间,则采用前面的步骤。
如果找不到合适的区域或者没有前面存在的段,则LMA被设置为等于VMA。
这些功能旨在使构建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;

3.6.8.3 Forced Output Alignment
你可以使用ALIGN增加输出段的对齐。作为替换,你可以通过ALIGN_WITH_INPUT属性强制VMA与LMA自始至终保持它们之间的区别。
您可以使用ALIGN来增加输出段的对齐方式。作为一种替代方法,您可以使用ALIGN_WITH_INPUT属性在整个输出段保持VMA和LMA之间的差异。

3.6.8.4 Forced Input Alignment
您可以使用SUBALIGN来强制输出段中的输入段对齐。指定的值将覆盖输入段提供的任何对齐方式,无论比原来大还是小。

3.6.8.5 Output Section Constraint
通过分别使用关键字 ONLY_IF_RO 和ONLY_IF_RW,可以指定只有在所有输入段都是只读或所有输入段都是读写的情况下才创建输出段。

3.6.8.6 Output Section Region
可以使用 ’>region’ 把一个段指定到此前设置的内存区域内。参见MEMORY。

下面是一个例子:

MEMORY rom : ORIGIN = 0x1000, LENGTH = 0x1000
SECTIONS ROM : *(.text) >rom

3.6.8.7 Output Section Phdr
您可以使用 ':phdr ’ 将一个段分配给先前定义的程序段。参见 PHDRS。如果一个段被分配给一个或多个段,那么所有后续分配的段也将被分配给这些段,除非它们显式地使用 :phdr 修饰符。您可以使用:NONE来告诉链接器根本不要将该段放在任何段中。

这里有一个简单的例子:

PHDRS text PT_LOAD ;
SECTIONS .text : *(.text) :text

3.6.8.8 Output Section Fill
你可以使用’=fillexp’为整个段设置填充模板。fillexp是一个表达式(参考Expressions)。任何其它的未被特殊指定的输出段的内存区域(例如,因为对其输入段产生的缝隙)将会被用fillexp的值填充,如果有需要可以重复填充。如果表达式是一个简单的hex数字,例如一个十六进制数字由’0x’开头且结尾没有 ’k’ 或 ’M’,则一个任意长的十六进制数字可以被用来给填充模板赋值,前面的0同样成为模板的一部分。在其它情况中,包含额外的括号或者一个一元+,填充模板为表达式值的最低4个有意义的字节。在所有情况中,数字总是大端的。

你也可以使用FILL命令设置填充值(参考Output Section Data)。

这里有一个简单的例子:

SECTIONS .text : *(.text) =0x90909090

3.6.9 Overlay Description
覆盖描述提供了一种简单的方法来描述将作为单个内存映像的一部分加载但将在相同内存地址上运行的段。在运行时,某种类型的覆盖管理器将根据需要从运行时

以上是关于链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)的主要内容,如果未能解决你的问题,请参考以下文章

链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)

链接脚本基本语法

链接脚本基本语法

无标题链接脚本基本语法

链接脚本和可执行文件

链接脚本和可执行文件