解決Xcode11無(wú)法運(yùn)行iOS9模擬器問(wèn)題

注: 本文已過(guò)時(shí)。macosx > 10.14.99 則此方法不行。

馬上2020年了,所以你可能基本不需要用到iOS9模擬器了,本文純灌水,不感興趣的同學(xué)可以不看。

問(wèn)題描述

當(dāng)Xcode11工程中引入了iOS11才有的CoreML等庫(kù)(雖然庫(kù)是weak依賴的),運(yùn)行iOS9模擬器,啟動(dòng)時(shí)會(huì)崩潰,提示如下:

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時(shí),它加載了MacOSX系統(tǒng)路徑下的CoreML.framework,導(dǎo)致后續(xù)加載它依賴的庫(kù)時(shí),發(fā)生錯(cuò)誤告警。然而,iOS10的模擬器,同樣沒(méi)有CoreML庫(kù),為什么不會(huì)有這問(wèn)題?

貌似Mac OSX系統(tǒng)低于10.14.1 加載相同的iOS9模擬器鏡像也不會(huì)有問(wèn)題。
另外,附錄A提供了手動(dòng)安裝低版本Xcode模擬器的方法供參考。

分析

首先,我們知道應(yīng)用啟動(dòng)時(shí),針對(duì)模擬器,/usr/lib/dyld會(huì)加載模擬器鏡像下的dyld_sim,然后轉(zhuǎn)交給dyld_sim來(lái)加載模擬器版本的庫(kù)。

(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

因?yàn)閐yld是開源的,可以拿到源碼來(lái)更好地分析。先通過(guò)dyld_sim中的LC_SOURCE_VERSION的load command來(lái)查看對(duì)應(yīng)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

這里查看到對(duì)應(yīng)源碼版本390.7,同樣的,拿到iOS 10.1的dyld_sim對(duì)應(yīng)的源碼版本421.0

dyld下載源碼,可以下載到421.0的源碼,但并沒(méi)有390.7的源碼,可以下個(gè)比較接近的360.22用來(lái)分析。

從錯(cuò)誤開始,查找mach-o, but not built for iOS simulator錯(cuò)誤提示。發(fā)現(xiàn)它是由isSimulatorBinary 函數(shù)判定的,通過(guò)該函數(shù)判定庫(kù)文件是Mac OSX還是模擬器版本的庫(kù),如果不是模擬器版,就會(huì)拋上述異常出去。

下面來(lái)分析360.22的源碼和421.0的源碼在判斷模擬器時(shí)的差別:

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

可以看到,兩個(gè)版本,判斷庫(kù)文件是否為模擬器,有兩個(gè)主要的區(qū)別:

  • 低版本只讀取了一頁(yè)(4096,4k大小)的macho頭部,而高版本按mh->sizeofcmds來(lái)確定cmdsReadEnd邊界(該版本最大可以取32K)。
  • 超過(guò)邊界cmdsReadEnd,低版本返回true,而高版本返回false。

看下目前問(wèn)題的表現(xiàn),能否由這兩段差異的代碼來(lái)解釋。目前的問(wèn)題表現(xiàn)如下:

  1. iOS9 dyld_sim,加載系統(tǒng)的/System/Library/Frameworks/CoreML.frameworks時(shí),沒(méi)有報(bào)異常,把它當(dāng)模擬器版本加載了。

  2. iOS9 dyld_sim,加載CoreML依賴的/System/Library/Frameworks/Accelerate.framework時(shí),卻提示它不是模擬器版,并崩潰。

  3. iOS10 dyld_sim,不會(huì)加載CoreML,因?yàn)檎_識(shí)別它為模擬器版本了。

  4. 老的MacOS(更低版本的CoreML.framework),iOS9 dyld_sim識(shí)別CoreML正常,模擬器可以正常運(yùn)行。

用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字節(jié),4080+20 > 4096,而Accelerate很小,沒(méi)有超過(guò)4096。

