打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
符号处理

符号处理

在输入文件处理期间,输入可重定位目标文件中的所有局部符号都将传递到输出文件映像。输入可重定位目标文件的所有全局符号以及共享目标文件依赖项的所有全局符号都在链接编辑器内部累积。

可以在此内部符号表中搜索输入文件提供的每个全局符号。如果遇到与上一个输入文件中名称相同的符号,则将调用符号解析过程。此符号解析过程决定保留可重定位目标文件两项中的哪一项。此解析过程还会决定共享目标文件依赖项外部引用的建立方式。

完成输入文件处理后,如果没有发生符号解析致命错误,链接编辑器会决定是否保留任何未解析的符号引用。未解析的符号引用可能导致链接编辑终止。

最后,将链接编辑器的内部符号表添加到要创建的映像的符号表中。

以下各小节详细说明了符号解析和未定义符号处理过程。

符号解析

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

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

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

  • 未定义-文件中已引用但尚未指定存储地址的符号。

  • 暂定-文件中已创建但尚未指定大小或分配存储空间的符号。这些符号在文件中显示为未初始化的 C 语言符号或 FORTRAN COMMON 块。

  • 已定义-文件中已创建并且已分配存储地址和空间的符号。

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

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

$ cat main.cextern 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.oSymbol 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

简单解析

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

要解析的符号可以具有全局绑定或弱绑定。在可重定位目标文件中,弱绑定的优先级低于全局绑定。具有不同绑定的可重定位目标文件符号的解析方式与基本规则稍有出入。

弱符号通常可通过编辑器单独定义或定义为全局符号的别名。一种机制使用 #pragma 定义。

$ cat main.c#pragma weak    bar#pragma weak    foo = _fooint             bar = 1;int _foo(){        return (bar);}$ cc -o main.o -c main.c$ elfdump -s main.oSymbol Table Section:  .symtab     index    value      size      type bind oth ver shndx          name     ....       [7]  0x00000010 0x00000018  FUNC GLOB  D    0 .text          _foo       [8]  0x00000010 0x00000018  FUNC WEAK  D    0 .text          foo       [9]  0x00000000 0x00000004  OBJT WEAK  D    0 .data          bar

请注意,对弱别名 foo 指定了与全局符号 _foo 相同的属性。此关系由链接编辑器维护,并将导致在输出映像中对符号指定相同的值。在符号解析过程中,定义的弱符号会被名称相同的任何全局定义覆盖,且没有任何提示。

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

结合使用共享目标文件内定义的弱符号以及相同共享目标文件上的符号插入,可提供有用的编程技术。例如,标准 C 库提供了多个允许重新定义的服务。但是,ANSI C 定义了一组必须出现在系统中的标准服务。在严格遵循规则的程序中不能替换这些服务。

例如,函数 fread(3C) 是 ANSI C 库函数。系统函数 read(2) 不是 ANSI C 库函数。遵循规则的 ANSIC 程序必须能重新定义 read(2),并且仍以可预测的方式使用 fread(3C)

此处的问题是,在标准 C 库中 read(2)fread(3C) 实现的基础。因此,重新定义 read(2) 的程序可能会混淆 fread(3C) 实现。为了避免出现这种情况,ANSI C声明实现只能使用为该实现保留的名称。使用以下 #pragma 指令可定义这种保留名称。使用此名称可生成函数 read(2) 的别名。

#pragma weak read = _read

因此,可以非常自由地定义自己的 read() 函数,而不会破坏 fread(3C) 实现,而后者是使用 _read() 函数实现的。

链接编辑器在链接共享目标文件或标准 C 库的归档版本时,可以轻松重新定义 read()。在前一种情况下,会正常执行插入。在后一种情况下,由于 C 库中 read(2) 的定义较弱,所以允许默认覆盖该定义。

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

复杂解析

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

$ cat foo.cint array[1];$ cat bar.cint array[2] = { 1, 2 };$ ld -r -o temp.o foo.c bar.cld: warning: symbol `array' has differing sizes:        (file foo.o value=0x4; file bar.o value=0x8);        bar.o definition taken

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

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

$ cat foo.cint bar(){        return (0);}$ cc -o libfoo.so -G -K pic foo.c$ cat main.cint     bar = 1;int main(){        return (bar);}$ cc -o main main.c -L. -lfoold: warning: symbol `bar' has differing types:        (file main.o type=OBJT; file ./libfoo.so type=FUNC);        main.o definition taken

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


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

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

