解决Xcode11无法运行iOS9模拟器问题

注: 本文已过时。macosx > 10.14.99 则此方法不行。

马上2020年了,所以你可能基本不需要用到iOS9模拟器了,本文纯灌水,不感兴趣的同学可以不看。

问题描述

当Xcode11工程中引入了iOS11才有的CoreML等库(虽然库是weak依赖的),运行iOS9模拟器,启动时会崩溃,提示如下:

dyld: Library not loaded: /System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate
  Referenced from: /System/Library/Frameworks/CoreML.framework/CoreML
  Reason: no suitable image found.  Did find:
    /System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate: mach-o, but not built for iOS simulator

看提示,加载CoreML.framework时,它加载了MacOSX系统路径下的CoreML.framework,导致后续加载它依赖的库时,发生错误告警。然而,iOS10的模拟器,同样没有CoreML库,为什么不会有这问题?

貌似Mac OSX系统低于10.14.1 加载相同的iOS9模拟器镜像也不会有问题。
另外,附录A提供了手动安装低版本Xcode模拟器的方法供参考。

分析

首先,我们知道应用启动时,针对模拟器,/usr/lib/dyld会加载模拟器镜像下的dyld_sim,然后转交给dyld_sim来加载模拟器版本的库。

(lldb) image list
[  0] 97B86B7D-AF80-3222-B291-A0973B774C3B 0x000000010c0ec000 /Users/vincent/Library/Developer/CoreSimulator/Devices/F56758C0-3F87-4ED6-A373-CA542AD17C13/data/Containers/Bundle/Application/06ED7A2E-E9AA-4974-BC21-DF22D204180E/MyiOS9.app/MyiOS9
[  1] CE635DB2-D47E-3C05-A0A3-6BD982E7E750 0x000000010e1b0000 /usr/lib/dyld
[  2] 49268249-F1CD-35FC-BFFD-B4B8F3751B0D 0x000000010c0fe000 /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 9.3.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim

因为dyld是开源的,可以拿到源码来更好地分析。先通过dyld_sim中的LC_SOURCE_VERSION的load command来查看对应dyld源码版本:

➜  ~ otool -l /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS\ 9.3.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim | grep -A 3 "LC_SOURCE_VERSION"
      cmd LC_SOURCE_VERSION
  cmdsize 16
  version 390.7

这里查看到对应源码版本390.7,同样的,拿到iOS 10.1的dyld_sim对应的源码版本421.0

dyld下载源码,可以下载到421.0的源码,但并没有390.7的源码,可以下个比较接近的360.22用来分析。

从错误开始,查找mach-o, but not built for iOS simulator错误提示。发现它是由isSimulatorBinary 函数判定的,通过该函数判定库文件是Mac OSX还是模拟器版本的库,如果不是模拟器版,就会抛上述异常出去。

下面来分析360.22的源码和421.0的源码在判断模拟器时的差别:

WeChatWorkScreenshot_f6bde04d-a64f-4ea8-99a8-89d8a6c4bda5.png

可以看到,两个版本,判断库文件是否为模拟器,有两个主要的区别:

  • 低版本只读取了一页(4096,4k大小)的macho头部,而高版本按mh->sizeofcmds来确定cmdsReadEnd边界(该版本最大可以取32K)。
  • 超过边界cmdsReadEnd,低版本返回true,而高版本返回false。

看下目前问题的表现,能否由这两段差异的代码来解释。目前的问题表现如下:

  1. iOS9 dyld_sim,加载系统的/System/Library/Frameworks/CoreML.frameworks时,没有报异常,把它当模拟器版本加载了。

  2. iOS9 dyld_sim,加载CoreML依赖的/System/Library/Frameworks/Accelerate.framework时,却提示它不是模拟器版,并崩溃。

  3. iOS10 dyld_sim,不会加载CoreML,因为正确识别它为模拟器版本了。

  4. 老的MacOS(更低版本的CoreML.framework),iOS9 dyld_sim识别CoreML正常,模拟器可以正常运行。

用otool查看CoreML.frameworkAccelerate.framework的sizeofcmds大小,如下:

➜ Frameworks otool -l CoreML.framework/CoreML| head -4
CoreML.framework/CoreML:
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777223          3  0x00           6    23       4080 0x02918085
➜  Frameworks otool -l Accelerate.framework/Accelerate| head -4
Accelerate.framework/Accelerate:
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777223          3  0x00           6    17        968 0x02000085

