两个 Framework 中如果定义了相同名字的 C 函数会发生什么?
本文基于大量猜测,如有错误,请指出 👻
0x01 起因
前一段时间给公司的项目升级 CocoaPods 1.5,打算把所有的 Pods 由 Dynamic 变为 Static 集成。在集成完毕调试的时候,到处乱点触发了一次 Crash,堆栈大概长这样,
大概的过程是,
ZipArchive
在内部调用 fill_fopen64_filefunc
这个 C 方法的时候,跳到了 Instabug
的同名函数实现中,然后就发生了 crash刚看到这个堆栈的时候,我也是一脸懵逼,在一个 Framework 内部调用方法,怎么就调到另一个 Framework 中的同名方法了呢?难不成是静态库导致的?
0x02 尝试
抱着试一试的心态,我创建了一个 Demo 工程,以 Development Pods 的形式依赖 A、B 两个 Pods(
use_framework!
),调用以下两个方法,
testA();
testB();
在都以 Dynamic Library(默认情况下)集成 A,B 的情况下,上面调用输出的结果是,
AAAAAA
BBBBBBB
嗯,意料之中
使用 CocoaPods 的新 feature,以 Static Library 的形式依赖 A 与 B,再看看上述调用的结果,
BBBBBBB
BBBBBBB
因缺思厅,看来复现了我们一开始遇到的问题
0x03 原因
动态库和静态库的一个明显的区别是,会不会被集成到最后的可执行文件中去。
静态库
在 link 静态库时候,linker 会把它需要的东西复制到可执行文件中,用
nm
查看以依赖 Static Library 集成的 binary,可以发现,在 Static Library 中的函数符号出现在了最后的 binary 中(T 代表全局代码段符号),但由于这两个函数的名字都是
test
,最终只有一个函数实现被合并进去了。这个
test
的实现为什么会指向 B 呢?通过修改 OTHER_LDFLAGS
多次实验后发现,这里的实现指向跟 link 的顺序有关,我猜测,如果是后 link B,那么 linker 在复制 B 中符号的时候会把已经复制过的 A 的符号覆盖,导致 A 的实现就不见了。如果后 link A,那同理,B 的实现会被覆盖。动态库
在以动态库集成的情况下,我们用
nm
查看一下 xxx.app/xxx
,xxx.app/Frameworks/A.framework/A
以及 xxx.app/Frameworks/B.framework/B
,可以发现,在最终 binary 中,并未包含 A 或 B 中的任何符号(U 代表未定义符号),真正的符号存在于它们各自的 framework 中,即使 C 函数名重复也不会影响真正的调用。
实际上公司的项目的场景比我上面介绍的还要复杂一些,Instabug 和 ZipArchive 都是用 Pods 集成的,还使用了 cocoapods-amimono 插件,在没有升级到 CocoaPods 1.5 的时候没有出现问题。
但当把 cocoapods-amimono 拿掉,使用 CocoaPods 1.5 的 Static Framework 功能,就会发现
fill_fopen64_filefunc
在 main binary 中成了未定义的符号,也就是 ZipArchive 中的函数并未全被拷贝进最终的二进制文件中,而是指向了 Instabug.framework 中。Instabug 的代码是提前编译好的,linker 没有办法把这部分代码合入 binary。在 ZipArchive 是静态库的情况下,linker 发现了 Instabug 中存在了这个函数,就没有把 ZipArchive 的拷贝进去(都是猜的 =。=
0x04 结论
如果你的库中包含某些 C 的函数或者全局变量,即使没有暴露到外面,也要记得加前缀。对于没有 namespace 的语言,加前缀总是没错的,如果还是有错,那就多加几位 🤣