彻底解决Xcode构建报错显示寻址超出范围(128MB)
报错内容
B(l) ARM64 branch out of range (180650532 max is +/-128MB): from _merge_frame_interval (0x028EBEF0) to _error (0x0D5342B8) in '_merge_frame_interval' from /Users/apple/Desktop/IOSProject/xxx/Pods/APMImageProcessing/APMImageProcessing.framework/APMImageProcessing(support.o) for architecture arm64
先说结论
符号冲突!!!
背景
在若干个旧版本之前,我们遇到了这个问题,stackoverflow有一个解决方案:https://stackoverflow.com/questions/64152275/how-to-fix-build-that-fails-with-arm64-branch-out-of-range-for-very-large-app
报错的意思是链接的时候二者距离过远,超过了寻址范围,根据地址结合linkmap文件发现:这是从_text链接到_data。我们通过这个段迁移的方式:缩进了二者的距离,使其在一个合理的范围内。但是这个并没有从根本解决问题,关于段迁移具体如何操作,没必要了解,有兴趣参考:https://mp.weixin.qq.com/s/TnqAqpmuXsGFfpcSUqZ9GQ
但是这个段迁移只是刚刚好可以掩盖这个问题,并没有真正解决这个问题。因为我们的app根本就没有那么大,理论上是不会超过这个寻址范围的,市面上比我们大的app多的去了,他们都不需要做段迁移。现在,我们要从根源上解决这个问题。
排查思路
先说结论:讯飞和高德都定义了一个_error的符号
首先分析错误:大致意思是,在静态库APMImageProcessing.framework(高德的库)中有一个support.o中的一个'_merge_frame_interval'符号,它链接到一个'_error'符号时距离超出寻址范围。我们在linkmap里(linkmap虽然较大,但是内部结构其实很简单,自己看几遍就摸清门道了,此文不详细说明)找到了这个"0x1090EF928 0x00000010 [17775] _error",根据[17775]这个.o文件索引,我们定位到了这个.o文件[17775] /Users/apple/Desktop/IOSProject/xxx/Pods/iFly/iFly/Classes/iflyMSC.framework/iflyMSC(IFlyImageSource.o)(讯飞的库)
1.排除版本需求导致
这个错误是某个版本突然出现的,那么先排查是否和这个版本变动有关呢。我切换到更早的版本,打了一个Archive包,并设置了输出App-LinkMap-normal-arm64.txt。在linkmap找到这两个符号所在位置。
0x101A43710 0x000010DC [7265] _merge_frame_interval 链接到0x1090EF928 0x00000010 [17775] _error,计算发现二者相距0x1090EF928-0x101A43710 = 0x76AC218 = 124437016,刚刚好没有超出范围,那么也就是说,这个问题一直存在,因为代码量逐渐增多超出范围所以突然出现,所以并不是因为版本需求的变动直接引起的。
- 查看support.o内部结构
使用ida工具查看这个静态库,打开support.o,发现support.o里面有对_error的定义,并且在_text段。(没有ida还有很多其他工具,MachOView,命令行都可以,问AI想办法)

继续看linkmap,发现这个位置的_errror消失了。

并且support.o定义的_error被标记为<

一般只有没有用到的符号或者重复的符号会被标记为<
- 查看IFlyImageSource.o的结构
直接上图,看得出来这个.o声明了一个在_data的符号_error。

解决方案
至此,着手处理符号冲突的问题。查了很多资料,基本都是去除重复的符号,但是这个思路很明显不适合我们这个问题,这两个符号定义完全不同,只是刚好名字一样而已。
可能得方案:
-
从源码层次换名字,三方库就联系三方,能拿到源码就自己改。
-
符号重命名。
-
......
我们使用第二个,要符号重命名,我们需要知道这个符号在哪个.o定义,以及哪些.o用到了这个符号。使用nm命令,可以查看.a或者.o内部有哪些定义的符号以及未定义的符号。
nm APMImageProcessing > a.txt
在a.txt中查看,找到"U _error"和"T _error",U就是Undefine,T就是定义。如下图:

最后可以得知,xform.o,gifsicle.o使用了_error,support.o定义了_error。
接下来使用llvm-objcopy对符号进行重命名,以下给一个例子,从安装llvm-objcopy开始
//brew安装llvm-objcopy
brew install llvm
code ~/.zshrc
export PATH="/usr/local/opt/llvm/bin:$PATH"
source ~/.zshrc
//处理多架构静态库得到.a
lipo -info APMImageProcessing
lipo APMImageProcessing -thin arm64 -output APMImageProcessing_arm64.a
//将.a打出.o输出到临时的文件夹
mkdir temp
cd temp
ar -x ../APMImageProcessing_arm64.a
//将上述的.o进行符号重命名,_error改为_APM_error
llvm-objcopy --redefine-sym _error=_APM_error xform.o //使用
llvm-objcopy --redefine-sym _error=_APM_error gifsicle.o //使用
llvm-objcopy --redefine-sym _error=_APM_error support.o //定义
//将所有.o打回一个.a
ar -rcs libAPMImageProcessing_fixed_arm64.a *.o
最后直接把得到的.a替换静态Framework里的文稿文件,名字换成一样的,去掉.a后缀即可。得到一个重命名处理的静态库,替换掉原来的库,在other link flag去掉段迁移的操作,执行构建,成功出包。linkmap也显示正常,_APM_error出现在了他应该出现的位置。

需要注意的是,这个方法存在弊端,我们只查看了APMImageProcessing.framework里面的.o哪些存在未定义的_error符号,很可能高德的其他静态库也使用_error这个符号,需要彻底的检查。
复盘总结
因为链接器的策略,导致重复的符号并没有报错提示,而是由链接器选择其中一个保留,进而导致链接错误。如果想知道自己的项目有哪些符号存在冲突,可以在other link flags加上-all_load,这样重复的符号就会报错提示出来(我们工程有4903个...)

浙公网安备 33010602011771号