致命解析

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

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

$ cat foo.cint bar = 1;$ cat bar.cint bar(){         return (0);}$ ld -r -o temp.o foo.c bar.cld: fatal: symbol `bar' is multiply-defined:        (file foo.o and file bar.o);ld: fatal: File processing errors. No output written to int.o

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

未定义符号

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

生成可执行输出文件

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

$ cat main.cextern int foo();int main(){        return (foo());}$ cc -o prog main.cUndefined           first referenced symbol                 in filefoo                     main.old: fatal: Symbol referencing errors. No output written to prog

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

$ cat foo.cextern int bar;int foo(){        return (bar);}$ cc -o libfoo.so -G -K pic foo.c$ cc -o prog main.c -L. -lfooUndefined           first referenced symbol                 in filebar                     ./libfoo.sold: fatal: Symbol referencing errors. No output written to prog

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


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


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

$ cat bar.cint 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. -lbarUndefined           first referenced symbol                 in filefoo                     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 指令

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

弱符号

无论要生成哪种类型的输出文件,未解析的弱符号引用不会产生致命错误状态。

如果要生成静态可执行文件,可将此符号转换为绝对符号,并指定零值。

如果要生成动态可执行文件或共享目标文件,可将此符号保留为未定义弱引用,并指定零值。在进程执行期间,运行时链接程序将搜索此符号。如果运行时链接程序未找到匹配项,会将此引用绑定到地址零,而不是生成致命重定位错误。

以前,这些未定义弱引用符号曾用作一种机制,用于测试功能是否存在。例如,在共享目标文件 libfoo.so.1 中可能使用了以下 C 代码段。

#pragma weak    fooextern  void    foo(char *);void bar(char *path){        void (*fptr)(char *);        if ((fptr = foo) != 0)                (*fptr)(path);}

生成引用 libfoo.so.1 的应用程序时,无论是否找到符号 foo 的定义,链接编辑都将成功完成。如果在执行应用程序过程中测试到函数地址为非零,将调用函数。但是,如果未找到符号定义,函数地址测试将为零,因此不调用函数。

编译系统将此地址比较方法视为未定义语义,这将导致在优化时删除测试语句。此外,运行时符号绑定机制会对使用此方法设定其他限制。这些限制可防止所有动态目标文件使用一致的模型。


注 - 建议不要以此方式使用未定义弱引用。相反,应将 dlsym(3C)RTLD_DEFAULTRTLD_PROBE 句柄配合使用,以测试符号是否存在。请参见测试功能


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

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

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

$ cat foo.cchar 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.cchar 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.cextern  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 2SYMBOL_SCOPE {        local:                foo;                bar;        global:                main;};$ cc -o prog -M mapfile lib.a$ progfoo: called from lib.abar: 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 2SYMBOL_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 定义不足以构造此数据存储,因此,这些符号必须保持为绝对值。与 sizeno 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 2SYMBOL_SCOPE {        global:                foo     { TYPE=COMMON; VALUE=0x4;   SIZE=0x200 };                bar     { TYPE=COMMON; VALUE=0x102; SIZE=0x40 };};$ cc -o prog -M mapfile main.cld: 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 声明和 no value 声明来生成此关联。请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令

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

$ cat mapfile$mapfile_version 2SYMBOL_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.cextern  const char *bar();const char *foo(){        return (bar());}$ cat bar.cconst 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 2SYMBOL_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 2SYMBOL_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。此版本名称建立一个内部版本定义,用于定义文件的符号接口。建议创建版本定义。此定义将成为可在文件演变过程中使用的内部更新控制机制的基础。请参见第 5 章


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


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

$ cat mapfile$mapfile_version 2SYMBOL_VERSION ISV_1.1 {        global:                foo;};$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.cUndefined           first referenced symbol                 in filestr                     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 2SYMBOL_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 符号表中维护。可以使用链接编辑器的 -s 选项或 strip(1) 从目标文件中完整删除这个表。有时,可能需要保留 .symtab 符号表,但要删除选择的局部符号定义。

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

$ cat mapfile$mapfile_version 2SYMBOL_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 选项在命令行中声明自动删除指令 "*"。

外部绑定

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

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

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

字符串表压缩

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

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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
GNU make中文手册-第十一章:使用make更新静态库文件
Lenky个人站点 ? 函数导出可视问题
第十一章 : 更新静态库
MapFile文件对象剖析
Scheme语言深入
Makefile的编写指导
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服