JavaScript is required to for searching.
跳过导航链接
退出打印视图
Oracle Solaris 11.1 链接程序和库指南     Oracle Solaris 11.1 Information Library (简体中文)
为本文档评分
search filter icon
search icon

文档信息

前言

第 1 部分使用链接编辑器和运行时链接程序

1.  Oracle Solaris 链接编辑器介绍

2.  链接编辑器

调用链接编辑器

直接调用

使用编译器驱动程序

跨链接编辑

指定链接编辑器选项

输入文件处理

归档处理

共享目标文件处理

与其他库链接

库命名约定

同时链接共享目标文件和归档

命令行中归档的位置

链接编辑器搜索的目录

运行时链接程序搜索的目录

初始化节和终止节

符号处理

符号可见性

符号解析

简单解析

复杂解析

致命解析

未定义符号

生成可执行输出文件

生成共享目标文件输出文件

弱符号

输出文件中的暂定符号顺序

定义其他符号

使用 -u 选项定义其他符号

定义符号引用

定义绝对符号

定义暂定 (tentative) 符号

扩充符号定义

缩减符号作用域

删除符号

外部绑定

字符串表压缩

生成输出文件

标识功能要求

标识平台功能

标识计算机功能

标识硬件功能

标识软件功能

创建符号功能函数系列

创建符号功能数据项系列

将目标文件功能转换为符号功能

功能系列试验

重定位处理

位移重定位

桩目标文件

辅助目标文件

调试器访问及辅助目标文件使用

父目标文件

调试帮助

3.  运行时链接程序

4.  共享目标文件

第 2 部分快速参考

5.  链接编辑器快速参考

第 3 部分高级主题

6.  直接绑定

7.  生成目标文件以优化系统性能

8.  Mapfile

9.  接口和版本控制

10.  使用动态字符串标记建立依赖性

11.  可扩展性机制

第 4 部分ELF 应用程序二进制接口

12.  目标文件格式

13.  程序装入和动态链接

14.  线程局部存储

第 5 部分附录

A.  链接程序和库的更新及新增功能

B.  System V 发行版 4(版本 1)Mapfile

索引

请告诉我们如何提高我们的文档:
过于简略
不易阅读或难以理解
重要信息缺失
错误的内容
需要翻译的版本
其他
Your rating has been updated
感谢您的反馈!

您的反馈将非常有助于我们提供更好的文档。 您是否愿意参与我们的内容改进并提供进一步的意见?

符号处理

可以将符号归类为局部的全局的。请参见符号可见性

在处理输入文件期间,会将局部符号从任何输入可重定位目标文件复制到要生成的输出目标文件中,且不进行检查。

在称为符号解析的过程中,将分析并组合所有输入可重定位目标文件中的全局符号和任何外部依赖项中的全局符号。链接编辑器按照遇到符号的顺序将每个符号放入一个内部符号表中。如果某个同名符号是由早期目标文件提供的,并且已经存在于符号表中,则符号解析过程将确定保留两个符号中的哪个。作为该过程的连带效果,链接编辑器可确定如何建立对外部目标文件依赖项的引用。

成功完成输入文件处理后,链接编辑器将应用任何符号可见性调整,并决定是否保留任何未解析的符号引用。如果发生了任何致命符号解析错误,或保留了任何未解析的符号引用,链接编辑会终止。最后,将链接编辑器的内部符号表添加到要创建的映像的符号表中。

以下各节详细说明了符号可见性、符号解析和未定义符号的处理过程。

符号可见性

可以将符号归类为局部的全局的。除了包含符号定义的目标文件以外,无法从其他目标文件中引用局部符号。缺省情况下,会将局部符号从任何输入可重定位目标文件复制到要生成的输出目标文件中。可以从输出目标文件中删除局部符号。请参见删除符号

除了包含符号定义的目标文件外,还可以从其他目标文件引用全局符号。收集并解析后,全局符号将添加到要在输出目标文件中创建的符号表中。尽管所有的全局符号是一起处理和解析的,但可以调整其最终可见性。全局符号可以定义其他可见性属性。请参见表 12-21。此外,在链接编辑期间可以使用 mapfile 符号指令指定符号可见性。请参见表 8-8。这些可见性属性和指令可能会导致全局符号在写入输出目标文件中时调整其可见性。

