链接程序和库指南

缩减符号范围

可以使用在 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 lib.so.1 -G foo.c bar.c

$ nm -x lib.so.1 | egrep "foo$|bar$|str$"

[29]    |0x000104d0|0x00000004|OBJT |GLOB |0x0  |12     |str

[32]    |0x00000418|0x00000028|FUNC |GLOB |0x0  |6      |bar

[33]    |0x000003f0|0x00000028|FUNC |GLOB |0x0  |6      |foo

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

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

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

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


$ cat mapfile

{

        local:

                bar;

                str;

};

$ cc -o lib.so.1 -M mapfile -G foo.c bar.c

$ nm -x lib.so.1 | egrep "foo$|bar$|str$"

[27]    |0x000003dc|0x00000028|FUNC |LOCL |0x0  |6      |bar

[28]    |0x00010494|0x00000004|OBJT |LOCL |0x0  |12     |str

[33]    |0x000003b4|0x00000028|FUNC |GLOB |0x0  |6      |foo

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

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


$ cat mapfile

lib.so.1.1

{

        global:

                foo;

        local:

                *;

};

$ cc -o lib.so.1 -M mapfile -G foo.c bar.c

$ nm -x lib.so.1 | egrep "foo$|bar$|str$"

[30]    |0x00000370|0x00000028|FUNC |LOCL |0x0  |6      |bar

[31]    |0x00010428|0x00000004|OBJT |LOCL |0x0  |12     |str

[35]    |0x00000348|0x00000028|FUNC |GLOB |0x0  |6      |foo

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


注 –

如果未提供版本名称,则将使用输出文件名标记版本定义。可以使用链接编辑器的 -z noversion 选项抑制在输出文件中创建的版本控制信息。


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


$ cat mapfile

lib.so.1.1 {

        global:

                foo;

};

$ cc -o lib.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 lib.so.1

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


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

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


$ cat mapfile

lib.so.1.1 {

        global:

                foo;

        local:

                *;

};

$ ld -o lib.o -M mapfile -r foo.o bar.o

$ nm -x lib.o | egrep "foo$|bar$|str$"

[17]    |0x00000000|0x00000004|OBJT |GLOB |0x0  |3      |str

[19]    |0x00000028|0x00000028|FUNC |GLOB |0x0  |1      |bar

[20]    |0x00000000|0x00000028|FUNC |GLOB |0x0  |1      |foo

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

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


$ ld -o lib.so.1 -G lib.o

$ nm -x lib.so.1 | egrep "foo$|bar$|str$"

[22]    |0x000104a4|0x00000004|OBJT |LOCL |0x0  |14     |str

[24]    |0x000003dc|0x00000028|FUNC |LOCL |0x0  |8      |bar

[36]    |0x000003b4|0x00000028|FUNC |GLOB |0x0  |8      |foo

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


$ ld -o lib.o -M mapfile -B reduce -r foo.o bar.o

$ nm -x lib.o | egrep "foo$|bar$|str$"

[15]    |0x00000000|0x00000004|OBJT |LOCL |0x0  |3      |str

[16]    |0x00000028|0x00000028|FUNC |LOCL |0x0  |1      |bar

[20]    |0x00000000|0x00000028|FUNC |GLOB |0x0  |1      |foo

删除符号

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

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


$ cat mapfile

lib.so.1.1

{

        global:

                foo;

        local:

                str;

        eliminate:

                *;

};

$ cc -o lib.so.1 -M mapfile -G foo.c bar.c

$ nm -x lib.so.1 | egrep "foo$|bar$|str$"

[31]    |0x00010428|0x00000004|OBJT |LOCL |0x0  |12     |str

[35]    |0x00000348|0x00000028|FUNC |GLOB |0x0  |6      |foo

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