野指針 Crash

野指針是指指向一個(gè)已刪除的對(duì)象或未申請(qǐng)?jiān)L問(wèn)受限內(nèi)存區(qū)域的指針。本文說(shuō)的Obj-C野指針,說(shuō)的是Obj-C對(duì)象釋放之后指針未置空,導(dǎo)致的野指針(Obj-C里面一般不會(huì)出現(xiàn)未初始化對(duì)象的常識(shí)性錯(cuò)誤)。

既然是訪問(wèn)已經(jīng)釋放的對(duì)象為什么不是必現(xiàn)Crash呢?

因?yàn)閐ealloc執(zhí)行后只是告訴系統(tǒng),這片內(nèi)存我不用了,而系統(tǒng)并沒(méi)有就讓這片內(nèi)存不能訪問(wèn)。

現(xiàn)實(shí)大概是下面幾種可能的情況:

1.對(duì)象釋放后內(nèi)存沒(méi)被改動(dòng)過(guò),原來(lái)的內(nèi)存保存完好,可能不Crash或者出現(xiàn)邏輯錯(cuò)誤(隨機(jī)Crash)。

2.對(duì)象釋放后內(nèi)存沒(méi)被改動(dòng)過(guò),但是它自己析構(gòu)的時(shí)候已經(jīng)刪掉某些必要的東西,可能不Crash、Crash在訪問(wèn)依賴的對(duì)象比如類成員上、出現(xiàn)邏輯錯(cuò)誤(隨機(jī)Crash)。

3.對(duì)象釋放后內(nèi)存被改動(dòng)過(guò),寫(xiě)上了不可訪問(wèn)的數(shù)據(jù),直接就出錯(cuò)了很可能Crash在objc_msgSend上面(必現(xiàn)Crash,常見(jiàn))。

4.對(duì)象釋放后內(nèi)存被改動(dòng)過(guò),寫(xiě)上了可以訪問(wèn)的數(shù)據(jù),可能不Crash、出現(xiàn)邏輯錯(cuò)誤、間接訪問(wèn)到不可訪問(wèn)的數(shù)據(jù)(隨機(jī)Crash)。

5.對(duì)象釋放后內(nèi)存被改動(dòng)過(guò),寫(xiě)上了可以訪問(wèn)的數(shù)據(jù),但是再次訪問(wèn)的時(shí)候執(zhí)行的代碼把別的數(shù)據(jù)寫(xiě)壞了,遇到這種Crash只能哭了(隨機(jī)Crash,難度大,概率低)!!

6.對(duì)象釋放后再次release(幾乎是必現(xiàn)Crash,但也有例外,很常見(jiàn))。


1

仔細(xì)看看上面的關(guān)鍵路徑只有出現(xiàn)被隨機(jī)填入的數(shù)據(jù)是不可訪問(wèn)的時(shí)候才會(huì)必現(xiàn)Crash。

所以把這一隨機(jī)的過(guò)程變成不隨機(jī)的過(guò)程。對(duì)象釋放后在內(nèi)存上填上不可訪問(wèn)的數(shù)據(jù),其實(shí)這種技術(shù)其實(shí)一直都有,xcode的Enable Scribble就是這個(gè)作用。


2

但是有個(gè)問(wèn)題:這個(gè)方法不能放在測(cè)試那邊用!因?yàn)榭偛荒茏寽y(cè)試裝了xcode來(lái)測(cè)試吧?

于是我們自己動(dòng)手實(shí)現(xiàn)一個(gè),這個(gè)過(guò)程中我們要解決幾個(gè)問(wèn)題:

1.怎么在內(nèi)存釋放后填上不可訪問(wèn)的數(shù)據(jù)?

內(nèi)存釋放很可能不在我們的代碼中。為此我們需要hook對(duì)象釋放的接口,內(nèi)存時(shí)候之后馬上執(zhí)行我們的破壞工作。

2.我們要重寫(xiě)對(duì)象釋放的接口,重寫(xiě)哪個(gè)呢?

NSObject的dealloc、runtime的 object_dispose,C的free應(yīng)該都是可以,但是各有優(yōu)點(diǎn),我選擇的是覆蓋面最廣的free,free是C的函數(shù),重寫(xiě)了它之后還可以順帶解決一部分C的野指針問(wèn)題。

3.怎么重寫(xiě)?

重寫(xiě)C的接口場(chǎng)景的有兩種:

a.替換系統(tǒng)動(dòng)態(tài)庫(kù)

b.hook

替換動(dòng)態(tài)庫(kù)太麻煩,還不知道行不行得通;hook我們就找現(xiàn)成的fishhook,github里面找的,但現(xiàn)成的代碼需要防止代碼沖突。

4.填充的不可訪問(wèn)的數(shù)據(jù)的長(zhǎng)度怎么確定?

獲取內(nèi)存長(zhǎng)度的接口不在標(biāo)準(zhǔn)庫(kù)中,好在在Mac和iOS中可以用malloc_size就可以。

5.填什么? ? ? ? ? ? ??和xcode一樣,填0x55。

上hook后的free代碼:

[size=0.85em]void safe_free(void* p){? ? size_t memSiziee=malloc_size(p);? ? memset(p, 0x55, memSiziee);? ? orig_free(p);? ? return;}

測(cè)試一下,出現(xiàn)了和Enable Scribble一樣的Crash!