创建可重定位目标文件时,会在输出目标文件中记录所有可见性属性和指令。但是,不会应用这些属性隐含的可见性更改。相反,任何可见性处理将推迟到读取这些目标文件作为输入的动态目标文件的后续链接编辑。在特殊情况下,可以使用 -B reduce 选项强制立即解释可见性属性或指令。

创建动态可执行文件或共享目标文件时,符号可见性属性和指令在将符号写入到任何符号表前应用。可见性属性可确保符号保持为全局性的,且不受任何符号缩减技术影响。可见性属性和指令还可导致全局符号降级为局部符号。后一种技术最常用于显式定义目标文件导出接口。请参见缩减符号作用域

符号解析

符号解析的方式很广,有简单直观的,也有错综复杂的。大多数解析由链接编辑器执行,且没有任何提示。但是,某些重定位可能伴随有警告诊断,而某些可能导致致命错误状态。

最常见的简单解析方式需要将一个目标文件中的符号引用与另一个目标文件中的符号定义绑定起来。此种绑定可用于两个可重定位目标文件之间,也可用于一个可重定位目标文件与在共享目标文件依赖项中找到的第一个定义之间。复杂解析方式通常用于两个或多个可重定位目标文件之间。

这两种符号解析方式取决于符号的属性、提供符号的文件类型以及要生成的文件类型。有关符号属性的完整说明,请参见符号表节。但是,对于以下论述,标识了三种基本符号类型。

形式最简单的符号解析需要使用优先级关系。此关系中,已定义符号优先于暂定符号,而暂定符号又优先于未定义符号。

以下 C 代码示例说明如何生成这些符号类型。未定义符号使用 u_ 作为前缀。暂定符号使用 t_ 作为前缀。已定义符号使用 d_ 作为前缀。

$ cat main.c
extern int      u_bar;
extern int      u_foo();

int             t_bar;
int             d_bar = 1;

int d_foo()
{
        return (u_foo(u_bar, t_bar, d_bar));
}
$ cc -o main.o -c main.c
$ elfdump -s main.o

Symbol Table Section:  .symtab
     index    value      size      type bind oth ver shndx          name
     ....
       [7]  0x00000000 0x00000000  FUNC GLOB  D    0 UNDEF          u_foo
       [8]  0x00000010 0x00000040  FUNC GLOB  D    0 .text          d_foo
       [9]  0x00000004 0x00000004  OBJT GLOB  D    0 COMMON         t_bar
      [10]  0x00000000 0x00000004  NOTY GLOB  D    0 UNDEF          u_bar
      [11]  0x00000000 0x00000004  OBJT GLOB  D    0 .data          d_bar

简单解析

简单符号解析是迄今最常见的解析方式。在这种情况下,将检测两个具有类似特征的符号,一个符号优先于另一个符号。此符号解析由链接编辑器执行,且没有任何提示。例如,对于具有相同绑定的符号,一个文件中的符号引用将绑定到另一个文件中的已定义或暂定符号定义。或者,一个文件中的暂定符号定义将绑定到另一个文件中的已定义符号定义。此种解析方式可用于两个可重定位目标文件之间,也可用于一个可重定位目标文件与在共享目标文件依赖项中找到的第一个定义之间。

要解析的符号可以具有全局绑定或弱绑定。处理可重定位目标文件时,弱绑定的优先级低于全局绑定。弱符号定义将在不给出任何提示的情况下被同一名称的全局定义所覆盖。

在可重定位目标文件与共享目标文件之间或者多个共享目标文件之间,还有一种简单符号解析方式,即插入。在这些情况下,如果多重定义了某个符号,链接编辑器会采用可重定位目标文件或多个共享目标文件之间的第一个定义,且不作任何提示。可重定位目标文件的定义或第一个共享目标文件的定义会在其他所有定义上插入。这种插入可用于覆盖其他共享目标文件提供的功能。可重定位目标文件与共享目标文件之间或多个共享目标文件之间的多重定义符号采用同样的处理方式。符号是弱绑定还是全局绑定无关紧要。无论哪种符号绑定,链接编辑器和运行时链接程序都行为一致,将其解析为第一个定义。

使用链接编辑器的 -m 选项可将所有插入的符号引用列表和节装入地址信息写入到标准输出中。

复杂解析

