核心物件 是 Windows 核心在系統記憶體中實作的基本數據物件。 它們代表裝置、驅動程式、檔案、登錄機碼、事件、旗號、進程和線程等實體。
大部分的核心物件都不是永久性的。 要防止 Windows 在內核模式驅動程式使用時刪除非永久核心物件,驅動程式會取得對該物件的計數參考。 當驅動程式不再需要物件時,驅動程式會釋放對物件的參考。
如果驅動程式未釋放物件的所有參考,對象的參考計數永遠不會達到零,而且物件管理員永遠不會刪除它。 在作系統重新啟動之前,您無法重複使用流失的資源。
如果驅動程式對物件引用不足,則會發生另一種類型的參考錯誤。 在此情況下,驅動程式釋放的物件參考數量比驅動程式實際持有的多。 此錯誤可能會導致物件管理員過早地刪除物件,而其他用戶端仍在嘗試存取物件。
核心物件的洩漏和參考不足,可能會有難以追蹤的漏洞。 例如,進程物件或裝置物件可能有數萬個參考。 在這些情況下,很難識別對象參考 Bug 的來源。
在 Windows 7 和更新版本的 Windows 中,您可以提供對象參考的標記,讓這些 Bug 更容易找到。 下列例程會將標籤與核心物件的參考取得和釋放建立關聯:
ObDereferenceObjectDeferDeleteWithTag
ObReferenceObjectByHandleWithTag
ObReferenceObjectByPointerWithTag
例如,適用於 Windows 7 及以後版本的 ObReferenceObjectWithTag 和 ObDereferenceObjectWithTag 是增強版的 ObReferenceObject 和 ObDereferenceObject 例程,而這些例程則在 Windows 2000 及以後版本中提供。 這些增強的例程可讓您提供四位元組的自定義標籤作為輸入參數。 您可以使用 Windows 偵錯工具來 檢查 物件參考追蹤 ,其中包含每個呼叫的標記值。 ObReferenceObject 和 ObDereferenceObject 不會讓呼叫端指定自定義標籤,但在 Windows 7 和更新版本的 Windows 中,這些例程會將默認卷標(具有標記值 “Dflt”) 新增至追蹤。 因此, 對 ObReferenceObject 或 ObDereferenceObject 的呼叫與對 ObReferenceObjectWithTag 或 ObDereferenceObjectWithTag 的呼叫具有相同的效果,指定 “Dflt” 的標記值。 (在您的程式中,此標記值會顯示為 0x746c6644 或 'tlfD'。
若要追蹤潛在的物件流失或參考不足,請在驅動程式中識別一組相關聯的 ObReferenceObjectXxxWithTag 和 ObDereferenceObjectXxxWithTag 呼叫,以遞增和遞減特定對象的參考計數。 選擇一個通用標籤值(例如 "Lky8")以用於此集合中的所有呼叫。 當您的驅動程式使用 物件完成之後,遞減的數目應該完全符合遞增數目。 如果這些數字不相符,您的驅動程式可能有對象參考錯誤。 調試程式可以比較每個標記值的遞增和遞減數目,並告訴您它們是否不相符。 透過這項功能,您可以快速找出參考計數不相符的來源。
若要在 Windows 偵錯工具中檢視對象參考追蹤,請使用 !obtrace 內核模式調試程式延伸模組。 如果物件參考追蹤開啟,您可以使用 !obtrace 延伸模組來顯示對象參考標記。 根據預設,對象參考追蹤已關閉。 使用 全域旗標編輯器 (Gflags) 來啟用對象參考追蹤。 如需 Gflags 的詳細資訊,請參閱 設定對象參考追蹤。
!obtrace 延伸模組的輸出包含 “Tag” 數據行,如下列範例所示:
0: kd> !obtrace 0x8a226130
Object: 8a226130
Image: leakyapp.exe
Sequence (+/-) Tag Stack
-------- ----- ---- --------------------------------------------
36 +1 Dflt nt!ObCreateObject+1c4
nt!NtCreateEvent+93
nt!KiFastCallEntry+12a
37 +1 Dflt nt!ObpCreateHandle+1c1
nt!ObInsertObjectEx+d8
nt!ObInsertObject+1e
nt!NtCreateEvent+ba
nt!KiFastCallEntry+12a
38 -1 Dflt nt!ObfDereferenceObjectWithTag+22
nt!ObInsertObject+1e
nt!NtCreateEvent+ba
nt!KiFastCallEntry+12a
39 +1 Lky8 nt!ObReferenceObjectByHandleWithTag+254
leakydrv!LeakyCtlDeviceControl+6c
nt!IofCallDriver+63
nt!IopSynchronousServiceTail+1f8
nt!IopXxxControlFile+6aa
nt!NtDeviceIoControlFile+2a
nt!KiFastCallEntry+12a
3a -1 Dflt nt!ObfDereferenceObjectWithTag+22
nt!ObpCloseHandle+7f
nt!NtClose+4e
nt!KiFastCallEntry+12a
-------- ----- ---- --------------------------------------------
References: 3, Dereferences 2
Tag: Lky8 References: 1 Dereferences: 0 Over reference by: 1
在此範例中的最後一行顯示,「Lky8」標記相關聯的參考計數與解參照計數不相符,這種不相符的結果是參考次數過高(也就是說,發生了洩漏)。
如果結果改為參考不足,則 !obtrace 輸出的最後一行可能如下所示:
Tag: Lky8 References: 1 Dereferences: 2 Under reference by: 1
根據預設,作業系統會在釋放物件後刪除物件的參考追蹤,以節省記憶體。 即使在系統釋放物件後,記憶體中保留相關痕跡,在追蹤參考不足問題時會很有幫助。 為了達到此目的,Gflags 工具會提供「永久」選項,以在電腦關閉后再次啟動時,保留記憶體中的追蹤。
Windows XP 引進了對象參考追蹤。 由於一開始追蹤未包含標記,因此開發人員必須使用較不方便的技術來識別對象參考錯誤。 調試程式可以追蹤開發人員依物件類型選定的物件群組的參考。 開發人員唯一能夠識別對象參考和取值的各種來源,就是比較其呼叫堆疊。 雖然先前 的 !obtrace 範例只包含五個堆疊,但某些類型的物件,例如進程 (EPROCESS) 物件,可能會參考和取值數千次。 檢查數千個堆疊時,若不使用標記,可能很難識別物件洩漏或引用不足的來源。