iOS问题记录 - Xcode 15安装低版本iOS模拟器(持续更新)

文章目录

  • 前言
  • 开发环境
  • 问题描述
  • 问题分析
    • [1. 定位问题](#1. 定位问题)
    • [2. 逆向分析](#2. 逆向分析)
      • [2.1. IDA Free](#2.1. IDA Free)
      • [2.2. Hopper Disassembler Demo](#2.2. Hopper Disassembler Demo)
    • [3. 模拟器日志](#3. 模拟器日志)
    • [4. supportedArchs](#4. supportedArchs)
  • 解决方案
  • 最后

前言

最近新需求很多,项目改动很大,开发完成后想测一遍在低版本iOS系统上的兼容性,没想到低版本的iOS模拟器设备突然无法创建了。

在去年年底我研究过这个问题,写下了这篇文章iOS问题记录 - Xcode 14安装低版本iOS模拟器(持续更新),虽然当时没有完全解决,但暂时也够用,没想到现在这个问题又出现了。如果还没有阅读过这篇文章,强烈建议先阅读这篇前置文章。

这里先说明一下,虽然标题是Xcode 15安装低版本iOS模拟器,但是在遇到这个问题时Xcode 15还处于Beta版,平时还是以Xcode 14.3.1版本为主,至于标题中为什么用Xcode 15,请继续往下看。

开发环境

  • macOS: 13.4
  • Xcode: 14.3.1 / 15 beta 5

问题描述

在Xcode 14.3.1版本中,低版本iOS模拟器文件已经修改了Info.plist文件中的CFBundleIdentifier属性值,但是创建iOS模拟器的时候却还是无法选择。从官网重新下载Xcode 14.0.1版本创建低版本iOS模拟器,还是无法选择无法创建,最低只能创建iOS 14.0.1模拟器。

问题分析

1. 定位问题

首先怀疑是不是Xcode 14.3.1版本又加了其他限制,导致原先的方法失效。找了一台打包机,将Xcode版本升到14.3.1,尝试创建低版本iOS模拟器,一切正常!这是怎么回事?

这就不得不提CoreSimulator框架,文件位于/Library/Developer/PrivateFrameworks/CoreSimulator.framework,Xcode的模拟器管理靠它来实现。现在Xcode版本一致,却出现一个正常一个不正常,难道是CoreSimulator框架版本不一致?

查看CoreSimulator框架版本的两种方法:

  • 通过命令查看
shell 复制代码
xcrun simctl --version

执行输出:

shell 复制代码
@(#)PROGRAM:simctl  PROJECT:CoreSimulator-917

simctl是用于管理和控制模拟器的命令行工具,更多用法可以通过xcrun simctl help命令查看。

  • 通过version.plist文件查看

找到CoreSimulator框架文件(前面有写路径),version.plist文件位于Versions/A/Resources目录路径下。

经过对比发现,我电脑上的版本是917,而打包机上的版本是885.2。咦🤔?这版本什么️时候升级的我怎么不知道。思来想去,很可能是在前几天体验Xcode 15 beta 5版本的时候更新了CoreSimulator框架。打开大版本更新的Xcode时,一般都会弹出一个安装界面,然后请求管理员权限安装其他组件:

这时候就会自动更新CoreSimulator框架,当然也不限于这个框架,还有其他一些组件也会更新。

接下来就是验证是不是因为CoreSimulator框架版本不一致导致的问题,最简单的验证方法是将917版本降回885.2版本,然后再测试能否创建低版本iOS模拟器。我本来打算先将已有的CoreSimulator框架文件删除,然后从打包机复制一份到原路径,结果刚删除立马就弹出这个:

允许操作后自动重新安装了885.2版本的CoreSimulator框架,用Xcode 14.3.1版本实测可以创建iOS 12.0版本的模拟器,同时运行与调试也一切正常。

由此可以确定当前问题是CoreSimulator框架升级版本后导致的,那么该怎么解决呢?如果是Xcode 14遇到这个问题,直接删除新版本的CoreSimulator框架然后自动重装老版本即可(如果没有自动重装,重新打开Xcode 14也可以完成安装)。不过,这治标不治本,后面Xcode 15还是无法创建低版本iOS模拟器。

2. 逆向分析

CoreSimulator框架的核心是Versions/Current/CoreSimulator可执行文件,想知道框架升级改了什么导致了当前问题的话,逆向分析这个可执行文件是最快的方式。

以下所使用的逆向工具虽然是免费版本,但是如果不开代理可能下载很慢,这里提供分享链接,希望能对你有所帮助。

2.1. IDA Free

关于逆向工具,我首选的是IDA Pro的免费版IDA Free,虽然功能受限,但是基本的静态分析是可用的,最重要的是免费。需要特别注意的一点是IDA Free被限制了只能处理x86/x64架构的二进制文件,这对CoreSimulator可执行文件不是问题,通过file命令可以知道这个可执行文件是个Mach-O通用二进制文件,包含了x86_64架构。

shell 复制代码
file /Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/CoreSimulator

执行输出:

shell 复制代码
/Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/CoreSimulator: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64] [arm64e]
/Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/CoreSimulator (for architecture x86_64):	Mach-O 64-bit dynamically linked shared library x86_64
/Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/CoreSimulator (for architecture arm64e):	Mach-O 64-bit dynamically linked shared library arm64e

建议复制一份CoreSimulator可执行文件到其他目录用于静态分析,用IDA Free打开时选择x86_64架构文件:

当底部的消息输出窗口提示初始化自动分析已完成("The initial autoanalysis has been finished.")就可以开始进行静态分析。

万事开头难,现在该怎么找到问题所在呢?之前通过修改Info.plist文件中的CFBundleIdentifier属性值绕过了Xcode的限制,那说明CoreSimulator可执行文件很可能内置了各个模拟器版本的CFBundleIdentifier值用于判断,以此为切入口或许能找到新增加的限制判断。

通过快捷键Shift + Fn + F12或[菜单栏 View ] -> [Open subviews ] -> [Strings ]打开字符串窗口,然后快捷键Command + F搜索关键词com.apple.CoreSimulator.SimRuntime.iOS-

选择任意一个搜索结果双击来到IDA View窗口,通过快捷键X查看引用:

结果没什么用,定位不了在哪里被引用。只好换个思路,从可能相关的函数入手。结合函数列表和模拟器文件的名称(iOS xx.x.simruntime),基本可以确定要找的函数应该和SimRuntime(模拟器运行时)相关。

从这些函数(方法)名可以看出CoreSimulator框架应该是用Objective-C开发的,一般首次加载文件时会弹框提示检测到Objective-C结构,询问是否要解析并重命名方法,以上的方法名经过了解析处理。看方法命名,严重怀疑-[SimRuntime isAvailableWithError:]方法就是我们要找的。在IDA View窗口使用快捷键Fn + F5查看该方法的伪代码:

相对于汇编代码,伪代码可读性确实高了些,方法调用(objc_msgSend)一目了然。不过一些引用数据,特别是字符串没有直接将指针转为实际指向的内容,例如&off_D9D70指针,需要点进去才知道指向的内容是arm64字符串,这导致阅读和搜索(文本搜索快捷键Option + T)伪代码都不方便。

分析一番,收获不大,尝试分析其他相关的方法,也没能找到问题所在。或许可以换个逆向工具试试?

2.2. Hopper Disassembler Demo

Mach-O文件的常用逆向工具还有Hopper Disassembler,它的功能会比IDA Pro弱些,但是更易上手使用。Demo版本虽然功能受限,但是基本够用,唯一需要注意的一点是每次打开只能用30分钟,到时间会弹框强制关闭。

打开Hopper Disassembler,点击Try the Demo开始使用。将需要分析的文件拖进去,会出现弹框:

这里没有被限制只能处理x86/x64架构的二进制文件,可以任选选择。等待自动分析完成就可以开始静态分析,详细使用教程请参考官方教程

相比IDA Free的伪代码,这个的可读性确实更高些。不过分析一番还是没能找到问题所在,难道只能暂时搁置了?

3. 模拟器日志

如果能向分析源码一样,随时能打印日志和调试断点,逆向分析就轻松了。对于修改和调试二进制文件,前面的逆向工具也不是不能做,只是比较麻烦,而且这些功能在免费版上基本受限用不了。

正当我考虑要不要暂时搁置问题的时候,灵光一闪,日志可能一直都存在!在静态分析中经常能看到日志打印:

而且日志打印好像也没什么限制,猜测应该是一直在输出:

通常日志会被写入文件系统(.log扩展名的文件),那这些日志被输出到哪了呢?尝试搜索.log字符串查找写入文件路径:

%s/Library/Logs/CoreSimulator/CoreSimulator.%s.log应该就是日志文件的路径,不过这路径前面部分的%s还不确定。在汇编显示模式下点击指定行,然后点击右键就可以查看相关引用:

日志文件路径被____SimLogASLInit_block_invoke函数引用了,从伪代码可知路径前面部分的%s是被环境变量HOME的值替换了:

执行env命令打印环境变量可知,HOME变量的值是用户目录路径,例如/Users/xxx,所以模拟器日志文件所在目录的完整路径是/Users/xxx/Library/Logs/CoreSimulator。打开该目录下的CoreSimulator.log文件,发现一直在打印这样的错误日志:

text 复制代码
Aug  9 21:36:53 mac com.apple.CoreSimulator.simctl[30026] <Error>: Error Domain=com.apple.CoreSimulator.SimError Code=401 "Unable to determine supported architectures for runtime (/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime)" UserInfo={NSLocalizedDescription=Unable to determine supported architectures for runtime (/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime)}

难道这个报错日志是问题的关键?将CoreSimulator框架版本降为885.2后,不再打印类似的错误日志。

补充一点:后续通过逆向发现CoreSimulator.log日志文件实际应该是CoreSimulator框架下的Versions/A/Resources/bin/simctl生成的,不过以上报错的关键信息确实来源于CoreSimulator可执行性文件。

4. supportedArchs

逆向CoreSimulator可执行文件,搜索关键词Unable to determine supported architectures for runtime,通过相关引用定位到-[SimRuntime initWithBundle:error:]方法:

相关伪代码(省略部分):

objectivec 复制代码
loc_9658b:
    rax = [r15 objectForKeyedSubscript:@"supportedArchs"];
    rax = [rax retain];
    var_268 = rax;
    if (rax != 0x0) {
            ...
    }
    else {
            rax = (r14)(@class(NSBundle), @selector(bundleWithIdentifier:), @"com.apple.CoreSimulator");
            rax = [rax retain];
            rbx = rax;
            r15 = @"Unable to determine supported architectures for runtime (%@)";
            rdx = r15;
            rcx = r15;
            rax = (r14)(rax, @selector(localizedStringForKey:value:table:), rdx, rcx, 0x0);
            rax = [rax retain];
            r12 = rax;
            if (rax != 0x0) {
                    r15 = rax;
            }
            rax = (r14)(var_218, @selector(bundlePath), rdx, rcx, 0x0);
            rax = [rax retain];
            r15 = [(r14)(@class(NSString), @selector(stringWithFormat:), r15, rax, 0x0) retain];
            [rax release];
            [r12 release];
            [rbx release];
            var_208 = r15;
            rax = [NSError errorWithSimErrno:0x191 localizedDescription:r15];
            rax = [rax retain];
            if (rax != 0x0) {
                    __SimLog(0x3, "-[SimRuntime initWithBundle:error:]", 0x1fa, @"%@", rax, r9, var_3C0);
                    rax = rax;
            }
            r14 = var_1E8;
            r15 = var_1F0;
            rbx = var_238;
            if (r14 != 0x0) {
                    rax = objc_retainAutorelease(rax);
                    *r14 = rax;
            }
            r12 = 0x0;
            r14 = var_1C0;
    }
    ...

逆向885.2版本的CoreSimulator可执行文件,对比同个方法的伪代码,这部分判断逻辑确实是新加的。从以上伪代码可知,r15(根据objectForKeyedSubscript可以推测应该是NSDictionary类型)通过supportedArchs键获取对应值时,因为键不存在所以返回NULL(也就是0x0),进而执行else代码块打印错误日志。那么r15的值是从哪来的呢?

通过不断溯源查找应该可以找到,不过这个方法的伪代码很长,需要耗费不少时间精力。换个思路,根据报错信息中出现的模拟器文件路径(/Library/.../Runtimes/iOS 12.4.simruntime)以及发生错误的方法(-[SimRuntime initWithBundle:error:]),可以大胆猜测错误是在加载模拟器文件初始化SimRuntime对象时发生的,所以r15的值很可能源于模拟器文件中的某个配置文件。

搜索关键词plist查找可能的配置文件:

咦🤔️?这行rax = [r12 pathForResource:@"profile" ofType:@"plist"];代码获取了profile.plist文件的完整路径,感觉很有可能就是它。找到iOS 12.4.simruntime文件,右键显示包内容,然后搜索profile.plist,搜索结果如下:

打开后确实没找到supportedArchs属性。另找一个没报错的模拟器文件,打开其中的profile.plist文件,确实有该属性:

iOS 12.4.simruntime文件中的profile.plist复制一份到其他地方,然后打开添加supportedArchs属性并保存,最后将修改后的文件替换原来的文件。

重新打开Xcode创建低版本iOS模拟器,还是不行。难道这么做无效?再次检查一下模拟器日志还有没有报错,好吧,原先的错误没了,来了新的错误:

text 复制代码
Aug  9 23:28:01 mac com.apple.CoreSimulator.simctl[30026] <Error>: Failed to setup launchHostClient for runtime com.apple.CoreSimulator.SimRuntime.iOS-12-4-1: Error Domain=NSPOSIXErrorDomain Code=88 "Malformed Mach-o file" UserInfo={NSLocalizedDescription=Failed to open liblaunch_sim: dlopen(/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/host/liblaunch_sim.dylib, 0x0101): tried: '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/host/liblaunch_sim.dylib' (fat file, but missing compatible architecture (have 'x86_64,i386', need 'arm64')), '/System/Volumes/Preboot/Cryptexes/OS/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/host/liblaunch_sim.dylib' (no such file), '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/host/liblaunch_sim.dylib' (fat file, but missing compatible architecture (have 'x86_64,i386', need 'arm64')), NSLocalizedFailureReason=Malformed Mach-o file}

关键报错信息:

text 复制代码
but missing compatible architecture (have 'x86_64,i386', need 'arm64')

报错信息说的很明白,缺少arm64架构。这些低版本的模拟器文件肯定是没有arm64架构的,毕竟发布的时候arm架构的M系列芯片还没出来。逐一查看电脑上已有的iOS模拟器文件,发现supportedArchs属性是从iOS 14.0.simruntime开始出现的,这就对了,iOS 14/Xcode 12/M1芯片都是2020年发布的,也就是说,当前问题只会出现在iOS 14以下。

看来不能完全照搬照抄,将supportedArchs属性中的arm64删除,重新打开Xcode创建低版本iOS模拟器,创建成功!不过尝试启动,却出现了弹框报错:

经过实测发现是模拟器相关的服务没有重启。模拟器服务CoreSimulatorServiceCoreSimulator框架的一部分,用于管理模拟器的创建、启动、停止和删除等功能。打开活动监视器,找到com.apple.CoreSimulator.CoreSimulatorService进程,然后结束进程。

重新运行iOS 12.4版本的模拟器,能启动但是黑屏。好吧,可用的低版本iOS模拟器又少了。实测iOS 13.7以下版本不是黑屏就是无法运行和调试应用,真是忙了个寂寞 !明明Xcode 15的部署目标最低还是iOS 12,却不支持iOS 13.7以下的模拟器。以下来自官方支持文档

文档中Xcode 14.3.1版本的iOS模拟器显示最低支持iOS 13.7,但实际可下载列表中最低版本已经是iOS 14.0.1。

解决方案

强烈建议先阅读前置文章iOS问题记录 - Xcode 14安装低版本iOS模拟器(持续更新),然后在修改Info.plist文件的基础上,再额外修改模拟器文件中的profile.plist(位于Contents/Resources目录)文件,增加supportedArchs属性,具体增加的内容如下:

注意事项:

  • 建议先复制一份profile.plist文件到其他地方,修改完成后再替换原文件
  • 如果遇到问题的iOS模拟器版本大等于iOS 14.0,那么应该不是同一个问题
  • 实测Xcode 15 beta 5版本最低只支持iOS 13.7版本,而且可能因为配套使用的Simulator.app(Xcode内置)也还处于Beta版,运行调试会有点问题。
  • 如果你还在用Xcode 14却遇到当前问题,建议按问题分析中的操作重装老版本的CoreSimulator框架

总的来说,如果Xcode 15正式版最低也只支持到iOS 13.7版本,那么后续想使用更低版本的iOS模拟器就只能通过Xcode 14。等正式版发布后,会重新测试并更新本篇文章。

以下是一些Xcode版本所支持的最低iOS模拟器版本:

最后更新日期:2023/08/10

Xcode version CoreSimulator framework version Minimum supported simulator version
14.0.1 857.7 12.0
14.2 857.14 12.0
14.3.1 885.2 12.0
15 beta 5 917 13.7

以上数据实测于M芯片的电脑,如果不是M芯片的电脑,可能会因为架构不一致有所差别。不过,更有可能是因为电脑上的Xcode版本和CoreSimulator框架版本不一致导致的,当这两个版本不一致时以CoreSimulator框架版本为主。

最后

如果这篇文章对你有所帮助,点赞👍加星🌟支持一下吧,谢谢~


本篇文章由@crasowas发布于CSDN。

相关推荐
MavenTalk2 小时前
前端技术选型之uniapp
android·前端·flutter·ios·uni-app·前端开发
豪冷啊4 小时前
iOS 17.4 Not Installed
ios
Topstip4 小时前
iOS 19 重大更新泄露,将带来更“聪明”的 Siri 挑战 ChatGPT
人工智能·ios·ai·chatgpt
dr李四维1 天前
macOS 版本对应的 Xcode 版本,以及 Xcode 历史版本下载
ide·macos·xcode·apple·xcode下载
软件聚导航1 天前
uniapp 安卓和ios震动方法,支持息屏和后台震动,ios和安卓均通过测试
android·ios·uni-app
键盘敲没电2 天前
【iOS】知乎日报总结
学习·ios·objective-c·xcode
安和昂2 天前
【iOS】UICollectionView的学习
学习·ios·cocoa
二流小码农2 天前
鸿蒙开发:自定义一个任意位置弹出的Dialog
android·ios·harmonyos
小江村儿的文杰3 天前
UE4 iOS Package的过程与XCode工程中没有游戏Content的原因
macos·ios·ue4·xcode
__WanG3 天前
Flutter将应用打包发布到App Store
前端·flutter·ios