做雜志的模板下載網(wǎng)站有哪些貴陽百度推廣電話
本文字?jǐn)?shù):2051字
預(yù)計(jì)閱讀時(shí)間:15分鐘
01
需要轉(zhuǎn)換架構(gòu)的原因
老版 Mac 使用 Intel 芯片,是x86_64架構(gòu),相應(yīng)地在老版 Mac 上運(yùn)行的模擬器使用的也就是 x86_64架構(gòu)。
由于模擬器的 x86_64 架構(gòu)與真機(jī)的 arm64、armv7 等架構(gòu)不沖突,業(yè)界為了方便庫文件管理,通常會(huì)將模擬器架構(gòu)與真機(jī)架構(gòu)通過 lipo 命令合并為一個(gè) fat 文件。
對于 Intel 芯片的 Mac 這樣處理就很高效合理的,但是 Apple 推出了 M 系列芯片的新版 Mac,這就導(dǎo)致模擬器也變成了 arm64 架構(gòu),同時(shí) lipo 命令合并二進(jìn)制文件時(shí)不允許出現(xiàn)同名的兩個(gè)不同架構(gòu)。也就是只要還采用通用二進(jìn)制文件(fat 文件)的庫管理方式,就無法同時(shí)使用真機(jī) arm64 架構(gòu)和模擬器 arm64 架構(gòu)。
對于上述的問題, Apple 提供了兩個(gè)解決方案:
1、XCFramework。可以根據(jù)編譯環(huán)境指定使用的庫文件,不用合成就避免了架構(gòu)重名的問題。但是項(xiàng)目使用的第三方庫很多,其他部門的庫內(nèi)又鏈接了其他的第三方庫,全部更換不現(xiàn)實(shí)。(我們項(xiàng)目目前只有一個(gè)部門支持了XCFramework);
2、Rosetta Simulator。在 Xcode -》Product -》Destination 選項(xiàng)中,可以選擇顯示 Rosetta 模擬器,這樣模擬器運(yùn)行時(shí)使用的依舊是 x86_64 架構(gòu),同時(shí)搭配 Excluded Architectures = arm64 ,可以解決大部分項(xiàng)目模擬器運(yùn)行的問題:
但是,visionOS Simulator?被?Apple?遠(yuǎn)程禁止了?Rosetta?選項(xiàng)!(早期的?Xcode15 beta?版還能顯示?Rosetta?的?visionOS?模擬器,更新了一次就不見了。解包下載下來的?visionOS_1_beta_7_Simulator_Runtime,在?Contents/Resources/profile.plist?文件中可以看到支持的架構(gòu)是包含x86_64的,應(yīng)該是在Xcode層面禁止了Rosetta選項(xiàng)的出現(xiàn))。如果我們想要提前驗(yàn)證項(xiàng)目在visionOS上跑起來的效果的話,就需要研究能否將鏈接庫中的真機(jī)arm64架構(gòu)提供給模擬器使用。
02
真機(jī)架構(gòu)與模擬器架構(gòu)的區(qū)別
為了弄清楚真機(jī)架構(gòu)與模擬器架構(gòu)的區(qū)別,我們需要將庫文件解剖并對比二進(jìn)制文件中的差異。
得益于外網(wǎng)的這篇博文arm64-to-sim,我們可以看到博主利用llvm對同一代碼分別編譯了真機(jī)與模擬器架構(gòu)版本,并直接對比出了差異:
#?in?the FirebaseAnalytics.xcframework directory?
$?otool?-fahl ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/FirebaseAnalytics?|?grep?-E?'cmd?|\.o'?>?simulator_cmds?
$?otool?-fahl ios-arm64_armv7/FirebaseAnalytics.framework/FirebaseAnalytics?|?grep?-E?'cmd?|\.o'?>?native_cmds?
$?diff?-u native_cmds simulator_cmds?
-ios-arm64_armv7/FirebaseAnalytics.framework/FirebaseAnalytics(FirebaseAnalytics_vers.o):?
+ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/FirebaseAnalytics(FirebaseAnalytics_vers.o):
cmd LC_SEGMENT_64?
-??????cmd LC_VERSION_MIN_IPHONEOS?
+??????cmd LC_BUILD_VERSION??????
cmd LC_SYMTAB?
(...)
從上面的結(jié)果可以看到,區(qū)別只有一處,真機(jī)架構(gòu)使用的loadcommand是LC_VERSION_MIN_IPHONEOS,而模擬器是LC_BUILD_VERSION。我們使用otool命令:
otool?-lV xxx.o
可以查看二進(jìn)制文件中l(wèi)oad command的內(nèi)容,對比結(jié)果如下圖:

03
如何修改
知道了區(qū)別在于load command,自然是要將LC_VERSION_MIN_IPHONEOS改為LC_BUILD_VERSION。但是Mach-O是一種很緊湊的文件格式,Mach-O中的三個(gè)區(qū)域Header & Load Commands & Data,其中Header記錄了平臺、文件類型、指令數(shù)、指令總大小等信息,Load Commands緊跟Header,其中部分Load Command包含了Data中數(shù)據(jù)段的起始位置和數(shù)據(jù)大小:
修改LC_VERSION_MIN_IPHONEOS為LC_BUILD_VERSION會(huì)導(dǎo)致Mach-O整體大小發(fā)生變化(如圖所示,cmdsize不同),Header中的大小信息需要同步修改,由于Data處于Load Commands位置后面,Data所在位置也就發(fā)生了偏移,部分load command所指向的位置也需要進(jìn)行對應(yīng)的偏移。
體現(xiàn)到代碼上,參照arm64-to-sim開源命令行工具。
1、提取靜態(tài)庫中的arm64架構(gòu):
private?static?func?extract(inputFileAtUrl url:?URL,?withArch arch:?String,?toURL:?URL)?throws?{try?shellOut(to:?"lipo",?arguments:?["-thin",arch,url.path,"-output","lib.\(arch)"],?at:?toURL.path)
}
2、將提取出的文件切為最小組成成分,并依次處理:
try?shellOut(to:?"ar",?arguments:?["x",?extractionUrl.appendingPathComponent("lib.arm64").path])
if?let?emulator?=?FileManager.default.enumerator(atPath:?extractionUrl.path)?{for?file?in?emulator?{if?let?fileString?=?file?as??String?{if?fileString.hasSuffix(".o")?{Transmogrifier.processBinary(atPath:?extractionUrl.appendingPathComponent(fileString).path,?minos:?minos,?sdk:?sdk)}}}
}
3、更新Header中數(shù)據(jù)大小:
var?header:?mach_header_64?=?headerData.asStruct()
header.sizeofcmds?=?UInt32(editedCommandsData.count)
4、替換LC_VERSION_MIN_IPHONEOS,根據(jù)cmdsize變化更新其他load command:
static?func?updatePreiOS12ObjectFile(lc:?Data,?minos:?UInt32,?sdk:?UInt32)?->?Data?{let?offset?=?UInt32(abs(MemoryLayout<build_version_command>.stride?-?MemoryLayout<version_min_command>.stride))let?cmd?=?Int32(bitPattern:?lc.loadCommand)switch??cmd?{case?LC_SEGMENT_64:return?updateSegment64(lc,?offset)case?LC_VERSION_MIN_IPHONEOS:return?createVersionMin(lc,?offset,?minos:?minos,?sdk:?sdk)case?LC_DATA_IN_CODE,?LC_LINKER_OPTIMIZATION_HINT:return?updateDataInCode(lc,?offset)case?LC_SYMTAB:return?updateSymTab(lc,?offset)case?LC_BUILD_VERSION:return?updateVersionMin(lc,?offset,?minos:?minos,?sdk:?sdk)default:return?lc}
}
5、將處理后的切片合并組裝:
try?shellOut(to:?"ar",?arguments:?["cr",?"lib.arm64",?"*.o"])
04
踩坑&進(jìn)階
arm64-to-sim在計(jì)算LC_BUILD_VERSION大小時(shí)沒有考慮build_tool_version:
Load command 11cmd LC_BUILD_VERSIONcmdsize 32platform IOSSIMULATORminos 15.5sdk 15.5ntools 1tool LDversion 764.0
Load?command?34cmd LC_BUILD_VERSIONcmdsize 24platform IOSSIMULATORminos 13.0sdk 13.0ntools 0
參照MachOView源碼:
struct?build_tool_version?{uint32_t?tool;uint32_t?version;
};struct?build_version_command?{uint32_t?cmd;???????//?LC_BUILD_VERSIONuint32_t?cmdsize;???//?sizeof(build_version_command)?+?(ntools?*?sizeof(build_tool_version)uint32_t?platform;??//?MachoPlatformuint32_t?minos;?????//?X.Y.Z is encoded in nibbles xxxx.yy.zzuint32_t?sdk;???????//?X.Y.Z is encoded in nibbles xxxx.yy.zzuint32_t?ntools;????//?number build_tool_version entries
};
build_version_command的cmdsize?需要加上build_tool_version?的大小,不然會(huì)讀取失敗。
有些庫中包含bitcode文件,bitcode文件與Mach-O文件結(jié)構(gòu)并不相同,上述的處理過程并不能完成架構(gòu)轉(zhuǎn)換。
llvm提供了llvm-dis、llvm-as工具,llvm-dis可以將bitcode文件轉(zhuǎn)換為文本格式的?.ll文件,llvm-as工具可以將?.ll文件轉(zhuǎn)換為bitcode文件。
打開一個(gè)轉(zhuǎn)換好的?.ll?文件,我們可以看到target?triple對應(yīng)的就是架構(gòu)信息:
參照Xcode Build Log:
-Xlinker?-reproducible?-target arm64-apple-ios9.0-simulator?-isysroot
此處target triple改為arm64-apple-ios8.0.0-simulator即可。
由于bitcode是llvm編譯過程的中間產(chǎn)物,不同版本的llvm所生成的bitcode格式并不兼容,需要采用和Xcodecommand line tool所使用的llvm相近的版本。
05
結(jié)尾
如果你順利地完成了以上所有步驟,將項(xiàng)目中的鏈接庫全部轉(zhuǎn)換完成,那么你就可以直接在arm64架構(gòu)的模擬器上運(yùn)行你們的項(xiàng)目了。
參考文檔:
1、https://bogo.wtf/arm64-to-sim.html
2、https://github.com/luosheng/arm64-to-sim