ld链接器脚本(五)

MEMORY 命令

链接器的默认配置允许分配所有可用内存。 您可以使用MEMORY命令覆盖它。

MEMORY命令描述目标中内存块的位置和大小。您可以使用它来描述链接器可能使用的存储区域,以及必须避免的存储区域。

然后,您可以将section分配给特定的内存区域。链接器将根据存储区域设置section地址,并警告太满的区域。

链接描述文件最多可以包含MEMORY命令的一种用法。
但是,您可以根据需要在其中定义尽可能多的内存块。 语法为:

MEMORY
{
    name [(attr)] : ORIGIN = origin, LENGTH = len
    ...
}

name是链接描述文件中用于引用该区域的名称。区域名称在链接描述文件之外没有任何意义。
区域名称存储在单独的名称空间中,不会与符号名称,文件名称或section名称冲突。
每个内存区域在MEMORY命令中必须有一个不同的名称。但是你可以为已经存在的内存区域,增加一个别名

attr字符串是可选的属性列表,用于指定是否对链接器脚本中未显式映射的输入section使用特定的内存区域。

如前面所述,如果未为某些输入section指定输出section,则链接器将创建一个与该输入section同名的输出section。

如果定义区域属性,则链接器将使用它们为它创建的输出section选择存储区域。

attr字符串必须仅包含以下字符:

R’ Read-only section
‘W’ Read/write section
‘X’ Executable section
‘A’ Allocatable section
‘I’ Initialized section
‘L’ Same asI’
‘!’ Invert the sense of any of the preceding attributes

如果未映射的section与“!”以外的任何列出的属性匹配,它将被放置在这个内存区域中。

“!”属性会翻转属性,因此,只有未映射的section与列出的任何属性都不匹配时,才会将其放置在这个内存区域中。

origin是存储区起始地址的数字表达式。该表达式必须计算为常数,并且不能包含任何符号。
关键字ORIGIN可以缩写为org或o(但不能缩写为ORG)。

len是存储区大小的表达式。与origin表达式一样,该表达式必须仅是数字,并且必须计算为常数。关键字LENGTH可以缩写为len或l。

在以下示例中,我们指定有两个可分配的存储区域:一个从“ 0”开始,大小为256 KB,另一个从“ 0x40000000”开始,大小为4兆字节。

链接器会将未显式映射到存储区中的每个section都放置在“ rom”存储区中,并且它是只读的或可执行的。

链接器会将其他未明确映射到内存区域的section放置到“ ram”内存区域中。

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

定义内存区域后,您就可以使用“> region”输出section属性来指导链接器将特定的输出section放置到该内存区域中。

例如,如果您有一个名为“ mem”的内存区域,则可以在输出section定义中使用“> mem”。
如果没有为输出section指定地址,则链接器会将地址设置为内存区域中的下一个可用地址。

如果指向一个内存区域的输出section对于该区域太大,则链接器将发出错误消息。

可以通过ORIGIN(memory)和LENGTH(memory)函数访问表达式中的存储器的起始位置和长度:

_fstack = ORIGIN(ram) + LENGTH(ram) - 4;

PHDRS 命令

ELF目标文件格式使用程序头,也称为段。程序头说明如何将程序加载到内存中。您可以通过将objdump程序与“ -p”选项一起使用来打印出来。

在本机ELF系统上运行ELF程序时,系统加载程序会读取程序标头,以了解如何加载程序。
仅在正确设置程序头的情况下才能使用。

本手册没有详细描述系统加载程序如何解释程序头。 有关更多信息,请参见ELF ABI。

默认情况下,链接器将创建合理的程序头。但是,在某些情况下,您可能需要更精确地指定程序头。为此,您可以使用PHDRS命令。当链接器在链接器脚本中看到PHDRS命令时,除了指定的程序头以外,它不会创建任何程序头。

链接器仅在生成ELF输出文件时注意PHDRS命令。 在其他情况下,链接器将仅忽略PHDRS。

这是PHDRS命令的语法。 关键字PHDRS,FILEHDR,AT和FLAGS是关键字

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

name仅用于链接脚本的SECTIONS命令中的引用。它不会放入输出文件中。程序头名称存储在单独的名称空间中,不会与符号名称,文件名称或section名称冲突。每个程序头必须有一个不同的名称。

