本文來自於騰訊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 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!