Net 高階偵錯之四:Windbg 動態偵錯

2023-11-01 18:02:18
一、簡介
    
今天是《Net 高階偵錯》的第四篇文章。到今天為止,也有三篇文章了,對 Windbg 也有初步的認識了,當然,一個工具流暢、熟練的使用,對於我們偵錯 Net 程式是至關重要的。在前幾篇文章的基礎上,我們這篇文章主要介紹一些和使用 Windbg 有關的命令和操作。就我個人而言,第一次接觸這個東西,還是挺難的,以前從來沒有用過 Windbg,用的最多的就是 Visual Studio 的偵錯功能。不怕大家笑話,如何通過 Windbg 載入一個 exe,我都不知道,更不要談載入 DUMP 檔案。我看第一遍視訊的時候,也不知道說了個啥,命令的執行,偵錯的開始,都感覺是一頭霧水,似懂非懂,自己一實操,總是得不到別人偵錯那樣的結果,很是鬱悶。怎麼辦呢?沒辦法,要想學會,除了努力那就是堅持。針對視訊,放慢速度,一幀一幀的按著視訊的操作,自己來一遍,速度雖然慢,但是有些操作開始有了感覺了,當整個視訊系列看了一遍,所有操作都操作一遍,終於有些頭緒了。還是那句老話,一遍不行,那就再來一遍,還不行,那就再來一遍,俗話說的好,書讀千遍,其意自現,我這是第三遍。
     如果在沒有說明的情況下,所有程式碼的測試環境都是 Net Framewok 4.8,但是,有時候為了檢視原始碼,可能需要使用 Net Core 的專案,我會在專案章節裡進行說明。好了,廢話不多說,開始我們今天的偵錯工作。
    偵錯環境我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
          作業系統:Windows Professional 10
          偵錯工具:Windbg Preview(可以去Microsoft Store 去下載)
          開發工具:Visual Studio 2022
          Net 版本:Net Framework 4.8
          CoreCLR原始碼:原始碼下載
二、相關知識
    
1、Windbg 動態偵錯
          1.1、偵錯概況
              在任何一種偵錯中都有兩個元件:偵錯程式本身,偵錯目標。
              當偵錯程式【Windbg】附加程序【Attach to Process】時,偵錯程式會給 目標程式 注入一個遠端執行緒並用 int 3 中斷程式,後續和 Net 程式中的 DebuggerRCThread 執行緒互動執行命令。int 3 指令之所以可以讓程序中斷,主要是來自於 CPU 硬體中斷,當偵錯程式發出了一個 int 3 的中斷請求,CPU會到核心態執行3號例程,也就是執行【中斷向量表】,核心執行中斷的操作。
              Actor------------》偵錯程式(Windbg)《------------》偵錯目標(Net 程式,C++程式...)
              以上圖例就是一個偵錯程式的作用和所處的地位。

     2、程式的中斷和恢復執行
          2.1、中斷執行(讓程式中斷有4中方式)
              a、使用WinDbg 啟動程式,在程序初始化函數中,如果發現有偵錯程式附加在上面,就會執行 break 中斷。 
                  說明一下:Windbg 偵錯程式剛開始的中斷就是 int 3 中斷,但是這個中斷的時機很早,我們可以做一些初始化的工作,比如:載入SOS.dll 等類似的工作。                
              b、附加程序,偵錯程式會注入遠端執行緒執行 int 3 中斷程式。
                  這個挺簡單的,我們雙擊程式,直接執行。然後通過 Windbg 的【Attach to process】附加程序,就可以進入偵錯程式介面,這個時候,其實什麼也不用做,偵錯程式已經暫停了,這個暫停就是 int 3 中斷。我們通過 Windbg 的【break】命令也是 int 3 中斷。當然,我們通過 C# Debugger.Break() 程式碼執行的也是 int 3 的中斷。
              c、使用 bp 命令給程式下斷點。
                  我們可以通過【u】命令檢視方法的組合程式碼,找到想要設斷點的程式碼的地址,直接通過這個地址來下斷點,當程式再次執行的時候,就會在這個斷點處暫停。                 
              d、異常中斷。
                   異常中斷的流程是:當你的程式發生異常,會從使用者態轉到核心態,核心態檢測到你的程式附加了偵錯程式,核心態就會把這個請求轉交給偵錯程式,偵錯程式也就能中斷了,可以偵錯了。
          2.2、恢復執行
              可以使用 g 命令回覆程式的執行。

     3、單步偵錯程式碼
          當我們偵錯程式的時候,最多的時候是使用 Visual Studio 的 f10、f11、f5這樣的命令,在 Windbg 中也有類似的命令可以使用。
        
  3.1、p 命令
              p(step):命令其實就是VS 中的 f10 快捷鍵,單步執行,遇到函數也是當成一條指令執行,不會進入函數體。
              
          3.2、t 命令
              