程序头按顺序处理,通常以升序加载地址的顺序映射到section。

某些程序头类型描述了系统加载程序将从文件中加载哪一个段。在链接器脚本中,可以通过将可分配的输出section放置在段中来指定这些段的内容。

您可以使用’:phdr’输出section属性将section放置在特定段中。

通常将某些section放在一个以上的段中。这仅意味着一个内存段包含另一段。一旦某个段,想要包含某个section时,你就可以重复使用“:phdr”.

如果您使用’:phdr’将section放在一个或多个段中,则链接器会将所有未指定’:phdr’的后续可分配的section放在同一段中。

这是为了方便起见,因为通常将一整套连续section放在单个段中。

您可以使用:NONE覆盖默认段,并告诉链接器不要将该段放在任何段中。

您可以在程序头类型之后使用FILEHDR和PHDRS关键字来进一步描述段的内容。

FILEHDR关键字意味着该段应包含ELF文件头。PHDRS关键字意味着该段应包括ELF程序头本身。如果应用于可加载段(PT_LOAD),则它必须是第一个可加载段。注意,此处的文件头和程序头的区别。

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

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表达式指定将段加载到内存中的特定地址。这与用作输出section属性的AT命令相同.程序头的AT命令将覆盖输出section属性。

链接程序通常会根据构成段的section来设置段标记。您可以使用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
...
}

VERSION命令

使用ELF时,链接器支持符号版本。符号版本仅在使用共享库时有用。动态链接器可以通过符号版本,选择指定的函数。

您可以直接在主链接程序脚本中包含版本脚本,也可以将版本脚本作为隐式链接程序脚本进行调用。您也可以使用“ --version-script”链接器选项。

VERSION命令的语法很简单

VERSION { version-script-commands }

版本脚本命令的格式与Solaris 2.5中Sun链接程序使用的格式相同。

版本脚本定义了版本节点树。您可以在版本脚本中指定节点名称和相互依赖性。

您可以指定将哪些符号绑定到哪些版本节点,并且可以将一组指定的符号作用域减少到本地范围,以使它们在共享库之外不全局可见

演示版本脚本语言的最简单方法是使用一些示例。

VERS_1.1 {
    global:
    foo1;
    local:
    old*;
    original*;
    new*;
};
VERS_1.2 {
    foo2;
} VERS_1.1;
VERS_2.0 {
    bar1; bar2;
    extern "C++" {
        ns::*;
        "int f(int, double)";
    }
} VERS_1.2;

此示例版本脚本定义了三个版本节点。定义的第一个版本节点是“ VERS_1.1”; 它没有其他依赖项。该脚本将符号“ foo1”绑定到“ VERS_1.1”。它将一些符号缩小到局部范围,这样它们在共享库之外是不可见的;这是使用通配符模式完成的,因此任何名称以“ old”,“ original”或“ new”开头的符号都将匹配。

可用的通配符模式与匹配文件名时在shell中使用的通配符模式相同。但是,如果在双引号中指定名称,则该名称将被视为字面量常量,而不是需要匹配的模式。

接下来,版本脚本定义节点“ VERS_1.2”。 该节点取决于“ VERS_1.1”。 该脚本将符号“ foo2”绑定到版本节点“ VERS_1.2”。

最后,版本脚本定义了节点“ VERS_2.0”。 该节点取决于“ VERS_1.2”。 脚本将符号“ bar1”和“ bar2”绑定到版本节点“ VERS_2.0”。

当链接器在库中找到一个未绑定到任何版本的符号时,会将其绑定到一个未指定的基本的版本中。

您可以使用版本脚本中的“ global:*;”将所有其他未指定的符号绑定到给定的版本节点。

请注意,在全局作用域中使用通配符有点疯狂,但最后一个版本节点除外。其他地方的全局通配符具有这样一种风险:将符号意外添加到旧版本的集合中。这是错误的,因为较旧的版本应具有一组固定的符号。

版本节点的名称除了可能向阅读它们的人建议以外,没有其他特定含义。“ 2.0”版本也可能出现在“ 1.1”和“ 1.2”之间。
但是,这将是一种编写版本脚本的混乱方式。

如果节点名称是版本脚本中唯一的版本节点,则可以省略。这样的版本脚本不会为符号分配任何版本,只会选择哪些符号在全局范围内可见,哪些不会:

{ global: foo; bar; local: *; };

当您将应用程序链接到具有符号版本控制的共享库时,应用程序本身知道它需要的每个符号的版本,并且还知道链接到的每个共享库中所需的版本节点。

因此,在运行时,动态加载程序可以进行快速检查,以确保所链接的库确实提供了应用程序所需的所有版本节点。

这样,动态链接器就可以确定地知道它需要的所有外部符号都可以解析,而不必搜索每个符号引用

符号版本控制实际上是SunOS进行次要版本检查的一种更为复杂的方法。此处要解决的基本问题是,通常在需要时绑定对外部函数的引用,并且在应用程序启动时不会全部绑定。

如果共享库已过期,则可能缺少所需的接口; 当应用程序尝试使用该接口时,它可能会突然失败。

使用符号版本控制时,如果与应用程序一起使用的库太旧,则用户在启动程序时将收到警告。

Sun的版本控制方法有多个GNU扩展。其中第一个功能是将符号绑定到源文件中的版本节点的能力,源文件中已定义符号而不是在版本控制脚本中。

这样做主要是为了减轻库维护人员的负担。您可以通过输入以下内容来做到这一点:

__asm__(".symver original_foo,foo@VERS_1.1");

这会将函数“ original_foo”重命名为绑定到版本节点“ VERS_1.1”的“ foo”的别名。“ local:”指令可用于防止导出符号“ original_foo”。“ .symver”指令优先于版本脚本。

第二个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”指令来特定地绑定到外部有关函数的版本。

您还可以在版本脚本中指定语言:

VERSION extern "lang" { version-script-commands }

受支持的“ lang”是“ C”,“ C ++”和“ Java”。链接器将在链接时遍历符号列表,并根据“ lang”对它们进行还原,然后再将它们与“ version-script-commands”中指定的模式进行匹配。

杂乱的名称可能包含空格和其他特殊字符。如上所述,您可以使用通配符来匹配名称,也可以使用双引号引起来的字符串来完全匹配该字符串。

在后一种情况下,请注意,版本脚本和实际真正输出之间的细微差别(例如,空白)将导致不匹配。

由于实际生成的确切字符串将来可能会更改,即使名称不正确,您也应检查所有版本指令的行为是否符合升级后的预期。

下一篇文章,介绍脚本中的表达式和隐式链接脚本

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要书写一个连接Oracle并执行一段代码的Shell脚本,需要以下步骤: 1. 安装并配置Oracle客户端:首先,需要安装适当版本的Oracle客户端。安装完成后,设置正确的环境变量(例如ORACLE_HOME和LD_LIBRARY_PATH),以确保Shell脚本能够找到Oracle客户端的相关文件。 2. 编写Shell脚本:使用文本编辑创建一个新的Shell脚本文件,例如`connect_oracle.sh`。 3. 在Shell脚本中编写连接Oracle的命令:在Shell脚本文件中,可以使用`sqlplus`命令连接到Oracle数据库。使用下面的命令行模板,将其添加到Shell脚本文件中: ```bash sqlplus username/password@hostname:port/service_name <<EOF [Oracle PL/SQL code] EOF ``` 其中,`username`是Oracle数据库的用户名,`password`是对应的密码,`hostname`是Oracle数据库所在的主机名,`port`是连接Oracle数据库的端口号,`service_name`是要连接的Oracle服务名。在`<<EOF`和`EOF`之间,可以编写具体的Oracle PL/SQL代码。 4. 编写要执行的Oracle PL/SQL代码:在连接Oracle的命令之后,可以编写要在数据库中执行的具体代码。根据实际需要,可以执行各种数据库操作,如查询、更新、创建表等。 5. 保存并运行Shell脚本:保存Shell脚本文件,并确保该文件具有可执行权限。使用终端进入Shell脚本所在的目录,并运行以下命令执行脚本: ```bash ./connect_oracle.sh ``` 执行过程中,Shell脚本将连接到Oracle数据库,并执行预先编写的PL/SQL代码。执行结果将在终端显示。 需要注意的是,以上的步骤中需要根据实际情况进行相应的配置和编写代码。确保Oracle客户端正常安装和配置,以及Shell脚本中的用户名、密码、主机名、端口号和服务名正确。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值