链接编辑器可执行许多操作,其中包括打开文件以及串联这些文件中的各节。监视并不时修改这些操作通常会对编译系统的各组件有利。
本节介绍了 ld-support 接口。通过此接口可以检查输入文件,并在某种程度上还可以修改链接编辑过程中所用到的那些文件的输入文件数据。使用此接口的两个应用程序分别为链接编辑器以及 make(1S) 实用程序。链接编辑器使用此接口处理可重定位目标文件内的调试信息。make 实用程序使用此接口保存状态信息。
ld-support 接口由提供一个或多个支持接口例程的支持库组成。该库是在链接编辑过程中装入的。在链接编辑的不同阶段会调用该库中的某些支持例程。
使用此接口时,应该熟悉 elf(3ELF) 结构和文件格式。
链接编辑器可接受一个或多个通过 SGS_SUPPORT 环境变量或链接编辑器的 -S 选项提供的支持库。此环境变量由冒号分隔的支持库列表组成:
$ SGS_SUPPORT=support.so.1:support.so.2 cc ...
-S 选项用于指定单个支持库。可以指定多个 -S 选项:
$ LD_OPTIONS="-Ssupport.so.1 -Ssupport.so.2" cc ...
支持库是共享目标文件。链接编辑器会使用dlopen(3C) 按照指定库的顺序打开每个支持库。如果遇到环境变量和 -S 选项,则首先处理通过此环境变量指定的支持库。然后,使用 dlsym(3C) 搜索每个支持库以查找所有支持接口例程。这些支持例程随后会在链接编辑的不同阶段调用。
支持库必须与所调用的链接编辑器的 ELF 类保持一致,可以是 32 位或 64 位。有关更多详细信息,请参见32 位环境和 64 位环境。
如32 位环境和 64 位环境中所述,64 位链接编辑器 ld(1) 可以生成 32 位目标文件。此外,32 位链接编辑器可以生成 64 位目标文件。对于其中每个目标文件,都定义关联支持接口。
64 位目标文件的支持接口类似于 32 位目标文件的接口,但是以 64 为后缀结尾。例如 ld_start() 和 ld_start64()。通过此约定,两种方式实现的支持接口可以分别位于 32 位类和 64 位类的单个共享目标文件中。
可以为 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_SUP_VNONE,则链接编辑器将其卸载而无任何提示,且在不使用它的情况下继续执行。如果返回的版本高于链接编辑器所支持的 ld-support 接口的版本,则将发出致命错误,且链接编辑器将终止执行。否则,使用指定的 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/bin/ld 或 /usr/ccs/bin/ld。
此函数会针对每个输入链接编辑的文件调用。版本 LD_SUP_VERSION3 中添加的此函数比 ld_file() 函数具有更大的灵活性。此函数允许支持库替换文件描述符、ELF 描述符以及关联的文件名。此函数提供以下可能的使用情况。
将新的节添加到现有 ELF 文件。在这种情况下,应使用支持更新 ELF 文件的描述符替换原始 ELF 描述符。请参见 elf_begin(3ELF) 的 ELF_C_RDWR 参数。
可以使用替代文件替换整个输入文件。在这种情况下,应使用与新文件关联的描述符替换原始文件描述符和 ELF 描述符。
在这两种情况下,可以使用表示输入文件已修改的替代名称替换路径名和文件名。
void ld_open(const char **pname, const char **fname, int *fd, int flags, Elf **elf, Elf *ref, size_t off, Elf_Kind kind); void ld_open64(const char **pname, const char **fname, int *fd, int flags, Elf **elf, Elf *ref, size_t off, Elf_Kind kind);
pname 是要处理的输入文件的路径名。fname 是要处理的输入文件的文件名。fname 通常是 pname 的基本名称。pname 和 fname 均可由支持库修改。
fd 是输入文件的文件描述符。此描述符可由支持库关闭,且新的文件描述符可返回至链接编辑器。可以返回值为 -1 的文件描述符,以表示应忽略该文件。
注 - 如果链接编辑器不允许 ld_open() 关闭文件描述符,则将传递至 ld_open() 的 fd 的值设为 -1。处理归档成员时最常发生这种情况。如果将值 -1 传递至 ld_open(),则无法关闭此描述符,支持库也不应返回替代描述符。
flags 字段表示链接编辑器获取文件的方式,可以是以下一个或多个定义:
LD_SUP_DERIVED-文件名不是在命令行中显式指定的。文件是从 -l 扩展派生而来,或者文件标识提取的归档成员。
LD_SUP_EXTRACTED-文件提取自归档。
LD_SUP_INHERITED-文件作为命令行共享目标文件的依赖项获取。
如果未指定 flags 值,则表明已在命令行中显式指定了输入文件。
elf 是输入文件的 ELF 描述符。此描述符可由支持库关闭,且新的 ELF 描述符可返回至链接编辑器。可以返回值为 0 的 ELF 描述符,以表示应忽略该文件。如果 elf 描述符与归档库的成员关联,则 ref 描述符是底层归档文件的 ELF 描述符。off 表示归档文件中归档成员的偏移。
kind 表示输入文件类型,可以是 ELF_K_AR 或 ELF_K_ELF,在 libelf.h 中定义。
此函数针对每个输入链接编辑的文件调用。并且在执行任何文件数据处理之前即会调用此函数。
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_open() 的 flags 字段相同的定义。
LD_SUP_DERIVED-文件名不是在命令行中显式指定的。文件是从 -l 扩展派生而来,或者文件标识提取的归档成员。
LD_SUP_EXTRACTED-文件提取自归档。
LD_SUP_INHERITED-文件作为命令行共享目标文件的依赖项获取。
如果未指定 flags 值,则表明已在命令行中显式指定了输入文件。
elf 是输入文件的 ELF 描述符。
此函数针对输入文件的每一节调用,并且在链接编辑器确定是否应将节传播给输出文件之前即会调用版本 LD_SUP_VERSION2 中添加的此函数。此函数不同于 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 元素设置为零可以有效地删除输出映像中的数据。
版本 LD_SUP_VERSION2 中添加的此函数在完成输入文件处理之后但在对输出文件进行布局之前调用。
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 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 .......
注 - 为了简化输出,已经减少了本示例中显示的节数。另外,编译器驱动程序所包含的文件也会有所不同。