链接编辑器可执行许多操作,其中包括打开文件以及串联这些文件中的各节。监视并不时修改这些操作通常会对编译系统的各组件有利。
本节介绍了 ld-support 接口,通过此接口可以检查输入文件,并在某种程度上还可以修改链接编辑过程中所用到的那些文件的输入文件数据。使用此接口的两个应用程序分别为链接编辑器本身(使用此接口处理可重定位目标文件内的调试信息)以及 make(1S) 实用程序(使用此接口保存状态信息)。
ld-support 接口由提供一个或多个支持接口例程的支持库组成。该库是在链接编辑过程中装入的。在链接编辑的不同阶段会调用该库中的某些支持例程。
使用此接口时,应该熟悉 elf(3ELF) 结构和文件格式。
链接编辑器可接受一个或多个通过 SGS_SUPPORT 环境变量或链接编辑器的 -S 选项提供的支持库。此环境变量由冒号分隔的支持库列表组成:
$ SGS_SUPPORT=./support.so.1:libldstab.so.1 cc ... |
-S 选项用于指定单个支持库。可以指定多个 -S 选项:
$ LD_OPTIONS="-S./support.so.1 -Slibldstab.so.1" cc ... |
支持库是共享库。链接编辑器会使用 dlopen(3C) 按照指定库的顺序打开每个支持库。如果遇到环境变量和 -S 选项,则首先处理通过此环境变量指定的支持库。然后,使用 dlsym(3C) 搜索每个支持库以查找所有支持接口例程。这些支持例程随后会在链接编辑的不同阶段调用。
支持库必须与所调用的链接编辑器的 ELF 类保持一致,可以是 32 位或 64 位。 有关更多详细信息,请参见32 位环境和 64 位环境。
缺省情况下,链接编辑器使用 Solaris 支持库 libldstab.so.1 来处理和压缩输入可重定位目标文件内提供的编译器生成的调试信息。如果对使用 -S 选项指定的任何支持库调用链接编辑器,则抑制此缺省处理。如果除支持库服务之外还要求 libldstab.so.1 的缺省处理,请将 libldstab.so.1 显式添加到提供给链接编辑器的支持库列表中。
如32 位环境和 64 位环境中所述,64 位链接编辑器 ld(1) 可以生成 32 位目标文件,32 位链接编辑器可以生成 64 位目标文件。对于其中每个目标文件,都定义关联支持接口。
64 位目标文件的支持接口类似于 32 位目标文件的接口,但是以 64 为后缀结尾。例如 ld_start() 和 ld_start64()。通过此约定,两种方式实现的支持接口可以分别位于 32 位类和 64 位类的单个共享库 libldstab.so.1 中。
可以为 SGS_SUPPORT 环境变量指定 _32 或 _64 后缀,并且可以使用链接编辑器选项 -z ld32 和 -z ld64 定义 -S 选项的要求。这些定义只能分别通过链接编辑器的 32 位或 64 位类来解释。通过此操作,可在可能不知道链接编辑器的类的情况下指定两类支持库。
所有 ld-support 接口均在头文件 link.h 中定义。所有接口参数均为基本的 C 类型或 ELF 类型。可以通过 ELF 访问库 libelf 检查 ELF 数据类型。有关 libelf 内容的说明,请参见 elf(3ELF)。以下接口函数由 ld-support 接口提供,并且按照预期的使用顺序进行了说明。
uint_t ld_version(uint_t version);
链接编辑器使用其可以支持的最高版本的 ld-support 接口来调用此接口。支持库可以检验此版本是否达到使用的最低要求,并返回支持库要求使用的版本。此版本通常为 LD_SUP_VCURRENT。
如果支持库没有提供此接口,则采用初始支持级别 LD_SUP_VERSION1。
如果支持库返回版本零,或者返回的版本值高于链接编辑器所支持的 ld-support 接口的版本,则无法使用支持库。
此函数在初始验证链接编辑器命令行之后调用,表示开始处理输入文件。
void ld_start(const char * name, const Elf32_Half type, const char * caller); void ld_start64(const char * name, const Elf64_Half type, const char * caller);
name 是所创建的输出文件名。type 是输出文件类型,可以为 ET_DYN、ET_REL 或 ET_EXEC,如 sys/elf.h 中所定义。caller 是调用接口的应用程序,通常为 /usr/ccs/bin/ld。
void ld_file(const char * name, const Elf_Kind kind, int flags, Elf * elf); void ld_file64(const char * name, const Elf_Kind kind, int flags, Elf * elf);
name 是要处理的输入文件。kind 表示输入文件类型,可以是 ELF_K_AR 或 ELF_K_ELF,如 libelf.h 中所定义。flags 表示链接编辑器获取文件的方式,可以是以下一个或多个定义:
LD_SUP_DERIVED-文件名不是在命令行中显式指定的。文件是从 -l 扩展派生而来,或者文件标识提取的归档成员。
LD_SUP_EXTRACTED-文件提取自归档。
LD_SUP_INHERITED-文件作为命令行共享库的依赖项获取。
如果未指定 flags 值,则表明已在命令行中显式指定了输入文件。elf 是指向文件的 ELF 描述符的指针。
此函数会针对输入文件的每一节调用,并且在链接编辑器确定是否应将节传播给输出文件之前即会调用此函数。此函数不同于 ld_section() 处理,后者仅针对组成输出文件的各节进行调用。
void ld_input_section(const char * name, Elf32_Shdr ** shdr, Elf32_Word sndx, Elf_Data * data, Elf * elf, unit_t flags); void ld_input_section64(const char * name, Elf64_Shdr ** shdr, Elf64_Word sndx, Elf_Data * data, Elf * elf, uint_t flags);
name 是输入节的名称。shdr 是指向关联节标题的指针。sndx 是输入文件内的节索引。data 是指向关联数据缓冲区的指针。elf 是指向文件的 ELF 描述符的指针。flags 保留供将来使用。
节标题的修改是通过重新分配节标题并为新标题重新指定 *shdr 来完成的。链接编辑器使用从 ld_input_section() 返回时 *shdr 所指向的节标题信息来处理节。
通过重新分配数据并重新指定 Elf_Data 缓冲区的 d_buf 指针,可以修改数据。对数据进行任何修改都应确保正确设置 Elf_Data 缓冲区的 d_size 元素。对于成为输出映像一部分的输入节,将 d_size 元素设置为零可以有效地删除输出映像中的数据。
flags 字段指向初始值为零的 uint_t 数据字段。虽然在将来的更新中可通过链接编辑器或支持库来指定标志,但是当前未指定任何标志。
此函数针对传播给输出文件的输入文件的每一节调用,并且在执行任何节数据处理之前即会调用此函数。
void ld_section(const char * name, Elf32_Shdr * shdr, Elf32_Word sndx, Elf_Data * data, Elf * elf); void ld_section64(const char * name, Elf64_Shdr * shdr, Elf64_Word sndx, Elf_Data * data, Elf * elf);
name 是输入节的名称。shdr 是指向关联节标题的指针。sndx 是输入文件内的节索引。data 是指向关联数据缓冲区的指针。elf 是指向文件 ELF 描述符的指针。
通过重新分配数据并重新指定 Elf_Data 缓冲区的 d_buf 指针,可以修改数据。对数据进行任何修改都应确保正确设置 Elf_Data 缓冲区的 d_size 元素。对于成为输出映像一部分的输入节,将 d_size 元素设置为零可以有效地删除输出映像中的数据。
使用链接编辑器的 -s 选项删除的各节,或者由于 SHT_SUNW_COMDAT 处理或SHF_EXCLUDE 标识而废弃的各节不会向 ld_section() 进行报告。请参见COMDAT 节和表 7–8。
此函数在完成输入文件处理之后但在对输出文件进行布局之前调用。
void ld_input_done(uint_t flags);
flags 字段指向初始值为零的 uint_t 数据字段。虽然在将来的更新中可通过链接编辑器或支持库来指定标志,但是当前未指定任何标志。
void ld_atexit(int status); void ld_atexit64(int status);
status 是将由链接编辑器返回的 exit(2) 代码,可以是 EXIT_FAILURE 或 EXIT_SUCCESS,如 stdlib.h 中所定义。
以下示例创建了一个支持库,其中列显了在 32 位链接编辑过程中处理的任何可重定位目标文件的节名。
$ cat support.c #include <link.h> #include <stdio.h> static int indent = 0; void ld_start(const char * name, const Elf32_Half type, const char * caller) { (void) printf("output image: %s\n", name); } void ld_file(const char * name, const Elf_Kind kind, int flags, Elf * elf) { if (flags & LD_SUP_EXTRACTED) indent = 4; else indent = 2; (void) printf("%*sfile: %s\n", indent, "", name); } void ld_section(const char * name, Elf32_Shdr * shdr, Elf32_Word sndx, Elf_Data * data, Elf * elf) { Elf32_Ehdr * ehdr = elf32_getehdr(elf); if (ehdr->e_type == ET_REL) (void) printf("%*s section [%ld]: %s\n", indent, "", (long)sndx, name); } |
此支持库依赖于 libelf 来提供用于确定输入文件类型的 ELF 访问函数 elf32_getehdr(3ELF)。此支持库通过使用以下命令生成:
$ cc -o support.so.1 -G -K pic support.c -lelf -lc |
以下示例说明了从可重定位目标文件和本地归档库构造普通应用程序所产生的节诊断信息。使用 -S 选项不仅可以处理缺省调试信息,还可以调用支持库。
$ LD_OPTIONS="-S./support.so.1 -Slibldstab.so.1" \ cc -o prog main.c -L. -lfoo output image: prog file: /opt/COMPILER/crti.o section [1]: .shstrtab section [2]: .text ....... file: /opt/COMPILER/crt1.o section [1]: .shstrtab section [2]: .text ....... file: /opt/COMPILER/values-xt.o section [1]: .shstrtab section [2]: .text ....... file: main.o section [1]: .shstrtab section [2]: .text ....... file: ./libfoo.a file: ./libfoo.a(foo.o) section [1]: .shstrtab section [2]: .text ....... file: /lib/libc.so file: /opt/COMPILER/crtn.o section [1]: .shstrtab section [2]: .text ....... |
为了简化输出,已经减少了本示例中显示的节数。另外,编译器驱动程序所包含的文件也会有所不同。