這里還要補(bǔ)充一個(gè)背景,新版本的CoreML.framework等系統(tǒng)庫(kù),除了load commands超過(guò)4K,還廢棄掉了LC_VERSION_MIN_MACOSX等load command,改用LC_BUILD_VERSION來(lái)描述系統(tǒng)版本。

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

所以,來(lái)總結(jié)下iOS9模擬器啟動(dòng)崩潰問(wèn)題的原因。新MacosX系統(tǒng)升級(jí)后(好像是10.14.1),/System/Library/Frameworks/CoreML.frameworks等庫(kù)廢棄了LC_VERSION_MIN_MACOSX等load command,導(dǎo)致之前的dyld_sim,無(wú)法判斷動(dòng)態(tài)庫(kù)是否模擬器版本。當(dāng)無(wú)法判斷時(shí),iOS10以下的dyld_sim對(duì)load command超過(guò)4K的庫(kù),認(rèn)為是模擬器版本進(jìn)行了加載,導(dǎo)致了后面的崩潰問(wèn)題。iOS10以上的dyld_sim沒(méi)法區(qū)別時(shí),默認(rèn)認(rèn)為不是模擬器版本,所以加載CoreML.frameworks時(shí)拋異常,但這個(gè)庫(kù)是weak依賴的,所以表現(xiàn)正常。

看起來(lái)理論可以解釋,但iOS9的模擬器沒(méi)有源碼,它的邏輯和390.7源碼的一致嗎?我們通過(guò)HopperDisassembler簡(jiǎn)單分析下這個(gè)isSimulatorBinary函數(shù)。

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

可以看到,除了多判斷了0x2f<=rsi<0x31(這里hopper錯(cuò)翻譯成rsi<0x31)其他邏輯和360.22基本一致。

這里也可以通過(guò)調(diào)試,來(lái)確認(rèn)邏輯。附錄B給出具體的調(diào)試方法,供參考。

解決

說(shuō)了這么多,你可能要問(wèn)了,一開始為啥模擬器要去加載Mac OSX里的動(dòng)態(tài)庫(kù)呢?原因是,dyld_sim默認(rèn)支持加載操作系統(tǒng)里任意路徑的動(dòng)態(tài)庫(kù),不過(guò)它會(huì)先加模擬器鏡像路徑前綴,沒(méi)找到才會(huì)嘗試原始路徑:

source3.png

所以看起來(lái),像/System/Library/Frameworks/CoreML.frameworks這個(gè)庫(kù),只要在模擬器鏡像路徑(DYLD_ROOT_PATH)下沒(méi)找到,它就會(huì)找到Mac OSX下面去。

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

道理都懂,但怎么修復(fù)呢?我們無(wú)法修改dyld_sim、/System/Library/Frameworks/CoreML.frameworks,因?yàn)檫@些都是蘋果的庫(kù),做了codesign,改完無(wú)法加載。通過(guò)lldb動(dòng)態(tài)修改也不靠譜,因?yàn)闊o(wú)法確認(rèn)debugger接入時(shí)機(jī),可能attach上時(shí),CoreML都加載進(jìn)去了。

好在這里有個(gè)trick,可以成功騙過(guò)了dyld_sim。只要拷貝一個(gè)模擬器鏡像目錄下其他正常的庫(kù),如CoreFoundation.frameworks,并把它改名為CoreML.frameworks(記得也要改名里面的macho文件),那么它加載 DYLD_ROOT_PATH/System/Library/Frameworks/CoreML.frameworks時(shí)就可以正常加載而不會(huì)報(bào)錯(cuò)(實(shí)際加載的是CoreFoundation),就不會(huì)找到Mac OSX系統(tǒng)里去導(dǎo)致后面的問(wèn)題。完美~~~

同樣發(fā)現(xiàn), Vision.framework和Intents.framework也有相同的問(wèn)題??梢酝瑯硬僮饕话研迯?fù)相關(guān)問(wèn)題。

附錄A. Xcode安裝低版本模擬器

