简介
很久之前写过一篇[Xcode]记录一个链接过程的陷阱,最近再解决oncall问题时,排查下来发现也是一个链接顺序问题引起的疑难杂症。希望能给读者一些启发。
Protocol数量不对
某天我被拉入一个群中,oncall的同学告诉我CI打出的IPA反汇编后某个Protocol只有21个方法。正常情况应该是28个方法。我有些疑惑🤔为什么要去反汇编IPA去看一个Protocol有几个方法呢?
正常情况运行时使用- (BOOL)respondsToSelector:(SEL)aSelector;
后检测实例是否存在对应方法,存在的话直接发送消息即可。有几个Protocol方法存在都无关紧要。
我建议结合linkmap去调查为什么respondsToSelector
是找不到对应的方法。经过一系列排查发现是BDXContainer
有着这样一段代码会根据Protocol中的方法数量来运行一些逻辑。其中protocol_copyMethodDescriptionList
会根据传入的第一个Protocol类型参数,获取其中方法列表。在异常时这里的count
始终返回21。间接导致后续respondsToSelector
判断失败。
排查过程
1.通过排查linkmap排查毫无进展。根据上面的信息。首先怀疑是CI上的组件版本号比较老或者存在缓存等因素导致使用了旧的版本。
验证后组件版本正确,头文件内容正确。
2.上述排查方式失效后陷入了困境,CI上的头条项目使用了jojo+bazel来进行构建,不好获取到对应的编译参数。不然可以单独获取到目标文件(.o)对齐反编译一探究竟。直到另外一个项目西瓜本地也遇到了这个问题。同一个问题在CI Bazel与本地Xcode同时出现。
先是排查头文件处理错误的问题。使用Xcode预处理将头文件展开。发现预处理一切正常。说明没有使用错误的头文件。
然后我们怀疑起了Dolphin(C/C++/OC/Swift分布式缓存)的问题。 我们使用Dolphin构建出一个目标文件(.o)。关闭Dolphin再构建出一个目标文件(.o)。
通过对两个目标文件反汇编均能找到对应Protocol的28个函数。Dolphin问题基本排除。
以下是使用hooper反编译的结果。可以看到Protocol方法数量存在于一个结构体之中。
3.确认编译问题后只能剩下链接问题。由于西瓜使用了zld,于是关闭zld和strip等再验证一次。
关闭zld后调试验证对应Protocol方法数量仍为21个。调查又陷入了窘境。此时剩下的排查方法可能只剩下调试ld64了。
4.机缘巧合下,让Oncall同学搜索用到BDXContainerLifecycleProtocol地方。发现组件库都用到了这个Protocol。结合反编译的结果。可知每个用到该Protocol目标文件中(.o)中都存在一个symbol(_OBJC$_PROTOCOL_INSTANCE_METHODS_OPT_BDXContainerLifecycleProtocol)。再结合之前的经验[[Xcode]记录一个链接过程的陷阱]。突然想到这可能是个链接顺序引起的问题。
按照链接顺序的思路排查。很快就找到了BDUGLuckyDog
中的BDUGLynxBaseVC
。BDUGLuckyDog
这个库在二进制组件化时锁定了BDXServiceCenter
的版本,打包出来的静态库中BDUGLynxBaseVC.o
总是只有21个方法。起初
BDXServiceCenter
也是21方法时运行起来可能没什么问题。随着BDXServiceCenter
Protocl中方法数量增加并且BDU前缀的库在链接时排名比较靠前。所以链接器面对多个BDXContainerLifecycleProtocol
总是选择了BDUGLynxBaseVC.o
中的BDXContainerLifecycleProtocol
(先到先得)。也就造成了此次的问题。
解决方案
既然找到了BDUGLuckyDog
那么升级其以来的BDXServiceCenter
即可。
但是二进制组件中仍存在需要同名Protocl且方法数量各不相同。说不定某一天还会遇到类似问题。我思考了许久。认为有以下几种办法可以从根本避免这类问题。
- 二进制架构中禁止使用protocol_copyMethodDescriptionList
- 严格遵守语义化版本号规范,对于Protocol变更属于API Breaking Change增加大版本号,所有依赖的组件都要重新出包。
- Pipeline增加检测手段治理重复代码问题。
- 切换成全源码。
总结
公司内部静态库关于静态库和动态库的使用仍然缺少一些方法论。缺少检测手段。久而久之便会影响研发效率。关于静态库、动态库开发规范于治理应当提上日程。
关于App-Infra DevOps 团队
App-Infra DevOps团队作为公司的移动研发中台,致力于优化公司各业务的研发和交付过程中的质量、成本、安全、效率和体验。到目前为止我们已经有超过上百个业务接入,1万+人日常使用;平均每天支撑 3000+研发任务顺利交付;建设了业界领先且可能是国内最大的构建集群,每天运行20万+次构建任务。
字节跳动当前作为国内甚至全球最大的"应用工厂",我们期待更多有热情和创造力的同学加入,为这座工厂打造业界最领先,最高效的生产工具。
我们的工作内容主要包括:
- 提供任务管理,工程管理,开发调试,交付流程支持的一站式同开发平台。同时提供Native版及Web版,目标成为下一代端云协同的研发基础设施。
- 客户端 IDE 云化的探索性项目,目的是提供一整套线上编码、调试环境,让用户打开浏览器就可以进行代码开发,满足不同场景的需要,提升研发效率。
加入我们
北京-高级iOS开发工程师 — 终端技术 job.toutiao.com/s/2YHxkXW
职位描述
1、负责头条公司产品的通用技术研发和性能优化,完成高质量编码和测试工作 2、框架维护,通用工具开发等 3、设计良好的代码结构,不断迭代重构; 4、前沿技术研究,承担重点、难点的技术攻坚
职位要求
1、本科及以上学历,计算机、通信等相关专业 2、具有扎实的编程功底,良好的设计能力和编程习惯 3、熟练掌握Objective-C,C++,熟悉Swift的优先 4、熟悉bash/python/ruby/js的优先
上海-高级iOS开发工程师 — 基础技术 job.toutiao.com/s/2YHm8a7
职位描述
- 负责公司客户端产品的通用基础技术研发,完成高质量编码和测试工作
- 通用研发工具开发、通用框架开发等
- 设计良好的代码结构,不断迭代重构
- 前沿技术研究,承担重点、难点的技术攻坚
职位要求
- 本科及以上学历,计算机、通信等相关专业
- 具有扎实的编程功底,良好的设计能力和编程习惯
- 熟练掌握Objective-C,C++,熟悉Swift的优先
- 熟悉前后端相关和跨平台技术、熟悉bash/python/ruby/js等语言和相关框架的优先
杭州-高级iOS开发工程师 — 基础技术 job.toutiao.com/s/2YHdPAE
职位描述
- 负责公司跨平台相关技术研发,完成高质量编码和测试工作
- 负责通用跨端框架及周边工具开发等
- 设计良好的代码结构,不断迭代重构
- 前沿技术研究,承担重点、难点的技术攻坚
职位要求
- 本科及以上学历,计算机、通信等相关专业
- 具有扎实的编程功底,良好的设计能力和编程习惯
- 熟练掌握Objective-C,C++,熟悉Swift的优先
- 熟悉前后端相关和跨平台技术,熟悉bash/python/ruby/js等语言和相关框架的优先