跳过导航链接 | |
退出打印视图 | |
Oracle Solaris 11.1 链接程序和库指南 Oracle Solaris 11.1 Information Library (简体中文) |
桩目标文件是可提供与实际目标文件相同的链接接口但不包含代码或数据的共享目标文件,完全由 mapfile 生成。桩目标文件不能在运行时使用。不过,可以根据桩目标文件生成应用程序,桩目标文件可以提供在运行时要使用的实际目标文件名称。
在生成桩目标文件时,链接编辑器将忽略在命令行中指定的任何目标文件或库文件,无需存在这些文件即可生成桩目标文件。由于可以省略编译步骤,并且相对而言链接编辑器只需进行少量操作,因此可以很快生成桩目标文件。
桩目标文件可用于解决各种生成问题。
速度
使用具有并行操作功能的 make 实用程序版本的现代计算机,能够同时编译和链接多个目标文件,这么做可显著提高速度。但是,通常给定目标文件会依赖于其他目标文件,而且还有一个几乎其他所有目标文件都与之相关的核心目标文件集合。因此,必须对生成进行排序,使所有目标文件在被其他目标文件使用之前生成。这种排序不仅会制造瓶颈降低可能的并行操作数量,还会限制代码生成的整体速度。
复杂性/正确性
在大型代码段中,各个目标文件之间可能存在大量的依赖项。这些目标文件的 makefiles 或其他生成说明可能会变得非常复杂,从而难以理解或维护。依赖项可能会随系统的演变而发生变化。这会导致给定的 makefile 集合随着时间的推移慢慢变得不准确,从而导致出现竞用情况以及各种莫名其妙的生成故障。
依赖项循环
理想的情况是将代码作为协作共享目标文件进行组织,其中的每个目标文件会利用其他目标文件提供的资源。在必须在其他目标文件使用之前生成目标文件的环境中,即使运行时链接程序完全能装入并使用此类目标文件(如果它们能生成),也无法支持此类循环。
桩共享目标文件提供了一种替代方法,用于生成可避免上述问题的代码。对于该生成产生的所有共享目标文件,可以快速生成桩目标文件。然后,使用桩目标文件代替链接时的实际目标文件,按照任意顺序并行生成所有实际共享目标文件和可执行文件。之后,保留可执行文件和实际共享目标文件,并废弃桩共享目标文件。
桩目标文件由一个或多个必须共同满足以下要求的 mapfile 生成。
至少一个 mapfile 必须指定 STUB_OBJECT 指令。请参见STUB_OBJECT 指令。
mapfile 中必须显式列出目标文件外部接口所包含的所有函数和数据符号。
mapfile 必须使用符号作用域缩减 ('*'),从外部接口删除未显式列出的所有符号。请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令。
从目标文件导出的所有全局数据必须在 mapfile 中具有 ASSERT 符号属性,以指定符号类型和大小。如果有多个符号引用同一个数据,那么这些符号中必须有一个符号的 ASSERT 指定 TYPE 和 SIZE 属性,其他符号必须使用 ALIAS 属性引用这个主符号。请参见ASSERT 属性。
如果提供了此类 mapfile,则可使用相同的命令行为每个共享目标文件生成桩共享目标文件版本和实际共享目标文件版本。将 -z stub 选项添加到桩目标文件的链接编辑中,并在实际目标文件的链接编辑中省略该选项。
为说明上述方法,以下代码将实现一个名为 idx5 的共享目标文件,该共享目标文件可从包含 5 个整数元素的数组中导出数据。对每个元素进行初始化以包含从零开始的数组索引。此数据采用全局数组、使用弱绑定的替代别名数据符号形式,并通过功能接口提供。
$ cat idx5.c int _idx5[5] = { 0, 1, 2, 3, 4 }; #pragma weak idx5 = _idx5 int idx5_func(int index) { if ((index < 0) || (index > 4)) return (-1); return (_idx5[index]); }
必须使用 mapfile 来描述此共享目标文件提供的接口。
$ cat mapfile $mapfile_version 2 STUB_OBJECT; SYMBOL_SCOPE { _idx5 { ASSERT { TYPE=data; SIZE=4[5] }; }; idx5 { ASSERT { BINDING=weak; ALIAS=_idx5 }; }; idx5_func; local: *; };
以下主程序用于打印 idx5 共享目标文件中的所有可用索引值。
$ cat main.c #include <stdio.h> extern int _idx5[5], idx5[5], idx5_func(int); int main(int argc, char **argv) { int i; for (i = 0; i < 5; i++) (void) printf("[%d] %d %d %d\n", i, _idx5[i], idx5[i], idx5_func(i)); return (0); }
以下命令在名为 stublib 的子目录中创建此共享目标文件的桩目标文件版本。elfdump 命令用于检验生成的目标文件是否为桩目标文件。用于生成桩目标文件的命令不同于生成实际目标文件所用命令,其差别仅在于前者添加了 -z stub 选项,并使用了不同的输出文件名。这说明很容易将桩目标文件生成代码添加到现有代码中。
$ cc -Kpic -G -M mapfile -h libidx5.so.1 idx5.c -o stublib/libidx5.so.1 -zstub $ ln -s libidx5.so.1 stublib/libidx5.so $ elfdump -d stublib/libidx5.so | grep STUB [11] FLAGS_1 0x4000000 [ STUB ]
现在可以使用桩目标文件代替实际共享目标文件,并设置会在运行时查找实际目标文件的运行路径,以此生成主程序。不过,由于实际目标文件尚未生成,此程序还不能运行。让系统装入桩目标文件的尝试将被拒绝,因为运行时链接程序知道桩目标文件缺少实际目标文件中的实际代码和数据,因而无法执行。
$ cc main.c -L stublib -R '$ORIGIN/lib' -lidx5 -lc $ ./a.out ld.so.1: a.out: fatal: libidx5.so.1: open failed: \ No such file or directory Killed $ LD_PRELOAD=stublib/libidx5.so.1 ./a.out ld.so.1: a.out: fatal: stublib/libidx5.so.1: stub shared object \ cannot be used at runtime Killed
使用生成桩目标文件时使用的命令生成实际目标文件。省略 -z stub 选项,并指定实际输出文件的路径。
$ cc -Kpic -G -M mapfile -h libidx5.so.1 idx5.c -o lib/libidx5.so.1
一旦在 lib 子目录下生成了实际目标文件,便可以运行程序。
$ ./a.out [0] 0 0 0 [1] 1 1 1 [2] 2 2 2 [3] 3 3 3 [4] 4 4 4