跳过导航链接 | |
退出打印视图 | |
Oracle Solaris 11.1 链接程序和库指南 Oracle Solaris 11.1 Information Library (简体中文) |
在完成输入文件处理和符号解析并且没有出现致命错误后,链接编辑器将生成输出文件。链接编辑器首先生成完成输出文件必需的其他节。这些节包括所有输入文件中的符号表,这些符号表包含局部符号定义以及已解析的全局符号和弱符号信息。
此外,还包括运行时链接程序需要的任何输出重定位和动态信息节。确定所有输出节信息后,将计算输出文件总大小。然后,相应地创建输出文件映像。
创建动态可执行文件或共享目标文件时,通常会生成两个符号表。.dynsym 表及其关联的字符串表 .dynstr 包含寄存器符号、全局符号、弱符号和节符号。这些节成为在运行时作为进程映像一部分映射的 text 段的一部分。请参见mmapobj(2)。使用此映射,运行时链接程序可以读取这些节,以便执行任何必需的重定位。
.symtab 表及其关联的字符串表 .strtab 包含在输入文件处理过程中收集的所有符号。这些节不能作为进程映像的一部分进行映射。使用链接编辑器的 -z strip-class 选项,或者在链接编辑器之后使用 strip(1),可以将这些节从映像中剥离。
生成符号表期间,将创建保留符号。这些符号对于链接进程有特殊意义。不能在代码中定义这些符号。
对链接编辑器提供的地址表(即 .got 节)的引用,与位置无关。此表由与位置无关的数据引用构造而成,这些数据引用出现在使用 -K pic 选项编译的目标文件中。请参见与位置无关的代码。
对链接编辑器提供的地址表(即 .plt 节)的引用,与位置无关。此表由与位置无关的函数引用构造而成,这些数据引用出现在已使用 -K pic 选项编译的目标文件中。请参见与位置无关的代码。
生成可执行文件时,链接编辑器将查找其他符号,以定义可执行文件的入口点。如果使用链接编辑器的 -e 选项指定了一个符号,那么将使用该符号。否则,链接编辑器会查找保留符号名称 _start,然后查找 main。
功能标识允许代码执行所需的系统属性。可以按优先级顺序使用以下功能。
以上每项功能既可单独定义,也可共同构成一个功能组。
仅当某些功能可用时才能执行的代码应当借助关联的 ELF 目标文件中的功能节来确定这些要求。目标文件中的记录功能要求允许系统在尝试执行关联代码之前验证目标文件。这些要求还可以提供一个框架,以便系统从目标文件系列中选择最适合的目标文件。目标文件系列由相同目标文件的变体组成,每个变体具有不同的功能要求。
动态目标文件以及目标文件中的各个函数或初始化数据项可以与功能要求相关联。理论上,功能要求记录在编译器生成的可重定位目标文件中,并反映了编译时指定的选项或优化。链接编辑器结合所有输入可重定位目标文件的功能来创建输出文件的最终功能节。请参见功能节。
此外,还可以在链接编辑器创建输出文件时定义功能。使用 mapfile 和链接编辑器的 -M 选项标识这些功能。使用 mapfile 定义的功能可以扩充或覆盖在所有输入可重定位目标文件中指定的功能。mapfile 通常用于扩充未生成必需的功能信息的编译器。
系统功能就是对运行中系统进行描述的功能。可以使用 uname(1) 以及 -i 选项和 -m 选项分别显示平台名称和计算机硬件名称。可以使用 isainfo(1)和 -v 选项显示系统硬件功能。运行时会将目标文件的功能要求与系统功能进行比较,以确定是否能装入目标文件,或者是否能在目标文件中使用符号。
目标文件功能就是与目标文件关联的功能。这些功能定义了整个目标文件的要求,并控制能否在运行时装入目标文件。如果系统无法满足目标文件所要求的功能,那么在运行时不能装入该目标文件。功能可用于提供给定目标文件的多个实例,每个实例均针对与目标文件要求匹配的系统进行了优化。运行时链接程序可以通过比较目标文件功能要求与系统提供的功能,从此类目标文件实例系列中透明地选择最佳实例。
符号功能即与目标文件中各个函数或初始化数据项关联的功能。这些功能定义了目标文件中一个或多个符号的要求,并控制在运行时能否使用符号。符号功能允许在单个目标文件中存在多个函数实例。每个函数实例均针对具有不同功能的系统进行了优化。符号功能还允许目标文件中存在多个初始化数据项实例。每个数据实例都可以定义特定于系统的数据。如果系统无法满足符号实例所要求的功能,那么在运行时不能使用该符号实例。相反,必须使用相同符号名的替代实例。通过符号功能,可以构造单个目标文件用在功能不断变化的系统上。函数系列可以为支持这些功能的系统提供经过优化的实例,并且为其他功能较弱的系统提供更为通用的实例。初始化数据项系列可以提供特定于系统的数据。运行时链接程序可以通过比较符号功能要求与系统提供的功能,从此类符号实例系列中透明地选择最佳实例。
目标文件功能和符号功能可用于为当前运行的系统选择最佳目标文件以及目标文件中的最佳符号。目标文件功能和符号功能是可选功能,彼此独立。但是,定义符号功能的目标文件也可以定义目标文件功能。这种情况下,任何功能符号系列都应当带有一个满足目标文件功能的符号实例。如果不存在目标文件功能,那么任何功能符号系列都应当带有一个不需要任何功能的符号实例。在给定系统无适用的功能实例的情况下,此符号实例会提供缺省实现。
以下 x86 示例显示了 foo.o 的目标文件功能。这些功能应用于整个目标文件。此示例中不存在任何符号功能。
$ elfdump -H foo.o Capabilities Section: .SUNW_cap Object Capabilities: index tag value [0] CA_SUNW_HW_1 0x840 [ SSE MMX ]
以下 x86 示例显示了 bar.o 的符号功能。这些功能应用于单个函数 foo 和 bar。每个符号存在两个实例,每个实例被指定给不同的功能集合。此示例中不存在任何目标文件功能。
$ elfdump -H bar.o Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_HW_1 0x40 [ MMX ] Symbols: index value size type bind oth ver shndx name [25] 0x00000000 0x00000021 FUNC LOCL D 0 .text foo%mmx [26] 0x00000024 0x0000001e FUNC LOCL D 0 .text bar%mmx Symbol Capabilities: index tag value [3] CA_SUNW_HW_1 0x800 [ SSE ] Symbols: index value size type bind oth ver shndx name [33] 0x00000044 0x00000021 FUNC LOCL D 0 .text foo%sse [34] 0x00000068 0x0000001e FUNC LOCL D 0 .text bar%sse
功能定义提供了许多种组合,可以使用这些组合来标识目标文件要求或目标文件中各个符号的要求。硬件功能可提供最大的灵活性。硬件功能定义硬件要求,但不决定某个特定的计算机硬件名称或平台名称。不过,有时候底层系统的某些属性只能根据计算机硬件名称或平台名称来确定。可以通过标识功能名称对非常具体的系统功能进行编码,但对已标识目标文件的使用可能会受限制。如果新计算机硬件名称或平台名称适用于目标文件,那么必须重新生成目标文件来标识新的功能名称。
以下各小节将介绍如何定义功能,以及链接编辑器如何使用功能。
目标文件的平台功能标识了可在其上执行目标文件或目标文件中特定符号的系统的平台名称。可以定义多个平台功能。此标识非常具体,且优先级高于其他任何功能类型。
可通过实用程序 uname(1) 和 -i 选项显示系统的平台名称。
可使用以下 mapfile 语法定义平台功能要求。
$mapfile_version 2 CAPABILITY { PLATFORM = platform_name...; PLATFORM += platform_name...; PLATFORM -= platform_name...; };
可使用一个或多个平台名称限定 PLATFORM 属性。"+=" 形式的赋值可以扩充输入目标文件指定的平台功能,而 "=" 形式可以覆盖这些功能。"-=" 形式的赋值用于从输出目标文件中排除平台功能。以下 SPARC 示例可将目标文件 foo.so.1 标识为特定于 SUNW,SPARC-Enterprise 平台。
$ cat mapfile $mapfile_version 2 CAPABILITY { PLATFORM = 'SUNW,SPARC-Enterprise'; }; $ cc -o foo.so.1 -G -K pic -Mmapfile foo.c -lc $ elfdump -H foo.so.1 Capabilities Section: .SUNW_cap Object Capabilities: index tag value [0] CA_SUNW_PLAT SUNW,SPARC-Enterprise
可重定位目标文件可以定义平台功能。可将这些功能收集到一起,定义要生成的目标文件的最终功能要求。
通过使用 "=" 形式的赋值覆盖任何输入可重定位目标文件可能提供的任何平台功能,可以从 mapfile 显式控制目标文件的平台功能。结合使用空 PLATFORM 属性和 "=" 形式的赋值,可以有效地删除要生成的目标文件中的所有平台功能要求。
运行时链接程序会根据系统的平台名称验证动态目标文件中定义的平台功能要求。仅当目标文件中记录的平台名称之一与系统的平台名称相匹配时,才使用目标文件。
在某些情况下,针对特定平台编写代码会很有用,但开发硬件功能系列可提供更大的灵活性,因此建议采用。硬件功能系列可提供经过优化的代码,应用于更广泛的系统上。
目标文件的计算机功能可以标识可在其上执行目标文件或目标文件中特定符号的系统的计算机硬件名称。可以定义多个计算机功能。此标识的优先级低于平台功能定义,但高于其他任何功能类型。
可通过实用程序 uname(1) 和 -m 选项显示系统的计算机硬件名称。
可使用以下 mapfile 语法定义计算机功能要求。
$mapfile_version 2 CAPABILITY { MACHINE = machine_name...; MACHINE += machine_name...; MACHINE -= machine_name...; };
可使用一个或多个计算机硬件名称限定 MACHINE 属性。"+=" 形式的赋值可以扩充输入目标文件指定的计算机功能,而 "=" 形式可以覆盖这些功能。"-=" 形式的赋值用于从输出目标文件中排除计算机功能。以下 SPARC 示例可将目标文件 foo.so.1 标识为特定于 sun4u 计算机硬件名称。
$ cat mapfile $mapfile_version 2 CAPABILITY { MACHINE = sun4u; }; $ cc -o foo.so.1 -G -K pic -Mmapfile foo.c -lc $ elfdump -H foo.so.1 Capabilities Section: .SUNW_cap Object Capabilities: index tag value [0] CA_SUNW_MACH sun4u
可重定位目标文件可以定义计算机功能。可将这些功能收集到一起,定义要生成的目标文件的最终功能要求。
通过使用 "=" 形式的赋值覆盖任何输入可重定位目标文件可能提供的任何计算机功能,从而从 mapfile 显式控制目标文件的计算机功能。结合使用空 MACHINE 属性和 "=" 形式的赋值,可以有效地删除要生成的目标文件中的所有计算机功能要求。
运行时链接程序会根据系统的计算机硬件名称验证动态目标文件中定义的计算机功能要求。仅当目标文件中记录的计算机名称之一与系统的计算机名称相匹配时,才使用目标文件。
在某些情况下,针对特定计算机编写代码会很有用,但开发硬件功能系列可提供更大的灵活性,因此建议采用。硬件功能系列可提供经过优化的代码,应用于更广泛的系统上。
目标文件的硬件功能可标识系统正确执行目标文件或特定符号所需的硬件要求。例如,标识需要在某些 &x86 体系结构上可用的 MMX 或 SSE 功能的代码。
可以使用以下 mapfile 语法标识硬件功能要求。
$mapfile_version 2 CAPABILITY { HW = hwcap_flag...; HW += hwcap_flag...; HW -= hwcap_flag...; };
使用一个或多个标识限定 CAPABILITY 指令的 HW 属性,这些标记是硬件功能的符号表示。"+=" 形式的赋值可以扩充输入目标文件指定的硬件功能,而 "=" 形式可以覆盖这些功能。"-=" 形式的赋值用于从输出目标文件中排除硬件功能。
对于 SPARC 系统,硬件功能定义为 sys/auxv_SPARC.h 中的 AV_ 值。对于 x86 系统,硬件功能定义为 sys/auxv_386.h 中的 AV_ 值。
以下 x86 示例说明如何将 MMX 和 SSE 声明为目标文件 foo.so.1 要求的硬件功能。
$ egrep "MMX|SSE" /usr/include/sys/auxv_386.h #define AV_386_MMX 0x0040 #define AV_386_SSE 0x0800 $ cat mapfile $mapfile_version 2 CAPABILITY { HW += SSE MMX; }; $ cc -o foo.so.1 -G -K pic -Mmapfile foo.c -lc $ elfdump -H foo.so.1 Capabilities Section: .SUNW_cap Object Capabilities: index tag value [0] CA_SUNW_HW_1 0x840 [ SSE MMX ]
可重定位目标文件可以包含硬件功能值。链接编辑器结合多个输入可重定位目标文件中的任何硬件功能值。产生的 CA_SUNW_HW_1 值是对关联的输入值进行按位 OR 运算的结果。缺省情况下,这些值将与 mapfile 指定的硬件功能组合。
通过使用 "=" 形式的赋值覆盖任何输入可重定位目标文件可能提供的任何硬件功能,可以从 mapfile 显式控制目标文件的硬件功能要求。结合使用空 HW 属性和 "=" 形式的赋值,可以有效地删除要生成的目标文件的所有硬件功能要求。
以下示例可抑制输入可重定位目标文件 foo.o 定义的所有硬件功能数据,使其不包括在输出文件 bar.o 中。
$ elfdump -H foo.o Capabilities Section: .SUNW_cap Object Capabilities: index tag value [0] CA_SUNW_HW_1 0x840 [ SSE MMX ] $ cat mapfile $mapfile_version 2 CAPABILITY { HW = ; }; $ ld -o bar.o -r -Mmapfile foo.o $ elfdump -H bar.o $
运行时链接程序会针对系统提供的硬件功能验证动态目标文件定义的任何硬件功能要求。如果无法满足任何硬件功能要求,就不会在运行时装入目标文件。例如,如果进程不能使用 SSE 功能,则 ldd(1) 将指示以下错误。
$ ldd prog foo.so.1 => ./foo.so.1 - hardware capability unsupported: \ 0x800 [ SSE ] ....
利用不同硬件功能的动态目标文件的多个变体可以提供使用过滤器的灵活运行时环境。请参见特定于功能的共享目标文件。
硬件功能还可以用于标识单个目标文件中个别函数的功能。这种情况下,运行时链接程序可以根据当前系统功能选择要使用的最恰当的函数实例。请参见创建符号功能函数系列。
目标文件的软件功能标识对于调试或监视进程可能很重要的软件特征。软件功能也能影响进程的执行。目前,可识别的唯一软件功能与目标文件使用的帧指针以及进程地址空间限制有关。
目标文件可以声明已知其帧指针的使用状态。然后,将帧指针声明为正在使用或未使用来限定此状态。
64 位目标文件可以指明:在运行时,必须在 32 位地址空间内使用它们。
软件功能标志是在 sys/elf.h 中定义的。
#define SF1_SUNW_FPKNWN 0x001 #define SF1_SUNW_FPUSED 0x002 #define SF1_SUNW_ADDR32 0x004
可以使用以下 mapfile 语法标识这些软件功能要求。
$mapfile_version 2 CAPABILITY { SF = sfcap_flags...; SF += sfcap_flags...; SF -= sfcap_flags...; };
CAPABILITY 指令的 SF 属性可赋值为标记 FPKNWN、FPUSED 和 ADDR32 中的任意一个。
可重定位目标文件可以包含软件功能值。链接编辑器可以组合多个输入可重定位目标文件中的软件功能值。软件功能还可随 mapfile 提供。缺省情况下,任何 mapfile 值都将与可重定位目标文件提供的值组合。
通过使用 "=" 形式的赋值覆盖任何输入可重定位目标文件可能提供的任何软件功能,可以从 mapfile 显式控制目标文件的软件功能要求。结合使用空 SF 属性和 "=" 形式的赋值,可以有效地删除要生成的目标文件的所有软件功能要求。
以下示例可抑制输入可重定位目标文件 foo.o 定义的所有软件功能数据,使其不包括在输出文件 bar.o 中。
$ elfdump -H foo.o Object Capabilities: index tag value [0] CA_SUNW_SF_1 0x3 [ SF1_SUNW_FPKNWN SF1_SUNW_FPUSED ] $ cat mapfile $mapfile_version 2 CAPABILITY { SF = ; }; $ ld -o bar.o -r -Mmapfile foo.o $ elfdump -H bar.o $
可按如下方法根据两个帧指针计算 CA_SUNW_SF_1 的值。
表 2-1 CA_SUNW_SF_1 帧指针标志组合状态表
|
此计算方法适用于每个可重定位目标文件值和 mapfile 值。如果不存在 .SUNW_cap 节,或者此节不包含 CA_SUNW_SF_1 值,或者未设置 SF1_SUNW_FPKNW 和 SF1_SUNW_FPUSED 标志,那么目标文件的帧指针软件功能是未知的。
使用 SF1_SUNW_ADDR32 软件功能标志标识的 64 位目标文件可包含需要 32 位地址空间的经过优化的代码。按照此方式标识的 64 位目标文件可与任何其他 64 位目标文件交互操作,无论它们是否使用 SF1_SUNW_ADDR32 标志进行标识。在 64 位输入可重定位目标文件中出现的 SF1_SUNW_ADDR32 标志将传播到 CA_SUNW_SF_1 值,该值是为链接编辑器将创建的输出文件创建的。
64 位可执行文件中存在的 SF1_SUNW_ADDR32 标志可确保将关联进程限定于低 32 位地址空间。此限定地址空间包括进程栈和所有进程依赖项。在此类进程内,所有目标文件都在限定的 32 位地址空间内装入,无论其是否使用 SF1_SUNW_ADDR32 标志进行标识。
64 位共享目标文件可以包含 SF1_SUNW_ADDR32 标志。但是,限定的地址空间要求只能由包含 SF1_SUNW_ADDR32 标志的 64 位可执行文件建立。因此,64 位 SF1_SUNW_ADDR32 共享目标文件必须是 64 位 SF1_SUNW_ADDR32 可执行文件的依赖项。
链接编辑器在生成不受限的 64 位可执行文件时若遇到 64 位 SF1_SUNW_ADDR32 共享目标文件,将导致生成警告。
$ cc -m64 -o main main.c -lfoo ld: warning: file libfoo.so: section .SUNW_cap: software capability ADDR32: \ requires executable be built with ADDR32 capability
通过不受限的 64 位可执行文件创建的进程在运行时若遇到 64 位 SF1_SUNW_ADDR32 共享目标文件,将导致生成致命错误。
$ ldd main libfoo.so => ./libfoo.so - software capability unsupported: \ 0x4 [ ADDR32 ] .... $ main ld.so.1: main: fatal: ./libfoo.so: software capability unsupported: 0x4 [ ADDR32 ]
可以使用 mapfile 随 SF1_SUNW_ADDR32 生成可执行文件。
$ cat mapfile $mapfile_version 2 CAPABILITY { SF += ADDR32; }; $ cc -m64 -o main main.c -Mmapfile -lfoo $ elfdump -H main Object Capabilities: index tag value [0] CA_SUNW_SF_1 0x4 [ SF1_SUNW_ADDR32 ]
开发者通常需要在单个目标文件中提供多个函数实例,且每个实例都针对特定功能集合进行了优化。理想情况下,这些实例的选择和使用对于任何使用者都是透明的。可以创建一个通用的前端函数以提供外部接口。此通用实例与经过优化的实例可以一起组合到一个目标文件中。此通用实例可以使用 getisax(2) 确定系统功能,然后调用适当的优化函数实例来处理任务。虽然此模式可行,但它不仅缺乏通用性,还会产生运行时开销。
符号功能为构造此类目标文件提供了一种替代机制。此机制更简单、更有效,而且不需要编写额外的前端代码。可以创建多个函数实例,并将其与不同的功能关联。这些实例与适合任何系统的缺省函数实例可以一起组合到单个动态目标文件中。运行时链接程序使用符号功能信息,从此符号系列中选择执行最适当的符号。
在以下示例中,x86 目标文件 foobar.mmx.o 和 foobar.sse.o 包含相同的函数 foo() 和 bar(),这两个函数已被编译成分别使用 MMX 和 SSE 指令。
$ elfdump -H foobar.mmx.o Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID mmx [2] CA_SUNW_HW_1 0x40 [ MMX ] Symbols: index value size type bind oth ver shndx name [10] 0x00000000 0x00000021 FUNC LOCL D 0 .text foo%mmx [16] 0x00000024 0x0000001e FUNC LOCL D 0 .text bar%mmx $ elfdump -H foobar.sse.o Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID sse [2] CA_SUNW_HW_1 0x800 [ SSE ] Capabilities symbols: index value size type bind oth ver shndx name [16] 0x00000000 0x0000002f FUNC LOCL D 0 .text foo%sse [18] 0x00000048 0x00000030 FUNC LOCL D 0 .text bar%sse
其中的每个目标文件都包含一个局部符号,可以标识功能函数 foo%*() 和 bar%*()。此外,每个目标文件还会定义一个指向函数 foo() 和 bar() 的全局引用。对 foo() 或 bar() 的任何内部引用均通过这些全局引用进行重定位,这和任何外部接口是一样的。
现在可以将这两个目标文件与 foo() 和 bar() 的缺省实例组合。这些缺省实例可满足全局引用,并提供与任何目标文件功能兼容的实现。可以说这些缺省实例引导着每个功能系列。如果不存在任何目标文件功能,那么此缺省实例也应当不需要任何功能。实际上,存在三个 foo() 和 bar() 的实例,全局实例提供缺省功能,局部实例提供在运行时所用的实现(如果关联的功能可用)。
$ cc -o libfoobar.so.1 -G foobar.o foobar.sse.o foobar.mmx.o $ elfdump -sN.dynsym libfoobar.so.1 | egrep "foo|bar" [2] 0x00000700 0x00000021 FUNC LOCL D 0 .text foo%mmx [4] 0x00000750 0x0000002f FUNC LOCL D 0 .text foo%sse [8] 0x00000784 0x0000001e FUNC LOCL D 0 .text bar%mmx [9] 0x000007b0 0x00000030 FUNC LOCL D 0 .text bar%sse [15] 0x000007a0 0x00000014 FUNC GLOB D 1 .text foo [17] 0x000007c0 0x00000014 FUNC GLOB D 1 .text bar
动态目标文件的功能信息会显示功能符号以及可用的功能系列。
$ elfdump -H libfoobar.so.1 Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID mmx [2] CA_SUNW_HW_1 0x40 [ MMX ] Symbols: index value size type bind oth ver shndx name [2] 0x00000700 0x00000021 FUNC LOCL D 0 .text foo%mmx [8] 0x00000784 0x0000001e FUNC LOCL D 0 .text bar%mmx Symbol Capabilities: index tag value [4] CA_SUNW_ID sse [5] CA_SUNW_HW_1 0x800 [ SSE ] Symbols: index value size type bind oth ver shndx name [4] 0x00000750 0x0000002f FUNC LOCL D 0 .text foo%sse [9] 0x000007b0 0x00000030 FUNC LOCL D 0 .text bar%sse Capabilities Chain Section: .SUNW_capchain Capabilities family: foo chainndx symndx name 1 [15] foo 2 [2] foo%mmx 3 [4] foo%sse Capabilities family: bar chainndx symndx name 5 [17] bar 6 [8] bar%mmx 7 [9] bar%sse
在运行时,对 foo() 和 bar() 的所有引用最初都绑定到全局符号。不过,运行时链接程序将这些函数视为功能系列的引导实例。运行时链接程序会检查每个系列成员以确定是否有更好的功能函数。第一次调用函数时,此操作会发生一次性开销。对 foo() 和 bar() 的后续调用会直接绑定到第一次调用选择的函数实例。通过使用运行时链接程序调试功能,可以观察此函数选择内容。
在以下示例中,底层系统不提供 MMX 或 SSE 支持。foo() 的引导实例不需要特殊的功能支持,因此可满足任何重定位引用。
$ LD_DEBUG=symbols main .... debug: symbol=foo; lookup in file=./libfoo.so.1 [ ELF ] debug: symbol=foo[15]: capability family default debug: symbol=foo%mmx[2]: capability specific (CA_SUNW_HW_1): [ 0x40 [ MMX ] ] debug: symbol=foo%mmx[2]: capability rejected debug: symbol=foo%sse[4]: capability specific (CA_SUNW_HW_1): [ 0x800 [ SSE ] ] debug: symbol=foo%sse[4]: capability rejected debug: symbol=foo[15]: used
在以下示例中,MMX 可用,但 SSE 不可用。foo() 的 MMX 功能实例可满足任何重定位引用。
$ LD_DEBUG=symbols main .... debug: symbol=foo; lookup in file=./libfoo.so.1 [ ELF ] debug: symbol=foo[15]: capability family default debug: symbol=foo[2]: capability specific (CA_SUNW_HW_1): [ 0x40 [ MMX ] ] debug: symbol=foo[2]: capability candidate debug: symbol=foo[4]: capability specific (CA_SUNW_HW_1): [ 0x800 [ SSE ] ] debug: symbol=foo[4]: capability rejected debug: symbol=foo[2]: used
如果在同一个系统上可使用多个功能实例,则将使用一组优先规则来选择一个实例。
定义平台名称的功能组优先于不定义平台名称的功能组。
定义计算机硬件名称的功能组优先于不定义计算机硬件名称的功能组。
较大的硬件功能值优先于较小的硬件功能值。
必须通过过程链接表项访问功能函数实例系列。请参见过程链接表(特定于处理器)。此过程链接引用需要运行时链接程序对函数进行解析。在此过程中,运行时链接程序可以处理关联的符号功能信息,并从可用的函数实例系列中选择最佳函数。
如果未使用符号功能,那么在某些情况下,链接编辑器无需过程链接表项即可解析代码引用。例如,在动态可执行文件中,可以在链接编辑时内部绑定可执行文件中存在的函数引用。共享目标文件中的隐藏函数和受保护函数也可以在链接编辑时内部绑定。在这些情况下,通常不需要运行时链接程序来解析这些函数的引用。
不过,如果使用了符号功能,就必须根据过程链接表项解析函数。为了使运行时链接程序参与适当的函数,同时维护只读文本段,此项是必需的。此机制导致功能函数的所有调用都通过过程链接表项间接进行。如果不使用符号功能,可能不一定要使用此间接方式。因此,在功能函数的调用开销与通过将功能函数用于其缺省对应项所获得的任何性能提高之间,应进行权衡。
注 - 虽然必须通过过程链接表项访问功能函数,但仍可将函数定义为隐藏函数或受保护函数。运行时链接程序会遵从这些可见性状态,并限制对这些函数的任何绑定。此行为导致的绑定与未将符号功能关联到函数时所生成的绑定相同。从外部目标文件无法绑定到隐藏函数。目标文件中对受保护函数的引用只能绑定到同一个目标文件中。
在同一个目标文件中可以提供多个初始化数据实例,其中每个实例均特定于一个系统。不过,通过功能接口提供此类数据通常更为简单,因此建议使用此方法。请参见创建符号功能函数系列。在单个可执行文件中提供多个初始化数据实例需要特别小心。
以下示例将初始化 foo.c 中的数据项 foo,以指向计算机名称字符串。此文件可针对不同计算机进行编译,每个实例均标识有计算机功能。从文件 bar.c 中的 bar() 进行对此数据项的引用。然后,通过将 bar() 与 foo 的两个功能实例组合,创建共享目标文件 foobar.so.1。
$ cat foo.c char *foo = MACHINE; $ cat bar.c #include <stdio.h> extern char *foo = MACHINE; void bar() { (void) printf("machine: %s\n", foo); } $ elfdump -H foobar.so.1 Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID sun4u [2] CA_SUNW_MACH sun4u Symbols: index value size type bind oth ver shndx name [1] 0x000108d4 0x00000004 OBJT LOCL D 0 .data foo%sun4u Symbol Capabilities: index tag value [4] CA_SUNW_ID sun4v [5] CA_SUNW_MACH sun4v Symbols: index value size type bind oth ver shndx name [2] 0x000108d8 0x00000004 OBJT LOCL D 0 .data foo%sun4v
应用程序可以引用 bar(),且运行时链接程序绑定到与底层系统关联的 foo 的实例。
$ uname -m sun4u $ main machine: sun4u
此代码能否正确运行取决于已编译为与位置无关的代码,就像正常情况下可共享目标文件中的代码一样。请参见与位置无关的代码。与位置无关的数据引用为间接引用,运行时链接程序可通过此引用来查找所需的引用并更新数据段元素。这种数据库重定位更新会将文本段保留为只读。
不过,可执行文件中的代码通常为位置相关代码。此外,可执行文件中的数据引用在链接编辑时进行绑定。在可执行文件中,符号功能数据引用必须通过全局数据项保持未解析状态,以便运行时链接程序从符号功能系列中进行选择。如果将上一个示例 bar.c 中自 bar() 的引用编译为位置相关代码,那么可执行文件的文本段必须在运行时进行重定位。缺省情况下,此状态会导致致命链接时错误。
$ cc -o main main.c bar.c foo.o foo.1.o foo.2.o ... warning: Text relocation remains referenced against symbol offset in file foo 0x0 bar.o foo 0x8 bar.o
解决此错误状态的一种方法是将 bar.c 编译为与位置无关。但是,请注意可执行文件中所有符号功能数据项的所有引用都必须编译为与位置无关,否则此方法无效。
虽然可以使用符号功能机制访问数据,但如何使数据项成为目标文件公共接口的一部分是个问题。一种更为灵活的替代模式是将每个数据项封装在符号功能函数中。此函数提供了单独的数据访问方法。将数据隐藏在符号功能函数之后具有很大好处,可允许将数据定义为静态数据并保持专用状态。可以对上一个示例编码以使用符号功能函数。
$ cat foobar.c cat bar.c #include <stdio.h> static char *foo = MACHINE; void bar() { (void) printf("machine: %s\n", foo); } $ elfdump -H main Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID sun4u [2] CA_SUNW_MACH sun4u Symbols: index value size type bind oth ver shndx name [1] 0x0001111c 0x0000001c FUNC LOCL D 0 .text bar%sun4u Symbol Capabilities: index tag value [4] CA_SUNW_ID sun4v [5] CA_SUNW_MACH sun4v Symbols: index value size type bind oth ver shndx name [2] 0x00011138 0x0000001c FUNC LOCL D 0 .text bar%sun4v $ uname -m sun4u $ main machine: sun4u
理论上,编译器可以生成标识有符号功能的目标文件。如果编译器无法创建符号功能,链接编辑器会提供一个解决方案。
可以使用链接编辑器将定义目标文件功能的可重定位目标文件转换为定义符号功能的可重定位目标文件。任何功能数据节都可使用链接编辑器 -z symbolcap 选项转换为定义符号功能。目标文件中的所有全局函数都将转换为局部函数,并与符号功能关联。所有全局初始化数据项都将转换为局部数据项,并与符号功能关联。这些转换的符号附加有指定为目标文件功能组部分的任何功能标识符。如果未定义功能标识符,就会附加缺省的组名。
对于每个原始全局函数或初始化数据项,将创建一个全局引用。此引用与所有重定位要求关联,在最终结合此目标文件来创建动态可执行文件或共享目标文件时,此引用将绑定到缺省的全局符号。
注 - -z symbolcap 选项适用于包含目标文件功能节的目标文件。对于已包含符号功能的可重定位目标文件,或同时包含目标文件功能和符号功能的可重定位目标文件,此选项无效。此设计允许链接编辑器将多个目标文件仅与那些包含受此选项影响的目标文件功能的目标文件组合。
在以下示例中,x86 可重定位目标文件包含两个全局函数 foo() 和 bar()。此目标文件已被编译为需要 MMX 和 SSE 硬件功能。在这些示例中,功能组已采用功能标识符项进行命名。此标识符名称会附加到经过转换的符号名称后。如果没有此显式标识符,链接编辑器会附加一个缺省的功能组名称。
$ elfdump -H foo.o Capabilities Section: .SUNW_cap Object Capabilities: index tag value [0] CA_SUNW_ID sse,mmx [1] CA_SUNW_HW_1 0x840 [ SSE MMX ] $ elfdump -s foo.o | egrep "foo|bar" [25] 0x00000000 0x00000021 FUNC GLOB D 0 .text foo [26] 0x00000024 0x0000001e FUNC GLOB D 0 .text bar $ elfdump -r foo.o | fgrep foo R_386_PLT32 0x38 .rel.text foo
接下来将这个可重定位目标文件转换为符号功能可重定位目标文件。
$ ld -r -o foo.1.o -z symbolcap foo.o $ elfdump -H foo.1.o Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID sse,mmx [2] CA_SUNW_HW_1 0x840 [ SSE MMX ] Symbols: index value size type bind oth ver shndx name [25] 0x00000000 0x00000021 FUNC LOCL D 0 .text foo%sse,mmx [26] 0x00000024 0x0000001e FUNC LOCL D 0 .text bar%sse,mmx $ elfdump -s foo.1.o | egrep "foo|bar" [25] 0x00000000 0x00000021 FUNC LOCL D 0 .text foo%sse,mmx [26] 0x00000024 0x0000001e FUNC LOCL D 0 .text bar%sse,mmx [37] 0x00000000 0x00000000 FUNC GLOB D 0 UNDEF foo [38] 0x00000000 0x00000000 FUNC GLOB D 0 UNDEF bar $ elfdump -r foo.1.o | fgrep foo R_386_PLT32 0x38 .rel.text foo
现在可以将此目标文件与包含相同函数实例的其他目标文件组合,与不同符号功能关联,以生成可执行文件或共享目标文件。此外,应当提供每个函数的缺省实例(即不与任何符号功能关联的实例)以引导每个功能系列。此缺省实例可供所有外部引用使用,并确保函数的实例在任何系统上均可用。
在运行时,foo() 和 bar() 的任何引用均定向到引导实例。不过,如果系统包含适当功能,运行时链接程序会从中选择最佳符号功能实例。
正常情况下,设计和生成目标文件是为了能在给定体系结构的所有系统上执行该目标文件。但是,经常要对具有特殊功能的个别系统进行优化。可以使用之前各节介绍的机制,用代码需要执行的功能来标识经过优化的代码。
要测试经过优化的实例,必须使用可提供所需功能的系统。对于每个系统,运行时链接程序会确定可用的功能,然后选择功能最强的实例。为帮助进行测试和实验,可以让运行时链接程序使用替代功能集合,而不是系统提供的功能集合。此外,可以指定应仅根据这些替代功能验证特定文件。
替代功能集合源自系统功能,可以重新初始化,或者添加或删除功能。
以下环境变量系列可用于创建并计划使用替代功能集合。
标识替代平台名称。
标识替代计算机硬件名称。
标识替代硬件功能值。
标识替代软件功能值。
标识应根据替代功能进行验证的文件。
功能环境变量 LD_PLATCAP 和 LD_MACHCAP 分别接受定义平台名称的字符串和计算机硬件名称的字符串。请参见标识平台功能和标识计算机功能。
功能环境变量 LD_HWCAP 和 LD_SFCAP 接受逗号分隔的标记列表作为功能的符号表现形式。请参见标识硬件功能和标识软件功能。标记也可以是数值。
"+" 前缀将导致将其后的功能添加到替代功能中。"-" 前缀将导致从替代功能中删除其后的功能。缺少 "+-" 将导致其后的功能替换替代功能。
删除某个功能会导致增加对要模拟的功能环境的限制。通常情况下,当功能实例系列可用时,还会提供一个非特定于功能的通用实例。因此,可以使用限制程度较高的功能环境来强制使用功能较弱的代码实例或通用代码实例。
添加功能会导致进一步强化要模拟的功能环境。应谨慎创建此环境,但可将其用于测试某个功能系列的框架。例如,可以创建函数系列以使用 mapfile 定义其预期功能。这些函数可以使用 printf(3C) 确认其执行情况。然后,采用各种功能组合验证并测试关联目标文件的创建。在对函数的实际功能要求进行编码之前,功能系列的此原型设计可以证明是有用的。不过,如果实例系列中的代码要求正确执行特定功能,但系统并未提供此功能,而是将其设置为替代功能,那么代码实例将无法正确执行。
如果不使用 LD_CAP_FILES 建立替代功能集合,那么会根据替代功能对进程的所有特定于功能的目标文件进行验证。此方法也应谨慎使用,因为许多系统目标文件要求正确执行系统功能。功能的任何更改都可能导致系统目标文件无法正确执行。
功能实验的最佳环境是使用可提供目标文件要使用的所有功能的系统。LD_CAP_FILES 还应当用于隔离要实验的目标文件。随后可以使用 "-" 语法禁用功能,从而使得能够试验功能系列的各种实例。系统的真实功能会完全支持每个实例。
例如,假设有两个 x86 功能目标文件 libfoo.so 和 libbar.so。这些目标文件包含针对 SSE2 指令进行优化的功能函数、针对 MMX 指令进行优化的函数,以及不需要功能的通用函数。底层系统同时提供了 SSE2 和 MMX。缺省情况下,使用经过全面优化的 SSE2 函数。
可以使用 LD_HWCAP 定义删除 SSE2 功能,从而限制 libfoo.so 和 libbar.so 使用针对 MMX 指令进行优化的函数。定义 LD_CAP_FILES 最灵活的方法是使用所需文件的基名。
$ LD_HWCAP=-sse2 LD_CAP_FILES=libfoo.so,libbar.so ./main
可以通过删除 SSE2 和 MMX 功能,进一步将 libfoo.so 和 libbar.so 限制为仅使用通用函数。
$ LD_HWCAP=-sse2,mmx LD_CAP_FILES=libfoo.so,libbar.so ./main
注 - 可以使用运行时链接程序诊断来观察应用程序的可用功能以及已设置的所有替代功能。
$ LD_DEBUG=basic LD_HWCAP=-sse2,mmx,cx8 ./main .... 02328: hardware capabilities (CA_SUNW_HW_1) - 0x5c6f \ [ SSE3 SSE2 SSE FXSR MMX CMOV SEP CX8 TSC FPU ] 02328: alternative hardware capabilities (CA_SUNW_HW_1) - 0x4c2b \ [ SSE3 SSE FXSR CMOV SEP TSC FPU ] ....