如果发现两个符号的名称相同,但属性不同,则将进行复杂解析。在这些情况下,链接编辑器将选择最适合的符号同时生成一条警告消息。此消息指出符号、发生冲突的属性以及包含符号定义的文件的标识。在以下示例中,包含数据项数组定义的两个文件有不同的大小要求。

$ cat foo.c
int array[1];

$ cat bar.c
int array[2] = { 1, 2 };

$ ld -r -o temp.o foo.c bar.c
ld: warning: symbol `array' has differing sizes:
        (file foo.o value=0x4; file bar.o value=0x8);
        bar.o definition taken

如果符号的对齐要求不同,将会生成一个类似的诊断。在这两种情况下,使用链接编辑器的 -t 选项可以不进行诊断。

另一种形式的属性差异是符号的类型。在以下示例中,符号 bar() 已同时定义为数据项和函数。

$ cat foo.c
int bar()
{
        return (0);
}
$ cc -o libfoo.so -G -K pic foo.c
$ cat main.c
int     bar = 1;

int main()
{
        return (bar);
}
$ cc -o main main.c -L. -lfoo
ld: warning: symbol `bar' has differing types:
        (file main.o type=OBJT; file ./libfoo.so type=FUNC);
        main.o definition taken

注 - 此上下文中的符号类型是可以用 ELF 表示的分类。除非编程语言以最原始的方式使用数据类型,否则这些符号类型与数据类型无关。


在类似以上示例的情况下,在可重定位目标文件与共享目标文件之间进行解析时将采用可重定位目标文件定义。或者,在两个共享目标文件之间进行解析时采用第一个定义。在弱绑定符号或全局绑定符号之间进行这种解析时,还会生成警告。

链接编辑器的 -t 选项不会抑制符号类型之间的不一致性。

致命解析

无法解析的符号冲突会导致致命错误状态,并生成相应的错误消息。此消息会指示符号名称以及提供符号的文件的名称。不会生成任何输出文件。虽然致命状态足以导致链接编辑终止,但会先完成所有输入文件处理。通过这种方式,所有致命解析错误都可识别。

当两个可重定位目标文件都定义相同名称的非弱符号时,就会出现最常见的致命错误状态。

$ cat foo.c
int bar = 1;

$ cat bar.c
int bar()
{ 
        return (0);
}

