Hexagon Binutils GNU 手册(17)

240 篇文章 11 订阅

3.3.10 ENTRY command

链接器命令语言包括一条专门用于定义输出文件中第一条可执行指令(其入口点)的命令。它的参数是一个符号名称。
ENTRY(symbol)
像符号分配一样,ENTRY命令可以作为一个独立的命令放在命令文件中,也可以放在SECTIONS命令中的章节定义中。只要对你的布局最合理即可。
ENTRY只是选择入口点的几种方法之一。你可以用以下任何一种方式来表示它(按优先级降序排列:列表中较高的方法覆盖较低的方法)。
■-e条目命令选项
■链接器控制脚本中的ENTRY(符号)命令
■符号start的值,如果存在的话
■.text部分的第一个字节的地址,如果存在的话
■地址0
例如,你可以使用这些规则来生成一个带有赋值语句的入口点。如果在你的输入文件中没有定义符号起点,你可以简单地定义它,给它分配一个适当的值。
start = 0x2020。
这个例子显示了一个绝对地址,但你可以使用任何表达式。例如,如果你的输入对象文件对入口点使用了一些其他的符号名称约定,你可以直接将包含起始地址的任何符号的值分配给起始点。
start = other_symbol ;

3.3.11 PHDRS command

ELF对象文件格式使用程序头,由系统加载器读取并描述程序应如何加载到内存中。这些程序头必须正确设置,以便在本地ELF系统上运行程序。链接器默认创建合理的程序头。然而,更精确地指定程序头是非常有用的;可以使用PHDRS命令来实现这一点。当使用PHDRS命令时,链接器本身不产生任何程序头。
注意 本文件没有描述系统加载器如何解释程序头的细节;更多信息请参见ELF ABI。ELF文件的程序头可以通过objdump工具的-p选项来显示。
PHDRS命令的语法如下:

PHDRS {
name type [FILEHDR] [PHDRS] [AT (address)] [ FLAGS (flags) ] ; }

名称仅用于链接器脚本的SECTIONS命令中的参考。它不会被放到输出文件中。
某些程序头类型描述了由系统加载器从文件中加载的内存段。在链接器脚本中,这些段的内容是通过指示分配的输出段放在段中来指定的。要做到这一点,在SECTIONS命令中描述输出段的命令应该使用:name,其中name是程序头的名称,因为它出现在PHDRS命令中。
参见第3.3.11节。
某些部分出现在一个以上的段中是正常的。这只是意味着一个内存段包含另一个内存段。这可以通过重复:名称来指定,在每个程序头中使用一次,该部分将出现在其中。
如果一个区段用:name放在一个或多个段中,所有后续分配的区段如果没有指定:name,就会被放在相同的段中。这是为了方便起见,因为通常情况下,一整套连续的节会被放置在一个段中。为了防止在通常情况下默认为段的情况下将一个段分配到一个段,请使用 :NONE。
FILEHDR和PHDRS关键字,可以出现在程序头类型之后,也表示内存段的内容。FILEHDR关键字意味着该段应该包括ELF文件头。PHDRS关键字意味着该段应该包括ELF程序头本身。
类型可以是以下之一。数字表示关键字的值。
pt_null (0) 表示一个未使用的程序头
pt_load (1) 表示这个程序头描述了一个要从文件中加载的段。
pt_dynamic (2) 表示一个可以找到动态连接信息的段。
pt_interp (3) 表示可以找到程序解释器的名称的段。
pt_note (4) 表示一个保存注释信息的段。
pt_shlib (5) 一个保留的程序头类型,由ELF ABI定义但不指定。
PT_PHDR (6) 表示一个可以找到程序头的段。
expression 一个表达式,给出程序头的数字类型。这可以用于上面没有定义的类型。
你可以指定一个段应该在内存中的一个特定地址加载。这可以通过AT表达式来实现。这与SECTIONS命令(第3.3.9节)中使用的AT命令相同。对程序头使用AT命令会覆盖SECTIONS命令中的任何信息。
通常情况下,段的标志是根据段来设置的。FLAGS关键字可以用来明确地指定段标志。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
     ...
}

3.3.12 VERSION command

链接器命令脚本包括一个用于指定版本脚本的命令。注意 版本脚本只对共享对象有意义。
版本脚本可以直接建立在你正在使用的链接器脚本中,或者你可以在链接时将版本脚本作为另一个输入文件提供给链接器。命令脚本的语法是:

VERSION { version_script_contents }