你的Xcode11不一定安裝了iOS9的模擬器。如果沒(méi)有安裝過(guò),需要手動(dòng)安裝(模擬器下載列表可能找不到iOS9了)。從iPhoneSimulatorSDK9_3 下載,并從dmg中提取出pkg安裝包。

我們知道,Xcode的模擬器鏡像都是存放在/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS x.x.simruntime下,但從蘋果下載的pkg安裝時(shí)默認(rèn)是安裝到/下。我們可以安裝后把/Contents下的文件全部拷到上述鏡像目錄下,也可以像如下方法,修改pkg安裝路徑。

# 1. 解壓pkg到temp目錄
pkgutil --expand iPhoneSimulatorSDK9_3.pkg temp

# 2. 修改解包目錄里的PackageInfo文件,設(shè)置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. 調(diào)試dyld_sim

這里介紹如何調(diào)試本文描述的問(wèn)題。dyld啟動(dòng)時(shí),很快就加載完了,如果庫(kù)加載有問(wèn)題,很快就崩潰了,都跑不到代碼里(初始化的代碼也沒(méi)機(jī)會(huì)運(yùn)行)。但通過(guò)shell里的lldb用waitfor方式等待調(diào)試,在attach時(shí),它會(huì)自動(dòng)發(fā)送SIGSTOP,此時(shí)就可以看到dyld_sim的代碼。

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

上述命令等待MyiOS9的模擬器App。點(diǎn)擊模擬器中的app,它就可以斷點(diǎn)進(jìn)去(當(dāng)然斷的時(shí)機(jī)不確定)。

lldb1.png
lldb2.png

同時(shí)加載的庫(kù)也不多。

(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*)

接下來(lái)就可以設(shè)置斷點(diǎn)了:

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

這里0x000000010eba8000是dyld_sim的隨機(jī)加載地址(通過(guò)image list查看),17971是函數(shù)相對(duì)dyld_sim.__TEXT.__text的偏移,0x1000是__TEXT.__text段在dyld_sim鏡像里的文件偏移。

你也可以調(diào)試loadPhase0__cxa_throw、__cxa_begin_catch等函數(shù),如果函數(shù)在attach上之后運(yùn)行的話。

注,如果要調(diào)本文說(shuō)的CoreML的加載情況,最好給工程加個(gè)動(dòng)態(tài)庫(kù),讓這個(gè)動(dòng)態(tài)庫(kù)再去依賴CoreML,不然lldb attach上時(shí),基本CoreML的依賴解析已經(jīng)處理完了。

參考資料

  1. XNU、dyld源碼分析Mach-O和動(dòng)態(tài)庫(kù)的加載過(guò)程

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 dyld全稱the dynamic link editor,即動(dòng)態(tài)鏈接器,其本質(zhì)是Mach-O文件,他是專門...
    01_Jack閱讀 4,012評(píng)論 2 14
  • 背景 一個(gè)項(xiàng)目做的時(shí)間長(zhǎng)了,啟動(dòng)流程往往容易雜亂,庫(kù)也用的越來(lái)越多,APP的啟動(dòng)時(shí)間也會(huì)慢慢變長(zhǎng)。本次將針對(duì)iOS...
    醬油瓶2閱讀 3,658評(píng)論 0 12
  • 一,市場(chǎng)狀況 德陽(yáng)旌陽(yáng)區(qū),劍南春大本營(yíng),人口750000,人均GDP70000元,是中國(guó)重裝設(shè)備生產(chǎn)基地,外來(lái)人口...
    殷石閱讀 376評(píng)論 0 0
  • 夜黑云掩月, 草青水流肥。 寒意已漸起, 并非是宮闕。 深圳大學(xué)城圖書館-小記。
    神秘嘉賓方閱讀 279評(píng)論 0 1
  • 前幾天跟我媽吵了幾句,這個(gè)星期都沒(méi)咋打電話,今天打電話,卻一直無(wú)法接通,有點(diǎn)兒心煩意亂。 有時(shí)候,我會(huì)想人為什么活...
    靜靜diary閱讀 215評(píng)論 0 0

友情鏈接更多精彩內(nèi)容