本节介绍用于创建运行程序的目标文件信息和系统操作。此处介绍的大多数信息适用于所有系统。特定于某处理器的信息位于带有相应标记的各节中。
可执行文件和共享目标文件静态表示应用程序。要执行这类程序,系统可使用这些文件创建动态程序表示形式(即进程映像)。进程映像具有包含其文本、数据、栈等内容的段。本节包括以下主要小节。
程序头,其中介绍了直接参与程序执行的目标文件结构。主数据结构是一种程序头表,用于定位文件中的段映像,并包含创建程序内存映像所需的其他信息。
程序装入(特定于处理器),其中介绍了用于将程序装入内存的信息。
运行时链接程序,其中介绍了用于指定和解析进程映像的目标文件之间的符号引用的信息。
可执行文件或共享目标文件的程序头表是一个结构数组。每种结构都描述了系统准备程序执行所需的段或其他信息。目标文件段包含一个或多个节,如段内容中所述。
程序头仅对可执行文件和共享目标文件有意义。文件使用 ELF 头的 e_phentsize 和 e_phnum 成员来指定各自的程序头大小。
程序头具有以下结构。请参见 sys/elf.h。
typedef struct { Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; } Elf32_Phdr; typedef struct { Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf64_Xword p_filesz; Elf64_Xword p_memsz; Elf64_Xword p_align; } Elf64_Phdr;
此数组元素描述的段类型或解释此数组元素的信息的方式。表 7-25 中指定了类型值及其含义。
相对段的第一个字节所在文件的起始位置的偏移。
段在与物理寻址相关的系统中的物理地址。由于此系统忽略了应用程序的物理地址,因此该成员对于可执行文件和共享目标文件具有未指定的内容。
段的文件映像中的字节数,可以为零。
段的内存映像中的字节数,可以为零。
与段相关的标志。表 7-26 中指定了类型值及其含义。
可装入的进程段必须具有 p_vaddr 和 p_offset 的同余值(以页面大小为模数)。此成员可提供一个值,用于在内存和文件中根据该值对齐各段。值 0 和 1 表示无需对齐。另外,p_align 应为 2 的正整数幂,并且 p_vaddr 应等于 p_offset(以 p_align 为模数)。请参见程序装入(特定于处理器)。
某些项用于描述进程段。其他项则提供补充信息,并且不会构成进程映像。除非明确指定了顺序,否则段的各项可以任何顺序显示。下表中列出了定义的类型值。
表 7-25 ELF 段类型
|
未使用。没有定义成员值。使用此类型,程序头表可以包含忽略的项。
指定可装入段,通过 p_filesz 和 p_memsz 进行描述。文件中的字节会映射到内存段的起始位置。如果段的内存大小 (p_memsz) 大于文件大小 (p_filesz),则将多余字节的值定义为 0这些字节跟在段的已初始化区域后面。文件大小不能大于内存大小。程序头表中的可装入段的各项按升序显示,并基于 p_vaddr 成员进行排列。
指定动态链接信息。请参见动态节。
指定要作为解释程序调用的以空字符结尾的路径名的位置和大小。对于动态可执行文件,必须设置此类型。此类型可出现在共享目标文件中。此类型不能在一个文件中多次出现。此类型(如果存在)必须位于任何可装入段的各项的前面。有关详细信息,请参见程序的解释程序。
指定辅助信息的位置和大小。有关详细信息,请参见注释节。
保留类型,但具有未指定的语义。
指定程序头表在文件及程序内存映像中的位置和大小。此段类型不能在一个文件中多次出现。此外,仅当程序头表是程序内存映像的一部分时,才可以出现此段。此类型(如果存在)必须位于任何可装入段的各项的前面。有关详细信息,请参见程序的解释程序。
指定线程局部存储模板。有关详细信息,请参见线程局部存储节。
此段包含栈扩展表。
此段包含栈扩展表。PT_SUNW_EH_FRAME 与 PT_SUNW_EH_UNWIND 等效。
与 PT_LOAD 元素相同的属性,用于描述 .SUNW_bss 节。
描述进程栈。只能存在一个 PT_SUNWSTACK 元素。仅访问权限(如 p_flags 字段中所定义)有意义。
保留供 dtrace(1M) 内部使用。
指定功能要求。有关详细信息,请参见功能节。
注 - 除非在其他位置具体要求,否则所有程序头的段类型都是可选的。文件的程序头表只能包含与其内容相关的那些元素。
可执行文件和共享目标文件都有一个基本地址,该地址是与程序目标文件的内存映像关联的最低虚拟地址。基本地址的其中一种用途是在动态链接过程中重定位程序的内存映像。
可执行文件或共享目标文件的基本地址是在执行过程中通过以下三个值计算得出的:内存装入地址、最大页面大小和程序可装入段的最低虚拟地址。程序头中的虚拟地址可能并不表示程序内存映像的实际虚拟地址。请参见程序装入(特定于处理器)。
要计算基本地址,首先需要确定与 PT_LOAD 段的最低 p_vaddr 值关联的内存地址。然后,将内存地址截断为最大页面大小的最接近倍数,从而获取基本地址。根据装入内存的文件的类型,内存地址可能与 p_vaddr 值不匹配。
系统要装入的程序必须至少包含一个可装入段,即使文件格式并不要求此限制也是如此。系统创建可装入段的内存映像时,将会授予如 p_flags 成员中所指定的访问权限。PF_MASKPROC 掩码中包括的所有位都保留用于特定于处理器的语义。
表 7-26 ELF 段标志
|
如果权限位是 0,则会拒绝该位的访问类型。实际内存权限取决于内存管理单元,该单元可随系统的不同而变化。尽管所有标志组合均有效,但系统仍可授予比请求更多的访问权限。不过,如果不显式指定写权限,则段在任何情况下都不会具有该权限。下表列出了确切的标志解释及允许的标志解释。
表 7-27 ELF 段权限
|
例如,典型的文本段具有读和执行权限,但没有写权限。数据段通常具有读、写和执行权限。
目标文件段由一节或多节组成,但此事实对程序头是透明的。另外,无论文件段包含一节还是包含多节,对程序装入都没有实际意义。但是,必须存在各种数据以便执行程序、进行动态链接等操作。下图使用一般术语说明了段内容。段中各节的顺序和成员关系可能会有所变化。
文本段包含只读指令和数据。数据段包含可写数据和指令。有关所有特殊节的列表,请参见表 7-10。
PT_DYNAMIC 程序头元素指向 .dynamic 节。.got 和 .plt 节还包含与位置无关的代码和动态链接的相关信息。
.plt 可以位于文本或数据段中,具体取决于处理器。有关详细消息,请参见全局偏移表(特定于处理器)和过程链接表(特定于处理器)。
类型为 SHT_NOBITS 的节不占用文件空间,但构成段的内存映像。通常,这些未初始化的数据驻留在段尾,从而使 p_memsz 大于关联程序头元素中的 p_filesz。
系统创建或扩充进程映像时,系统会以逻辑方式将文件的段复制到虚拟内存段。系统以物理方式读取文件的时间和可能性取决于程序的执行行为、系统负载等。
除非进程在执行过程中引用了逻辑页,否则进程不需要物理页。进程通常会保留许多页面不对其进行引用。因此,延迟物理读取可以提高系统性能。要实际达到这种效率,可执行文件和共享目标文件必须具有文件偏移和虚拟地址同余(以页面大小为模数)的段映像。
32 位段的虚拟地址和文件偏移对模数 64 K (0x10000) 同余。64 位段的虚拟地址和文件偏移对模数 1 MB (0x100000) 同余。通过将各段与最大页面大小对齐,无论物理页大小如何,文件都适合进行换页。
缺省情况下,64 位 SPARC 程序与 0x100000000 的起始地址链接。整个程序位于 4 GB 以上的地址空间内,包括其文本、数据、堆、栈和共享目标文件依赖项。这有助于确保 64 位程序正确,因为如果程序截断其任何指针,则程序在其最低有效的 4 GB 地址空间中将出现错误。尽管 64 位程序在 4 GB 以上的地址空间内进行链接,但您仍可以使用 mapfile 和链接编辑器的 -M 选项,链接 4 GB 以下的地址空间内的程序。请参见 /usr/lib/ld/sparcv9/map.below4G。
下图显示了 SPARC 版本的可执行文件。
图 7-8 SPARC: 可执行文件(64 K 对齐)
下表定义了上图中可装入段的各元素。
表 7-28 SPARC: ELF 程序头段(64 K 对齐)
|
下图显示了 x86 版本的可执行文件。
图 7-9 32 位 x86: 可执行文件(64 K 对齐)
下表定义了上图中可装入段的各元素。
表 7-29 32 位 x86: ELF 程序头段(64 K 对齐)
|
此示例的文件偏移和虚拟地址以文本和数据的最大页面大小为模数同余。根据页面大小和文件系统块大小,最多可有四个文件页包含混合文本或数据。
第一个文本页包含 ELF 头、程序头表和其他信息。
最后一个文本页包含数据起始部分的副本。
第一个数据页包含文本结尾的副本。
最后一个数据页可以包含与运行的进程无关的文件信息。从逻辑上而言,系统会强制执行内存权限,如同每个段是完整而独立的一样。为确保地址空间中的每个逻辑页都具有单独一组权限,各段的地址会进行调整。在前面的示例中,包含文本结尾和数据起始部分的文件区域映射了两次:一次映射到文本的虚拟地址,另一次映射到数据的与之不同的虚拟地址。
注 - 前面的示例反映了对文本段取整的典型的 Oracle Solaris OS 二进制文件。
数据段结尾要求对未初始化的数据进行特殊处理,系统将其定义为从零值开始。如果文件的最后一个数据页包含不属于逻辑内存页的信息,则必须将无关数据设置为零,而不是设置为可执行文件的未知内容。
其他三个页面中的混合内容在逻辑上不属于进程映像。没有指定系统是否会清除这些混合内容。以下各图中显示了此程序的内存映像,假定页面大小为 4 KB (0x1000)。为简单起见,这些图仅对一种页面大小进行说明。
图 7-10 32 位 SPARC: 进程映像段
图 7-11 x86: 进程映像段
可执行文件和共享目标文件在段装入的某个方面有所不同。可执行文件段通常包含绝对代码。为使进程正确执行,段必须位于用于创建可执行文件的虚拟地址处。系统会使用未更改的 p_vaddr 值作为虚拟地址。
另一方面,共享目标文件段通常包含与位置无关的代码。使用此代码,段的虚拟地址在不同进程之间会进行更改,而不会使执行行为无效。
尽管系统会为各个进程选择虚拟地址,但仍会保持各段之间的相对位置。由于与位置无关的代码在各段之间使用相对地址,因此内存中虚拟地址之间的差值必须与文件中虚拟地址之间的差值匹配。
以下各表显示针对多个进程可能指定的共享目标文件虚拟地址,从而说明了固定的相对位置。此外,这些表中还包括基本地址计算。
表 7-30 32 位 SPARC: ELF 共享目标文件段地址示例
|
表 7-31 32 位 x86: ELF 共享目标文件段地址示例
|
启动动态链接的动态可执行文件或共享目标文件可以包含一个 PT_INTERP 程序头元素。在 exec(2) 过程中,系统将从 PT_INTERP 段检索路径名,并通过解释程序文件段创建初始进程映像。解释程序负责从系统接收控制并为应用程序提供环境。
在 Oracle Solaris OS 中,解释程序称为运行时链接程序,即 ld.so.1(1)。
创建启动动态链接的动态目标文件时,链接编辑器将向可执行文件中添加一个类型为 PT_INTERP 的程序头元素。该元素指示系统将运行时链接程序作为程序的解释程序进行调用。exec(2) 和运行时链接程序进行协作,为程序创建进程映像。
链接编辑器可为可执行文件和共享目标文件构造协助运行时链接程序运行的各种数据。这些数据位于可装入段中,从而使数据在执行过程中可用。这些段包括:
类型为 SHT_DYNAMIC 的 .dynamic 节,其中包含各种数据。位于该节起始位置的结构包含其他动态链接信息的地址。
类型为 SHT_PROGBITS 的 .got 和 .plt 节,其中分别包含以下两个表:全局偏移表和过程链接表。以下各节说明了运行时链接程序如何使用和更改这些表,以便为目标文件创建内存映像。
共享目标文件可以占用虚拟内存地址,这些虚拟内存地址与文件的程序头表中记录的地址不同。运行时链接程序会重定位内存映像,从而在应用程序获取控制权之前更新绝对地址。
如果目标文件参与动态链接,则其程序头表将包含一个类型为 PT_DYNAMIC 的元素。此段包含 .dynamic 节。特殊符号 _DYNAMIC 用于标记包含以下结构的数组的节。请参见 sys/link.h。
typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; Elf32_Off d_off; } d_un; } Elf32_Dyn; typedef struct { Elf64_Xword d_tag; union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; } Elf64_Dyn;
对于此类型的每个目标文件,d_tag 将控制 d_un 的解释。
这些目标文件表示具有各种解释的整数值。
这些目标文件表示程序虚拟地址。在执行过程中,文件虚拟地址可能与内存虚拟地址不匹配。对动态结构中包含的地址进行解释时,运行时链接程序会根据原始文件值和内存基本地址来计算实际地址。为确保一致性,文件不应包含用于更正动态结构中的地址的重定位项。
通常,每个动态标记的值决定了 d_un 联合的解释。借助此约定,第三方工具可进行更简单的动态标记解释。值为偶数的标记表示使用 d_ptr 的动态节项。值为奇数的标记表示使用 d_val 的动态节项,或该标记既不使用 d_ptr,也不使用 d_val。值包含在以下特殊兼容性范围中的标记不遵循这些规则。第三方工具必须逐项明确处理这些例外范围。
值小于特定值 DT_ENCODING 的标记。
值位于 DT_LOOS 和 DT_SUNW_ENCODING 之间的标记。
值位于 DT_HIOS 和 DT_LOPROC 之间的标记。
下表概述了可执行文件和共享目标文件的标记要求。如果某标记带有强制标志,则动态链接数组必须包含此类型的项。同样,可选表示该标记的项可以出现但不是必需的。
表 7-32 ELF 动态数组标记
|
标记 _DYNAMIC 数组的结尾。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于提供所需依赖项的名称。动态数组可以包含多个此类型的项。尽管这些项与其他类型的项的关系不重要,但其相对顺序却很重要。请参见共享目标文件依赖项。
与过程链接表关联的重定位项的总大小(以字节为单位)。请参见过程链接表(特定于处理器)。
与过程链接表或全局偏移表关联的地址。请参见过程链接表(特定于处理器)和全局偏移表(特定于处理器)。
符号散列表的地址。该表引用 DT_SYMTAB 元素指示的符号表。请参见散列表节。
字符串表的地址。运行时链接程序所需的符号名称、依赖项名称和其他字符串位于该表中。请参见字符串表节。
符号表的地址。请参见符号表节。
重定位表的地址。请参见重定位节。
目标文件可以有多个重定位节。为可执行文件或共享目标文件创建重定位表时,链接编辑器会连接这些节以形成一个表。尽管这些节在目标文件中可以保持独立,但运行时链接程序将看到一个表。运行时链接程序为可执行文件创建进程映像或将共享目标文件添加到进程映像中时,运行时链接程序将会读取该重定位表并执行关联操作。
此元素要求同时存在 DT_RELASZ 和 DT_RELAENT 元素。如果文件必须重定位,则可以存在 DT_RELA 或 DT_REL。
DT_RELA 重定位表的总大小(以字节为单位)。
DT_RELA 重定位项的大小(以字节为单位)。
DT_STRTAB 字符串表的总大小(以字节为单位)。
DT_SYMTAB 符号项的大小(以字节为单位)。
初始化函数的地址。请参见初始化节和终止节。
终止函数的地址。请参见初始化节和终止节。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于标识共享目标文件的名称。请参见记录共享目标文件名称。
以空字符结尾的库搜索路径字符串的 DT_STRTAB 字符串表偏移。此元素的用途已被 DT_RUNPATH 取代。请参见运行时链接程序搜索的目录。
表示目标文件包含在其链接编辑过程中应用的符号绑定。此元素的用途已被 DF_SYMBOLIC 标志取代。请参见使用 -B symbolic 选项。
与 DT_RELA 类似,但其表中包含隐式加数。此元素要求同时存在 DT_RELSZ 和 DT_RELENT 元素。
DT_REL 重定位表的总大小(以字节为单位)。
DT_REL 重定位项的大小(以字节为单位)。
表示过程链接表指向的重定位项的类型(DT_REL 或 DT_RELA)。过程链接表中的所有重定位都必须使用相同的重定位项。请参见过程链接表(特定于处理器)。此元素要求同时存在 DT_JMPREL 元素。
用于调试。
表示一个或多个重定位项可能会要求修改非可写段,并且运行时链接程序可以相应地进行准备。此元素的用途已被 DF_TEXTREL 标志取代。请参见与位置无关的代码。
与过程链接表单独关联的重定位项的地址。请参见过程链接表(特定于处理器)。通过分隔这些重定位项,运行时链接程序可在装入启用了延迟绑定的目标文件时忽略这些项。此元素要求同时存在 DT_PLTRELSZ 和 DT_PLTREL 元素。
应用于紧邻的 DT_ 元素的各种状态标志。请参见表 7-35。
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此项优先于使用延迟绑定的指令。此元素的用途已被 DF_BIND_NOW 标志取代。请参见执行重定位的时间。
初始化函数的指针数组的地址。此元素要求同时存在 DT_INIT_ARRAYSZ 元素。请参见初始化节和终止节。
终止函数的指针数组的地址。此元素要求同时存在 DT_FINI_ARRAYSZ 元素。请参见初始化节和终止节。
DT_INIT_ARRAY 数组的总大小(以字节为单位)。
DT_FINI_ARRAY 数组的总大小(以字节为单位)。
以空字符结尾的库搜索路径字符串的 DT_STRTAB 字符串表偏移。请参见运行时链接程序搜索的目录。
特定于此目标文件的标志值。请参见表 7-33。
大于或等于 DT_ENCODING、小于或等于 DT_LOOS 的动态标记值遵循 d_un 联合的解释规则。
预初始化函数的指针数组的地址。此元素要求同时存在 DT_PREINIT_ARRAYSZ 元素。仅在可执行文件中处理该数组。如果该数组包含在共享目标文件中,则会被忽略。请参见初始化节和终止节。
DT_PREINIT_ARRAY 数组的总大小(以字节为单位)。
正动态数组标记值的数量。
此范围内包含的值(包括这两个值)保留用于特定于操作系统的语义。所有这类值都遵循 d_un 联合的解释规则。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于逐符号指定一个或多个辅助 filtee。请参见生成辅助过滤器。
保留供运行时链接程序内部使用。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于逐符号指定一个或多个标准 filtee。请参见生成标准过滤器。
功能节的地址。请参见功能节。
符号表的地址,其中包含用于扩充 DT_SYMTAB 所提供的符号的局部函数符号。这些符号始终在紧邻 DT_SYMTAB 所提供的符号之前的位置。请参见符号表节。
DT_SUNW_SYMTAB 和 DT_SYMTAB 提供的符号表的组合大小。
大于或等于 DT_SUNW_ENCODING、小于或等于 DT_HIOS 的动态标记值遵循 d_un 联合的解释规则。
DT_SUNW_SYMSORT 和 DT_SUNW_TLSSORT 符号排序项的大小(以字节为单位)。
符号表索引数组的地址,这些索引提供对 DT_SUNW_SYMTAB 所引用的符号表中函数和变量符号的排序访问。请参见符号排序节。
DT_SUNW_SYMSORT 数组的总大小(以字节为单位)。
符号表索引数组的地址,这些索引提供对 DT_SUNW_SYMTAB 所引用的符号表中线程局部符号的排序访问。请参见符号排序节。
DT_SUNW_TLSSORT 数组的总大小(以字节为单位)。
符号表索引数组的地址,这些索引提供符号与其功能要求之间的关联。请参见功能节。
动态字符串表末尾未使用的保留空间的总大小(以字节为单位)。如果目标文件中不存在 DT_SUNW_STRPAD,则没有保留空间可用。
功能系列索引数组的地址。每个索引系列都以 0 项结尾。
生成目标文件的链接编辑器的计算机体系结构。DT_SUNW_LDMACH 使用与 ELF 头的 e_machine 字段相同的 EM_ 整数值。请参见ELF 头。DT_SUNW_LDMACH 用于标识生成目标文件的链接编辑器的类(32 位或 64 位)和平台。此信息不会用于运行时链接程序,而仅用于说明目的。
DT_SUNW_CAPCHAIN 项的大小(以字节为单位)。
DT_SUNW_CAPCHAIN 链的总大小(以字节为单位)。
符号信息表的地址。此元素要求同时存在 DT_SYMINENT 和 DT_SYMINSZ 元素。请参见Syminfo 表节。
DT_SYMINFO 信息项的大小(以字节为单位)。
DT_SYMINFO 表的总大小(以字节为单位)。
版本定义表的地址。该表中的元素包含字符串表 DT_STRTAB 的索引。此元素要求同时存在 DT_VERDEFNUM 元素。请参见版本定义章节。
DT_VERDEF 表中的项数。
版本依赖性表的地址。该表中的元素包含字符串表 DT_STRTAB 的索引。此元素要求同时存在 DT_VERNEEDNUM 元素。请参见版本依赖性节。
DT_VERNEEDNUM 表中的项数。
表示 RELATIVE 重定位计数,该计数是通过串联所有 Elf32_Rela 或 Elf64_Rela 重定位项生成的。请参见组合重定位节。
表示 RELATIVE 重定位计数,该计数是通过串联所有 Elf32_Rel 重定位项生成的。请参见组合重定位节。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于指定一个或多个辅助 filtee。请参见生成辅助过滤器。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于指定一个或多个标准 filtee。请参见生成标准过滤器。
目标文件中选定的节的简单校验和。请参见 gelf_checksum(3ELF)。
DT_MOVETAB 移动项的大小(以字节为单位)。
DT_MOVETAB 表的总大小(以字节为单位)。
移动表的地址。此元素要求同时存在 DT_MOVEENT 和 DT_MOVESZ 元素。请参见移动节。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义配置文件。该配置文件仅在可执行文件中有意义,并且通常是特定于此目标文件的。请参见配置缺省搜索路径。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义一个或多个审计库。请参见运行时链接程序审计接口。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义一个或多个审计库。请参见运行时链接程序审计接口。
特定于此目标文件的标志值。请参见表 7-34。
此范围内包含的值(包括这两个值)使用动态结构的 d_un.d_val 字段。
此范围内包含的值(包括这两个值)使用动态结构的 d_un.d_ptr 字段。如果生成 ELF 目标文件后对其进行了任何调整,则必须相应地更新这些项。
DT_SYMTAB 符号表中 STT_SPARC_REGISTER 符号的索引。该符号表中的每个 STT_SPARC_REGISTER 符号都存在一个动态项。请参见寄存器符号。
此范围内包含的值(包括这两个值)保留用于特定于处理器的语义。
除动态数组末尾的 DT_NULL 元素以及 DT_NEEDED 和 DT_POSFLAG_1 元素的相对顺序以外,各项可以采用任何顺序显示。未显示在该表中的标记值为保留值。
表 7-33 ELF 动态标志 DT_FLAGS
|
表示目标文件要求 $ORIGIN 处理。请参见查找关联的依赖项。
表示目标文件包含在其链接编辑过程中应用的符号绑定。请参见使用 -B symbolic 选项。
表示一个或多个重定位项可能会要求修改非可写段,并且运行时链接程序可以相应地进行准备。请参见与位置无关的代码。
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此项优先于使用延迟绑定的指令。请参见执行重定位的时间。
表示目标文件包含使用静态线程局部存储方案的代码。在通过 dlopen(3C) 或延迟装入而动态装入的目标文件中,不能使用静态线程局部存储。
表 7-34 ELF 动态标志 DT_FLAGS_1
|
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此标志优先于使用延迟绑定的指令。请参见执行重定位的时间。
表示目标文件是组的成员。此标志通过链接编辑器的 -B group 选项记录在目标文件中。请参见目标文件分层结构。
表示不能从进程中删除目标文件。如果使用 dlopen(3C) 通过直接或依赖性方式将目标文件装入进程,则无法使用 dlclose(3C) 卸载该目标文件。此标志通过使用链接编辑器的 -z nodelete 选项记录在目标文件中。
仅对过滤器有意义。表示立即处理所有关联 filtee。此标志通过使用链接编辑器的 -z loadfltr 选项记录在目标文件中。请参见filtee 处理。
表示在装入其他任何目标文件之前首先运行此目标文件的初始化节。此标志仅适用于专用系统库,并通过使用链接编辑器的 -z initfirst 选项记录在目标文件中。
表示无法使用 dlopen(3C) 将目标文件添加到正在运行的进程。此标志通过使用链接编辑器的 -z nodlopen 选项记录在目标文件中。
表示目标文件要求 $ORIGIN 处理。请参见查找关联的依赖项。
表示目标文件应使用直接绑定信息。请参见附录 D。
表示目标文件符号表将在除主装入目标文件(通常为可执行文件)外的所有符号之前插入。此标志通过使用链接编辑器的 -z interpose 选项进行记录。请参见运行时插入。
表示此目标文件的依赖性搜索会忽略所有缺省的库搜索路径。此标志通过使用链接编辑器的 -z nodefaultlib 选项记录在目标文件中。请参见运行时链接程序搜索的目录。
表示此目标文件不通过 dldump(3C) 进行转储。此选项的替代选项包括没有重定位项的目标文件,这些目标文件可能会包括在使用 crle(1) 生成的替代目标文件中。此标志通过使用链接编辑器的 -z nodump 选项记录在目标文件中。
将此目标文件标识为 crle(1) 生成的配置替代目标文件。此标志可触发运行时链接程序来搜索配置文件 $ORIGIN/ld.config.app-name。
仅对 filtee 有意义。终止对其他任何 filtee 的过滤器搜索。此标志通过使用链接编辑器的 -z endfiltee 选项记录在目标文件中。请参见减少 filtee 搜索。
表示此目标文件应用了位移重定位。由于位移重定位记录在应用重定位后被丢弃,因此该目标文件中将不再存在这些记录。请参见位移重定位。
表示此目标文件暂挂了位移重定位。由于此目标文件中存在位移重定位,因此可在运行时完成重定位。请参见位移重定位。
表示此目标文件包含无法直接绑定的符号。请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令。
保留供内核运行时链接程序内部使用。
保留供内核运行时链接程序内部使用。
保留供内核运行时链接程序内部使用。
表示此目标文件在最初由链接编辑器构造后,已被编辑或被修改。此标志用于警告调试器,某个目标文件在最初生成后进行了更改。
保留供内核运行时链接程序内部使用。
表示目标文件包含应在除主装入目标文件(通常为可执行文件)外的所有符号之前插入的各个符号。使用 mapfile 和 INTERPOSE 关键字生成目标文件时记录此标志。请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令。
表示动态可执行文件要求全局审计。请参见记录全局审计程序。
表示目标文件定义或引用 singleton 符号。请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令。
表 7-35 ELF 动态位置标志 DT_POSFLAG_1
|
将以下 DT_NEEDED 项标识为要延迟装入的目标文件。此标志通过使用链接编辑器的 -z lazyload 选项记录在目标文件中。请参见延迟装入动态依赖项。
将以下 DT_NEEDED 项标识为要作为组装入的目标文件。此标志通过使用链接编辑器的 -z groupperm 选项记录在目标文件中。请参见隔离组。
通常,与位置无关的代码不能包含绝对虚拟地址。全局偏移表在专用数据中包含绝对地址。因此这些地址可用,并且不会破坏程序文本的位置独立性和共享性。程序使用与位置无关的地址来引用其 GOT 并提取绝对值。此方法可将与位置无关的引用重定向到绝对位置。
最初,GOT 包含其重定位项所需的信息。系统为可装入目标文件创建内存段后,运行时链接程序将会处理这些重定位项。某些重定位项的类型可以为 R_xxxx_GLOB_DAT,用于引用 GOT。
运行时链接程序可确定关联符号值,计算其绝对地址以及将相应的内存表各项设置为正确的值。尽管链接编辑器创建目标文件时绝对地址未知,但运行时链接程序知道所有内存段的地址,因此可以计算其中包含的符号的绝对地址。
如果程序要求直接访问某符号的绝对地址,则该符号将具有一个 GOT 项。由于可执行文件和共享目标文件具有不同的 GOT,因此一个符号的地址可以出现在多个表中。运行时链接程序在向进程映像中的任何代码授予控制权之前,将首先处理所有的 GOT 重定位项。此处理操作可确保绝对地址在执行过程中可用。
表项零保留用于存储动态结构(使用符号 _DYNAMIC 引用)的地址。使用此符号,运行时链接程序等程序可在尚未处理其重定位项的情况下查找各自的动态结构。此方法对于运行时链接程序尤其重要,因为它必须对自身进行初始化,而不依赖于其他程序来重定位其内存映像。
系统可为不同程序中的同一共享目标文件选择不同的内存段地址。系统甚至可以为同一程序的不同执行方式选择不同的库地址。但是,一旦建立进程映像,内存段即不会更改各地址。只要存在进程,其内存段就会位于固定的虚拟地址。
GOT 的格式和解释是特定于处理器的。符号 _GLOBAL_OFFSET_TABLE_ 可用于访问该表。此符号可以位于 .got 节的中间,以提供地址数组的负下标和非负下标。对于 32 位代码,符号类型是 Elf32_Addr 数组;对于 64 位代码,符号类型是 Elf64_Addr 数组。
extern Elf32_Addr _GLOBAL_OFFSET_TABLE_[]; extern Elf64_Addr _GLOBAL_OFFSET_TABLE_[];
全局偏移表可将与位置无关的地址计算结果转换为绝对位置。同样,过程链接表也可将与位置无关的函数调用转换为绝对位置。链接编辑器无法解析不同动态目标文件之间的执行传输(如函数调用)。因此,链接编辑器会安排程序将控制权转移给过程链接表中的各项。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享目标文件包含不同的过程链接表。
对于 32 位 SPARC 动态目标文件,过程链接表位于专用数据中。运行时链接程序可确定目标的绝对地址,并相应地修改过程链接表的内存映像。
过程链接表的前四项是保留项。尽管表 7-36 中显示了过程链接表的示例,但未指定这些项的原始内容。该表中的每一项都占用 3 个字(12 字节),并且表的最后一项后跟 nop 指令。
重定位表与过程链接表关联。_DYNAMIC 数组中的 DT_JMP_REL 项指定了第一个重定位项的位置。对于非保留的过程链接表的每一项,重定位表中都包含相同顺序的对应项。所有这些项的重定位类型均为 R_SPARC_JMP_SLOT。重定位偏移可指定关联的过程链接表项的第一个字节的地址。符号表索引会指向相应的符号。
为说明过程链接表,表 7-36 显示了四项。其中,前两项是初始保留项。第三项是对 name101 的调用。第四项是对 name102 的调用。此示例假定对应 name102 的项是表的最后一项。在该最后一项的后面是 nop 指令。左列显示了进行动态链接之前目标文件中的指令。右列说明了运行时链接程序会用于修复过程链接表各项的可能的指令序列。
表 7-36 32 位 SPARC: 过程链接表示例
|
以下步骤介绍了运行时链接程序和程序如何通过过程链接表来共同解析符号引用。所介绍的这些步骤仅用于说明。没有指定运行时链接程序的准确执行时行为。
初始创建程序的内存映像时,运行时链接程序会更改初始过程链接表的各项。修改这些项是为了可将控制权转移给运行时链接程序自己的其中一个例程。运行时链接程序还会在第二项中存储一个字的标识信息。运行时链接程序获取控制权后,会检查该字以标识调用者。
过程链接表的其他所有项最初都会传输给第一项。因此,运行时链接程序会在首次执行表项时获取控制权。例如,该程序会调用 name101,以将控制权转移给标签 .PLT101。
sethi 指令可分别计算当前过程链接表各项和初始过程链接表各项(.PLT101 和 .PLT0)之间的距离。该值会占用 %g1 寄存器最高有效的 22 位。
接下来,ba,a 指令会跳至 .PLT0 以建立栈帧,然后调用运行时链接程序。
通过标识值,运行时链接程序可获取其用于目标文件的数据结构,包括重定位表。
通过将 %g1 值移位并除以过程链接表各项的大小,运行时链接程序可计算对应 name101 的重定位项的索引。重定位项 101 的类型为 R_SPARC_JMP_SLOT。此重定位偏移可指定 .PLT101 的地址,并且其符号表索引会指向 name101。因此,运行时链接程序可获取符号的实际值、展开栈、修改过程链接表项并将控制权转移给所需目标。
运行时链接程序不必在内存段列下创建指令序列。如果运行时链接程序创建了指令序列,则某些点需要更多说明。
要使代码可重复执行,可按特定顺序更改过程链接表的指令。如果运行时链接程序在修复函数的过程链接表项时收到信号,则信号处理代码必须能够调用具有可预测的正确结果的原始函数。
运行时链接程序更改三个字才能转换一项。对于指令执行,运行时链接程序只能自动更新一个字。因此,通过以相反顺序更新每个字可实现重复执行。如果仅在最后一个修补程序之前调用可重复执行的函数,则运行时链接程序会再次获取控制权。尽管两次调用运行时链接程序修改的过程链接表项都相同,但这些更改不会相互干扰。
过程链接表项的第一条 sethi 指令可以填充 jmp1 指令的延迟插槽。尽管 sethi 会更改 %g1 寄存器的值,但可以安全废弃以前的内容。
转换之后,过程链接表的最后一项 .PLT102 需要一条延迟指令用于其 jmp1。所需的结尾 nop 将填充此延迟插槽。
注 - 为 .PLT101 和 .PLT102 显示的不同指令序列说明了如何优化关联目标的更新。
LD_BIND_NOW 环境变量更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_SPARC_JMP_SLOT 重定位项。
对于 64 位 SPARC 动态目标文件,过程链接表位于专用数据中。运行时链接程序可确定目标的绝对地址,并相应地修改过程链接表的内存映像。
过程链接表的前四项是保留项。尽管表 7-37 中显示了过程链接表的示例,但未指定这些项的原始内容。在该表中,前 32,768 项的每一项都占用 8 个字(32 字节),并且必须与 32 字节边界对齐。整个表必须与 256 字节边界对齐。如果所需项数大于 32,768,则其余各项由 6 个字(24 字节)和 1 个指针(8 字节)组成。指令以 160 项并后跟 160 个指针的块方式收集到一起。最后一组项和指针可以包含的项数少于 160。不需要进行填充。
注 - 数字 32,768 和 160 分别基于分支和装入目标文件位移的限制,并且位移会向下舍入以使代码和数据之间的分区落到 256 字节边界上,从而提高高速缓存的性能。
重定位表与过程链接表关联。_DYNAMIC 数组中的 DT_JMP_REL 项指定了第一个重定位项的位置。对于非保留的过程链接表的每一项,重定位表中都包含相同顺序的对应项。所有这些项的重定位类型均为 R_SPARC_JMP_SLOT。对于前 32,767 个插槽,重定位偏移将指定关联过程链接表项的第一个字节的地址,并且加数字段为零。符号表索引会指向相应的符号。对于插槽 32,768 及其之后的插槽,重定位偏移将指定关联指针的第一个字节的地址。加数字段是未重定位的值 -(.PLTN + 4)。符号表索引会指向相应的符号。
为说明过程链接表,表 7-37 显示了若干项。前三项显示了初始保留项。接下来的三项显示了初始的 32,768 个项的示例以及可能的解析格式,这些格式分别应用于目标地址位于项上下 2 GB 的地址空间内、目标地址位于低位的 4 GB 地址空间内或目标地址位与其他任意位置的情形。最后两项显示了后续各项的示例,这些项由指令和指针对组成。左列显示了进行动态链接之前目标文件中的指令。右列说明了运行时链接程序会用于修复过程链接表各项的可能指令序列。
表 7-37 64 位 SPARC: 过程链接表示例
|
以下步骤介绍了运行时链接程序和程序如何通过过程链接表来共同解析符号引用。所介绍的这些步骤仅用于说明。没有指定运行时链接程序的准确执行时行为。
初始创建程序的内存映像时,运行时链接程序会更改初始过程链接表的各项。修改这些项是为了将控制权转移给运行时链接程序自己的例程。运行时链接程序还会在第三项中存储一个扩展字的标识信息。运行时链接程序获取控制权后,会检查该字以标识调用者。
过程链接表的其他所有项最初都会传输给第一项或第二项。这些项将建立栈帧并调用运行时链接程序。
通过标识值,运行时链接程序可获取其用于目标文件的数据结构,包括重定位表。
运行时链接程序会计算表插槽对应的重定位项的索引。
使用索引信息,运行时链接程序可获取符号的实际值、展开栈、修改过程链接表项并将控制权转移给所需目标。
运行时链接程序不必在内存段列下创建指令序列。如果运行时链接程序创建了指令序列,则某些点需要更多说明。
要使代码可重复执行,可按特定顺序更改过程链接表的指令。如果运行时链接程序在修复函数的过程链接表项时收到信号,则信号处理代码必须能够调用具有可预测的正确结果的原始函数。
运行时链接程序最多可更改八个字来转换一项。对于指令执行,运行时链接程序只能自动更新一个字。因此,通过以下方法可实现重复执行:首先将 nop 指令覆写为其替换指令,如果使用 64 位存储,则之后还要修补 ba,a 和 sethi。如果仅在最后一个修补程序之前调用可重复执行的函数,则运行时链接程序会再次获取控制权。尽管两次调用运行时链接程序修改的过程链接表项都相同,但这些更改不会相互干扰。
如果更改初始 sethi 指令,则只能将该指令替换为 nop。
按照更改第二种格式的项的方式更改指针是通过使用一个原子的 64 位存储器完成的。
注 - 为 .PLT101、.PLT102 和 .PLT103 显示的不同指令序列说明了如何优化关联目标的更新。
LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_SPARC_JMP_SLOT 重定位项。
对于 32 位 x86 动态目标文件,过程链接表位于共享文本中,但使用专用全局偏移表中的地址。运行时链接程序可确定目标的绝对地址,并相应地修改全局偏移表的内存映像。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享目标文件包含不同的过程链接表。
表 7-38 32 位 x86: 绝对过程链接表示例
|
表 7-39 32 位 x86: 与位置无关的过程链接表示例
|
注 - 如前面的示例所示,对于绝对代码和与位置无关的代码,过程链接表指令会使用不同的操作数寻址模式。但是,它们的运行时链接程序接口却相同。
以下步骤介绍了运行时链接程序和程序如何通过过程链接表和全局偏移表来协作解析符号引用。
初始创建程序的内存映像时,运行时链接程序会将全局偏移表中的第二项和第三项设置为特殊值。以下步骤说明了这些值。
如果过程链接表与位置无关,则全局偏移表的地址必须位于 %ebx 中。进程映像中的每个共享目标文件都有各自的过程链接表,并且控制权仅转移给位于同一目标文件内的过程链接表项。因此,调用函数在调用过程链接表项之前,必须首先设置全局偏移表基本寄存器。
例如,该程序会调用 name1,以将控制权转移给标签 .PLT1。
第一条指令会跳至全局偏移表项中对应于 name1 的地址。最初,全局偏移表包含以下 pushl 指令的地址,而不是 name1 的实际地址。
该程序将在栈中推送一个重定位偏移 (offset)。该重定位偏移是重定位表中一个 32 位的非负字节偏移。指定的重定位项的类型为 R_386_JMP_SLOT,其偏移指定了前面的 jmp 指令中使用的全局偏移表项。该重定位项还包含符号表索引,以供运行时链接程序用于获取引用的符号 name1。
推送该重定位偏移后,程序将跳至过程链接表中的第一项 .PLT0。pushl 指令会在栈中推送全局偏移表的第二项(got_plus_4 或 4(%ebx))的值,从而为运行时链接程序提供一个字的标识信息。然后,程序将跳至全局偏移表的第三项(got_plus_8 或 8(%ebx))中的地址,以继续跳至运行时链接程序。
运行时链接程序将展开栈、检查指定的重定位项、获取符号的值、在全局偏移项表中存储 name1 的实际地址并跳至目标。
过程链接表项的后续执行结果会直接传输给 name1,而不会再次调用运行时链接程序。位于 .PLT1 的 jmp 指令将跳至 name1,而不是对 pushl 指令失败。
LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_386_JMP_SLOT 重定位项。
对于 x64 动态目标文件,过程链接表位于共享文本中,但使用专用全局偏移表中的地址。运行时链接程序可确定目标的绝对地址,并相应地修改全局偏移表的内存映像。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享目标文件包含不同的过程链接表。
表 7-40 x64: 过程链接表示例
|
以下步骤介绍了运行时链接程序和程序如何通过过程链接表和全局偏移表来协作解析符号引用。
初始创建程序的内存映像时,运行时链接程序会将全局偏移表中的第二项和第三项设置为特殊值。以下步骤说明了这些值。
进程映像中的每个共享目标文件都有各自的过程链接表,并且控制权仅转移给位于同一目标文件内的过程链接表项。
例如,该程序会调用 name1,以将控制权转移给标签 .PLT1。
第一条指令会跳至全局偏移表项中对应于 name1 的地址。最初,全局偏移表包含以下 pushq 指令的地址,而不是 name1 的实际地址。
该程序将在栈中推送一个重定位索引 (index1)。该重定位索引是重定位表中一个 32 位的非负索引。重定位表由 DT_JUMPREL 动态节项标识。指定的重定位项的类型为 R_AMD64_JMP_SLOT,其偏移指定了前面的 jmp 指令中使用的全局偏移表项。该重定位项还包含符号表索引,以供运行时链接程序用于获取引用的符号 name1。
推送该重定位索引后,程序将跳至过程链接表中的第一项 .PLT0。pushq 指令会在栈中推送全局偏移表的第二项 (GOT+8) 的值,从而为运行时链接程序提供一个字的标识信息。然后,程序将跳至第三个全局偏移表项 (GOT+16) 中的地址,以继续跳至运行时链接程序。
运行时链接程序将展开栈、检查指定的重定位项、获取符号的值、在全局偏移项表中存储 name1 的实际地址并跳至目标。
过程链接表项的后续执行结果会直接传输给 name1,而不会再次调用运行时链接程序。位于 .PLT1 的 jmp 指令将跳至 name1,而不是对 pushq 指令失败。
LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_AMD64_JMP_SLOT 重定位项。