注意 版本脚本也可以用–版本脚本选项来指定。
(第3.2.2节)。
在脚本中,通过定义一棵具有版本脚本中指定的名称和相互依赖关系的版本节点树来指定版本。版本脚本可以指定哪些符号被绑定到哪些版本节点上,它可以将指定的符号集减少到本地范围,这样它们在共享对象之外就不是全局可见的。
下面的例子演示了版本脚本语言:

 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版本的定义之间。然而,这将是一个混乱的版本脚本编写方式。
当你针对一个有版本符号的共享对象链接一个应用程序时,应用程序本身知道它需要每个符号的哪个版本;它也知道它需要来自它所链接的每个共享对象的哪个版本节点。因此,在运行时,动态加载器可以确保你所链接的库确实提供了应用程序所需的所有版本节点,以解决所有的动态符号。通过这种方式,动态链接器可以肯定地知道它所需要的所有外部符号都是可以解析的,而不必去搜索每个符号的引用。
这种方法要解决的根本问题是,对外部函数的引用通常是根据需要进行绑定的,而且在应用程序启动时并不是全部绑定。因此,如果一个共享对象过时了,一个必要的接口可能会丢失;当应用程序试图使用该接口时,它可能会突然意外地失败。在启动程序时有了符号版本控制,如果应用程序使用的库太旧,用户就会收到一个警告。

3.3.12.1 Source file symbol binding

符号可以被绑定到定义符号的源文件中的版本节点,而不是在版本控制脚本中。这样做是为了减轻库维护者的负担。例如,下面的声明可以出现在一个C源文件中。
asm(".symver original_foo,foo@VERS_1.1")。
这个声明将函数original_foo重命名为foo的别名,并与
版本节点 VERS_1.1。
注意 可以使用 local: 指令来防止符号 original_foo 被导出。
被导出。

3.3.12.2 Multiple function versions

同一函数的多个版本可以出现在一个特定的共享对象中。通过这种方式,对一个接口的不兼容的改变可以在不增加共享对象的主要版本号的情况下发生,同时仍然允许与旧接口链接的应用程序继续运行。
这只能通过在汇编器中使用多个.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与未指定的基础版本的符号绑定。包含这个例子的源文件将定义四个C函数:original_foo, old_foo, old_foo1, and new_foo。

3.3.12.3 Multiple symbol definitions

当你有一个给定符号的多个定义时,需要有一些方法来指定一个默认的版本,以绑定对这个符号的外部引用。这可以通过foo@@VERS_2.0类型的.symver指令来完成。一个符号只有一个版本可以以这种方式被声明为 “默认”,否则你将有效地拥有同一个符号的多个定义。

3.3.12.4 External symbol binding

要将一个引用绑定到共享对象中的一个特定版本的符号,你可以使用方便的别名(例如,old_foo)。或者使用.symver指令来专门绑定到有关函数的外部版本。
你也可以在版本脚本中指定语言。
VERSION extern “lang” { version-script-commands }
lang的可能值是 "C "和 “C++”。在链接时,链接器会遍历符号列表,并在将它们与version-script-commands中指定的模式匹配之前,根据lang对它们进行拆分。
解除标记的名称可以包含空格和其他特殊字符。通配符模式(第3.3.9.4节)可以用来匹配被拆分的名字,或者用双引号字符串来精确匹配字符串。在后一种情况下,要注意版本脚本和脱肉器输出之间的微小差异(比如不同的空白)会导致不匹配。由于demangler生成的确切字符串在未来可能会改变,即使被篡改的名字没有改变,你应该检查你所有的版本指令在升级时的行为是否符合你的期望。

3.3.13 Other commands

链接器脚本命令语言包括一些用于专门用途的命令。它们在目的上与命令行选项相似。
AS_NEEDED ( file, file, …)
对待指定的文件,就像它们直接出现在INPUT或GROUP命令中一样,但共享库除外,它们只有在实际需要时才被添加。
该命令将DT_NEEDED标签的添加限制在满足符号引用(来自常规对象)的库上,该库被链接时未被定义,或者–如果该库在链接到该点的其他库的DT_NEEDED列表中没有找到–来自另一个共享库的引用。它等同于 --as-needed 选项。
这个命令只能在INPUT或GROUP命令中使用,在其他文件名中也可以使用。
ASSERT (expr, message)
验证指定的表达式是否为非零。如果是零,以错误代码退出链接器,并打印指定的消息。
EXTERN ( symbol symbol … )
强制将指定的符号作为未定义的符号输入到输出文件中。这样做可能会引发从标准库中链接其他模块。这个命令可以多次使用。它与-u命令行选项的效果相同。
force_common_allocation
即使指定了一个可重定位的输出文件(-r),也要强制链接器为公共符号分配空间。这个命令与-d命令行选项的效果相同。
GROUP ( file, file, … )
GROUP ( file file … )

