如何在不公开所有符号的情况下为 iOS 创建静态库

Posted

技术标签:

【中文标题】如何在不公开所有符号的情况下为 iOS 创建静态库【英文标题】:How to create static library for iOS without making all symbols public 【发布时间】:2011-08-01 04:27:10 【问题描述】:

以前有人问过这个问题,但是深入研究各种开发工具的文档,这似乎可能的,只是不明显。

动机: 制作一个供其他 ios 开发人员使用的静态库。如果导出库中的某些符号会导致问题,因此我希望将它们设为内部符号。使用动态库这很容易,只需使用 -exported_symbols_list libtool (ld) 参数并列出您想要公开的那些。 libtool 文档不允许静态库使用此参数。

库有几个 ObjectiveC .m 文件,它们使用彼此的代码。组中只有一个类需要对最终 .a 静态库文件的用户公开。

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

不能使用属性将符号设为私有(如果这甚至可以工作的话),因为组中的其他 .m 文件需要它们。

看起来ld 可以获取多个 .o 文件并将它们链接到一个新的 .o 文件中(通过 -r 参数),并且它没有 -exported_symbols_list 参数的“仅动态”免责声明(这可能只是不清楚的文档......)。

作为一个测试,我使用 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不兼容,文件包含加载命令 0 中不支持的第 3 节类型(_TEXT,_picsymbolstub4)(必须指定要使用的“-dynamic”)

所以那里似乎有些不对劲......

有谁知道一个巧妙的方法来完成这项工作?谢谢。

【问题讨论】:

你需要隐藏什么样的符号? 如果它们有相同的子库已经链接到他们的应用程序中可能会发生冲突的符号(JSONkit 说)。显然,我可以只包含 JSONKit 文件,并说如果它们还没有在您的项目中,也可以包含它们,但我希望将单个 .h 文件和 .a 文件添加到项目中以实现更清晰的集成. 【参考方案1】:

这真的是不可能的,我很抱歉。它与静态库的工作方式有关。静态库只不过是捆绑在一起的一堆对象*.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 文件,这与链接在一起的动态库不同。没有人费心扩展存档格式以增加符号可见性。当您与静态库链接时,您会得到与将库的源代码添加到程序中一样的结果。

简短版:动态库有一个包含所有导出符号的符号表,但没有私有符号。同样,目标文件有一个列表,其中包含所有extern 符号,但没有static 符号。但是静态库没有符号表,它只是一个存档。因此,没有任何机制可以使静态库的代码私有(除了定义对象static,但这不适用于Objective-C 类)。

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

【讨论】:

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

可能的!正如 Dietrich 所说,静态库中 .o 文件中的所有导出符号都是公共的,如果一个文件需要引用另一个 .o 文件中的符号,则需要从该文件中导出(因此是公共的)。但是有一个简单的解决方法 - 将所有 .o 文件预先链接到一个文件中。然后你只需要导出公共符号。

这显然被称为“单一对象预链接”,并且在 treert 提到的 XCode 中有一个选项可以做到这一点。但是您可以只使用标准命令行工具(例如 repo here):

检查一下(这是在 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

以下是 Apple 的链接器对 -x 的评价:

不要将非全局符号放入输出文件的符号表中。非全局符号在调试和获取回溯中的符号名称时很有用,但在运行时不使用。如果 -x 与 -r 一起使用,则不会删除非全局符号名称,而是替换为唯一的虚拟名称,该名称将在链接到最终链接图像时自动删除。这允许使用符号分解代码和数据的死代码剥离正常工作,并提供删除源符号名称的安全性。

除了-exported_symbols_list 之外,所有这些在 Linux 上都是一样的。在 Linux 上,我认为您必须将 --version-script 与这样的文件一起使用:

V0 
  global:
    _public_function;
  local:
    *;
;

但我还没有测试过这个。此文件和exported_symbols_list 文件都支持通配符。

【讨论】:

我的小贡献。 -exported_symbols_list--version-script 支持通配符,是的。但第一个可能只适用于损坏的符号。即对于my::nms::Foo 类,通配符将类似于*2my3nms3Foo*。而第二个更灵活,对于提到的类,它应该是: global: extern "C++" my::nms::Foo*; ;当地的: *; ;【参考方案3】:

XCode BuildSetting 可以做到这一点! 1.将Perform Single-Object Prelink设置为YES 2.将Exported Symbols File设置为path_for_symbols_file

也许你应该删除-static-exported_symbols_list 不能工作静态库,但可以在目标文件上生效。

【讨论】:

以上是关于如何在不公开所有符号的情况下为 iOS 创建静态库的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用任何形式的查找表的情况下在 Swift for iOS 8 中列出(几乎)所有表情符号?

如何在不破坏范式的情况下为多对多关系构建连接表

在不配对的情况下为 iOS 附加广告负载

iOS:Xcode7创建静态库

如何在不使用 @Composable 注释的情况下为撰写函数创建扩展?

有没有一种方法可以在不使用大量分支语句的情况下为无符号整数编写 qsort 比较函数?