BAD_ACCESS在什么情況下出現(xiàn)?
- 訪問(wèn)了野指針,比如對(duì)一個(gè)已經(jīng)釋放的對(duì)象執(zhí)行了release、訪問(wèn)已經(jīng)釋放對(duì)象的成員變量或者發(fā)消息。
- 死循環(huán)
EXC_BAD_ACCESS 的本質(zhì)
在C和Objective-C中,你一直在處理指針。指針無(wú)非是存儲(chǔ)另一個(gè)變量的內(nèi)存地址的變量。當(dāng)您向一個(gè)對(duì)象發(fā)送消息時(shí),指向該對(duì)象的指針將會(huì)被引用。這意味著,你獲取了指針?biāo)傅膬?nèi)存地址,并訪問(wèn)該存儲(chǔ)區(qū)域的值。
當(dāng)該存儲(chǔ)器區(qū)域不再映射到您的應(yīng)用時(shí),或者換句話說(shuō),該內(nèi)存區(qū)域在你認(rèn)為使用的時(shí)候卻沒(méi)有使用,也就是說(shuō)該內(nèi)存區(qū)域是無(wú)法訪問(wèn)的。 這時(shí)內(nèi)核會(huì)拋出一個(gè)異常( EXC ),表明你的應(yīng)用程序不能訪問(wèn)該存儲(chǔ)器區(qū)域(BAD ACCESS) 。
當(dāng)你碰到EXC_BAD_ACCESS ,這意味著你試圖發(fā)送消息到的內(nèi)存塊,但內(nèi)存塊無(wú)法執(zhí)行該消息。但是,在某些情況下, EXC_BAD_ACCESS是由被損壞的指針引起的。每當(dāng)你的應(yīng)用程序嘗試引用損壞的指針,一個(gè)異常就會(huì)被內(nèi)核拋出。
如何調(diào)試BAD_ACCESS錯(cuò)誤
1.重寫object的respondsToSelector方法,實(shí)現(xiàn)出現(xiàn)EXEC_BAD_ACCESS前訪問(wèn)的最后一個(gè)object
2.僵尸調(diào)試模式:通過(guò) Zombie
3.設(shè)置全局?jǐn)帱c(diǎn)快速定位問(wèn)題代碼所在行
4.Xcode 7 已經(jīng)集成了BAD_ACCESS捕獲功能:Address Sanitizer。
下面會(huì)對(duì)著四種方法一一介紹
1.重寫object的respondsToSelector方法,實(shí)現(xiàn)出現(xiàn)EXEC_BAD_ACCESS前訪問(wèn)的最后一個(gè)object
2.僵尸調(diào)試模式:通過(guò) Zombie
僵尸聽(tīng)起來(lái)有點(diǎn)戲劇性,但它實(shí)際上是為幫助我們調(diào)試 EXC_BAD_ACCESS 功能而取得一個(gè)偉大的名字。讓我來(lái)解釋它是如何工作的。
在Xcode中,您可以啟用僵尸對(duì)象,這意味著被釋放的對(duì)象將會(huì)以僵尸的形式被保留。換言之,保留釋放的對(duì)象就是為了調(diào)試。這里沒(méi)有涉及任何魔法。如果您向僵尸對(duì)象發(fā)送消息,你的應(yīng)用程序?qū)?huì)由于 EXC_BAD_ACCESS 而崩潰。
這樣做的好處在于,讓EXC_BAD_ACCESS難以調(diào)試的原因是,你不知道你的應(yīng)用程序試圖訪問(wèn)哪個(gè)對(duì)象。僵尸對(duì)象在許多情況下解決這個(gè)問(wèn)題。通過(guò)保留已釋放的對(duì)象,Xcode可以告訴你你試圖訪問(wèn)哪個(gè)對(duì)象,這使的查找問(wèn)題原因容易得多。
在Xcode中啟用僵尸對(duì)象是很容易的。注意,這可能會(huì)因的Xcode的版本而不同的。以下方法適用于Xcode的8,單擊左上角的Edit Scheme,并選中Edit Scheme。
在左側(cè)選中Run ,在上方打開(kāi) Diagnostics選項(xiàng)。要啟用僵尸對(duì)象,勾選 Enable Zombie Objects選框。

這時(shí)如果遇到 EXC_BAD_ACCESS ,在Xcode的控制臺(tái)輸出,會(huì)告訴你問(wèn)題出在哪里。
2015-08-12 06:31:55.501 Debug[2371:1379247] -[ChildViewController respondsToSelector:] message sent to deallocated instance 0x17579780
在上面的例子中, 告訴我們, respondsToSelector的消息:被發(fā)送到一個(gè)僵尸對(duì)象。然而,僵尸對(duì)象不再是ChildViewController類的一個(gè)實(shí)例。以前分配給ChildViewController實(shí)例的內(nèi)存區(qū)域不再映射到您的應(yīng)用程序。這為你了解問(wèn)題產(chǎn)生的根本原因提供一個(gè)不錯(cuò)的建議。
不幸的是,僵尸對(duì)象將無(wú)法保存您的一天每次崩潰的EXC_BAD_ACCESS的記錄。既然僵尸對(duì)象沒(méi)有這些方法,那么你可以采取其他的方法進(jìn)行一些適當(dāng)?shù)姆治觥?/p>
3.設(shè)置全局?jǐn)帱c(diǎn)快速定位問(wèn)題代碼所在行
4.Xcode 7 已經(jīng)集成了BAD_ACCESS捕獲功能:Address Sanitizer。 用法如下:在配置中勾選?Enable Address Sanitizer