这个命令和INPUT一样,只是命名的文件应该都是档案,而且要反复搜索,直到没有新的未定义引用产生为止。参见第3.2.1节中对"-("的描述。
INCLUDE file
在这一点上包括链接器脚本文件。该文件将在当前目录和用-L选项指定的任何目录中被搜索到。你可以将对INCLUDE的调用嵌套到最多10层。
INCLUDE可以在链接器脚本、MEMORY或SECTIONS命令、或输出部分描述中的最高层使用。
inhibit_common_allocation
强制链接器省略对公共符号的地址分配,即使是对于不可重定位的输出文件。这个命令与–no-define-common命令行选项的效果相同。
INPUT ( file, file, … ) INPUT ( file file … )
在链接中包括二进制输入文件,就像它们在命令行中被指定一样。
如果所有的链接器输入文件都是在链接器脚本中指定的,那么可以通过调用链接器,除了命令选项-T之外没有任何参数来链接程序。
如果配置了系统根前缀,并且文件名以/字符开头,而被处理的脚本位于系统根前缀内,则在系统根前缀中搜索文件名。否则,链接器会尝试在当前目录中打开该文件。如果没有找到,链接器会通过归档库搜索路径进行搜索。(详见命令选项-L)。
如果在INPUT中指定文件名参数为-lfile,链接器会自动将该文件名转换为libfile.a(正如命令选项-l所做的那样)。
当你在一个隐含的链接器脚本中使用INPUT命令时,文件会在链接器脚本文件被包含的时候被包含在链接中。这可能会影响存档搜索。
INSERT [ AFTER | BEFORE ] output_section
在指定部分之前或之后插入所有先前的链接器脚本语句,并使-T不覆盖默认脚本。具体的插入点与无主部分的插入点相同(第3.3.6.3节)。这个命令用于由-T选项指定的链接器脚本中。

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

注意
链接器脚本语句是在链接器将输入部分映射到输出部分之后插入的。在插入之前,在脚本的内部链接器表示中,-T 脚本中的语句出现在默认脚本语句之前(因为 -T 脚本在默认脚本之前被解析)。特别是,输入部分的赋值会在默认脚本中的赋值之前被映射到-T输出部分。
NOCROSSREFS ( section section …)
指示链接器对指定的部分中的任何引用产生一个错误。
在某些类型的程序中,特别是在嵌入式系统中,当一个部分被加载到内存中时,另一个部分却没有。这两个部分之间的任何直接引用都是错误的。例如,如果一个部分的代码调用另一个部分定义的函数,就会发生错误。
NOCROSSREFS命令接受一个部分名称的列表。如果链接器检测到这些部分之间有任何交叉引用,它将产生一个错误并返回一个非零的退出状态。NOCROSSREFS命令使用在SECTIONS命令中定义的输出部分名称。它不使用输入部分的名称。
OUTPUT ( file )
使用这条命令可以用指定的文件名命名链接输出文件。OUTPUT(file)的作用与-o file的作用相同,后者取代了它。你可以用这个命令提供一个默认的输出文件名。
OUTPUT_ARCH ( bfdname )
指定一个特定的输出机器结构,使用BFD后端程序使用的名称之一(第3.5节)。这条命令通常是不必要的;架构通常是由系统的BFD配置或作为OUTPUT_FORMAT命令的副作用隐含地设置。
OUTPUT_FORMAT ( bfdname )
当链接器被配置为支持多种目标代码格式时,你可以使用这个命令来指定一个特定的输出格式。bfdname是BFD后端例程使用的名称之一(3.5节)。其效果与–oformat命令行选项的效果相同。这个选择只影响到输出文件。相关命令TARGET主要影响输入文件。
REGION_ALIAS ( alias, region )
为指定的内存区域创建一个名为alias的别名。这允许灵活地将输出部分映射到内存区域。
SEARCH_DIR ( path )
将路径添加到链接器寻找存档库的路径列表中。SEARCH_DIR(path) 的作用与命令选项 -Lpath 相同。
如果同时使用脚本命令和命令选项,链接器会搜索这两个路径;使用选项指定的路径会先被搜索到。
STARTUP ( file )
确保文件是链接过程中使用的第一个输入文件。
该命令等同于INPUT命令,只是指定的文件名会成为第一个被链接的输入文件(就像在命令行中首先指定的一样)。
TARGET ( format )
当链接器被配置为支持多种目标代码格式时,你可以使用这个命令来改变输入文件的目标代码格式(就像命令行选项-b或其同义词-format)。参数format是BFD用来命名二进制格式的字符串之一。如果指定了TARGET,但没有指定OUTPUT_FORMAT,最后一个TARGET参数也被用作链接器输出文件的默认格式(第3.5节)。如果你不使用TARGET命令,链接器会使用环境变量GNUTARGET的值(如果有的话)来选择输出文件格式。如果该变量也不存在,链接器将使用BFD库中为你的机器配置的默认格式。