t(trace):命令其實就是 VS 的 f11 快捷鍵,它是一種進入函數的單步執行偵錯。
              
          3.3、pc 命令
              pc(Step to Next Call)    就是一直執行直到遇到 call 為止,不會進入函數體,call 是一個函數呼叫,組合指令。
              
          3.4、tc 命令
           
tc(Trace to Next Call)    和 pc 不同的是,tc 會進入方法體,直到遇到 call 為止。

          3.5、pt 命令
              pt(Step to Next Return)    遇到下一個 ret 為止。   
              
          3.6、tt 命令
              tt(Trace to Next Return)    會進入函數體直到遇到 ret 為止。遞迴的意思。
              
    4、退出測試對談
          結束偵錯對談,有兩個目的,看是否保留程式的執行。
          4.1、q(quit):結束偵錯對談+偵錯程式退出
              偵錯對談結束,應用程式也會退出。
          4.2、qd(quit and detach):結束偵錯對談+偵錯程式繼續執行
              偵錯對談結束,應用程式保持執行態,不會退出。

三、偵錯過程
    
廢話不多說,這一節是具體的偵錯操作的過程,又可以說是眼見為實的過程,在開始之前,我還是要囉嗦兩句,這一節分為兩個部分,第一部分是測試的原始碼部分,沒有程式碼,當然就談不上測試了,偵錯必須有載體。第二部分就是根據具體的程式碼來證實我們學到的知識,是具體的眼見為實。
    1、測試原始碼
        
1.1、Example_4_1_1
 1 namespace Example_4_1_1
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             for (int i = 0; i < int.MaxValue; i++)
 8             {
 9                 Console.WriteLine($"i={i}");
10                 Thread.Sleep(1000);
11             }
12             Console.ReadLine();
13         }
14     }
15 }
View Code
        1.2、Example_4_1_2
 1 namespace Example_4_1_2
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Debugger.Break();
 8 
 9             int a = 10;
10             int b = 12;
11 
12             var sum = a + b;
13 
14             Console.WriteLine($"sum={sum}");
15 
16             Console.ReadLine();
17         }
18     }
19 }
View Code
        1.3、Example_4_1_3
 1 namespace Example_4_1_3
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Run();
 8 
 9             Console.ReadLine();
10         }
11 
12         static void Run()
13         {
14             Console.WriteLine("請輸入一個除數:");
15             var num = Console.ReadLine();
16             var result = 10 / Convert.ToInt32(num);
17         }
18     }
19 }
View Code
        1.4、Example_4_1_4
 1 namespace Example_4_1_4
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Sum1(10);
 8             Debugger.Break();
 9 
10             int i = 10;
11             int j = 20;
12 
13             var sum = Sum1(i);
14             Console.WriteLine($"sum={sum}");
15 
16             Console.ReadLine();
17         }
18 
19         private static int Sum1(int a)
20         {
21             var i = a;
22             var j = 11;
23             int sum = Sum2(i, j);
24 
25             return sum;
26         }
27 
28         private static int Sum2(int a, int b)
29         {
30             var i = a;
31             var j = b;
32             var k = 13;
33 
34             var sum = Sum3(i, j, k);
35             return sum;
36         }
37 
38         private static int Sum3(int i, int j, int k)
39         {
40             return i + j + k;
41         }
42     }
43 }
View Code

    2、眼見為實
          2.1、Windbg【Attach to Process】附加程序,通過 int 3 命令中斷程式。
              測試程式碼:Example_4_1_1
            程式很簡單,直接執行 exe 程式,開啟 Windbg,點選選單【attach to process】進入偵錯程式介面。其實什麼操作都不用做,我們就可以看到偵錯程式的輸出結果。特別強調,紅色標註的就是 int 3中斷。
