如何在iOS上创建静态库而不使所有符号公开?

19

这个问题之前已经被问过了,但是深入挖掘各种开发工具的文档后,似乎这是可能的,只是不太明显。

动机: 为其他iOS开发者制作静态库。库中的某些符号如果导出将会引起问题,因此我希望将它们设置为仅在内部使用。对于动态库,这很容易,只需使用-exported_symbols_list libtool (ld)参数并列出您要公开的内容。然而,libtool文档不允许在静态库中使用此参数。

库中有几个ObjectiveC .m文件,它们相互使用代码。只需要将组中的一个类公开给最终的.a静态库文件的用户即可。

尝试过libtool -exported_symbols_list publicsymbols.exp,但该参数对于静态库的-static不支持。

无法使用属性使符号变为私有(如果这样做真的有效),因为其他组中的.m文件需要这些符号。

看起来ld可以接受多个.o文件并将它们链接到一个新的.o文件中(通过-r参数),而且此参数没有仅限于动态库的声明(这可能只是文档不清楚……)。

只是作为测试,我使用Xcode构建了我的项目,所以我有了所有的.o文件,然后尝试在命令行上调用ld,就像这样:

ld -r -x -all_load -static -arch armv6 -syslibroot {path} 
   -filelist /Users/Dad/ABCsdk/iphone-ABClib/build/ABCLib.build/Distribution-iphoneos/ABCLib-device.build/Objects-normal/armv6/ABCsdk.LinkFileList 
   -exported_symbols_list {exp file path} -o outputfile.o

在那里,{path}类型的东西长长的路径通向相应的位置。

但我遇到了以下错误:

/usr/bin/ld_classic: /Users/Dad/ABCsdk/iphone-ABClib/build/ABCLib.build/Distribution-iphoneos/ABCLib-device.build/Objects-normal/armv6/ABCmain.o不兼容,文件中包含不支持的第3个部分类型(_TEXT,_picsymbolstub4)在加载命令0中(必须指定“-dynamic”才能使用)

所以似乎有些问题...

有人知道一个聪明的方法让这个工作吗?谢谢。


你需要隐藏哪种符号? - Macmade
可能会发生冲突的符号,如果它们已经链接到应用程序中相同的子库中(例如JSONkit)。显然,我可以只包括JSONKit文件,并说如果它们尚未在您的项目中,则也要包含它们,但我希望添加单个.h文件和.a文件以实现更清晰的集成。 - Dad
3个回答

19

很抱歉,这是不可能的。这与静态库的工作方式有关。 静态库实际上只是一堆*.o文件捆绑在一起,而动态库则是可加载的二进制映像,就像可执行文件一样。

假设您有四个文件:

  • common.c定义了common,它是“私有”的
  • fn1.c定义了fn1,它调用common.
  • fn2.c定义了fn2,它调用common.
  • other.c定义了other

在动态库中,链接器将所有内容捆绑在一起形成一个大的代码块。该库导出otherfn1fn2。 您必须全部加载或全部不加载该库,但两个程序都可以加载它而无需在内存中放置多个副本。对于common的入口点仅仅是从符号表中删除 - 您无法从库外部调用它,因为链接器找不到它。

请注意,应用程序和共享库基本上具有相同的格式:应用程序基本上是只导出一个符号main的共享库。 (这不完全正确,但接近。)

在静态库中,链接器从未运行。所有文件都被编译为*.o文件并放入*.a库档案中。内部引用将无法解决。

假设您的应用程序调用了fn1。 链接器看到一个未解析的调用fn1,然后查找库文件。 它在fn1.o中找到了fn1的定义。 然后链接器注意到对common的未解析调用,因此它在common.o中查找它。 该程序不会从fn2.c或other.c获取代码,因为它不使用那些文件中的定义。

静态库非常古老,它们没有动态库的任何功能。你可以将静态库视为装满已编译源代码的zip文件,不像动态库是链接在一起的。没有人费心扩展存档格式以添加符号可见性。当你与静态库链接时,你得到的结果就像是将库的源代码添加到你的程序中。

简短版:动态库有一个包含所有导出符号但不包含私有符号的符号表。同样地,目标文件有一个列出所有外部符号但不包含静态符号的列表。但是,静态库没有符号表,它只是一个归档文件。因此,没有机制使代码对静态库私有(除了定义对象为static,但这对Objective-C类无效)。

如果我们知道你为什么要这样做,也许我们可以给你建议。(是为了安全性?名称冲突?所有这些问题都有解决方案。)


我不想写一个新答案,因为你已经在这里写了一个很好的解释,但是看起来解决方法就是将所有源文件合并到一个编译为单个单位的文件中。(自动地,而非手动操作。)SQLite项目就是这样做的。 - benzado
好的,即使他可以隐藏其他符号(函数、常量),类名仍然会暴露出来,所以没有意义。 - benzado
谢谢@dietrich,这正是我想到的。我曾希望可能有一个聪明的解决方案我错过了,但不幸的是似乎没有 :) 我考虑的另一个选项是重命名所有的Objective C类等,以确保它们不会发生冲突。 - Dad
@爸爸,我曾经在Objective C中见过这种方法。例如,Google Protocol Buffers for iOS允许您指定一个前缀,该前缀应用于所有生成的Objective C类,以避免冲突。 - Bob Whiteman