3.4 Processor-specific memory layout

Hexagon处理器的内存布局需要以下数值,这些数值不是由MEMORY和SECTIONS命令(第3.3.8节和第3.3.9节)提供的。
■ Hexagon处理器内存(EBI、TCM、SMI)在虚拟和物理内存中的基础地址。
■ 海克斯康处理器内存部分(第2.4.4节)在其相应内存中的基础相对地址。
这些值通常作为应用程序构建过程的一部分自动分配;但是,对于需要非标准内存布局的独立应用程序,这些值必须由用户明确分配。
本节介绍 Hexagon 处理器内存和支持用户指定 Hexagon 内存布局的功能。

3.4.1 Hexagon processor memories

Hexagon处理器可以访问不同类型的存储器。表3-2列出了这些存储器及其用途。
Table 3-2 Processor memories

NameDescriptionFunction
EBIExternal bus interfaceMain memory
TCMTightly-coupled memoryFast internal memory
SMIStacked memory interfaceExternal memory

程序代码和数据默认存储在EBI存储器中;但是,用户可以通过使用相应的Hexagon处理器存储器部分(第2.4.4节)明确地将它们分配给EBI、TCM或SMI。例如。
C代码:

int ainit[4]__attribute__((section(".tcm_data_uncached"))) =
                               {1, 2, 3, 4}; void foo()
   __attribute__((section(".tcm_code_cached")));
Assembly code:
   .section .ebi_code_cached

注意 静态数据(如C全局)和中断服务程序代码通常被分配到TCM,以提高性能。
部分内存的分配
在使用处理器内存段之前,必须将其分配给特定的内存区域。这可以通过以下两种方式之一完成。
■使用-tcm选项(第3.2.2节),使用链接器预定义的内存地址符号(第3.4.2节)自动分配内存部分。
■修改链接器脚本,使用SECTIONS命令(第3.3.9节)将默认的内存区(.text,.data等)映射到处理器内存区。

3.4.2 Address mapping

这些存储器被映射到Hexagon处理器的虚拟和物理存储器地址空间中的特定地址范围。
例如,图3-2显示了一个使用EBI、TCM和SMI存储器的应用程序的存储器映射。
■应用程序的EBI、TCM和SMI部分被定义为在虚拟内存中占据一个连续的地址范围(0-9000)。
■ 应用程序的非TCM(即EBI和SMI)部分被定义为在虚拟和物理内存地址之间有一个直接的1:1映射。
■TCM内存被定义为占用256K字节的内存,从基础地址0xD8000000(默认值)开始。
■应用程序的TCM内存部分(即tcm_data_cached和tcm_data_cached_wt)在虚拟和物理地址空间中都与4K页边界对齐。
默认的应用程序构建过程会自动将所有的应用程序代码和数据分配到EBI内存的一个连续区域,从基址0开始。
为了让用户控制EBI/TCM/SMI存储器和部分的基址,链接器预先定义了一些符号,可以从链接器命令行中设置。比如说
hexagon-ld --defsym TCM_PA_START=0xD8000000 app1.o
hexagon-ld --defsym TCM_PA_START=0xD8000000+sizeof(app1.o) app2.o
在上述例子中,预定义符号TCM_PA_START被用来为两个应用程序app1和app2设置TCM内存的物理基址。app1的基地址被设置为0xD8000000,而app2的基地址被设置为紧跟app1使用的TCM。
用户定义的内存布局必须符合以下限制。
■在虚拟和物理内存中,部分必须从4K页边界开始。
■在虚拟内存中,一个应用程序内的区段不能重叠。
■应用程序内和应用程序之间的部分不得在物理内存中重叠。
EBI/TCM/SMI部分被默认为在虚拟和物理内存的4K页边界上开始。
为了让用户控制这些部分的内存对齐,链接器预先定义了*_ALIGN符号,可以从链接器命令行中设置。比如说:

hexagon-ld --defsym TCM_DATA_CACHED_ALIGN=0x4000

注意 --section-start选项(第3.2.2节)也可以用来将各个部分重新定位到其内存区域的特定地址。
将各个部分重新定位到其内存区域的特定地址。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值