1 ModLoad: 6fa30000 6faba000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
2 ModLoad: 75f50000 75feb000   C:\Windows\System32\OLEAUT32.dll
3 (5384.3250): Break instruction exception - code 80000003 (first chance)
4 eax=00270000 ebx=00000000 ecx=7790cee0 edx=7790cee0 esi=7790cee0 edi=7790cee0
5 eip=778d3410 esp=04c2f92c ebp=04c2f958 iopl=0         nv up ei pl zr na pe nc
6 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
7 ntdll!DbgBreakPoint:
8 778d3410 cc              int     3

              當然,我們也可以通過【g】命令,繼續執行程式,然後點選工具列的【break】按鈕,程式就進入中斷,這裡的結果和上面是一樣的。

1 0:006> g
2 (5384.528c): Break instruction exception - code 80000003 (first chance)
3 eax=00279000 ebx=00000000 ecx=7790cee0 edx=7790cee0 esi=7790cee0 edi=7790cee0
4 eip=778d3410 esp=04eaf960 ebp=04eaf98c iopl=0         nv up ei pl zr na pe nc
5 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
6 ntdll!DbgBreakPoint:
7 778d3410 cc              int     3

              當然,也可以通過【u】命令,檢視 ntdll!DbgBreakPoint的組合程式碼。          

1 0:008> u ntdll!DbgBreakPoint
2 ntdll!DbgBreakPoint:
3 778d3410 cc              int     3
4 778d3411 c3              ret(方法返回)

              我們也可以檢視【Disassembly】檢視,截圖如下:
              


            解釋核心態:涉及到核心態的執行,我們也可以通過Windbg 檢視,重新在開啟一個 Windbg,點選【檔案】----》【Attach kernel】,選擇【local】項,點選【ok】按鈕,進入偵錯程式介面。然後,我們可以輸入【!idt】命令來檢視。
 1 lkd> !idt
 2 
 3 Dumping IDT: fffff80069a91000
 4 
 5 00:    fffff80064b93100 nt!KiDivideErrorFaultShadow
 6 01:    fffff80064b93180 nt!KiDebugTrapOrFaultShadow    Stack = 0xFFFFF80069A959D0
 7 02:    fffff80064b93240 nt!KiNmiInterruptShadow    Stack = 0xFFFFF80069A957D0
 8 03:    fffff80064b932c0 nt!KiBreakpointTrapShadow
 9 04:    fffff80064b93340 nt!KiOverflowTrapShadow
10 ..................

              紅色標註的就是在核心態的中斷函數,我們可以使用【u】命令,檢視他的組合程式碼。

 1 lkd> u nt!KiBreakpointTrapShadow
 2 nt!KiBreakpointTrapShadow:
 3 fffff800`64b932c0 f644240801      test    byte ptr [rsp+8],1
 4 fffff800`64b932c5 7467            je      nt!KiBreakpointTrapShadow+0x6e (fffff800`64b9332e)
 5 fffff800`64b932c7 0f01f8          swapgs
 6 fffff800`64b932ca 0faee8          lfence
 7 fffff800`64b932cd 650fba24251890000001 bt  dword ptr gs:[9018h],1
 8 fffff800`64b932d7 720c            jb      nt!KiBreakpointTrapShadow+0x25 (fffff800`64b932e5)
 9 fffff800`64b932d9 65488b242500900000 mov   rsp,qword ptr gs:[9000h]
10 fffff800`64b932e2 0f22dc          mov     cr3,rsp
            解釋:,我們可以通過【~* k】命令,列印出所有執行緒棧,......表示省略,內容太多,沒必要,顯示重要的就可以了。
              
 1 0:008> ~*k
 2 
 3    0  Id: 5384.4128 Suspend: 1 Teb: 0025e000 Unfrozen
 4  # ChildEBP RetAddr      
 5 00 0057ee00 75942d3b     ntdll!NtDelayExecution+0xc
 6 ......
 7 
 8    1  Id: 5384.22a4 Suspend: 1 Teb: 00261000 Unfrozen
 9  # ChildEBP RetAddr      
