可以使用在 mapfile 中定义为具有局部作用域的符号定义来缩减符号的最终绑定。对于将来使用生成的文件作为其输入一部分的链接编辑,此机制删除对这些链接编辑的符号可见性。事实上,此机制可以提供准确的文件接口定义,从而限制其他文件可以使用的功能。
例如,要从文件 foo.c 和 bar.c 生成一个简单的共享目标文件。文件 foo.c 包含全局符号 foo,此符号提供了您希望提供给其他文件的服务。文件 bar.c 包含符号 bar 和 str,这两个符号提供了共享目标文件的底层实现。使用这些文件创建的共享目标文件通常导致创建三个具有全局作用域的符号。
$ 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] 0x560 0x18 FUNC GLOB D 0 .text bar [44] 0x520 0x2c FUNC GLOB D 0 .text foo [45] 0x106b8 0x4 OBJT GLOB D 0 .data str
现在,可以在另一个应用程序的链接编辑过程中使用 libfoo.so.1 提供的功能。对符号 foo 的引用被绑定到共享目标文件所提供的实现。
由于符号 bar 和 str 具有全局绑定,因此还可以直接引用这两个符号。此可见性会产生严重后果,因为以后可能会更改作为函数 foo 基础的实现。这样做可能会无意中导致已绑定到 bar 或 str 的现有应用程序失败或行为异常。
符号 bar 和 str 的全局绑定的另一个结果是,可以在这些符号中插入同名的符号。简单解析一节中将介绍在共享目标文件中插入符号。此插入可以是有意的,用于避开共享目标文件提供的预期功能。另一方面,此插入可能是无意的,是将相同通用符号名称同时用于应用程序和共享目标文件的结果。
在开发共享目标文件时,可以通过将符号 bar 和 str 的作用域缩减为局部绑定来防止出现这种情况。在以下示例中,符号 bar 和 str 不再作为共享目标文件的接口的一部分提供。因此,外部目标文件不能引用或插入这些符号。您已经有效地定义了共享目标文件的接口。可以在隐藏底层实现详细信息的同时管理此接口。
$ 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] 0x548 0x18 FUNC LOCL H 0 .text bar [25] 0x106a0 0x4 OBJT LOCL H 0 .data str [45] 0x508 0x2c FUNC GLOB D 0 .text foo
缩减符号作用域还具有其他性能方面的优点。现在,以前运行时必需的针对符号 bar 和 str 的符号重定位已缩减为相对重定位。有关符号重定位开销的详细信息,请参见执行重定位的时间。
随着链接编辑期间处理的符号数的增加,在 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] 0x570 0x18 FUNC LOCL H 0 .text bar [27] 0x106d8 0x4 OBJT LOCL H 0 .data str [50] 0x530 0x2c FUNC GLOB D 0 .text foo
此示例还在 mapfile 指令中定义了一个版本名称 ISV_1.1。此版本名称建立一个内部版本定义,用于定义文件的符号接口。建议创建版本定义。此定义将成为可在文件演变过程中使用的内部更新控制机制的基础。请参见Chapter 9, 接口和版本控制。
每次指定版本名称时,都必须将所有全局符号指定给版本定义。如果有任何全局符号未指定给版本定义,链接编辑器将生成致命错误状态。
$ 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
可以使用 –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$' [29] 0x10 0x2c FUNC GLOB D 2 .text foo [30] 0 0x4 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] 0x508 0x18 FUNC LOCL H 0 .text bar [25] 0x10644 0x4 OBJT LOCL H 0 .data str [42] 0x4c8 0x2c 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] 0x50 0x18 FUNC LOCL H 0 .text bar [21] 0 0x4 OBJT LOCL H 0 .data str [30] 0x10 0x2c 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] 0x10690 0x4 OBJT LOCL H 0 .data str [44] 0x4e8 0x2c FUNC GLOB D 0 .text foo