以上就是一種在內(nèi)存釋放后填充0x55使野指針后數(shù)據(jù)不能訪問(wèn),從而使某些野指針從不必現(xiàn)Crash變成了必現(xiàn);



3

其實(shí)這就是上一篇文中留下了幾個(gè)問(wèn)題之一,如果我們填充0x55后內(nèi)存又被別的內(nèi)存覆蓋了,最終還是會(huì)出現(xiàn)隨機(jī)Crash。而在真實(shí)環(huán)境中,這種情況是非常常見(jiàn)的。

我們?cè)偈崂硪幌逻@個(gè)過(guò)程:

1.我們?cè)诩磳⒁尫诺奶盍?x55,之后調(diào)用了free真正釋放,內(nèi)存被系統(tǒng)回收。

2.這個(gè)時(shí)候系統(tǒng)隨時(shí)可能把這片內(nèi)存給別的代碼使用,也就是說(shuō)我們的0x55被再次寫(xiě)上隨機(jī)的數(shù)據(jù)(在這里再?gòu)?qiáng)調(diào)一下,訪問(wèn)野指針是不會(huì)Crash的,只有野指針指向的地址被寫(xiě)上了有問(wèn)題的數(shù)據(jù)才會(huì)引發(fā)Crash)。

3.假如釋放的內(nèi)存上又填上了另一個(gè)對(duì)象的指針,而那個(gè)對(duì)象也有同樣的一個(gè)方法,那很可能只是邏輯上有問(wèn)題,并不會(huì)直接Crash,甚至悄無(wú)聲息地像什么事情都沒(méi)發(fā)生一樣。(這個(gè)地方可能會(huì)發(fā)生多種情況,可以參考之上一篇文章中的圖)

沒(méi)有發(fā)生Crash可不是好事,因?yàn)檫@種情況如果后續(xù)再Crash,問(wèn)題就非常難查,因?yàn)槟憧吹降腃rash棧很可能和出錯(cuò)的代碼完全沒(méi)有關(guān)聯(lián)。既然這個(gè)問(wèn)題這么棘手,最好還是和之前一樣,讓這個(gè)Crash提前暴露。

首先,我們要解決的問(wèn)題就是怎么讓系統(tǒng)不再往這片釋放的內(nèi)存上亂放東西。

要控制底層內(nèi)存管理機(jī)制讓它不使用這些內(nèi)存可能很困難。但是,我們變通一下,簡(jiǎn)單粗暴地,我們干脆就不釋放這片內(nèi)存了。也就是當(dāng)free被調(diào)用的時(shí)候我們不真的調(diào)用free,而是自己保留著內(nèi)存,這樣系統(tǒng)不知道這片內(nèi)存已經(jīng)不需要用了,自然就不會(huì)被再次寫(xiě)上別的數(shù)據(jù)

為了防止系統(tǒng)內(nèi)存過(guò)快耗盡,還需要額外多做幾件事:

1.自己保留的內(nèi)存大于一定值的時(shí)候就釋放一部分,防止被系統(tǒng)殺死。

2.系統(tǒng)內(nèi)存警告的時(shí)候,也要釋放一部分內(nèi)存。

4

在safe_free以及它調(diào)用的函數(shù)里面盡量不要再用帶鎖的函數(shù),不然很容易導(dǎo)致死鎖。

加上這個(gè)代碼之后APP的內(nèi)存占用會(huì)增大不少,拿過(guò)來(lái)測(cè)試可以,但萬(wàn)萬(wàn)不能放在正式的發(fā)布版本中

關(guān)于性能問(wèn)題,我的機(jī)器是iPhone5,跑在App里面運(yùn)行,還算流暢(不同App性能可能會(huì)有些不同)。

可能由于鎖的存在,會(huì)使cpu線程切換變得頻繁,這樣多線程的問(wèn)題Crash率也可能會(huì)提升(最近遇到一個(gè)多線程引起的Crash很難重現(xiàn),但我加了這個(gè)代碼后就變成了必現(xiàn)Crash)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • [這是第15篇] 導(dǎo)語(yǔ):在當(dāng)前的iOS開(kāi)發(fā)中,雖然ARC為開(kāi)發(fā)者解決了手動(dòng)內(nèi)存管理時(shí)代 的許多麻煩,但是內(nèi)存方面的...
    南華coder閱讀 7,745評(píng)論 10 78
  • 前言 iOS崩潰是讓iOS開(kāi)發(fā)人員比較頭痛的事情,app崩潰了,說(shuō)明代碼寫(xiě)的有問(wèn)題,這時(shí)如何快速定位到崩潰的地方很...
    齊滇大圣閱讀 65,873評(píng)論 29 443
  • void* 類型指針:通用變體類型指針;可以不經(jīng)轉(zhuǎn)換,賦給其他指針,函數(shù)指針除外;malloc返回的就是void*...
    冰吉凌閱讀 3,511評(píng)論 0 18
  • 錯(cuò)位沒(méi)錯(cuò)好,加油吧時(shí)光稍縱即逝。
    salen小倫閱讀 322評(píng)論 0 0
  • 文:饅頭 題記: 如果你問(wèn)我有沒(méi)有一個(gè)人的人生可以讓我有跡可循,那一定是達(dá)達(dá)令。關(guān)注達(dá)達(dá)令快一年了,因?yàn)檫_(dá)達(dá)令走上...
    她的小森林閱讀 510評(píng)論 0 1

友情鏈接更多精彩內(nèi)容