CoreML加上header的20字节,4080+20 > 4096,而Accelerate很小,没有超过4096。

这里还要补充一个背景,新版本的CoreML.framework等系统库,除了load commands超过4K,还废弃掉了LC_VERSION_MIN_MACOSX等load command,改用LC_BUILD_VERSION来描述系统版本。

Frameworks otool -l CoreML.framework/CoreML| grep -A 4 LC_BUILD_VERSION
       cmd LC_BUILD_VERSION
   cmdsize 32
  platform 1
       sdk 10.14
     minos 10.14

所以,来总结下iOS9模拟器启动崩溃问题的原因。新MacosX系统升级后(好像是10.14.1),/System/Library/Frameworks/CoreML.frameworks等库废弃了LC_VERSION_MIN_MACOSX等load command,导致之前的dyld_sim,无法判断动态库是否模拟器版本。当无法判断时,iOS10以下的dyld_sim对load command超过4K的库,认为是模拟器版本进行了加载,导致了后面的崩溃问题。iOS10以上的dyld_sim没法区别时,默认认为不是模拟器版本,所以加载CoreML.frameworks时抛异常,但这个库是weak依赖的,所以表现正常。

看起来理论可以解释,但iOS9的模拟器没有源码,它的逻辑和390.7源码的一致吗?我们通过HopperDisassembler简单分析下这个isSimulatorBinary函数。

WeChatWorkScreenshot_25d0f1a2-c240-4328-aa14-0675f32ecac9.png

可以看到,除了多判断了0x2f<=rsi<0x31(这里hopper错翻译成rsi<0x31)其他逻辑和360.22基本一致。

这里也可以通过调试,来确认逻辑。附录B给出具体的调试方法,供参考。

解决

说了这么多,你可能要问了,一开始为啥模拟器要去加载Mac OSX里的动态库呢?原因是,dyld_sim默认支持加载操作系统里任意路径的动态库,不过它会先加模拟器镜像路径前缀,没找到才会尝试原始路径:

source3.png

所以看起来,像/System/Library/Frameworks/CoreML.frameworks这个库,只要在模拟器镜像路径(DYLD_ROOT_PATH)下没找到,它就会找到Mac OSX下面去。

这里DYLD_ROOT_PATH=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS\ 9.3.simruntime/Contents/Resources/RuntimeRoot

道理都懂,但怎么修复呢?我们无法修改dyld_sim、/System/Library/Frameworks/CoreML.frameworks,因为这些都是苹果的库,做了codesign,改完无法加载。通过lldb动态修改也不靠谱,因为无法确认debugger接入时机,可能attach上时,CoreML都加载进去了。

好在这里有个trick,可以成功骗过了dyld_sim。只要拷贝一个模拟器镜像目录下其他正常的库,如CoreFoundation.frameworks,并把它改名为CoreML.frameworks(记得也要改名里面的macho文件),那么它加载 DYLD_ROOT_PATH/System/Library/Frameworks/CoreML.frameworks时就可以正常加载而不会报错(实际加载的是CoreFoundation),就不会找到Mac OSX系统里去导致后面的问题。完美~~~

同样发现, Vision.framework和Intents.framework也有相同的问题。可以同样操作一把修复相关问题。

附录A. Xcode安装低版本模拟器

你的Xcode11不一定安装了iOS9的模拟器。如果没有安装过,需要手动安装(模拟器下载列表可能找不到iOS9了)。从iPhoneSimulatorSDK9_3 下载,并从dmg中提取出pkg安装包。

我们知道,Xcode的模拟器镜像都是存放在/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS x.x.simruntime下,但从苹果下载的pkg安装时默认是安装到/下。我们可以安装后把/Contents下的文件全部拷到上述镜像目录下,也可以像如下方法,修改pkg安装路径。

# 1. 解压pkg到temp目录
pkgutil --expand iPhoneSimulatorSDK9_3.pkg temp

# 2. 修改解包目录里的PackageInfo文件,设置install-location,类似如下:

<pkg-info auth="root" identifier="com.apple.pkg.iPhoneSimulatorSDK9_3" install-location="/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 9.3.simruntime/" postinstall-action="none" format-version="2" version="9.3.1.1460411551">

