相關知識:Windows kernel driver,x86 knowledge, Windbg command
原文連結
技巧4::幫助發現記憶體洩漏的中斷點:記憶體標籤(Tag)
在開發Windows Kernel Driver的時候,最常使用的記憶體配置的函式之一是ExAllocatePoolWithTag。此函式可以指定一個四個字的標籤(Tag),而配置出來的記憶體便會"貼上"這個標籤。在記憶體洩漏(memory leak)的情況,此標籤就成為解決問題的重要工具!
不過在使用記憶體標籤功能之前,如果系統的不是Windows 2003、Vista或是更新的版本,那我們需要調整一下系統設定。下載Windbg並且安裝完成之後,此時會發現同一包裡面除了Windbg還有另一個小工具:GFlags。這個小工具讓我們可以藉由UI對系統的各項設定進行調整,好處就是不用去死記這些設定對應的Registry。繼續偵錯之前,先讓我們把"Enable Pool Tagging"打開:
題外話,我覺得周大師這篇"Live Debugging環境設定"寫的很棒。
回到本文,現在我們覺得有Memory leak發生,想中斷在ExAllocatePoolWithTag偵錯。除了前一篇所提利用IAT位置來中斷,也可以使用條件中斷,檢查ExAllocatePoolWithTag的第三個參數,如果是我們的標籤就中斷,其餘放行。
假設標籤是"BaPd",轉成ACSII碼 B = 42、a = 61、P = 50、d = 64。因為Intel-CPU處理資料的時候遵守Little endian(小頭派)的運作原則,所以開頭的資料會放在記憶體的低位(B在記憶體的地址比a少一個位元組,a又比P少一個位元組),但是在Windbg顯示32bits的時候是由高位(也就是d)到低位(最低的是開頭的B)顯示,所以會看到d(64)、P(50)、a(61)、B(42),串起來就是64506142。依照本系列第一篇所提及的條件中斷語法:
1: bp nt!ExAllocatePoolWithTag ".if(poi(@esp+0xC) == 64506142){da @esp+0xC L4;k}.else{g}"
這樣就會在每次(呼叫ExAllocatePoolWithTag) + (Tag = "BaPd")的時候,列印出標籤、堆疊的內容並且中斷。但缺點是條件中斷會讓系統效能明顯的降低X10。由於標籤實在是太重要了,Windows提供了另外一種方法:用Windbg以ed(enter value)指令把想要中斷的標籤輸入到nt!poolhittag,如此一來每當屬於該標籤的記憶體被配置,系統會中斷。記得Intel的小頭政策,輸入標籤時要反向輸入CPU才看的懂。
下面這個例子我們先清掉原來的條件中斷點,然後輸入'dPaB'到nt!poolhittag,待中斷發生時檢查堆疊與標籤內容是否是我們想要的:
1: kd> bl
2: 0 e 82734b10 0001 (0001) nt!ExAllocatePoolWithTag ".if(poi(@esp+0xC) == 64506142){da @esp+0xC l4;kvn}.else{g}"
3: kd> bc 0
4: kd> ed nt!PoolHitTag 'dPaB'
5: d> g
6: Break instruction exception - code 80000003 (first chance)
7: nt!DbgBreakPoint:
8: 8269ef0c cc int 3
9: kd> kvn
10: # ChildEBP RetAddr Args to Child
11: 00 8273ca88 82735401 80809f70 83d816f8 80829ae0 nt!DbgBreakPoint (FPO: [0,0,0])
12: 01 8273cadc 829a66c6 00000000 00000038 64506142 nt!ExAllocatePoolWithTag+0x8eb
13: 02 8273cafc 829acc8f 80831cb8 82743100 00000000 nt!BootApplicationPersistentDataInitialize+0x50
14: 03 8273cca0 8292ab03 8080a648 39332b42 83540c00 nt!InitBootProcessor+0x412
15: 04 8273cd0c 82733804 827433c0 82743100 8273d000 nt!KiInitializeKernel+0x770
16: 05 00000000 00000000 00000000 00000000 00000000 nt!KiSystemStartup+0x32c
17: kd> da 8273cadc+0x10 L4
18: 8273caec "BaPd"
技巧5:手動重建堆疊
分析dump的時候,如果遇到沒有symbol的module,又恰巧它用了Frame pointer omission的最佳化,在函式的開頭跟結尾省去了對ebp的操作,那麼stack的分析很可能就落的沒有結果,譬如下面的這個例子,Windbg分析堆疊最後斷在savrt,無法得知堆疊更早的資訊,對於偵錯幫助有限:
1: bd74e438 8045163d nt!IopParseDevice+0xa04
2: bd74e4ac 804a4e8a nt!ObpLookupObjectName+0x4d5
3: bd74e5bc 80496b85 nt!ObOpenObjectByName+0xc5
4: bd74e690 be7728c6 nt!IoCreateFile+0x3ec
5: bd74e6e0 be77382b savrt+0x458c6
6: bd74e6ec be7752a5 savrt+0x4682b
7: bd74e768 be744773 savrt+0x482a5
8: bd74e77c be742e4b savrt+0x17773
9: bd74e7a4 be745e75 savrt+0x15e4b
10: 00000000 00000000 savrt+0x18e75
怎棒?幸好k指令(Display stack backtrace)可以指定參數,讓Windbg重新分析堆疊,進而逼近原始的真實資料。
k = ebp esp eip
大致流程是這樣的,當發生先前所舉的那個情況,偵錯者便印出堆疊上的所有資訊,然後依照x86堆疊運作規則,以及邏輯推理,判斷"斷掉的"堆疊所記錄的ebp、esp以及eip(我覺得,應該看成ReturnAddress),然後餵給k指令。其實Call stack的整個追蹤就是靠這三個資訊,Windbg也是使用這三個訊息,反推出整個Call Stack的"長相"。Nt Insider:Stacking the deck是一篇很不錯的參考。
怎麼起頭呢?我們先在堆疊上找一個"可能是正確"的ebp。ebp會有什麼特徵?ebp會存放前一個stack frame的ebp之位置,而ebp+4則是存放了return address。首先注意到的是第21行:
1: kd> dps bd74e7a4
2: bd74e7a4 00000000
3: bd74e7a8 be745e75 savrt+0x18e75
4: bd74e7ac e5d590b0
5: bd74e7b0 e369d7a8
6: bd74e7b4 be74355a savrt+0x1655a
7: bd74e7b8 bd74e7c8
8: bd74e7bc e369d7a8
9: bd74e7c0 e369d7a8
10: bd74e7c4 be741b0f savrt+0x14b0f
11: bd74e7c8 00000000
12: bd74e7cc e5da1e68
13: bd74e7d0 e5da1e70
14: bd74e7d4 be741c26 savrt+0x14c26
15: bd74e7d8 00000000
16: bd74e7dc e5da1e70
17: bd74e7e0 e1bffb48
18: bd74e7e4 be8329bc SYMEVENT+0xd9bc
19: bd74e7e8 00000000
20: bd74e7ec e5da1e70
21: bd74e7f0 bd74e810
22: bd74e7f4 be834e74 SYMEVENT+0xfe74
23: bd74e7f8 e5da1e70
24: bd74e7fc e1374c28
25: bd74e800 e5da1e68
26: bd74e804 bd74e870
27: bd74e808 e1374c30
28: bd74e80c e1374c28
29: bd74e810 bd74e824
30: bd74e814 be835023 SYMEVENT+0x10023
31: bd74e818 e5da1e68
32: bd74e81c bd74e870
33: bd74e820 e1374c48
34: bd74e824 bd74e838
35: bd74e828 be82b07f SYMEVENT+0x607f
36: bd74e82c bd74e870
1: kd> k = bd74e810 bd74e810-8 be834e74
2: ChildEBP RetAddr
3: WARNING: Stack unwind information not available. Following frames may be wrong.
4: bd74e810 be835023 SYMEVENT+0xfe74
5: bd74e824 be82b07f SYMEVENT+0x10023
6: bd74e838 be833d28 SYMEVENT+0x607f
7: bd74e854 be82c7b9 SYMEVENT+0xed28
8: bd74e894 bfd7fd2a SYMEVENT+0x77b9
9: bd74e8fc 8041fb8b <EDIT - removed module name>+0xcd2a
10: bd74e910 8049c945 nt!IopfCallDriver+0x35
11: bd74ea98 8045163d nt!IopParseDevice+0xa04
12: bd74eb0c 804a4e8a nt!ObpLookupObjectName+0x4d5
13: bd74ec1c 80496b85 nt!ObOpenObjectByName+0xc5
14: bd74ecf0 80497f27 nt!IoCreateFile+0x3ec
15: bd74ed30 80465691 nt!NtCreateFile+0x2e
16: bd74ed30 77f8f9c5 nt!KiSystemService+0xc4
17: 0006e808 00000000 ntdll!NtCreateFile+0xb
2 comments:
Hey Royce 大師
借轉貼到我的 Blog 唷...感激!
之前只有試著指定 ebp 給 k 指令,多半仍無法還原call stack的輪廓,最後總是得在stack裡尋找屬於process address space的return address殘跡,且就算找到也不算是很可靠的資訊.
>.^Y 我的榮幸
歡迎交流喔!!
Post a Comment