【腾讯Bugly干货分享】聊聊苹果的Bug - iOS 10 nano_free Crash
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/hnwj24xqrtOhcjEt_TaQ9w
作者:张三华
导语
精神哥最近发现, 很多开发者在 iOS10 上遇到了一类堆栈为nano_free字样的Crash,也有很多人向我们Bugly客服反馈遇到了这类问题,但并没有好的解决方案。正当大家都束手无策的时候,微信强大的技术团队针对这类Crash进行了深度研究,并提出了一个解决方案。原来微信也遇到了这个问题呢,我们一起来看看他们是如何干掉这个Crash的吧!
背景
iOS 10.0-10.1.1上,新出现了一类堆栈为nano_free字样的crash问题,困扰了我们一段时间,这里主要分享解决这个问题的思路,最后尝试提出一个解决方案可供参考。
它的crash堆栈大致为:
- 这种crash我们并不陌生,一般野指针的问题,也是这样的堆栈。但在iOS 10发布之后,这类crash就嗖地窜到了微信的crash排行榜的前列,而此时微信并没有发布新版本。
- 通过和一些内部、外部团队的交流,发现这是个共性问题,例如:https://forums.developer.apple.com/thread/63546
这两种迹象表明,这很可能是苹果的bug。按流程,我们向苹果提了bug report,并得到回复:“iOS 10.2 Beta有稳定性提升”。
终于等到iOS 10.2 Beta发布,我们重新统计了此类crash的系统版本分布。发现不仅在10.2 Beta正常,而且iOS 9也没有crash。
苹果给我们的建议是:“引导用户升级系统”。这当然能解决问题,但用户升级系统是个漫长的周期。
而其实我们非常关注这个问题的原因,不仅是线上版本的crash,更是在我们的开发分支,它的crash概率异常的高。如果不搞清楚触发crash的原因,那这将是一颗定时炸弹,不知道何时就会被我们合入主线,发布出去。因此我们着手开始做一些尝试。
尝试
首先我们的切入点是iOS 9和10.2 Beta没有crash。既然如此,能否将正常的代码合入微信,替换掉系统的呢?
尝试一:替换dylib
各版本的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是可以解决问题。
尝试四:hook
既然无法完全关闭nano zone,那就尝试跳过它。
因为我们自己通过malloc_zone_create
创建的zone都属于scalable zone,不会导致crash。因此我们可以
- 通过
malloc_zone_create
创建一个新的zone,并命名为guard zone - 用fishhook,将
malloc
和malloc_zone_malloc
等一众常用的内存管理的方法,转发到guard zone
使用这个方案后,crash的概率确实降了一些。但并不彻底解决问题。
因为fishhook无法hook掉其他dylib的调用,也就是说,系统的调用(如Cocoa、CoreFoundation等)依然是走nano zone。
尝试五:跳过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 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!
【腾讯Bugly干货分享】聊聊苹果的Bug - iOS 10 nano_free Crash的更多相关文章
- 【腾讯Bugly干货分享】H5 视频直播那些事
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57a42ee6503dfcb22007ede8 Dev Club 是一个交流移动 ...
- 【腾讯Bugly干货分享】JSPatch 成长之路
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/579efa7083355a9a57a1ac5b Dev Club 是一个交流移动 ...
- 【腾讯Bugly干货分享】微信终端跨平台组件 Mars 系列 - 我们如约而至
导语 昨天上午,微信在广州举办了微信公开课Pro.于是,精神哥这两天的朋友圈被小龙的"八不做"刷屏了.小伙伴们可能不知道,下午,微信公开课专门开设了技术分论坛.在分论坛中,微信开源 ...
- 【腾讯Bugly干货分享】程序员们也该知道的事——“期权和股票”
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/pfj9NLLuKYAfJJF84R9WAw 作者:B ...
- 【腾讯Bugly干货分享】跨平台 ListView 性能优化
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/FbiSLPxFdGqJ00WgpJ94yw 导语 精 ...
- 【腾讯Bugly干货分享】美团大众点评 Hybrid 化建设
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/rNGD6SotKoO8frmxIU8-xw 本期 T ...
- 【腾讯Bugly干货分享】从0到1打造直播 App
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5811d42e7fd6ec467453bf58 作者:李智文 概要 分享内容: ...
- 【腾讯Bugly干货分享】QQ电话适配iOS10 Callkit框架
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/58009392302e4725036142fc Dev Club 是一个交流移动 ...
- 【腾讯Bugly干货分享】安卓单元测试:What, Why and How
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d28349101cd07a5404c415 Dev Club 是一个交流移动 ...
随机推荐
- 【.net 深呼吸】设置序列化中的最大数据量
欢迎收看本期的<老周吹牛>节目,由于剧组严重缺钱,故本节目无视频无声音.好,先看下面一个类声明. [DataContract] public class DemoObject { [Dat ...
- 看完SQL Server 2014 Q/A答疑集锦:想不升级都难!
看完SQL Server 2014 Q/A答疑集锦:想不升级都难! 转载自:http://mp.weixin.qq.com/s/5rZCgnMKmJqeC7hbe4CZ_g 本期嘉宾为微软技术中心技术 ...
- Python学习--04条件控制与循环结构
Python学习--04条件控制与循环结构 条件控制 在Python程序中,用if语句实现条件控制. 语法格式: if <条件判断1>: <执行1> elif <条件判断 ...
- Ubuntu 16.10 开启PHP错误提示
两个步骤: 修改php.ini配置文件中的error_reporting 和 display_errors两地方内容: sudo vim /etc/php/7.0/apache2/php.ini er ...
- [APUE]文件和目录(上)
一.文件权限 1. 各种ID 我在读这一章时遇到了各种ID,根据名字完全不清楚什么意思,幸好看到了这篇文章,http://blog.csdn.net/ccjjnn19890720/article/de ...
- JavaScript动画-拖拽改变元素大小
▓▓▓▓▓▓ 大致介绍 拖拽改变元素大小是在模拟拖拽上增加了一些功能 效果:拖拽改变元素大小 ▓▓▓▓▓▓ 拖拽改变元素大小原理 首先这个方块得知道我们想要改变这个它的大小,所以我给它设定一个范围,当 ...
- Axure 8.0.0.3312可用注册码
用户名:aaa 注册码:2GQrt5XHYY7SBK/4b22Gm4Dh8alaR0/0k3gEN5h7FkVPIn8oG3uphlOeytIajxGU 用户名:axureuser 序列号:8wFfI ...
- css选择器
常用css选择器,希望对大家有所帮助,不喜勿喷. 1.*:通用选择器 * { margin: 0; padding: 0; } 选择页面上的全部元素,通常用于清除浏览器默认样式,不推荐使用. 2.#i ...
- 安卓GreenDao框架一些进阶用法整理
大致分为以下几个方面: 一些查询指令整理 使用SQL语句进行特殊查询 检测表字段是否存在 数据库升级 数据库表字段赋初始值 一.查询指令整理 1.链式执行的指令 return mDaoSession. ...
- Android中Fragment与Activity之间的交互(两种实现方式)
(未给Fragment的布局设置BackGound) 之前关于Android中Fragment的概念以及创建方式,我专门写了一篇博文<Android中Fragment的两种创建方式>,就如 ...