$ ld -r -o temp.o foo.c bar.c
ld: fatal: symbol `bar' is multiply-defined:
        (file foo.o and file bar.o);
ld: fatal: File processing errors. No output written to int.o

foo.cbar.c 对于符号 bar 具有冲突的定义。因为链接编辑器无法确定哪个符号优先,所以链接编辑通常会终止,并生成一条错误消息。可以使用链接编辑器的 -z muldefs 选项抑制出现此错误状态。此选项允许采用第一个符号定义。

未定义符号

在读取了所有输入文件并完成了所有符号解析后,链接编辑器将搜索内部符号表,以查找尚未绑定到符号定义的任何符号引用。这些符号引用称为未定义符号。未定义符号对链接编辑过程的影响因要生成的输出文件类型以及符号类型而异。

生成可执行输出文件

生成可执行输出文件时,如果有任何未定义符号,链接编辑器的缺省行为是终止并生成相应的错误消息。如果可重定位目标文件中的符号引用从未与符号定义匹配,则符号将保持未定义状态。

$ cat main.c
extern int foo();

int main()
{
        return (foo());
}
$ cc -o prog main.c
Undefined           first referenced
 symbol                 in file
foo                     main.o
ld: fatal: Symbol referencing errors. No output written to prog

同样,如果使用共享目标文件创建动态可执行文件,并且该共享目标文件中包含未解析的符号定义,则会产生未定义符号错误。

$ cat foo.c
extern int bar;
int foo()
{
        return (bar);
}

$ cc -o libfoo.so -G -K pic foo.c
$ cc -o prog main.c -L. -lfoo
Undefined           first referenced
 symbol                 in file
bar                     ./libfoo.so
ld: fatal: Symbol referencing errors. No output written to prog

要像在上个示例中一样,允许未定义的符号,可使用链接编辑器的 -z nodefs 选项抑制缺省错误状态。


注 - 使用 -z nodefs 选项时应谨慎。如果在执行进程期间需要不可用的符号引用,会发生致命的运行时重定位错误。在初始执行和测试应用程序期间可能会检测到此错误。然而,执行路径越复杂,检测此错误状态需要的时间就越长,这将非常耗时且开销很大。


将可重定位目标文件中的符号引用绑定到隐式定义共享目标文件中的符号定义时,符号也可以保持未定义状态。例如,继续使用上一个示例中使用的文件 main.cfoo.c

$ cat bar.c
int bar = 1;

$ cc -o libbar.so -R. -G -K pic bar.c -L. -lfoo
$ ldd libbar.so
        libfoo.so =>     ./libfoo.so

$ cc -o prog main.c -L. -lbar
Undefined           first referenced
 symbol                 in file
foo                     main.o  (symbol belongs to implicit \
                        dependency ./libfoo.so)
ld: fatal: Symbol referencing errors. No output written to prog

prog 是使用对 libbar.so显式引用生成的。libbar.so 依赖于 libfoo.so。因此,将从 prog 建立对 libfoo.so 的隐式引用。

因为 main.clibfoo.so 提供的接口进行特定引用,所以 prog 确实依赖于 libfoo.so。但是,在要生成的输出文件中将仅记录显式共享目标文件的依赖项。因此,如果开发一种不再依赖于 libfoo.so 的新版本 libbar.soprog 将无法运行。

因此,此类型的绑定被认为是致命错误。必须通过在链接编辑 prog 期间直接引用库来使隐式引用变为显式引用。前面示例中显示的致命错误消息中会提示需要的引用。

生成共享目标文件输出文件

链接编辑器在生成共享目标文件输出文件时,允许在链接编辑结束时仍存在未定义符号。此缺省行为允许共享目标文件从将其定义为依赖项的动态可执行文件导入符号。

可以使用链接编辑器的 -z defs 选项在存在任何未定义符号的情况下强制生成致命错误。建议在创建任何共享目标文件时使用此选项。引用应用程序中符号的共享目标文件可以使用 -z defs 选项,并可以使用 extern mapfile 指令定义符号。请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令

自包含的共享目标文件(其中的所有对外部符号的引用都通过指定的依赖项得到满足)可提供最大的灵活性。此共享目标文件可由许多用户使用,并且这些用户无需确定和建立依赖项来满足共享目标文件的要求。

弱符号

以前,使用弱符号来禁用插入,或对可选功能进行测试。但是,经验表明,弱符号在当前编程环境中易变化且不可靠,建议不要使用它们。

在系统共享目标文件中经常使用弱符号别名。目的在于提供替代接口名称,通常符号名称使用 "_" 字符作为前缀。可从其他系统共享目标文件中引用该别名,以避免由于应用程序导出其自己的符号名称实现而导致的插入问题。在实践中,证明了该技术过于复杂且使用不一致。Oracle Solaris 的最新版本可在使用直接绑定的系统目标文件之间建立显式绑定。请参见第 6 章

经常使用弱符号引用,以在运行时测试接口是否存在。该技术对生成环境、运行时环境进行限制,可通过编译器优化禁用该技术。使用具有 RTLD_DEFAULTRTLD_PROBE 句柄的 dlsym(3C),可提供一致、强健的方式来测试符号是否存在。请参见测试功能

输出文件中的暂定符号顺序

构成输入文件的符号通常以这些符号的顺序出现在输出文件中。但暂定符号例外,因为完成这些符号的解析后才会完全定义这些符号。输出文件中暂定符号的顺序可能不遵循其原始顺序。

如果需要控制一组符号的顺序,应将所有暂定定义重新定义为初始化为零的数据项。例如,与源文件 foo.c 中说明的原始顺序相比,以下暂定定义将导致对输出文件中的数据项重新排序。

$ cat foo.c
char One_array[0x10];
char Two_array[0x20];
char Three_array[0x30];

$ cc -o libfoo.so -G -Kpic foo.c
$ elfdump -sN.dynsym libfoo.so | grep array | sort -k 2,2
      [11]  0x00010614 0x00000020  OBJT GLOB  D    0 .bss           Two_array
       [3]  0x00010634 0x00000030  OBJT GLOB  D    0 .bss           Three_array
       [4]  0x00010664 0x00000010  OBJT GLOB  D    0 .bss           One_array

根据符号地址对符号排序将导致符号的输出顺序与其在源文件中定义的顺序不同。相反,通过将这些符号定义为已初始化的数据项,可确保这些符号在输入文件中的相对顺序传递到输出文件。

$ cat foo.c
char A_array[0x10] = { 0 };
char B_array[0x20] = { 0 };
char C_array[0x30] = { 0 };

$ cc -o libfoo.so -G -Kpic foo.c
$ elfdump -sN.dynsym libfoo.so | grep array | sort -k 2,2
       [4]  0x00010614 0x00000010  OBJT GLOB  D    0 .data          One_array
      [11]  0x00010624 0x00000020  OBJT GLOB  D    0 .data          Two_array
       [3]  0x00010644 0x00000030  OBJT GLOB  D    0 .data          Three_array

定义其他符号

除输入文件中提供的符号外,还可以为链接编辑提供其他全局符号引用或全局符号定义。生成符号引用的最简单形式是使用链接编辑器的 -u 选项。使用链接编辑器的 -M 选项和关联的 mapfile 的灵活性更大。使用此 mapfile,可以定义全局符号引用和各种全局符号定义。可以指定符号可见性和类型之类的符号属性。有关可用选项的完整说明,请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令

使用 -u 选项定义其他符号

-u 选项提供了一种在链接编辑命令行中生成全局符号引用的机制。可以使用此选项完全从归档执行链接编辑。选择要从多个归档中提取的目标文件时,此选项还可以提供更多灵活性。有关归档提取的概述,请参见归档处理一节。

例如,可能要从可重定位目标文件 main.o 生成动态可执行文件,此目标文件引用符号 foobar。您需要从 lib1.a 中包含的可重定位目标文件 foo.o 获取符号定义 foo,并从 lib2.a 中包含的可重定位目标文件 bar.o 获取符号定义 bar

但是,归档 lib1.a 中也包含定义符号 bar 的可重定位目标文件。此可重定位目标文件的功能可能与 lib2.a 中提供的可重定位目标文件不同。要指定需要的归档提取,可以使用以下链接编辑。

$ cc -o prog -L. -u foo -l1 main.o -l2

-u 选项生成对符号 foo 的引用。此引用导致从归档 lib1.a 中提取可重定位目标文件 foo.o。对符号 bar 的第一次引用出现在 main.o 中,这是在处理了 lib1.a 之后遇到的。因此,将从归档 lib2.a 获取可重定位目标文件 bar.o


注 - 此简单示例假定 lib1.a 中的可重定位目标文件 foo.o 既没有直接引用也没有间接引用符号 bar。如果 lib1.a 引用 bar,在处理 lib1.a 期间还会从中提取可重定位目标文件 bar.o。有关链接编辑器处理归档多遍的介绍,请参见归档处理


定义符号引用

以下示例说明如何定义三种符号引用。然后,使用这些引用提取归档成员。虽然可以通过对链接编辑指定多个 -u 选项来实现归档提取,但此示例还说明了如何将符号的最终作用域缩减到局部

$ cat foo.c
#include <stdio.h>
void foo()
{
        (void) printf("foo: called from lib.a\n");
}
$ cat bar.c
#include <stdio.h>
void bar()
{
        (void) printf("bar: called from lib.a\n");
}
$ cat main.c
extern  void    foo(), bar();

void main()
{
        foo();
        bar();
}
$ cc -c foo.c bar.c main.c
$ ar -rc lib.a foo.o bar.o main.o
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        local:
                foo;
                bar;
        global:
                main;
};
$ cc -o prog -M mapfile lib.a
$ prog
foo: called from lib.a
bar: called from lib.a
$ elfdump -sN.symtab prog | egrep 'main$|foo$|bar$'
      [29]  0x00010f30 0x00000024  FUNC LOCL  H    0 .text          bar
      [30]  0x00010ef8 0x00000024  FUNC LOCL  H    0 .text          foo
      [55]  0x00010f68 0x00000024  FUNC GLOB  D    0 .text          main

缩减符号作用域一节中更详细地说明了将符号作用域从全局缩减为局部的重要性。

定义绝对符号

以下示例说明如何定义两种绝对符号定义。然后,使用这些定义解析输入文件 main.c 中的引用。

$ cat main.c
#include <stdio.h>
extern  int     foo();
extern  int     bar;

void main()
{
        (void) printf("&foo = 0x%p\n", &foo);
        (void) printf("&bar = 0x%p\n", &bar);
}
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=FUNCTION; VALUE=0x400 };
                bar     { TYPE=DATA;     VALUE=0x800 };
};
$ cc -o prog -M mapfile main.c
$ prog
&foo = 0x400
&bar = 0x800
$ elfdump -sN.symtab prog | egrep 'foo$|bar$'
      [45]  0x00000800 0x00000000  OBJT GLOB  D    0 ABS            bar
      [69]  0x00000400 0x00000000  FUNC GLOB  D    0 ABS            foo

从输入文件获取函数或数据项的符号定义时,这些符号定义通常与数据存储元素关联。mapfile 定义不足以构造此数据存储,因此,这些符号必须保持为绝对值。与 size 关联、但 value 的简单 mapfile 定义会导致创建数据存储。这种情况下,符号定义将带有节索引。但是,带有 valuemapfile 定义会导致创建绝对符号。如果在共享目标文件中定义符号,应当避免绝对定义。请参见扩充符号定义

定义暂定 (tentative) 符号

还可以使用 mapfile 定义 COMMON(即暂定)符号。与其他类型的符号定义不同,暂定符号在文件中不占用存储空间,而定义在运行时必须分配的存储空间。因此,定义此类型的符号有助于要生成的输出文件的存储分配。

暂定符号与其他符号类型的一个不同特性在于,暂定符号的 value 属性会指示其对齐要求。因此,可以使用 mapfile 定义重新对齐从链接编辑的输入文件中获取的暂定定义。

以下示例给出了两个暂定符号的定义。符号 foo 定义新的存储区域,而符号 bar 实际上用于更改文件 main.c 中相同暂定定义的对齐方式。

$ cat main.c
#include <stdio.h>
extern  int     foo;
int             bar[0x10];

void main()
{
        (void) printf("&foo = 0x%p\n", &foo);
        (void) printf("&bar = 0x%p\n", &bar);
}
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=COMMON; VALUE=0x4;   SIZE=0x200 };
                bar     { TYPE=COMMON; VALUE=0x102; SIZE=0x40 };
};
$ cc -o prog -M mapfile main.c
ld: warning: symbol 'bar' has differing alignments:
    (file mapfile value=0x102; file main.o value=0x4);
    largest value applied
$ prog
&foo = 0x21264
&bar = 0x21224
$ elfdump -sN.symtab prog | egrep 'foo$|bar$'
      [45]  0x00021224 0x00000040  OBJT GLOB  D    0 .bss           bar
      [69]  0x00021264 0x00000200  OBJT GLOB  D    0 .bss           foo

注 - 可以使用链接编辑器的 -t 选项抑制此符号解析诊断。


扩充符号定义

应避免在共享目标文件中创建绝对数据符号。从动态可执行文件对共享目标文件中数据项的外部引用通常需要创建复制重定位。请参见复制重定位。要提供此重定位,应当将数据项与数据存储关联。可通过在可重定位目标文件中定义符号来生成此关联。也可以通过在 mapfile 中定义符号并使用 size 声明( value 声明)来生成此关联。请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令

可以过滤数据符号。请参见作为过滤器的共享目标文件。要提供此过滤,可以通过 mapfile 定义扩充目标文件定义。以下示例创建包含函数和数据定义的过滤器。

$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=FUNCTION;       FILTER=filtee.so.1 };
                bar     { TYPE=DATA; SIZE=0x4; FILTER=filtee.so.1 };
        local:
                *;
};
$ cc -o filter.so.1 -G -Kpic -h filter.so.1 -M mapfile -R.
$ elfdump -sN.dynsym filter.so.1 | egrep 'foo|bar'
       [1]  0x000105f8 0x00000004  OBJT GLOB  D    1 .data          bar
       [7]  0x00000000 0x00000000  FUNC GLOB  D    1 ABS            foo
$ elfdump -y filter.so.1 | egrep 'foo|bar'
       [1]  F        [0] filtee.so.1        bar
       [7]  F        [0] filtee.so.1        foo

在运行时,会将从外部目标文件到以上任一符号的引用解析为 filtee 中的定义。

缩减符号作用域

可以使用在 mapfile 中定义为具有局部作用域的符号定义来缩减符号的最终绑定。对于将来使用生成的文件作为其输入一部分的链接编辑,此机制删除对这些链接编辑的符号可见性。事实上,此机制可以提供准确的文件接口定义,从而限制其他文件可以使用的功能。

例如,要从文件 foo.cbar.c 生成一个简单的共享目标文件。文件 foo.c 包含全局符号 foo,此符号可提供您希望提供给其他文件的服务。文件 bar.c 包含符号 barstr,这两个符号可提供共享目标文件的底层实现。使用这些文件创建的共享目标文件通常导致创建三个具有全局作用域的符号。

$ cat foo.c
extern  const char *bar();

const char *foo()
{
        return (bar());
}
$ cat bar.c
const char *str = "returned from bar.c";

const char *bar()
{
        return (str);
}
$ cc -o libfoo.so.1 -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [41]  0x00000560 0x00000018  FUNC GLOB  D    0 .text          bar
      [44]  0x00000520 0x0000002c  FUNC GLOB  D    0 .text          foo
      [45]  0x000106b8 0x00000004  OBJT GLOB  D    0 .data          str

现在,可以在另一个应用程序的链接编辑过程中使用 libfoo.so.1 提供的功能。对符号 foo 的引用会绑定到共享目标文件提供的实现。

由于符号 barstr 具有全局绑定,因此还可以直接引用这些符号。此可见性会产生严重后果,因为以后可能会更改作为函数 foo 基础的实现。这样做可能会无意中导致已绑定到 barstr 的现有应用程序失败或行为异常。

全局绑定符号 barstr 的另一个后果是,可以在这些符号中插入相同名称的符号。简单解析一节中将介绍在共享目标文件中插入符号。此插入可以是有意的,用于避开共享目标文件提供的预期功能。另一方面,此插入可能是无意的,是将相同通用符号名称同时用于应用程序和共享目标文件的结果。

在开发共享目标文件时,可以通过将符号 barstr 的作用域缩减为局部绑定来防止出现这种情况。在以下示例中,不能再在共享目标文件的接口中提供符号 barstr。因此,外部目标文件不能引用或插入这些符号。您已经有效地定义了共享目标文件的接口。可以在隐藏底层实现详细信息的同时管理此接口。

$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        local:
                bar;
                str;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [24]  0x00000548 0x00000018  FUNC LOCL  H    0 .text          bar
      [25]  0x000106a0 0x00000004  OBJT LOCL  H    0 .data          str
      [45]  0x00000508 0x0000002c  FUNC GLOB  D    0 .text          foo

缩减符号作用域还具有其他性能方面的优点。现在,运行时必需的针对符号 barstr 的符号重定位已缩减为相对重定位。有关符号重定位开销的详细信息,请参见执行重定位的时间

随着链接编辑期间处理的符号数的增加,在 mapfile 中定义局部作用域缩减将变得越来越难维护。有一种更灵活的替代机制,可以根据应维护的全局符号来定义共享目标文件的接口。全局符号定义允许链接编辑器将所有其他符号缩减为局部绑定。可使用特殊的自动缩减指令 "*" 实现此机制。例如,可以重新编写上面的 mapfile 定义,将 foo 定义为生成的输出文件中需要的唯一全局符号。

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
        local:
                *;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [26]  0x00000570 0x00000018  FUNC LOCL  H    0 .text          bar
      [27]  0x000106d8 0x00000004  OBJT LOCL  H    0 .data          str
      [50]  0x00000530 0x0000002c  FUNC GLOB  D    0 .text          foo

此示例还在 mapfile 指令中定义了版本名称 libfoo.so.1.1。此版本名称建立一个内部版本定义,用于定义文件的符号接口。建议创建版本定义。此定义将成为可在文件演变过程中使用的内部更新控制机制的基础。请参见第 9 章


注 - 如果未提供版本名称,那么将使用输出文件名作为版本定义的标签。可以使用链接编辑器的 -z noversion 选项抑制在输出文件中创建的版本更新信息。


每次指定版本名称时,必须将所有全局符号指定给版本定义。如果有任何全局符号未指定给版本定义,链接编辑器将生成致命错误状态。

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
Undefined           first referenced
 symbol                 in file
str                     bar.o  (symbol has no version assigned)
bar                     bar.o  (symbol has no version assigned)
ld: fatal: Symbol referencing errors. No output written to libfoo.so.1

可以使用 -B local 选项在命令行中声明自动缩减指令 "*"。可按照以下方式成功编译上一个示例。

$ cc -o libfoo.so.1 -M mapfile -B local -G foo.c bar.c

生成可执行文件或共享目标文件时,缩减任何符号都会导致在输出映像中记录版本定义。生成可重定位目标文件时,将创建版本定义,但不会处理符号缩减。结果是所有符号缩减的符号项仍保持全局。例如,将上一个 mapfile 与自动缩减指令和关联的可重定位目标文件配合使用时,会创建一个中间可重定位目标文件,但不缩减任何符号。

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
        local:
                *;
};
$ ld -o libfoo.o -M mapfile -r foo.o bar.o
$ elfdump -s libfoo.o | egrep 'foo$|bar$|str$'
      [28]  0x00000050 0x00000018  FUNC GLOB  H    0 .text          bar
      [29]  0x00000010 0x0000002c  FUNC GLOB  D    2 .text          foo
      [30]  0x00000000 0x00000004  OBJT GLOB  H    0 .data          str

此映像中创建的版本定义显示需要缩减符号。最终使用可重定位目标文件生成可执行文件或共享目标文件时,将会缩减符号。也就是说,链接编辑器将按照与在 mapfile 中处理版本更新数据相同的方式,来读取并解释可重定位目标文件中包含的符号缩减信息。

因此,现在可以使用上一个示例中产生的中间可重定位目标文件来生成共享目标文件。

$ ld -o libfoo.so.1 -G libfoo.o
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [24]  0x00000508 0x00000018  FUNC LOCL  H    0 .text          bar
      [25]  0x00010644 0x00000004  OBJT LOCL  H    0 .data          str
      [42]  0x000004c8 0x0000002c  FUNC GLOB  D    0 .text          foo

创建可执行文件或共享目标文件时缩减符号通常是最常见的要求。不过,使用链接编辑器的 -B reduce 选项可以强制在创建可重定位目标文件时缩减符号。

$ ld -o libfoo.o -M mapfile -B reduce -r foo.o bar.o
$ elfdump -sN.symtab libfoo.o | egrep 'foo$|bar$|str$'
      [20]  0x00000050 0x00000018  FUNC LOCL  H    0 .text          bar
      [21]  0x00000000 0x00000004  OBJT LOCL  H    0 .data          str
      [30]  0x00000010 0x0000002c  FUNC GLOB  D    2 .text          foo

删除符号

对符号缩减的扩展是指从目标文件的符号表中删除符号项。局部符号仅在目标文件的 .symtab 符号表中维护。使用链接编辑器的 -z strip-class 选项,或者在链接编辑之后使用 strip(1),可以将此整个表从目标文件中删除。有时,可能需要保留 .symtab 符号表,但要删除选择的局部符号定义。

可以使用 mapfile 关键字 ELIMINATE 执行符号删除。与 local 指令一样,可以单独定义符号,也可以将符号名称定义为特殊的自动删除指令 "*"。以下示例说明如何删除上一个符号缩减示例中的符号 bar

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
        local:
                str;
        eliminate:
                *;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [26]  0x00010690 0x00000004  OBJT LOCL  H    0 .data          str
      [44]  0x000004e8 0x0000002c  FUNC GLOB  D    0 .text          foo

可以使用 -B eliminate 选项在命令行中声明自动删除指令 "*"。

外部绑定

共享目标文件中的定义满足要创建的目标文件中的符号引用时,符号将保持未定义状态。可以在运行时查找与此符号关联的重定位信息。提供定义的共享目标文件通常会成为依赖项。

运行时链接程序在运行时使用缺省搜索模型来查找此定义。通常,将搜索每个目标文件(从动态可执行文件开始),并按装入目标文件的顺序处理每个依赖项。

还可以创建目标文件以使用直接绑定。使用此方法时,可以在要创建的目标文件中维护符号引用与提供符号定义的目标文件之间的关系。运行时链接程序使用此信息将引用直接绑定到定义符号的目标文件,从而绕过缺省符号搜索模型。请参见第 6 章

字符串表压缩

链接编辑器可以通过删除重复项和尾部子串来压缩字符串表。此压缩可显著减小任何字符串表的大小。例如,.dynstr 表压缩后可缩小文本段,从而减少运行时分页活动。由于这些优点,缺省情况下将启用字符串表压缩。

由于字符串表压缩,提供大量符号的目标文件可能会增加链接编辑时间。为了避免开发期间产生此开销,应使用链接编辑器的 -z nocompstrtab 选项。可以使用链接编辑器的调试标记 -D strtab,detail 显示链接编辑期间执行的任何字符串表压缩。