10 00 0088fab8 778b0f30     ntdll!NtWaitForWorkViaWorkerFactory+0xc
11 ......
12 
13    2  Id: 5384.3034 Suspend: 1 Teb: 00264000 Unfrozen
14  # ChildEBP RetAddr      
15 00 00affb9c 778b0f30     ntdll!NtWaitForWorkViaWorkerFactory+0xc
16 .......
17 
18    3  Id: 5384.35fc Suspend: 1 Teb: 00267000 Unfrozen
19  # ChildEBP RetAddr      
20 00 00bff9f0 778b0f30     ntdll!NtWaitForWorkViaWorkerFactory+0xc
21 ......
22 
23    4  Id: 5384.3c50 Suspend: 1 Teb: 0026a000 Unfrozen
24  # ChildEBP RetAddr      
25 00 0258f804 75939623     ntdll!NtWaitForMultipleObjects+0xc
26 01 0258f804 711567d7     KERNELBASE!WaitForMultipleObjectsEx+0x103
27 02 0258f86c 711566ff     clr!DebuggerRCThread::MainLoop+0x99
28 03 0258f898 71156620     clr!DebuggerRCThread::ThreadProc+0xd0
29 04 0258f8c4 7711f989     clr!DebuggerRCThread::ThreadProcStatic+0xa3
30 05 0258f8d4 778c7084     KERNEL32!BaseThreadInitThunk+0x19
31 06 0258f930 778c7054     ntdll!__RtlUserThreadStart+0x2f
32 07 0258f940 00000000     ntdll!_RtlUserThreadStart+0x1b
33 
34    5  Id: 5384.2050 Suspend: 1 Teb: 0026d000 Unfrozen
35  # ChildEBP RetAddr      
36 00 0475fabc 75939623     ntdll!NtWaitForMultipleObjects+0xc
37 ......
38 
39    6  Id: 5384.2ce0 Suspend: 1 Teb: 00276000 Unfrozen
40  # ChildEBP RetAddr      
41 00 04c2f84c 778b0f30     ntdll!NtWaitForWorkViaWorkerFactory+0xc
42 ......
43 
44    7  Id: 5384.489c Suspend: 1 Teb: 00273000 Unfrozen
45  # ChildEBP RetAddr      
46 ......
47 04 04d6ffb0 00000000     ntdll!_RtlUserThreadStart+0x1b
48 
49 #  8  Id: 5384.528c Suspend: 1 Teb: 00279000 Unfrozen
50  # ChildEBP RetAddr      
51 00 04eaf98c 7790cf19     ntdll!DbgBreakPoint(int 3 中斷)
52 01 04eaf98c 7711f989     ntdll!DbgUiRemoteBreakin+0x39
53 02 04eaf99c 778c7084     KERNEL32!BaseThreadInitThunk+0x19
54 03 04eaf9f8 778c7054     ntdll!__RtlUserThreadStart+0x2f
55 04 04eafa08 00000000     ntdll!_RtlUserThreadStart+0x1b


          2.2、使用WinDbg 啟動程式,在程序初始化函數中斷程序。
              測試程式碼:Example_4_1_2
            
我們編譯專案,開啟 Windbg,點選【檔案】----》【launch executable】附加程式,開啟偵錯程式的介面,程式已經處於中斷狀態。我們可以看到,如下輸出:
 1 Executable search path is: 
 2 ModLoad: 00be0000 00be8000   Example_4_1_2.exe
 3 ModLoad: 77860000 77a02000   ntdll.dll
 4 ModLoad: 717e0000 71832000   C:\Windows\SysWOW64\MSCOREE.DLL
 5 ModLoad: 77100000 771f0000   C:\Windows\SysWOW64\KERNEL32.dll
 6 ModLoad: 75820000 75a33000   C:\Windows\SysWOW64\KERNELBASE.dll
 7 ModLoad: 5efe0000 5f07f000   C:\Windows\SysWOW64\apphelp.dll
 8 (300c.20b8): Break instruction exception - code 80000003 (first chance)
 9 eax=00000000 ebx=00000000 ecx=534d0000 edx=00000000 esi=77871f64 edi=7787252c
10 eip=77910de2 esp=00f7f824 ebp=00f7f850 iopl=0         nv up ei pl zr na pe nc
11 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
12 ntdll!LdrpDoDebuggerBreak+0x2b:
13 77910de2 cc              int     3

              紅色部分需要注意,然後我們使用【u】命令檢視它的組合程式碼。

 1 0:000> u ntdll!LdrpDoDebuggerBreak+0x2b
 2 ntdll!LdrpDoDebuggerBreak+0x2b:
 3 77910de2 cc              int     3
 4 77910de3 eb07            jmp     ntdll!LdrpDoDebuggerBreak+0x35 (77910dec)
 5 77910de5 33c0            xor     eax,eax
 6 77910de7 40              inc     eax
 7 77910de8 c3              ret
 8 77910de9 8b65e8          mov     esp,dword ptr [ebp-18h]
 9 77910dec c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh
10 77910df3 8b4df0          mov     ecx,dword ptr [ebp-10h]

              我們可以使用【k】命令,繼續檢視。

1 0:000> k
2  # ChildEBP RetAddr      
3 00 00f7f850 7790b2f8     ntdll!LdrpDoDebuggerBreak+0x2b(int 3 中斷)
4 01 00f7fab0 778ba3d1     ntdll!LdrpInitializeProcess+0x1c98(程序初始化的時候執行的 break中斷,)
5 02 00f7fb08 778ba2c1     ntdll!_LdrpInitialize+0xba
6 03 00f7fb14 00000000     ntdll!LdrInitializeThunk+0x11

               ntdll是一個閘道器函數dll,如果想使用核心的功能幾必須通過 ntdll 裡面的函數。ntdll!LdrpDoDebuggerBreak 這個中斷是在程序初始化之前進行的,是很早的一個時機,載入的東西也不多,只有【Example_4_1_2.exentdll.dllMSCOREE.DLLKERNEL32.dll...】,之所以這樣,可以讓我們設定一些或者說設定一些初始化的東西,比如:載入 SOS等。

            
        
2.3、使用 bp 命令給程式下斷點,可以讓程式中斷。
            
測試程式碼:Example_4_1_2
            比如,我們在【int a = 10;】這樣程式碼下斷點,行數:12.
            

               我們編譯專案,開啟 Windbg,點選【檔案】----》【launch executable】附加程式,開啟偵錯程式的介面,程式已經處於中斷狀態。我們使用【g】命令,繼續執行,然後我們使用【!clrstack】命令,檢視執行緒棧。

1 0:000> !clrstack
2 OS Thread Id: 0x3c54 (0)
3 Child SP       IP Call Site
4 00d5ed38 7597f262 [HelperMethodFrame: 00d5ed38] System.Diagnostics.Debugger.BreakInternal()
5 00d5edb4 7064f195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91]
6 00d5eddc 02ba0886 Example_4_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\...\Example_4_1_2\Program.cs @ 10]
7 00d5ef78 70faf036 [GCFrame: 00d5ef78] 

                紅色標註的是 Main 方法的地址,然後執行【!u 02ba0886】命令,檢視他的組合程式碼。

