野指標定位

2021-09-11 09:41:48 字數 4583 閱讀 6131

當所指向的物件被釋放或者收回,但是對該指標沒有作任何的修改,以至於該指標仍舊指向已經**的記憶體位址,此情況下該指標便稱野指標

野指標異常堪稱crash界的半壁江山,相比起n***ception而言,野指標有這麼兩個特點:

解決野指標最大的難點在於定位。通常線上出現了crash需要修復時,開發者最重要的乙個步驟是重現crash。而上文提到了野指標的兩個特性會阻礙我們定位問題,對於這兩個特性,確實也能做一些對應的處理來降低它們的干擾性:

整理一下上述的內容,可以看到目前存在輔助資訊+物件記憶體填充以及zombie objects這兩種主要的應對方式。拿前者來說,填充已釋放物件的記憶體風險高,經過嘗試xcode9malloc 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一共有nsproxynsobject兩個根類。另外,為了實現對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方法就能保證對物件型別的重定位。在hookdealloc之後有幾個需要注意的點:

為了滿足保證物件能夠在達成釋放條件完成記憶體的**,需要儲存根類的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指標被重置的呼叫。甚至使用了llvmwatch set var weakobj監控弱指標,依舊無法找到呼叫。但weakobjdealloc呼叫之後,不管物件有沒有被釋放,都被重置成了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 指向乙個已刪除的物件,出現像這樣的指標成為野指標。野指標的危害 就是...