29

我正在寻找限制导出到 Linux 静态库(存档)的 C 符号数量的方法。我想将它们限制为仅属于库官方 API 的那些符号。我已经使用“静态”将大多数函数声明为静态,但这将它们限制在文件范围内。我正在寻找一种方法来限制图书馆的范围。

我可以使用 Ulrich Drepper 的How to Write Shared Libraries中的技术为共享库执行此操作,但我无法将这些技术应用于静态档案。在他早期的图书馆设计良好实践论文中,他写道:

唯一的可能性是使用“ld -r”将需要某些内部资源的所有目标文件组合成一个,然后限制由该组合目标文件导出的符号。GNU 链接器有选项可以做到这一点。

谁能帮我发现这些选项可能是什么?我用'strip -w -K prefix_*'取得了一些成功,但这感觉很野蛮。理想情况下,我想要一个适用于 GCC 3 和 4 的解决方案。

谢谢!

4

5 回答 5

15

我不相信 GNU ld 有任何这样的选择。Ulrich 一定是指objcopy,它有很多这样的选项:--localize-hidden, --localize-symbol=symbolname, --localize-symbols=filename.

特别--localize-hidden是允许人们对暴露的符号进行非常精细的控制。考虑:

int foo() { return 42; }
int __attribute__((visibility("hidden"))) bar() { return 24; }

gcc -c foo.c
nm foo.o
000000000000000b T bar
0000000000000000 T foo

objcopy --localize-hidden foo.o bar.o
nm bar.o
000000000000000b t bar
0000000000000000 T foo

所以bar()不再从对象中导出(即使它仍然存在并且可用于调试)。你也可以bar()objcopy --strip-unneeded.

于 2009-01-06T08:40:52.870 回答
12

对于使用 GCC 3.x 或 4.x 编译的代码,静态库无法执行您想要的操作。

如果您可以使用共享对象(库),GNU 链接器可以使用称为版本脚本的功能来满足您的需求。这通常用于提供特定于版本的入口点,但退化的情况只是区分公共符号和私有符号而没有任何版本控制。使用 ld 的 --version-script= 命令行选项指定版本脚本。

使入口点 foo 和 bar 公开并隐藏所有其他接口的版本脚本的内容:

{ global: foo; bar; local: *; };

请参阅 ld 文档:http: //sourceware.org/binutils/docs/ld/VERSION.html#VERSION

我是共享库的大力倡导者,这种限制全局变量可见性的能力是他们的一大优点。

在http://developers.sun.com/solaris/articles/linker_mapfiles.html上提供了一份文档,它提供了共享对象的更多优点,但为 Solaris 编写(由快乐记忆的 Greg Nakhimovsky 编写)

我希望这有帮助。

于 2008-12-26T19:49:02.413 回答
10

这个答案的优点将取决于您使用静态库的原因。如果它允许链接器稍后删除未使用的对象,那么我几乎没有什么可添加的。如果它是为了组织的目的 - 最大限度地减少必须传递以链接应用程序的对象的数量 - 这个扩展的 Employed Russian's answer 可能是有用的。

在编译时,可以使用以下方法设置编译单元中所有符号的可见性:

-fvisibility=hidden
-fvisibility=default

这意味着可以编译具有默认可见性的单个文件“interface.c”和具有隐藏可见性的大量实现文件,而无需注释源。然后,可重定位链接将生成一个对象文件,其中“隐藏”了非 api 函数:

ld -r interface.o implementation0.o implementation1.o -o relocatable.o

现在可以对组合的目标文件进行 objcopy:

objcopy --localize-hidden relocatable.o mylibrary.o

因此,我们有一个目标文件“库”或“模块”,它只公开预期的 API。


上述策略与链接时间优化的交互效果适中。使用 -flto 编译并通过编译器将 -r 传递给链接器来执行可重定位链接:

gcc -fuse-linker-plugin -flto -nostdlib -Wl,-r {objects} -o relocatable.o

像以前一样使用 objcopy 本地化隐藏符号,然后最后一次调用链接器以剥离本地符号以及它可以在 post-lto 对象中找到的任何其他死代码。遗憾的是,relocatable.o 不太可能保留任何 lto 相关信息:

gcc -nostdlib -Wl,-r,--discard-all relocatable.o mylibrary.o

lto 的当前实现似乎在可重定位链接阶段处于活动状态。开启 lto 后,隐藏的=>local 符号被最终的可重定位链接剥离。如果没有 lto,隐藏的=>local 符号会在最终的可重定位链接中幸存下来。

lto 的未来实现似乎可能会通过可重定位链接阶段保留所需的元数据,但目前可重定位链接的结果似乎是一个普通的旧目标文件。

于 2015-05-12T07:50:00.500 回答
4

这是对 EmployedRussian 和 JonChesterfield 答案的改进,如果您同时生成动态库和静态库,这可能会有所帮助。

从在 DSO 中隐藏符号的标准机制(lib 的动态版本)开始。用 .编译所有文件-fvisibility=hidden。在定义 API 的头文件中,更改要公开的类和函数的声明:

   #define DLL_PUBLIC __attribute__ ((visibility ("default")))
   extern DLL_PUBLIC int my_api_func(int);

有关详细信息,请参见此处。这适用于 C 和 C++。这对于 DSO 来说已经足够了,但是您需要为静态库添加这些构建步骤:

ld -r obj1.o obj2.o ... objn.o -o static1.o
objcopy --localize-hidden static1.o static2.o
ar -rcs mylib.a static2.o

ar步骤是可选的 - 您可以只链接到static2.o.

于 2018-07-14T16:58:13.443 回答
0

我的做法是用 INTERNAL 标记不导出的所有内容,包括保护所有 .h 文件,使用 -DINTERNAL= 编译开发版本,并使用包含所有其他库 .c 文件的单个 .c 文件编译发布版本使用 -DINTERNAL=静态。

于 2008-12-26T17:09:01.113 回答