順便對(duì) AddressSanitizer 和 Zombie 做些比較
原理
-
zombie:
zombie的原理是用生成僵尸對(duì)象來(lái)替換dealloc的實(shí)現(xiàn),當(dāng)對(duì)象引用計(jì)數(shù)為0的時(shí)候,將需要dealloc的對(duì)象轉(zhuǎn)化為僵尸對(duì)象。如果之后再給這個(gè)僵尸對(duì)象發(fā)消息,則拋出異常,并打印出相應(yīng)的信息,調(diào)試者可以很輕松的找到異常發(fā)生位置。 -
AddressSanitizer:
AddressSanitizer的原理是當(dāng)程序創(chuàng)建變量分配一段內(nèi)存時(shí),將此內(nèi)存后面的一段內(nèi)存也凍結(jié)住,標(biāo)識(shí)為中毒內(nèi)存。如圖所示,黃色是變量所占內(nèi)存,紫色是凍結(jié)的中毒內(nèi)存。

當(dāng)程序訪問(wèn)到中毒內(nèi)存時(shí)(越界訪問(wèn)),就會(huì)拋出異常,并打印出相應(yīng)log信息。調(diào)試者可以根據(jù)中斷位置和的log信息,識(shí)別bug。如果變量釋放了,變量所占的內(nèi)存也會(huì)標(biāo)識(shí)為中毒內(nèi)存,這時(shí)候訪問(wèn)這段內(nèi)存同樣會(huì)拋出異常(訪問(wèn)已經(jīng)釋放的對(duì)象)。
適用性
了解原理之后我們可以大概猜到Zombie和AddressSanitizer的適用性,不過(guò)一切還得以實(shí)驗(yàn)結(jié)果為準(zhǔn):

實(shí)驗(yàn)后發(fā)現(xiàn)AddressSanitizer比Zombie擁有更強(qiáng)大的捕獲能力,特別是在malloc對(duì)象和內(nèi)存越界方面,zombie幾乎無(wú)能為力。如果在debug的時(shí)候無(wú)法捕獲異常,上線之后crash log中概率性的EXC_BAD_ACCESS簡(jiǎn)直是一種災(zāi)難。
缺陷
上面研究發(fā)現(xiàn)AddressSanitizer比zombie更有優(yōu)勢(shì),那么AddressSanitizer有什么缺陷呢?
- AddressSanitizer可能會(huì)沒(méi)有l(wèi)og,不過(guò)會(huì)在訪問(wèn)中毒內(nèi)存的代碼處斷住,這倒是對(duì)debug影響不大
- 使用AddressSanitizer除了分配對(duì)象的內(nèi)存之外,還需要額外的內(nèi)存,這會(huì)導(dǎo)致App內(nèi)存大量增加,用起來(lái)有可能會(huì)比較卡
雖然AddressSanitizer有一些缺陷,但是總的來(lái)說(shuō)AddressSanitizer還是一個(gè)非常好用的debug工具。
AddressSanitizer使用
在了解AddressSanitizer的功能之后,我們來(lái)看看AddressSanitizer用。
AddressSanitizer的使用其實(shí)非常簡(jiǎn)單,在Xcode上方選擇設(shè)備的地方,點(diǎn)擊工程名字,選擇Edit Scheme.

在Diagnostics中選中enable address sanitizer即可。

AddressSanitizer開(kāi)啟之后,在debug過(guò)程中,如果遇到EXC_BAD_ACCESS的問(wèn)題,Xcode會(huì)自動(dòng)中斷,拋出異常
其他compiler flags
實(shí)際AddressSanitizer很早以前就有了,只是沒(méi)在Xcode中集成而已。除了AddressSanitizer還有很多其他的compiler flags,undefined-trap就是其中的一種。undefined-trap的功能也非常強(qiáng)大,它可以檢測(cè)出程序中的不明確行為,如數(shù)據(jù)溢出等。
下面我們以u(píng)ndefined-trap舉例,看看怎么用其他的compiler flags:
在Build Settings中的Custom Compiler Flags下為other C Flags添加-fsanitize=undefined-trap -fsanitize-undefined-trap-on-error

完成undefined-trap的設(shè)置之后,當(dāng)程序的數(shù)據(jù)發(fā)生溢出行為時(shí),系統(tǒng)就會(huì)拋出異常。

End
經(jīng)過(guò)ARC的洗禮之后,普通的訪問(wèn)釋放對(duì)象產(chǎn)生的EXC_BAD_ACCESS已經(jīng)大量減少了,現(xiàn)在出現(xiàn)的EXC_BAD_ACCESS有很大一部分來(lái)自malloc的對(duì)象或者越界訪問(wèn)。簡(jiǎn)單的敵人已經(jīng)被干掉,剩下的都是難纏的對(duì)手了。還好Apple給我們升級(jí)了裝備,以后遇到EXC_BAD_ACCESS應(yīng)該不用那么心驚膽戰(zhàn)了吧?
Reference
Xcode7中你一定要知道的炸裂調(diào)試神技
在Xcode 7上直接使用Address Sanitizer
Clang 3.8 documentation
參考 出處