作者:滒娶伱 | 来源:互联网 | 2023-08-23 17:52
iOS 底层探索(二十五) 启动优化
冷启动
:在内存中不包含相关的数据,由系统决定
热启动
:在杀掉应用后,内存数据依然在
测试App启动,用main
函数做分界点,main函数之前叫pre-main
,main函数之前的时间是系统决定的,启动时间不能统计,main函数之后的启动时间是可以启动的。由于在main之后的操作在每个App都会有所不同。
配置系统启动时间打印
在Arguments
中添加DYLD_PRINT_STATISTICS
选项
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/97a8697d52388261.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc=,size_16,color_FFFFFF,t_70#pic_center)
运行查看结果
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/96687c209a8ca72b.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc=,size_16,color_FFFFFF,t_70#pic_center)
因为当前项目是个空项目,没有参考性,因此我们需要借助一个完整的项目进行重签名查看。
在项目的根目录下,创建App
文件夹,将.ipa
文件拷入
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/9293d999e8150e98.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc=,size_16,color_FFFFFF,t_70#pic_center)
创建App签名的appSign.sh
文件,与App
文件夹同级,内容如下
# ${SRCROOT} 它是工程文件所在的目录
TEMP_PATH="${SRCROOT}/Temp"
#资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包
ASSETS_PATH="${SRCROOT}/App"
#目标ipa包路径
TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa"
#清空Temp文件夹
rm -rf "${SRCROOT}/Temp"
mkdir -p "${SRCROOT}/Temp"#----------------------------------------
# 1. 解压IPA到Temp下
unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH"
# 拿到解压的临时的APP的路径
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
# echo "路径是:$TEMP_APP_PATH"#----------------------------------------
# 2. 将解压出来的.app拷贝进入工程下
# BUILT_PRODUCTS_DIR 工程生成的APP包的路径
# TARGET_NAME target名称
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
echo "app路径:$TARGET_APP_PATH"rm -rf "$TARGET_APP_PATH"
mkdir -p "$TARGET_APP_PATH"
cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH"#----------------------------------------
# 3. 删除extension和WatchAPP.个人证书没法签名Extention
rm -rf "$TARGET_APP_PATH/PlugIns"
rm -rf "$TARGET_APP_PATH/Watch"#----------------------------------------
# 4. 更新info.plist文件 CFBundleIdentifier
# 设置:"Set : KEY Value" "目标文件路径"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"#----------------------------------------
# 5. 给MachO文件上执行权限
# 拿到MachO文件的路径
APP_BINARY&#61;&#96;plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<&#96;
#上可执行权限
chmod &#43;x "$TARGET_APP_PATH/$APP_BINARY"#----------------------------------------
# 6. 重签名第三方 FrameWorks
TARGET_APP_FRAMEWORKS_PATH&#61;"$TARGET_APP_PATH/Frameworks"
if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ];
then
for FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"*
do#签名
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"
done
fi#注入
#yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HankHook.framework/HankHook"
编译配置&#xff0c;添加配置选项
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/a1c81f697f2e3dd4.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
运行查看打印内容&#xff0c;如下
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/82fe7c142e7b0df3.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
-
当前项目整体耗时1.8s
-
dylib loading
&#xff1a;加载动态库的耗时&#xff0c;官方建议自定义的动态库数量为6
个&#xff0c;从包内部可知我们加载的包的动态库也是6
个&#xff0c;如果多于6个&#xff0c;就需要考虑动态库合并
了。
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/8c5f09c74c33cdc8.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
-
rebase/binding
&#xff1a;rebase
&#xff1a;偏移修正&#xff0c;binding
&#xff1a;符号绑定
rebase
&#xff1a;偏移修正&#xff0c;任何一款App生成的二进制
文件&#xff0c;在二进制文件的方法
、函数调用
都会存在地址&#xff0c;这个地址是二进制文件的偏移地址
。一旦在运行时刻&#xff0c;系统会给二进制文件的头部加入ASLR
(安全机制&#xff0c;分配一个随机值)&#xff0c;因此在运行时刻&#xff0c;方法的地址会存在偏移&#xff0c;因此需要偏移修正
。binding
&#xff1a;绑定&#xff0c;例如在方法内做NSLog
打印时&#xff0c;在编译中就会在MatchO
文件中&#xff0c;创建一个符号叫做NSLog
。当运行时进行地址关联
&#xff0c;即关联到NSLog函数真正的地址&#xff0c;这个关联的过程叫做绑定
。如果想缩短这个时间&#xff0c;处理方法就是少用一点外部的库
-
ObjC setup
&#xff1a;OC类的耗时&#xff0c;即OC的类越多
&#xff0c;就越耗时
&#xff0c;相比swift来说&#xff0c;swift性能要好一点&#xff0c;优化方法删除不用的类以及方法&#xff0c;因为他会参与编译。
-
initializer
&#xff1a;load和C&#43;&#43;的构造函数的耗时
WeChat
&#xff1a;是主程序的耗时&#xff0c;其他均为系统的耗时
优化方案
- 启动时刻的页面最好使用
代码
&#xff0c;而不是xib
以及storyboard
- 影响启动的耗时操作&#xff0c;尽量使用
多线程
- 删除不用的
类
以及方法&#xff0c;因为他会参与编译。
二进制重排
虚拟内存
与物理内存
早期的计算机没有虚拟内存的概念&#xff0c;都是物理内存。在早期的计算机中&#xff0c;如果打开的软件过多时&#xff0c;会出现内存不足
的弹窗警告。这是因为在应用程序运行时&#xff0c;需要将应用程序加载到内存中&#xff0c;然后再进行加载&#xff0c;但是物理内存空间有限&#xff0c;一旦你打开的应用过多时&#xff0c;就会出现内存不足的警告。
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/3254ff321834fcd5.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
问题
内存不够用
&#xff0c;即应用软件的发展速度远比硬件的发展速度快 - 软件越来越大后&#xff0c;用户使用这款软件的功能可能是部分功能&#xff0c;因此将应用全部载入内存是一种物理内存的
浪费
。
内存数据安全问题
&#xff0c;以前的游戏外挂中进行内存搜索时&#xff0c;可以就行内存的数据修改&#xff0c;绕过操作系统的阻碍。
虚拟内存
让应用程序以为载入了内存&#xff0c;又没有加载到真正的物理内存中。由此引发虚拟内存。
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/1441cbe7af05757e.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
虚拟内存流程
- 系统将应用程序进行
分页
&#xff0c;将应用加入到虚拟
内存地址&#xff0c;每个应用的虚拟内存都是独立
的。 - 物理内存中只有正在活跃的应用程序的某一个
部分
。 - 当访问数据时&#xff0c;
CPU
首先访问虚拟
内存&#xff0c;从虚拟地址与物理地址对应关系表
查看物理内存中是否有该虚拟地址的内容&#xff0c;如果有则直接使用&#xff0c;如果没有则重新载入物理内存中&#xff0c;然后进行访问 - 这样做可以
节省
内存空间。 - 这样解决了内存不够用以及安全问题&#xff0c;因为应用程序只能访问虚拟内存&#xff0c;无法直接访问物理内存。
- 将应用程序以页来分可以方便管理&#xff0c;并方便虚拟内存映射。在Linux、macOS系统中一页是
4K
&#xff0c;iOS系统中一页是16K
。 - 五大分区指的就是虚拟内存。
- 虚拟内存空间为
4G
- 虚拟内存说白了就是一张表&#xff0c;一张映射物理内存的表。
PageFault
当CPU
访问数据时&#xff0c;发现虚拟内存中的数据没有加载到物理内存中时&#xff0c;会执行缺页中断
&#xff1a;缺页异常(pagefault)&#xff0c;将虚拟内存中的数据加载到物理内存中&#xff0c;然后再访问物理内存。这个过程是消耗时间
的&#xff0c;但消耗的时间是毫秒级别&#xff0c;用户基本感知不到。但是在应用程序启动阶段&#xff0c;会同时出现大量的PageFault
&#xff0c;因为启动时加载的代码远远不止一页&#xff0c;有可能几百页甚至上千页。
我们知道一页有16K
&#xff0c;但是一页中可能只访问了一个方法。而在启动时&#xff0c;可能所需要的数据的总量才16K
&#xff0c;即一页大小&#xff0c;但是他却被分配到了1000个页
中&#xff0c;因此为了那16K
的数据&#xff0c;加载16 * 1000
页大小的数据。
物理内存不够用
当物理内不够用时&#xff0c;操作系统会直接将某一块不活跃的内存覆盖
掉。进行加载。
ASLR
早期的计算机是用物理内存
直接访问的。但应用加载到内存
中某个位置是无法确定的&#xff0c;那么每次加载时就需要重新定位
&#xff08;重定向&#xff09;
而现在的计算机是用虚拟内存
访问的&#xff0c;虚拟内存都是从0
开始访问的&#xff0c;这个就出现了个安全隐患的问题。即可以定位到某个地址进行直接访问&#xff0c;由此引发ASLR
技术。
加载顺序
自定义代码&#xff0c;如下&#xff0c;查看他的加载顺序
void test1() {printf("1");
}void test2() {printf("1");
}- (void)viewDidLoad {[super viewDidLoad];printf("viewDidLoad");test1();
}&#43; (void)load {printf("load");test2();
}
我们代码的实现会变成二进制文件&#xff0c;那么代码的排序顺序呢&#xff1f;
修改Write Link Map File
选项为YES
。
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/0a6afe3d14094e8a.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
编译之后找到当前项目对应的Intermediates.noindex
文件夹。找到TestDemo-LinkMap-normal-arm64.txt
文件
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/fc4a0ece589c38c5.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
查看该文件内容
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/5542b5ff72fdd5b5.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
Address
地址Size
大小File
文件Name
名字
在链接时首先是按照文件顺序&#xff0c;即编译的顺序Conmpile Sources
&#xff0c;然后在按照函数的书写顺序进行排序。
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/47814437e01ac920.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
这种方式就造成了在启动时刻的代码分别分布在了不同的文件里面&#xff0c;以及页中。
优化思路
将所有启动时刻需要调用的方法&#xff0c;排列在一起&#xff0c;这就是二进制重排
。
查看页面中断的次数
使用Instruments
软件中的System Trace
功能进行查看。在测试时&#xff0c;你的应用一定要进行冷启动。
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/080765e9a6f07d40.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
我这里并没有真正的冷启动&#xff0c;大家可以多测几次&#xff0c;这主要是物理内存中并没有删除&#xff0c;而是在再次启动时&#xff0c;直接使用了物理内存中的内容。
二进制重排初体验
ld提供了order_file
文件&#xff0c;用于二进制重排。
在objc
源码中我们可以看到libobjc.order
文件&#xff0c;这就是order_file
文件
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/6a9efc46ffff63c6.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
打开这个文件&#xff0c;进行查看
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/f5f0e441e88441f1.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
这里都是一些二进制名称&#xff0c;因此苹果的objc
也进行了二进制重排。
使用MachOView
软件打开编译完成的可执行文件。
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/78a40791f34fca91.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
其中TEXT
为代码文件&#xff0c;DATA
为数据文件。那么通过link map
文件可以查找到函数对应的位置&#xff0c;以及该函数的汇编实现。
在工程的根目录下创建test.order
文件
内容如下
_main
-[ViewController viewDidLoad]
-[Controller hello]
_test1
其中-[Controller hello]
方法不存在&#xff0c;在工程中配置Order file
文件。如下
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/1a2cd364f38963ed.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
运行或者Build
一下&#xff0c;重新查看link map
文件
![在这里插入图片描述](https://img8.php1.cn/3cdc5/18d35/9f3/ba16392d5d69f82f.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpcXVuWmhhbmc&#61;,size_16,color_FFFFFF,t_70#pic_center)
有文件内容可知&#xff0c;不仅进行了重排&#xff0c;而且还进行了错误去除。
问题
如果做这样一件事&#xff0c;需要写一个完整的order_file
出来。那么所有的方法名称怎么获取&#xff1f;
思路
fishhook
可以hook系统函数 - 如果我们hook住
objc_msgSend
函数是否可行&#xff1f; - 由于
objc_msgSend
函数是可变参数&#xff0c;因此hook的部分需要写汇编 - 但是
Block
、Swift
、C函数
并不是使用objc_msgSend
函数调用的
- Clang插桩&#xff1a;100%符号的覆盖&#xff0c;因为Clang解析时会将文件的进行词法分析与语法分析。
—持续更新中&#xff01;—