彻底解决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,刚刚好没有超出范围,那么也就是说,这个问题一直存在,因为代码量逐渐增多超出范围所以突然出现,所以并不是因为版本需求的变动直接引起的。

  1. 查看support.o内部结构
    使用ida工具查看这个静态库,打开support.o,发现support.o里面有对_error的定义,并且在_text段。(没有ida还有很多其他工具,MachOView,命令行都可以,问AI想办法)

​​
d43603d93a8146e1a2195a80023c7d33

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

39b8e8fa354d4f4ea3f0cc2b7c0a8f40

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

e4cc4e6efe554a6dad23b86a6717305b

一般只有没有用到的符号或者重复的符号会被标记为<>,但我们很明确他被使用,不然也不会报错,所以答案很明显了:IFlyImageSource.o内部定义的_error符号把support.o定义的_error符号顶替了,所以链接的时候链接到了一个错误的符号。说白了就是高德的库和讯飞的库之间冲突了。

  1. 查看IFlyImageSource.o的结构
    直接上图,看得出来这个.o声明了一个在_data的符号_error。

379453a2694941e79d097319e6b019c7

解决方案
至此,着手处理符号冲突的问题。查了很多资料,基本都是去除重复的符号,但是这个思路很明显不适合我们这个问题,这两个符号定义完全不同,只是刚好名字一样而已。

可能得方案:

  1. 从源码层次换名字,三方库就联系三方,能拿到源码就自己改。

  2. 符号重命名。

  3. ......

我们使用第二个,要符号重命名,我们需要知道这个符号在哪个.o定义,以及哪些.o用到了这个符号。使用nm命令,可以查看.a或者.o内部有哪些定义的符号以及未定义的符号。

nm APMImageProcessing > a.txt

在a.txt中查看,找到"U _error"和"T _error",U就是Undefine,T就是定义。如下图:

31b8f9d2a1954a60a00fc74b3df0b734

最后可以得知,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出现在了他应该出现的位置。

904ded5e747f419eb86ce24af62715b6

需要注意的是,这个方法存在弊端,我们只查看了APMImageProcessing.framework里面的.o哪些存在未定义的_error符号,很可能高德的其他静态库也使用_error这个符号,需要彻底的检查。

复盘总结
因为链接器的策略,导致重复的符号并没有报错提示,而是由链接器选择其中一个保留,进而导致链接错误。如果想知道自己的项目有哪些符号存在冲突,可以在other link flags加上-all_load,这样重复的符号就会报错提示出来(我们工程有4903个...)

posted @ 2025-12-17 10:34  xl554  阅读(3)  评论(0)    收藏  举报