1 0:000> !u 02ba0886
2 Normal JIT generated code
3 Example_4_1_2.Program.Main(System.String[])
4 Begin 02ba0848, size a3
5 
6 ......
7 
8 E:\Visual Studio 2022\...\Example_4_1_2\Program.cs @ 12:(這個行號就是C# 程式碼的行號)
9 02ba0887 c745f00a000000  mov     dword ptr [ebp-10h],0Ah

                我們找到了程式碼的位置,就可以下斷點了,使用【bp】命令。

0:000> bp 02ba0887

                【g】繼續執行,就會到斷點出暫停。

              

                效果如圖。


        
2.4、觸發異常,也可以讓程式中斷。
            
測試程式碼:Example_4_1_3
              程式碼很簡單,我簡單說一些流程,我們首先將要測試的專案編譯好,然後開啟 Windbg,通過【launch executable】附加應用程式,偵錯程式會響應一個 int 3中斷,我們通過【g】命令,繼續執行程式。程式提示輸入一個數位,我輸入0,肯定就會異常了。
           效果如圖:
           

              異常中斷的程式碼是:014b08ea f77df4         idiv    eax, dword ptr [ebp-0Ch],效果如圖:
              
              我們使用【dp】命令,檢視【ebp-0Ch】程式碼的值。

1 0:000> dp ebp-0Ch l1
2 012ff2fc  00000000

              我們可以使用【dp】命令,也可以使用【?】命令,檢視一下eax 是什麼,其實 eax就是十進位制的10。

1 0:000> ? eax
2 Evaluate expression: 10 = 0000000a

              程式碼【idiv】就是表示除法觸發的異常。
              我們也可以使用【k】命令,檢視呼叫棧,也能看出在哪裡中斷。

1 0:000> k
2  # ChildEBP RetAddr      
3 00 012ff308 014b086b     Example_4_1_3!COM+_Entry_Point <PERF> (Example_4_1_3+0x6308ea) [E:\...\Example_4_1_3\Program.cs @ 18] 
4 01 012ff318 7077f036     Example_4_1_3!COM+_Entry_Point <PERF> (Example_4_1_3+0x63086b) [E:\...\Example_4_1_3\Program.cs @ 9] 
5 ......
6 0f 012ffde4 77057054     ntdll!__RtlUserThreadStart+0x2f
7 10 012ffdf4 00000000     ntdll!_RtlUserThreadStart+0x1b

              紅色部分就是中斷的 C# 程式碼的行號。


          2.5、單步偵錯命令測試。
              測試程式碼:Example_4_1_4
              說明一下,這些測試命令,不需要每個命令建立一個獨立的測試專案,所以我這裡就使用了一個專案做測試。為了更明確,每個命令單獨測試。但是編譯專案,通過Windbg 載入專案就不詳述了。都是通過 Windbg的【launch executable】附加應用程式的,進入到的偵錯程式後,通過【g】命令繼續執行,會在 C# 【Debugger.Break()】程式碼出中斷,我們通過【!clrstack】執行緒棧,找到 Program 的 Main 方法的地址,然後在這個地址上通過【bp】命令下斷點,剩下就可以自由偵錯了。
1 0:000> !clrstack
2 OS Thread Id: 0x35ec (0)
3 Child SP       IP Call Site
4 00aff270 76e8f262 [HelperMethodFrame: 00aff270] System.Diagnostics.Debugger.BreakInternal()
5 00aff2ec 6fe1f195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91]
6 00aff314 00cc0895 Example_4_1_4.Program.Main(System.String[]) [E:\Visual Studio\...\Example_4_1_4\Program.cs @ 11]
7 00aff4b4 7077f036 [GCFrame: 00aff4b4] 

              在紅色標註的地址上設定斷點。

0:000> bp 00cc0895

              【g】繼續執行。

              

              開始偵錯了。

              p命令:                  
 1 0:000> p
 2 eax=00000022 ebx=00aff3e4 ecx=0000000a edx=00000000 esi=02ab24bc edi=00aff330
 3 eip=00cc08b3 esp=00aff314 ebp=00aff348 iopl=0         nv up ei pl nz ac pe nc
 4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
 5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x6408b3):
 6 00cc08b3 8945e8          mov     dword ptr [ebp-18h],eax ss:002b:00aff330=00000000
 7 0:000> p
 8 eax=00000022 ebx=00aff3e4 ecx=0000000a edx=00000000 esi=02ab24bc edi=00aff330
 9 eip=00cc08b6 esp=00aff314 ebp=00aff348 iopl=0         nv up ei pl nz ac pe nc
10 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
11 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x6408b6):
12 00cc08b6 b9a8422a6f      mov     ecx,offset mscorlib_ni!GetObjectData+0x102 (6f2a42a8)
13 0:000> p
14 eax=00000022 ebx=00aff3e4 ecx=6f2a42a8 edx=00000000 esi=02ab24bc edi=00aff330
15 eip=00cc08bb esp=00aff314 ebp=00aff348 iopl=0         nv up ei pl nz ac pe nc
16 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
17 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x6408bb):
18 00cc08bb e83428faff      call    00c630f4

              t命令:
 1 0:000> t
 2 eax=00000000 ebx=0053ee2c ecx=0000000a edx=00779910 esi=025224bc edi=0053ed70
 3 eip=023808a7 esp=0053ed54 ebp=0053ed88 iopl=0         nv up ei pl zr na pe nc
 4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
 5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x21e08a7):
 6 023808a7 ff156c4d3302    call    dword ptr ds:[2334D6Ch] ds:002b:02334d6c=02380918
 7 0:000> t
 8 eax=00000000 ebx=0053ee2c ecx=0000000a edx=00779910 esi=025224bc edi=0053ed70
 9 eip=02380918 esp=0053ed50 ebp=0053ed88 iopl=0         nv up ei pl zr na pe nc
10 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
11 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x21e0918):
12 02380918 55              push    ebp

                  效果如圖:
                  

                  執行【t】命令後,效果如圖:
                  


              pc命令:                  