# 3. 重新生成pkg包
pkgutil --flatten temp MyiPhoneSimulatorSDK9_3.new.pkg

# 4. 双击新的pkg安装,并看镜像目录下是否有iOS 9.3.simruntime的目录

安装完后,重启Xcode,在模拟器列表中就可以看到9.3版本的模拟器了。

附录B. 调试dyld_sim

这里介绍如何调试本文描述的问题。dyld启动时,很快就加载完了,如果库加载有问题,很快就崩溃了,都跑不到代码里(初始化的代码也没机会运行)。但通过shell里的lldb用waitfor方式等待调试,在attach时,它会自动发送SIGSTOP,此时就可以看到dyld_sim的代码。

➜  ~ lldb -n MyiOS9 -w
(lldb) process attach --name "MyiOS9" --waitfor

上述命令等待MyiOS9的模拟器App。点击模拟器中的app,它就可以断点进去(当然断的时机不确定)。

lldb1.png
lldb2.png

同时加载的库也不多。

(lldb) image list
[  0] 97B86B7D-AF80-3222-B291-A0973B774C3B 0x000000010eb2a000 /Users/vincent/Library/Developer/CoreSimulator/Devices/F56758C0-3F87-4ED6-A373-CA542AD17C13/data/Containers/Bundle/Application/06ED7A2E-E9AA-4974-BC21-DF22D204180E/MyiOS9.app/MyiOS9
[  1] CE635DB2-D47E-3C05-A0A3-6BD982E7E750 0x00000001152d1000 /usr/lib/dyld
[  2] 49268249-F1CD-35FC-BFFD-B4B8F3751B0D 0x000000010eb3c000 /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 9.3.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim
[  3] 29506256-362E-38EB-9D36-D03C6D57E363 0x000000010eba8000 /Users/vincent/Library/Developer/CoreSimulator/Devices/F56758C0-3F87-4ED6-A373-CA542AD17C13/data/Containers/Bundle/Application/06ED7A2E-E9AA-4974-BC21-DF22D204180E/MyiOS9.app/Frameworks/lolz.dylib

查看isSimulatorBinary位置:

(lldb) image lookup -rn "isSimulatorBinary" dyld_sim
1 match found in /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 9.3.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim:
        Address: dyld_sim[0x00007fff5fc05633] (dyld_sim.__TEXT.__text + 17971)
        Summary: dyld_sim`dyld::isSimulatorBinary(unsigned char const*, char const*)

接下来就可以设置断点了:

br set -a '0x000000010eba8000+17971+0x1000' -c '(int)strstr($rsi, "CoreML") != 0'

这里0x000000010eba8000是dyld_sim的随机加载地址(通过image list查看),17971是函数相对dyld_sim.__TEXT.__text的偏移,0x1000是__TEXT.__text段在dyld_sim镜像里的文件偏移。

你也可以调试loadPhase0__cxa_throw__cxa_begin_catch等函数,如果函数在attach上之后运行的话。

注,如果要调本文说的CoreML的加载情况,最好给工程加个动态库,让这个动态库再去依赖CoreML,不然lldb attach上时,基本CoreML的依赖解析已经处理完了。

参考资料

  1. XNU、dyld源码分析Mach-O和动态库的加载过程

  2. dyld:iOS8 and iOS9 simulators in macOS 10.14.1 system crash caused by dyld loading error dynamic library

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 前言 dyld全称the dynamic link editor,即动态链接器,其本质是Mach-O文件,他是专门...
    01_Jack阅读 3,717评论 2 14
  • 背景 一个项目做的时间长了,启动流程往往容易杂乱,库也用的越来越多,APP的启动时间也会慢慢变长。本次将针对iOS...
    酱油瓶2阅读 3,487评论 0 12
  • 一,市场状况 德阳旌阳区,剑南春大本营,人口750000,人均GDP70000元,是中国重装设备生产基地,外来人口...
    殷石阅读 347评论 0 0
  • 夜黑云掩月, 草青水流肥。 寒意已渐起, 并非是宫阙。 深圳大学城图书馆-小记。
    神秘嘉宾方阅读 230评论 0 1
  • 前几天跟我妈吵了几句,这个星期都没咋打电话,今天打电话,却一直无法接通,有点儿心烦意乱。 有时候,我会想人为什么活...
    静静diary阅读 164评论 0 0