當所指向的物件被釋放或者收回,但是對該指標沒有作任何的修改,以至於該指標仍舊指向已經**的記憶體位址,此情況下該指標便稱野指標野指標異常堪稱
crash界
的半壁江山,相比起n***ception
而言,野指標有這麼兩個特點:
解決野指標最大的難點在於定位。通常線上出現了crash
需要修復時,開發者最重要的乙個步驟是重現crash
。而上文提到了野指標的兩個特性會阻礙我們定位問題,對於這兩個特性,確實也能做一些對應的處理來降低它們的干擾性:
整理一下上述的內容,可以看到目前存在輔助資訊+物件記憶體填充
以及zombie objects
這兩種主要的應對方式。拿前者來說,填充已釋放物件的記憶體風險高,經過嘗試xcode9
的malloc scribble
啟動後已經不會填充物件的記憶體位址。其次,填充記憶體需要去hook
更加底層的api
,這意味著對**能力要求更高。因此,借鑑zombie objects
的實現思路去定位野指標異常是乙個可行的方案
**是一項有趣的機制,它通過在通訊雙方中間,插入乙個中間層。傳送方不再耦合接收方,它只需要將資料傳送給中間層,由中間層來派發給具體的接收方。基於**的思想,可以做許多有趣的東西:
都說訊息傳送
是objective-c
的核心機制,任何乙個物件方法呼叫都會被轉換成objc_msgsend
的方式執行。這一過程中涉及到乙個重要的變數:isa
指標。多數開發者對isa
指標停留在它指向了類的類結構本身的位址,用來表示物件的型別。但是實際上isa
指標要比我們想想的複雜的多,比如objc_msgsend
依賴於isa
來完成訊息的查詢,通過閱讀通過彙編解讀 objc_msgsend可以了解更詳細的匹配過程:
union isa_t
isa_t(uintptr_t value) : bits(value)
class cls;
uintptr_t bits;
struct ;
};複製**
由於方法呼叫與isa
指標相關,因此如果我們修改乙個類的isa
指標使其指向乙個目標類,那麼可以實現物件方法呼叫的攔截,也可以稱作物件方法**。我們並不能直接修改isa
指標,但runtime
提供了乙個object_setclass
介面允許我們動態的對某個類進行重定位
一般來說我們都不應該違背重定位類的記憶體結構對齊原則。但在野指標問題中,物件擁有的記憶體被釋放後是不確定狀態,因此做classa
被重定位成classb
需要保證兩個類的記憶體結構是對齊的,否則可能會發生超出意外的問題
適當的破壞
並不一定是壞事,只是記住在最終釋放物件記憶體時,應當再次重定位回來,防止記憶體洩漏的風險
借鑑於zombie objects
的機制,我們可以實現一套類zombie proxy
機制。通過重定位型別
的做法,在物件dealloc
之前將其isa
指標指向乙個目標類,實現後續呼叫的**。而目標類中所有的方法呼叫都採用n***ception
的機制丟擲異常,並且輸出呼叫物件的實際型別和呼叫方法幫助定位:
重定位後的類由於其實際用於**的用途,更符合proxy
的屬性,因此我將其設定為nsproxy
的子類,多數人可能不知道ios
一共有nsproxy
跟nsobject
兩個根類。另外,為了實現對retain
等記憶體管理相關方法的重寫,目標類應該設定為不支援arc
:
@inte***ce lxdzombieproxy : nsproxy
@property (nonatomic, assign) class originclass;
@end
@implementation lxdzombieproxy
- (void)_throwmessagesentexceptionwithselector: (sel)selector
#define lxdzombiethrowmesssagesentexception() [self _throwmessagesentexceptionwithselector: _cmd]
- (id)retain
- (oneway void)release
- (id)autorelease
- (void)dealloc
- (nsuinteger)retaincount
@end
複製**
由於ios
的方法實際上是以向上呼叫
的鏈式機制實現的,因此只需要hook
掉兩個根類的dealloc
方法就能保證對物件型別的重定位。在hook
掉dealloc
之後有幾個需要注意的點:
為了滿足保證物件能夠在達成釋放條件完成記憶體的**,需要儲存根類的dealloc
原實現,以根類類名作為key
儲存在全域性字典中。並且提供介面__lxd_dealloc
來完成物件的釋放工作:
static inline void __lxd_dealloc(__unsafe_unretained id obj)
nsstring *clsname = nsstringfromclass(rootcls);
lxddeallocpointer deallocimp = null;
[[_rootclassdeallocimps objectforkey: clsname] getvalue: &deallocimp];
if (deallocimp != null)
}nsmutabledictionary *deallocimps = [nsmutabledictionary dictionary];
for (class rootclass in _rootclasses)
複製**
在物件的dealloc
被調起之後,檢測物件型別是否存在白名單中。如果存在,直接繼續完成對物件的釋放工作。否則的話,延後30s
進行釋放工作。為了解除block
引用造成的crash
,使用nsvalue
儲存物件資訊以及使用__unsafe_unretained
來防止臨時變數的引用:
swizzleddeallocblock = [^void(id obj) else );
}} copy];
複製**
野指標問題是訪問了非法記憶體導致的crash
,也就是說要符合兩個條件:記憶體非法
以及指標位址不為null
。在ios
中存在三種不同修飾的指標:
根據野指標異常的引發條件來說,三種修飾指標只有__strong
和__unsafed_unretained
可以導致野指標訪問異常。但是在使用類別重定位
之後,本該釋放的物件會被延時或者不釋放,也就是本該被重置的弱指標也不會發生重置,這時使用弱指標訪問物件應該會被**到zombieproxy
當中發生crash
:
__weak id weakobj = nil;
@autoreleasepool
/// the operate should be crashed
nslog(@"%@", weakobj);
複製**
然而在上面的測試中,發現即便物件被重定位為zombie
並且被阻止釋放之後,weakobj
依舊被成功的設定成了nil
。然後經過objc_runtime原始碼執行和新增斷點測試之後,也沒有weak
指標被重置的呼叫。甚至使用了llvm
的watch set var weakobj
監控弱指標,依舊無法找到呼叫。但weakobj
在dealloc
呼叫之後,不管物件有沒有被釋放,都被重置成了nil
。這也是截止文章出來為止,匪夷所思的疑難雜症
如何定位obj-c野指標隨機crash(一)
如何定位obj-c野指標隨機crash(二)
如何定位obj-c野指標隨機crash(三)
什麼是野指標?野指標的危害?如何避免野指標?
什麼是野指標?野指標是指隨機指向一塊記憶體的指標 野指標的危害?如何避免野指標?我們要在以後養成良好的編碼習慣 1.將沒有指向的指標初始化指向null 指向null的指標不能對他的指向進行修改 2.當想給乙個指標指向的空間賦值時,一定要給這個指標分配空間 malloc 3.當空間分配完後,要檢查這個...
什麼是野指標?如何避免野指標?
野指標不是 null指標,它是隨即指向一塊記憶體的指標。野指標是很危險的,會導致記憶體洩漏,if語句對它不起作用。導致野指標的原因有兩種 1 野指標指向了一塊沒有訪問許可權的記憶體。即指標沒有初始化 2 野指標指向了乙個已經釋放的記憶體。因為野指標是因為我們的不良程式設計習慣造成的,所以我們養成良好...
關於野指標
什麼是野指標?例如 int p 或 int p new int 1 int i 3 p i delete p 像上面的例子所示的,沒有對p申請指定的訪問記憶體區域 也就是說p指標的指向是隨機的,指向的是記憶體空間的隨機位址 或是p 指向乙個已刪除的物件,出現像這樣的指標成為野指標。野指標的危害 就是...