1 0:000> pc
2 eax=00000000 ebx=00bfee64 ecx=0000000a edx=00f69910 esi=02f124bc edi=00bfedb0
3 eip=014108a7 esp=00bfed94 ebp=00bfedc8 iopl=0         nv up ei pl zr na pe nc
4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x9b08a7):
6 014108a7 ff156c4d1301    call    dword ptr ds:[1134D6Ch] ds:002b:01134d6c=01410918

                  效果如圖:
                  

                  執行後的效果,如圖:
                  


              tc命令:               
1 0:000> tc
2 eax=00000000 ebx=010fee04 ecx=0000000a edx=013998f0 esi=031424bc edi=010fed50
3 eip=02f708a7 esp=010fed34 ebp=010fed68 iopl=0         nv up ei pl zr na pe nc
4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x22608a7):
6 02f708a7 ff156c4d5e01    call    dword ptr ds:[15E4D6Ch] ds:002b:015e4d6c=02f70918

                  第一次執行【tc】命令,效果如圖:
                  

1 0:000> tc
2 eax=0000000a ebx=010fee04 ecx=0000000a edx=0000000b esi=031424bc edi=010fed50
3 eip=02f7095c esp=010fed14 ebp=010fed2c iopl=0         nv up ei pl zr na pe nc
4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x226095c):
6 02f7095c ff15784d5e01    call    dword ptr ds:[15E4D78h] ds:002b:015e4d78=02f70990

                  第二次執行【tc】命令,效果如圖:
                  

1 0:000> tc
2 eax=0000000b ebx=010fee04 ecx=0000000a edx=0000000b esi=031424bc edi=010fed50
3 eip=02f709e5 esp=010fece8 ebp=010fed0c iopl=0         nv up ei pl zr na pe nc
4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x22609e5):
6 02f709e5 ff15844d5e01    call    dword ptr ds:[15E4D84h] ds:002b:015e4d84=02f70a18

                  第三次執行【tc】命令,效果如圖:
                  


              pt命令:
                  初始狀態,如圖:
                  

                  執行了【pt】命令,遇到【ret】就暫停,也就是回到29行。

1 0:000> pt
2 eax=00000022 ebx=010ff29c ecx=0000000a edx=00000000 esi=030624bc edi=010ff1e0
3 eip=015f097a esp=010ff1c0 ebp=010ff1f8 iopl=0         nv up ei pl nz ac pe nc
4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x97097a):
6 015f097a c3              ret

                  效果如圖:
                  


              tt命令:
                如果我們執行【tt】命令,第一暫停會到【Sum3】方法的44行。
1 0:000> tt
2 eax=00000022 ebx=00aff158 ecx=0000000a edx=00000000 esi=02c924bc edi=00aff0a0
3 eip=00e00a4d esp=00aff034 ebp=00aff05c iopl=0         nv up ei pl nz ac pe nc
4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x730a4d):
6 00e00a4d c20400          ret     4

                  效果如圖:
                                    

1 0:000> tt
2 eax=00000022 ebx=00aff158 ecx=0000000a edx=00000000 esi=02c924bc edi=00aff0a0
3 eip=00e00a03 esp=00aff060 ebp=00aff07c iopl=0         nv up ei pl nz ac pe nc
4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
5 Example_4_1_4!COM+_Entry_Point <PERF> (Example_4_1_4+0x730a03):
6 00e00a03 c3              ret

                  第二次執行【tt】命令,會在Sum2方法的39行暫停,以此類推。
                  


        
2.6、退出偵錯對談
            
這裡不需要更多的程式碼,也不用專門寫一個測試程式了,所以就直接用 Example_4_1_4 專案,很簡單,就不作圖例了。
              測試程式碼:Example_4_1_4

              A、結束對談,並退出程式
                  使用【q】命令。

              B、結束對談,程式繼續執行。
                 
使用【qd】命令。

四、總結
    
終於寫完了,為什麼說是終於,因為寫這一篇文章,不是一天完成的,還要寫文章,記錄操作過程,作圖例,所以時間就長了。今天介紹的是 Windbg 動態偵錯的命令,我們要想讓偵錯任務更容易,掌握這些偵錯技巧還是很有必要的。好了,不說了,不忘初心,繼續努力,希望老天不要辜負努力的人。