4
这是可能的!正如Dietrich所说,静态库中所有导出的符号 .o 文件都是公开的,如果一个文件需要引用另一个.o文件中的符号,则需要从该文件导出(因此是公共的)。但是有一个简单的解决方法 - 预链接所有的.o文件到一个单独的文件中。然后您只需要导出公共符号即可。
这显然称为“单个对象预链接”,在XCode中有一个选项可以执行,由treert提到。但是您可以仅使用标准命令行工具来执行它(示例存储库在这里):
检查一下(这是在Mac上)。
首先让我们创建一些测试文件。
$ cat private.c
int internal_private_function() {
    return 5;
}
$ cat public.c
extern int internal_private_function();

int public_function() {
    return internal_private_function();
}

编译它们。
$ clang -c private.c -o private.o
$ clang -c public.c -o public.o

将它们添加到静态库中(它基本上是一个 ZIP 文件,但采用了几十年前的格式)。

$ ar -r libeverything_public.a public.o private.o

检查其中包含哪些符号。

$ objdump -t libeverything_public.a

libeverything_public.a(private.o):  file format Mach-O 64-bit x86-64

SYMBOL TABLE:
0000000000000000 g     F __TEXT,__text _internal_private_function

libeverything_public.a(public.o):   file format Mach-O 64-bit x86-64

SYMBOL TABLE:
0000000000000000 g     F __TEXT,__text _public_function
0000000000000000         *UND* _internal_private_function

可以看到,这两个函数都是可见的,并且两个符号都是g,表示全局。

现在让我们将其预链接成一个单独的文件,然后将其放在一个静态库中。

$ ld -r -o prelinked.o private.o public.o
$ ar -r libeverything_public_prelinked.a prelinked.o
$ objdump -t libeverything_public_prelinked.a

libeverything_public_prelinked.a(prelinked.o):  file format Mach-O 64-bit x86-64

SYMBOL TABLE:
0000000000000020 l     O __TEXT,__eh_frame EH_Frame1
0000000000000038 l     O __TEXT,__eh_frame func.eh
0000000000000060 l     O __TEXT,__eh_frame EH_Frame1
0000000000000078 l     O __TEXT,__eh_frame func.eh
0000000000000000 g     F __TEXT,__text _internal_private_function
0000000000000010 g     F __TEXT,__text _public_function

类似的结果-它们在同一个文件中,但两者仍然存在且是全局的。最后让我们将它们过滤掉(这是针对Mac系统的)。我们需要导出一份符号列表:
$ cat exported_symbols_osx.lds
_public_function

然后使用-exported_symbols_list选项。

$ ld -r -exported_symbols_list exported_symbols_osx.lds -o prelinked_filtered.o private.o public.o
$ ar -r libfiltered_prelinked.a prelinked_filtered.o
ar: creating archive libfiltered_prelinked.a
$ objdump -t libfiltered_prelinked.a

libfiltered_prelinked.a(prelinked_filtered.o):  file format Mach-O 64-bit x86-64

SYMBOL TABLE:
0000000000000000 l     F __TEXT,__text _internal_private_function
0000000000000020 l     O __TEXT,__eh_frame EH_Frame1
0000000000000038 l     O __TEXT,__eh_frame func.eh
0000000000000060 l     O __TEXT,__eh_frame EH_Frame1
0000000000000078 l     O __TEXT,__eh_frame func.eh
0000000000000010 g     F __TEXT,__text _public_function

哇塞!_internal_private_function 现在是一个本地符号了。您可以添加 -x 选项(或者运行 strip -x)来将其名称更改为随机无意义的值(这里使用 l001)。

$ ld -r -x -exported_symbols_list exported_symbols_osx.lds -o prelinked_filtered.o private.o public.o
$ objdump -t prelinked_filtered.o

prelinked_filtered.o:   file format Mach-O 64-bit x86-64

SYMBOL TABLE:
0000000000000000 l     F __TEXT,__text l001
0000000000000020 l     O __TEXT,__eh_frame EH_Frame1
0000000000000038 l     O __TEXT,__eh_frame func.eh
0000000000000060 l     O __TEXT,__eh_frame EH_Frame1
0000000000000078 l     O __TEXT,__eh_frame func.eh
0000000000000010 g     F __TEXT,__text _public_function

以下是苹果的链接器对于-x的说明:
不要将非全局符号放入输出文件的符号表中。非全局符号在调试和获取回溯符号名称时很有用,但在运行时不使用。如果与-r一起使用-x,则非全局符号名称不会被删除,而是替换为一个独特的虚拟名称,该名称在链接到最终的链接映像时将自动删除。这允许死代码剥离(dead code stripping)正常工作,并提供源符号名称被删除的安全性。
Linux上的所有内容都相同,只不过使用-exported_symbols_list代替。在Linux上,我认为您需要使用如下文件的--version-script
V0 {
  global:
    _public_function;
  local:
    *;
};

但我尚未测试过这个。此文件和exported_symbols_list文件都支持通配符。


1
我的小贡献。-exported_symbols_list--version-script支持通配符。但是,第一个只适用于已编码的符号。例如,对于类my::nms::Foo,通配符将是类似于*2my3nms3Foo*的东西。而第二个则更加灵活,对于提到的类应该是:{ global: extern "C++" { my::nms::Foo*; }; local: *; }; - Hsilgos

3
XCode的构建设置可以完成这个功能! 1. 将Perform Single-Object Prelink设置为YES 2. 将Exported Symbols File设置为符号文件路径 也许您应该删除-static-exported_symbols_list不能在静态库上工作,但是可以对目标文件生效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接