本文来自于腾讯Bugly公众号(weixinBugly),未经做者赞成,请勿转载,原文地址:https://mp.weixin.qq.com/s/hnwj24xqrtOhcjEt_TaQ9w微信
做者:张三华app
精神哥最近发现, 不少开发者在 iOS10 上遇到了一类堆栈为nano_free字样的Crash,也有不少人向咱们Bugly客服反馈遇到了这类问题,但并无好的解决方案。正当你们都一筹莫展的时候,微信强大的技术团队针对这类Crash进行了深度研究,并提出了一个解决方案。原来微信也遇到了这个问题呢,咱们一块儿来看看他们是如何干掉这个Crash的吧!函数
iOS 10.0-10.1.1上,新出现了一类堆栈为nano_free字样的crash问题,困扰了咱们一段时间,这里主要分享解决这个问题的思路,最后尝试提出一个解决方案可供参考。工具
它的crash堆栈大体为:测试
这两种迹象代表,这极可能是苹果的bug。按流程,咱们向苹果提了bug report,并获得回复:“iOS 10.2 Beta有稳定性提高”。优化
终于等到iOS 10.2 Beta发布,咱们从新统计了此类crash的系统版本分布。发现不只在10.2 Beta正常,并且iOS 9也没有crash。操作系统
苹果给咱们的建议是:“引导用户升级系统”。这固然能解决问题,但用户升级系统是个漫长的周期。scala
而其实咱们很是关注这个问题的缘由,不只是线上版本的crash,更是在咱们的开发分支,它的crash几率异常的高。若是不搞清楚触发crash的缘由,那这将是一颗定时炸弹,不知道什么时候就会被咱们合入主线,发布出去。所以咱们着手开始作一些尝试。指针
首先咱们的切入点是iOS 9和10.2 Beta没有crash。既然如此,可否将正常的代码合入微信,替换掉系统的呢?调试
各版本的dylib能够在macOS的~/Library/Developer/Xcode/iOS DeviceSupport/
找到,咱们选了iOS 9.3.5的libsystem_malloc.dylib
。尝试编入时却报连接错误:
ld: cannot link directly with /Users/sanhuazhang/Desktop/TestNanoCrash/libsystem_malloc.dylib. Link against the umbrella framework 'System.framework' instead. for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation)
这个是由于dylib的LY_SUB_FRAEWORK
段指明它属于System.framework
,直接被编译器拒绝了。看来没有办法。(若是有同窗知道如何绕过这个保护,烦请赐教。)
libsystem_malloc.dylib
的源码能够在 https://opensource.apple.com/tarballs/libmalloc/ 找到。这里有多个版本,用otool找到iOS 9.3.5对应的源码是libmalloc-67.40.1.tar.gz。
然而这份源码是不完整的,只能读不能编译。看来这个方法也行不通。
上述两个方法不行,就有点一筹莫展了,只能阅读源码,尝试找突破口。 在libsystem_malloc.dylib
中,对内存的管理有两个实现:nano zone和scalable zone。他们分别管理不一样大小的内存块:
其中nano zone分配nano类型的指针,而scalable zone则分配其余三种类型。nano zone的管理区间和scalable zone是有重叠的,能够理解为nano zone是scalable在小内存下的一个优化。
这两种方法经过MallocZoneNano
的环境变量进行配置:
MallocZoneNano=1
时,default zone为nano zone,不知足nano zone的内存会fall through到它的helper zone,而helper zone是一个scalable zone。MallocZoneNano=0
时,deafult zone为scalable zone。经过getenv("MallocZoneNano")
能够拿到环境变量的值,咱们发现,在iOS 9和iOS 10.2 Beta中,MallocZoneNano=0
,而其余系统MallocZoneNano=1
。
换句话说,苹果并非修复了这个问题,而只是屏蔽了。所以其实咱们在尝试一中提到替换dylib,即便替换成功,也是不解决问题的。
结合最初的crash堆栈,咱们知道crash是发生在nano zone内的,那是否能够关掉nano zone呢?
MallocZoneNano=0
经过setenv
方法,能够设置环境变量,修改MallocZoneNano=0
。然而并无效果,由于dylib的初始化在微信以前,此时微信还未启动。
根据苹果的文档,Info.plist的LSEnvironment
字段,能够设置环境变量,然而这个只适用于macOS。
在Xcode的Schema里设置MallocZoneNano=0
后,本地再也不出现crash。但schema只适用于调试阶段,不能编进app里。
看来这个方法也行不通,但起码验证了,关掉nano zone是能够解决问题。
既然没法彻底关闭nano zone,那就尝试跳过它。
由于咱们本身经过malloc_zone_create
建立的zone都属于scalable zone,不会致使crash。所以咱们能够
malloc_zone_create
建立一个新的zone,并命名为guard zonemalloc
和malloc_zone_malloc
等一众经常使用的内存管理的方法,转发到guard zone使用这个方案后,crash的几率确实降了一些。但并不完全解决问题。
由于fishhook没法hook掉其余dylib的调用,也就是说,系统的调用(如Cocoa、CoreFoundation等)依然是走nano zone。
从上面咱们知道,nano zone管理的是0-256字节的内存,若是内存不在这个区间,则会fall through到helper zone。而zone的结构是公开的:
那么能够用tricky一点的方法:修改nano zone和helper zone的函数指针,让nano zone的内存申请虚增,超过256字节,以骗过nano zone,而fall through到helper zone后,再恢复为真正的大小。以malloc为例,具体实现为:
因为内存有限,size的最高位通常不会被使用,所以咱们能够用这一位来标记。
当我满心觉得终于解决问题时,却发现,crash几率不只没有下降,反而到了几乎必现的程度。而此时除了少数在替换前就申请的内存是走的nano zone,其余内存都是在scalable zone内被管理。这一现象不由让人怀疑,nano_free的crash,极可能是zone判断错误。即在scalable zone申请的内存,却在nano zone中释放。
为了验证,咱们还得从源码中搞清楚怎么区分一个指针属于nano zone仍是scalable zone:
能够看到,在x86下,是经过获取指针地址所属的段来判断zone的。当signature知足0x00006这个段时,则属于nano zone。
虽然这份代码里没有提供arm下的判断方式,但能够结合源码中对signature判断的函数,并经过符号断点,很快就能找到arm下比较signature的汇编。
即:当ptr>>28==0x17时,属于nano zone。
经过测试代码能够发现,小于256字节的指针确实在0x17段。然而,代码跑了一阵子以后,大于256字节的指针也落在了0x17段。
彷佛咱们已经很接近问题的核心了。再来一段测试代码验明真身。
先经过循环不断地申请257字节的内存,并保存起来。这些内存应该都落在scalable zone中。当出现0x17段的内存时,咱们break掉。
能够假设在此以后scalable zone内申请的内存,都在0x17段,具体代码为:
咱们新建了一个iOS的Single View Application,除了这段代码,没有作其余任何的修改。问题重现了:
从重现的代码来看,要真正规避nano_free类型的crash出现,只能是减小内存的使用,但这并很差操做。所以,解决思路仍是回到保护上。
结合上面提到尝试3和4,咱们进行了这样的修改。
建立一个本身的zone,命名为guard zone。
修改nano zone的函数指针,重定向到guard zone。 a.对于没有传入指针的函数,直接重定向到guard zone。 b.对于有传入指针的函数,先用size判断所属的zone,再进行分发。
这里须要特别注意的是,由于在修改函数指针前,已经有一部分指针在nano zone中申请了。所以对于每一个传入的指针,咱们都须要找到它所属的zone。代码示例为:
注:
更多精彩内容欢迎关注腾讯 Bugly的微信公众帐号:
腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的状况以及解决方案。智能合并功能帮助开发同窗把天天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同窗定位到出问题的代码行,实时上报能够在发布后快速的了解应用的质量状况,适配最新的 iOS, Android 官方操做系统,鹅厂的工程师都在使用,快来加入咱们吧!