Net 高階偵錯之八:程式碼審查及雜項命令

2023-11-14 12:00:23
一、簡介
    今天是《Net 高階偵錯》的第八篇文章。這篇文章設計的內容挺多的,比如:如何檢視方法的組合程式碼,如何獲取方法的描述符,物件同步塊的轉儲,物件方法表的轉儲,託管堆和垃圾回收器資訊的轉儲,CLR 的版本,GC 模式,等等,內容挺多的。內容雖然挺多,但是這些都是高階偵錯的基礎。雖然這些都是基礎,如果這些掌握不好,以後的高階偵錯的道路,也不好走。當然了,第一次看視訊或者看書,是很迷糊的,不知道如何操作,還是那句老話,一遍不行,那就再來一遍,還不行,那就再來一遍,俗話說的好,書讀千遍,其意自現。
     如果在沒有說明的情況下,所有程式碼的測試環境都是 Net Framewok 4.8,但是,有時候為了檢視原始碼,可能需要使用 Net Core 的專案,我會在專案章節裡進行說明。好了,廢話不多說,開始我們今天的偵錯工作。

       偵錯環境我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
          作業系統:Windows Professional 10
          偵錯工具:Windbg Preview(可以去Microsoft Store 去下載)
          開發工具:Visual Studio 2022
          Net 版本:Net Framework 4.8
          CoreCLR原始碼:原始碼下載

二、基礎知識
    1、程式碼審查
        1.1、簡介
            程式碼審查就是觀察程式碼,程式碼由三種心態:機器程式碼、IL程式碼、C#程式碼。高階偵錯屬於逆向分析,更多的是以 組合程式碼 為主,如果對組合無感,想學好 Net 高階偵錯,也是比較困難的。

        1.2、觀察組合程式碼
            1)u命令
                這個命令用來將非託管函數的機器程式碼轉成組合程式碼,當然,我們想要檢視組合程式碼,不一定非要使用 Windbg,我們也可以使用 Visual Studio IDE 的反組合視窗。我們可以通過點選選單【偵錯】--》【視窗】----》【反組合】,開啟反組合視窗。
                

 

            2)!u命令
                這個命令是由 SOS 提供的,專門用於觀察 託管函數 的組合表示,接下來,我們檢視一下 Main 方法的組合程式碼。

        1.3、觀察 IL 程式碼
            SOS 提供了一個叫 !dumpil 的命令用來將託管函數的組合指令轉成可讀的 IL 程式碼。

        1.4、觀察 C# 程式碼
            要觀察 C# 程式碼,需要將記憶體中的 module 給剝離出來,然後使用 ILSpy 或者 DnSpy 等反編譯工具反轉即可。這就需要使用 Windbg 的【!savemodule】命令。大家也要防止 Dump 洩露,這個是嚴重的,別人就可以看到你的完整程式碼。
        
    2、雜項命令
        2.1、獲取 CLR 版本、GC模式
            如果想獲取 CLR 的版本,可以使用【!eeversion】命令,當然,我們也可以通過檢視堆的數量,來了解 GC 模式,使用【!eeheap -gc】命令。

        2.2、檢視執行緒棧物件
            如果我們想檢視執行緒棧上的物件,一般都會使用【!clrstack -a】命令,但是這個命令只是看表面,不能對映到物件,那麼我們可以使用【!dso】命令,這個命令可以把執行緒棧中的所有物件全部顯示出來。

        2.3、觀察物件參照根
            我們知道了如何檢視物件參照根,我們就知道了為什麼沒有被 GC 回收,很輕鬆找到問題的所在,這個命令就是【!gcroot】。

三、偵錯過程
    廢話不多說,這一節是具體的偵錯操作的過程,又可以說是眼見為實的過程,在開始之前,我還是要囉嗦兩句,這一節分為兩個部分,第一部分是測試的原始碼部分,沒有程式碼,當然就談不上測試了,偵錯必須有載體。第二部分就是根據具體的程式碼來證實我們學到的知識,是具體的眼見為實。

    1、測試原始碼
        1.1、Example_8_1_1 
 1 namespace Example_8_1_1
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             var sum = Sum(10, 11);
 8             Console.WriteLine($"sum={sum}");
 9             Console.ReadLine();
10         }
11 
12         private static int Sum(int a, int b)
13         {
14             int i = a;
15             int j = b;
16             var sum = i + j;
17             return sum;
18         }
19     }
20 }
View Code

        1.2、Example_8_1_2
 1 namespace Example_8_1_2
 2 {
 3     internal class Program
 4     {
 5         private static List<byte[]> list = new List<byte[]>();
 6 
 7         static void Main(string[] args)
 8         {
 9             for (int i = 0; i < 100; i++)
10             {
11                 list.Add(new byte[100000]);
12             }
13             Console.WriteLine("資料新增完畢!");
14             Console.ReadLine();
15         }
16     }
17 }
View Code
 
    2、眼見為實
        專案的所有操作都是一樣的,所以就在這裡說明一下,但是每個測試例子,都需要重新啟動,並載入相應的應用程式,載入方法都是一樣的。流程如下:我們編譯專案,開啟 Windbg,點選【檔案】----》【launch executable】附加程式,開啟偵錯程式的介面,程式已經處於中斷狀態。我們需要使用【g】命令,繼續執行程式,然後到達指定地點停止後,我們可以點選【break】按鈕,就可以偵錯程式了。有時候可能需要切換到主執行緒,可以使用【~0s】命令。

        2.1、檢視 clr!CallDescrWorkerInternal+0x34 處的組合程式碼。
            測試原始碼:Example_8_1_1
            說明一下,在這裡用其他的程式都是可以的,只是檢視組合程式碼,可以檢視非託管函數的、也可以檢視託管函數的組合程式碼。
            接下來,我們先找一個非託管函數的,看看它們的組合程式碼,到底是哪個函數,是隨機的。
            我們使用【k】命令,檢視一下非託管的呼叫棧,然後從裡面隨便找一個函數,看看它的組合程式碼。
 1 0:000> k
 2  # ChildEBP RetAddr      
 3 00 00efeff4 7533f25c     ntdll!NtReadFile+0xc
 4 01 00efeff4 705f9b71     KERNELBASE!ReadFile+0xec
 5 02 00eff064 70d2b275     mscorlib_ni+0x4b9b71
 6 03 00eff090 70d2b17b     mscorlib_ni!System.IO.__ConsoleStream.ReadFileNative+0x89 [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205] 
 7 04 00eff0bc 705de6a3     mscorlib_ni!System.IO.__ConsoleStream.Read+0x9f [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134] 
 8 05 00eff0d4 705deb5b     mscorlib_ni!System.IO.StreamReader.ReadBuffer+0x33 [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595] 
 9 06 00eff0f0 70e73786     mscorlib_ni!System.IO.StreamReader.ReadLine+0xe3 [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748] 
10 07 00eff100 70cd1845     mscorlib_ni!System.IO.TextReader.SyncTextReader.ReadLine+0x1a [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363] 
11 08 00eff108 011808d1     mscorlib_ni!System.Console.ReadLine+0x15 [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984] 
12 WARNING: Frame IP not in any known module. Following frames may be wrong.
13 09 00eff138 7162f036     0x11808d1
14 0a 00eff144 716322da     clr!CallDescrWorkerInternal+0x34
15 0b 00eff198 7163859b     clr!CallDescrWorkerWithHandler+0x6b
16 0c 00eff204 717db11b     clr!MethodDescCallSite::CallTargetWorker+0x16a
17 0d 00eff328 717db7fa     clr!RunMain+0x1b3
18 0e 00eff594 717db727     clr!Assembly::ExecuteMainMethod+0xf7
19 0f 00effa78 717db8a8     clr!SystemDomain::ExecuteMainMethod+0x5ef
20 10 00effad0 717db9ce     clr!ExecuteEXE+0x4c
21 11 00effb10 717d7305     clr!_CorExeMainInternal+0xdc
22 12 00effb4c 71ddfa84     clr!_CorExeMain+0x4d
23 13 00effb84 71ede81e     mscoreei!_CorExeMain+0xd6
24 14 00effb94 71ee4338     MSCOREE!ShellShim__CorExeMain+0x9e
25 15 00effbac 7515f989     MSCOREE!_CorExeMain_Exported+0x8
26 16 00effbac 772a7084     KERNEL32!BaseThreadInitThunk+0x19
27 17 00effc08 772a7054     ntdll!__RtlUserThreadStart+0x2f
28 18 00effc18 00000000     ntdll!_RtlUserThreadStart+0x1b
View Code
            呼叫棧還是很多的,列表裡有:00eff144 716322da clr!CallDescrWorkerInternal+0x34,這樣一行程式碼,我們使用【u】命令檢視一下。
 1 0:000> u clr!CallDescrWorkerInternal+0x34
 2 clr!CallDescrWorkerInternal+0x34:
 3 7162f036 8b4b0c          mov     ecx,dword ptr [ebx+0Ch]
 4 7162f039 83f900          cmp     ecx,0
 5 7162f03c 740c            je      clr!CallDescrWorkerInternal+0x48 (7162f04a)
 6 7162f03e 83f904          cmp     ecx,4
 7 7162f041 7412            je      clr!CallDescrWorkerInternal+0x53 (7162f055)
 8 7162f043 83f908          cmp     ecx,8
 9 7162f046 7412            je      clr!CallDescrWorkerInternal+0x58 (7162f05a)
10 7162f048 eb06            jmp     clr!CallDescrWorkerInternal+0x4e (7162f050)

            程式碼很清楚,可以自行檢視。當然我們也可以在Windbg的【Disassembly】視窗檢視組合程式碼,效果如圖:
                    

            【u】命令是從上向下看組合程式碼,我們也可以使用【ub】命令,從下向上檢視組合程式碼。

 1 0:000> ub clr!CallDescrWorkerInternal+0x34
 2 clr!CallDescrWorkerInternal+0x21:
 3 7162f023 83e804          sub     eax,4
 4 7162f026 ff30            push    dword ptr [eax]
 5 7162f028 49              dec     ecx
 6 7162f029 75f8            jne     clr!CallDescrWorkerInternal+0x21 (7162f023)
 7 7162f02b 8b4308          mov     eax,dword ptr [ebx+8]
 8 7162f02e 8b10            mov     edx,dword ptr [eax]
 9 7162f030 8b4804          mov     ecx,dword ptr [eax+4]
10 7162f033 ff5310          call    dword ptr [ebx+10h]

             兩個命令的效果,如圖:
             

              如果我們想把一個函數的所有的組合程式碼全部顯示出來,我們可以使用【uf】命令,我們看看 clr!_CorExeMain 函數的組合程式碼吧。
 1 0:000> uf clr!_CorExeMain
 2 clr!_CorExeMain:
 3 717d72e0 6a14            push    14h
 4 717d72e2 6818737d71      push    offset clr!`dynamic atexit destructor for 'AppDataPathHolder''+0x27e0 (717d7318)
 5 717d72e7 e8349de4ff      call    clr!_SEH_prolog4 (71621020)
 6 717d72ec 33c0            xor     eax,eax
 7 717d72ee 8985e0ffffff    mov     dword ptr [ebp-20h],eax
 8 717d72f4 8985e4ffffff    mov     dword ptr [ebp-1Ch],eax
 9 717d72fa 8985fcffffff    mov     dword ptr [ebp-4],eax
10 717d7300 e81b460000      call    clr!_CorExeMainInternal (717db920)
11 717d7305 c785fcfffffffeffffff mov dword ptr [ebp-4],0FFFFFFFEh
12 717d730f 33c0            xor     eax,eax
13 717d7311 e8509de4ff      call    clr!_SEH_epilog4 (71621066)
14 717d7316 c3              ret

        2.2、我們檢視 Program 型別的 Main 方法的組合程式碼。
            測試原始碼:Example_8_1_1
            
 1 0:000> !clrstack
 2 OS Thread Id: 0x4164 (0)
 3 Child SP       IP Call Site
 4 006fee34 772b10fc [InlinedCallFrame: 006fee34] 
 5 006fee30 705f9b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
 6 006fee34 70d2b275 [InlinedCallFrame: 006fee34] Microsoft.Win32.Win32Native.ReadFile(.....) 7 006fee98 70d2b275 System.IO.__ConsoleStream.ReadFileNative(oolean, Int32 ByRef) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205]
 8 006feecc 70d2b17b System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134]
 9 006feeec 705de6a3 System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595]
10 006feefc 705deb5b System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748]
11 006fef18 70e73786 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363]
12 006fef28 70cd1845 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]
13 006fef30 025808d1 Example_8_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\..\Example_8_1_1\Program.cs @ 11]
14 006ff0d0 7162f036 [GCFrame: 006ff0d0] 

            025808d1 Main 方法的地址,我們可以使用【!u】檢視一下。

 1 0:000> !u 025808d1
 2 Normal JIT generated code
 3 Example_8_1_1.Program.Main(System.String[])
 4 Begin 02580848, size 95
 5 
 6 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_8_1_1\Program.cs @ 8:
 7 02580848 55              push    ebp
 8 02580849 8bec            mov     ebp,esp
 9 0258084b 57              push    edi
10 0258084c 56              push    esi
11 0258084d 83ec20          sub     esp,20h
12 02580850 8bf1            mov     esi,ecx
13 02580852 8d7dd8          lea     edi,[ebp-28h]
14 02580855 b906000000      mov     ecx,6
15 0258085a 33c0            xor     eax,eax
16 0258085c f3ab            rep stos dword ptr es:[edi]
17 0258085e 8bce            mov     ecx,esi
18 02580860 894df4          mov     dword ptr [ebp-0Ch],ecx
19 02580863 833df042c40000  cmp     dword ptr ds:[0C442F0h],0
20 0258086a 7405            je      02580871
21 0258086c e80ff5446f      call    clr!JIT_DbgIsJustMyCode (719cfd80)
22 02580871 33d2            xor     edx,edx
23 02580873 8955f0          mov     dword ptr [ebp-10h],edx
24 02580876 90              nop
25 
26 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_8_1_1\Program.cs @ 9:
27 02580877 b90a000000      mov     ecx,0Ah
28 0258087c ba0b000000      mov     edx,0Bh
29 02580881 ff156c4dc400    call    dword ptr ds:[0C44D6Ch] (Example_8_1_1.Program.Sum(Int32, Int32), mdToken: 06000002)
30 02580887 8945ec          mov     dword ptr [ebp-14h],eax
31 0258088a 8b45ec          mov     eax,dword ptr [ebp-14h]
32 0258088d 8945f0          mov     dword ptr [ebp-10h],eax
33 
34 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_8_1_1\Program.cs @ 10:
35 02580890 b9a8421570      mov     ecx,offset mscorlib_ni!GetObjectData+0x102 (701542a8) (MT: System.Int32)
36 02580895 e85a286bfe      call    00c330f4 (JitHelp: CORINFO_HELP_NEWSFAST)
37 0258089a 8945e8          mov     dword ptr [ebp-18h],eax
38 0258089d 8b0544237403    mov     eax,dword ptr ds:[3742344h] ("sum={0}")
39 025808a3 8945dc          mov     dword ptr [ebp-24h],eax
40 025808a6 8b45e8          mov     eax,dword ptr [ebp-18h]
41 025808a9 8b55f0          mov     edx,dword ptr [ebp-10h]
42 025808ac 895004          mov     dword ptr [eax+4],edx
43 025808af 8b45e8          mov     eax,dword ptr [ebp-18h]
44 025808b2 8945d8          mov     dword ptr [ebp-28h],eax
45 025808b5 8b4ddc          mov     ecx,dword ptr [ebp-24h]
46 025808b8 8b55d8          mov     edx,dword ptr [ebp-28h]
47 025808bb e890d7f86d      call    mscorlib_ni!System.String.Format (7050e050)
48 025808c0 8945e4          mov     dword ptr [ebp-1Ch],eax
49 025808c3 8b4de4          mov     ecx,dword ptr [ebp-1Ch]
50 025808c6 e89946076e      call    mscorlib_ni!System.Console.WriteLine (705f4f64)
51 025808cb 90              nop
52 
53 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_8_1_1\Program.cs @ 11:
54 025808cc e85f0f756e      call    mscorlib_ni!System.Console.ReadLine (70cd1830)
55 >>> 025808d1 8945e0          mov     dword ptr [ebp-20h],eax
56 025808d4 90              nop
57 
58 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_8_1_1\Program.cs @ 12:
59 025808d5 90              nop
60 025808d6 8d65f8          lea     esp,[ebp-8]
61 025808d9 5e              pop     esi
62 025808da 5f              pop     edi
63 025808db 5d              pop     ebp
64 025808dc c3              ret
View Code

            程式碼太多,不變展示。>>> 025808d1 8945e0 mov dword ptr [ebp-20h],eax,三個小於號,表示當前執行到的位置。


        2.3、使用【!dumpil】命令檢視 IL 程式碼。
            測試原始碼:Example_8_1_1
            我們要想檢視一個方法的 IL 程式碼,必須先找到 這個方法的 MD,也就是方法描述符。
            我們可以使用【!clrstack】命令找到 Main 方法的 IP,根據 IP 再找到 MD。
 1 0:000> !clrstack
 2 OS Thread Id: 0x4164 (0)
 3 Child SP       IP Call Site
 4 006fee34 772b10fc [InlinedCallFrame: 006fee34] 
 5 006fee30 705f9b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
 6 006fee34 70d2b275 [InlinedCallFrame: 006fee34] Microsoft.Win32.Win32Native.ReadFile(... Int32 ByRef, IntPtr)
 7 006fee98 70d2b275 System.IO.__ConsoleStream.ReadFileNative(...) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205]
 8 006feecc 70d2b17b System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134]
 9 006feeec 705de6a3 System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595]
10 006feefc 705deb5b System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748]
11 006fef18 70e73786 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363]
12 006fef28 70cd1845 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]
13 006fef30 025808d1 Example_8_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\...\Example_8_1_1\Program.cs @ 11]
14 006ff0d0 7162f036 [GCFrame: 006ff0d0] 

            025808d1 紅色標註的就是 Main 方法的IP,我們使用【!ip2md】命令,就可以找到方法描述符Md了。

 1 0:000> !ip2md 025808d1
 2 MethodDesc:   00c44d58
 3 Method Name:  Example_8_1_1.Program.Main(System.String[])
 4 Class:        00c41290
 5 MethodTable:  00c44d78
 6 mdToken:      06000001
 7 Module:       00c44044
 8 IsJitted:     yes
 9 CodeAddr:     02580848
10 Transparency: Critical
11 Source file:  E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_8_1_1\Program.cs @ 11

           MethodDesc: 00c44d58 紅色標註的就是方法描述符的而地址。有了方法描述(MD),我們就可以使用【!dumpil】命令檢視 IL 程式碼了。

 1 0:000> !dumpil 00c44d58
 2 ilAddr = 00362050
 3 IL_0000: nop 
 4 IL_0001: ldc.i4.s 10
 5 IL_0003: ldc.i4.s 11
 6 IL_0005: call Example_8_1_1.Program::Sum
 7 IL_000a: stloc.0 
 8 IL_000b: ldstr "sum={0}"
 9 IL_0010: ldloc.0 
10 IL_0011: box System.Int32
11 IL_0016: call System.String::Format 
12 IL_001b: call System.Console::WriteLine 
13 IL_0020: nop 
14 IL_0021: call System.Console::ReadLine 
15 IL_0026: pop 
16 IL_0027: ret 

            除了以上方法,我們也可以使用【!name2ee】命令達到同樣的效果。那我麼檢視一下 Sum 方法 IL 程式碼。

1 0:000> !name2ee Example_8_1_1!Example_8_1_1.Program.Sum
2 Module:      00c44044
3 Assembly:    Example_8_1_1.exe
4 Token:       06000002
5 MethodDesc:  00c44d64
6 Name:        Example_8_1_1.Program.Sum(Int32, Int32)
7 JITTED Code Address: 025808f0

           MethodDesc: 00c44d64 紅色標註的就是方法描述符的地址,有了這個地址,我們就可以使用【!dumpil】命令了。

 1 0:000> !dumpil 00c44d64
 2 ilAddr = 00362084
 3 IL_0000: nop 
 4 IL_0001: ldarg.0 
 5 IL_0002: stloc.0 
 6 IL_0003: ldarg.1 
 7 IL_0004: stloc.1 
 8 IL_0005: ldloc.0 
 9 IL_0006: ldloc.1 
10 IL_0007: add 
11 IL_0008: stloc.2 
12 IL_0009: ldloc.2 
13 IL_000a: stloc.3 
14 IL_000b: br.s IL_000d
15 IL_000d: ldloc.3 
16 IL_000e: ret 

        2.4、通過 Dump 檢視 C# 原始碼。
            測試原始碼:Example_8_1_1
            我們想要檢視 Example_8_1_1專案的原始碼,必須找到這個專案的【模組】,也就是 module,我們切換到主執行緒,也就是 0 號執行緒,使用命令【~0s】,使用命令【!clrstack】找到呼叫棧。
 1 0:000> !clrstack
 2 OS Thread Id: 0x3438 (0)
 3 Child SP       IP Call Site
 4 00cfef94 772910fc [InlinedCallFrame: 00cfef94] 
 5 00cfef90 6d869b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
 6 00cfef94 6df9b275 [InlinedCallFrame: 00cfef94] Microsoft.Win32.Win32Native.ReadFile(... Int32 ByRef, IntPtr)
 7 00cfeff8 6df9b275 System.IO.__ConsoleStream.ReadFileNative(.. Int32 ByRef) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205]
 8 00cff02c 6df9b17b System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134]
 9 00cff04c 6d84e6a3 System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595]
10 00cff05c 6d84eb5b System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748]
11 00cff078 6e0e3786 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363]
12 00cff088 6df41845 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]
13 00cff090 013e08d1 Example_8_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\...\Example_8_1_1\Program.cs @ 11]
14 00cff228 6fdbf036 [GCFrame: 00cff228] 

            013e08d1 紅色標註的地址就是 Main 方法的 IP,有了 IP,我們可以通過 IP 找到 MD(方法描述符),在方法描述符裡就包含了module 資訊。

 1 0:000> !ip2md 013e08d1
 2 MethodDesc:   00f14d58
 3 Method Name:  Example_8_1_1.Program.Main(System.String[])
 4 Class:        00f11290
 5 MethodTable:  00f14d78
 6 mdToken:      06000001
 7 Module:       00f14044
 8 IsJitted:     yes
 9 CodeAddr:     013e0848
10 Transparency: Critical
11 Source file:  E:\Visual Studio 2022\...\Example_8_1_1\Program.cs

             Module: 00f14044 這個資訊就是模組資訊,我們就可以使用【!savemodule】命令,儲存檔案了。

1 0:000> !savemodule 00f14044 F:\Test\SaveDump\test.dll
2 3 sections in file
3 section 0 - VA=2000, VASize=818, FileAddr=200, FileSize=a00
4 section 1 - VA=4000, VASize=5f0, FileAddr=c00, FileSize=600
5 section 2 - VA=6000, VASize=c, FileAddr=1200, FileSize=200

            檔案就儲存下來了,截圖效果:
            

            有了這個檔案,我們就可以使用 ILSpy 反編譯工具檢視原始碼了,很簡單,就不寫了。


        2.5、如何檢視 CLR 版本和 GC 模式。
            測試原始碼:Example_8_1_1
1 0:000> !eeversion
2 4.8.4300.0 retail
3 Workstation mode(工作站模式,一般指:Winform,WPF,Console等桌面應用)
4 SOS Version: 4.8.4300.0 retail build

            還有一種方式檢視 GC 模式,我們可以檢視 託管堆的個數,一個堆的就是工作站模式,其他就是伺服器模式。

 1 0:000> !eeheap -gc
 2 Number of GC Heaps: 1(這裡就表示是工作站模式)
 3 generation 0 starts at 0x02d91018
 4 generation 1 starts at 0x02d9100c
 5 generation 2 starts at 0x02d91000
 6 ephemeral segment allocation context: none
 7  segment     begin  allocated      size
 8 02d90000  02d91000  02d95ff4  0x4ff4(20468)
 9 Large object heap starts at 0x03d91000
10  segment     begin  allocated      size
11 03d90000  03d91000  03d95558  0x4558(17752)
12 Total Size:              Size: 0x954c (38220) bytes.
13 ------------------------------
14 GC Heap Size:    Size: 0x954c (38220) bytes.

 

        2.6、檢視執行緒棧物件。
            測試原始碼:Example_8_1_1
 1 0:000> !dso
 2 OS Thread Id: 0x3438 (0)
 3 ESP/REG  Object   Name
 4 00CFEFB0 02d94d64 Microsoft.Win32.SafeHandles.SafeFileHandle
 5 00CFEFD4 02d94d64 Microsoft.Win32.SafeHandles.SafeFileHandle
 6 00CFEFE0 02d94d64 Microsoft.Win32.SafeHandles.SafeFileHandle
 7 00CFF008 02d94d64 Microsoft.Win32.SafeHandles.SafeFileHandle
 8 00CFF030 02d94da0 System.IO.StreamReader
 9 00CFF034 02d94da0 System.IO.StreamReader
10 00CFF04C 02d95124 System.IO.TextReader+SyncTextReader
11 00CFF068 02d95124 System.IO.TextReader+SyncTextReader
12 00CFF078 02d924bc System.String[]
13 00CFF090 02d924e4 System.Int32
14 00CFF094 02d924c8 System.String    sum={0}
15 00CFF09C 02d93a4c System.String    sum=21
16 00CFF0A0 02d924e4 System.Int32
17 00CFF0AC 02d924bc System.String[]
18 00CFF124 02d924bc System.String[]
19 00CFF260 02d924bc System.String[]
20 00CFF28C 02d924bc System.String[]
21 00CFF358 02d9141c System.AppDomain
22 00CFF674 03d92338 System.Object[]    (System.Object[])
23 00CFF6B8 03d92338 System.Object[]    (System.Object[])
24 00CFF6BC 03d92338 System.Object[]    (System.Object[])
25 00CFF7CC 02d91238 System.SharedStatics

        2.7、如何檢視物件的參照根。
            測試原始碼:Example_8_1_2
            第一步,我們先在託管堆中查詢 Byte[] 陣列物件,執行命令【!dumpheap -type Byte[]】,程式碼如下:
  1 0:006> !dumpheap -type Byte[]
  2  Address       MT     Size
  3 029724c8 6d3f49d8       24     
  4 029724e0 6d4216e4       12     
  5 0297259c 6d4216e4      140     
  6 02972634 6d4216e4      268     
  7 02972740 6d4216e4      524     
  8 02974438 6d3c5c40      526     
  9 02974eec 6d3c5c40      268     
 10 02975208 6d3c5c40       12     
 11 03975568 6d3c5c40   100012     
 12 0398dc28 6d3c5c40   100012     
 13 039a62e8 6d3c5c40   100012     
 14 039be9a8 6d3c5c40   100012     
 15 039d7068 6d3c5c40   100012     
 16 039ef728 6d3c5c40   100012     
 17 03a07de8 6d3c5c40   100012     
 18 03a204a8 6d3c5c40   100012     
 19 03a38b68 6d3c5c40   100012     
 20 03a51228 6d3c5c40   100012     
 21 03a698e8 6d3c5c40   100012     
 22 03a81fa8 6d3c5c40   100012     
 23 03a9a668 6d3c5c40   100012     
 24 03ab2d28 6d3c5c40   100012     
 25 03acb3e8 6d3c5c40   100012     
 26 03ae3aa8 6d3c5c40   100012     
 27 03afc168 6d3c5c40   100012     
 28 03b14828 6d3c5c40   100012     
 29 03b2cee8 6d3c5c40   100012     
 30 03b455a8 6d3c5c40   100012     
 31 03b5dc68 6d3c5c40   100012     
 32 03b76328 6d3c5c40   100012     
 33 03b8e9e8 6d3c5c40   100012     
 34 03ba70a8 6d3c5c40   100012     
 35 03bbf768 6d3c5c40   100012     
 36 03bd7e28 6d3c5c40   100012     
 37 03bf04e8 6d3c5c40   100012     
 38 03c08ba8 6d3c5c40   100012     
 39 03c21268 6d3c5c40   100012     
 40 03c39928 6d3c5c40   100012     
 41 03c51fe8 6d3c5c40   100012     
 42 03c6a6a8 6d3c5c40   100012     
 43 03c82d68 6d3c5c40   100012     
 44 03c9b428 6d3c5c40   100012     
 45 03cb3ae8 6d3c5c40   100012     
 46 03ccc1a8 6d3c5c40   100012     
 47 03ce4868 6d3c5c40   100012     
 48 03cfcf28 6d3c5c40   100012     
 49 03d155e8 6d3c5c40   100012     
 50 03d2dca8 6d3c5c40   100012     
 51 03d46368 6d3c5c40   100012     
 52 03d5ea28 6d3c5c40   100012     
 53 03d770e8 6d3c5c40   100012     
 54 03d8f7a8 6d3c5c40   100012     
 55 03da7e68 6d3c5c40   100012     
 56 03dc0528 6d3c5c40   100012     
 57 03dd8be8 6d3c5c40   100012     
 58 03df12a8 6d3c5c40   100012     
 59 03e09968 6d3c5c40   100012     
 60 03e22028 6d3c5c40   100012     
 61 03e3a6e8 6d3c5c40   100012     
 62 03e52da8 6d3c5c40   100012     
 63 03e6b468 6d3c5c40   100012     
 64 03e83b28 6d3c5c40   100012     
 65 03e9c1e8 6d3c5c40   100012     
 66 03eb48a8 6d3c5c40   100012     
 67 03eccf68 6d3c5c40   100012     
 68 03ee5628 6d3c5c40   100012     
 69 03efdce8 6d3c5c40   100012     
 70 03f163a8 6d3c5c40   100012     
 71 03f2ea68 6d3c5c40   100012     
 72 03f47128 6d3c5c40   100012     
 73 03f5f7e8 6d3c5c40   100012     
 74 03f77ea8 6d3c5c40   100012     
 75 03f90568 6d3c5c40   100012     
 76 03fa8c28 6d3c5c40   100012     
 77 03fc12e8 6d3c5c40   100012     
 78 03fd99a8 6d3c5c40   100012     
 79 03ff2068 6d3c5c40   100012     
 80 0400a728 6d3c5c40   100012     
 81 04022de8 6d3c5c40   100012     
 82 0403b4a8 6d3c5c40   100012     
 83 04053b68 6d3c5c40   100012     
 84 0406c228 6d3c5c40   100012     
 85 040848e8 6d3c5c40   100012     
 86 0409cfa8 6d3c5c40   100012     
 87 040b5668 6d3c5c40   100012     
 88 040cdd28 6d3c5c40   100012     
 89 040e63e8 6d3c5c40   100012     
 90 040feaa8 6d3c5c40   100012     
 91 04117168 6d3c5c40   100012     
 92 0412f828 6d3c5c40   100012     
 93 04147ee8 6d3c5c40   100012     
 94 041605a8 6d3c5c40   100012     
 95 04178c68 6d3c5c40   100012     
 96 04191328 6d3c5c40   100012     
 97 041a99e8 6d3c5c40   100012     
 98 041c20a8 6d3c5c40   100012     
 99 041da768 6d3c5c40   100012     
100 041f2e28 6d3c5c40   100012     
101 0420b4e8 6d3c5c40   100012     
102 04223ba8 6d3c5c40   100012     
103 0423c268 6d3c5c40   100012     
104 04254928 6d3c5c40   100012     
105 0426cfe8 6d3c5c40   100012     
106 042856a8 6d3c5c40   100012     
107 0429dd68 6d3c5c40   100012     
108 042b6428 6d3c5c40   100012     
109 042ceae8 6d3c5c40   100012     
110 042e71a8 6d3c5c40   100012     
111 
112 Statistics:
113       MT    Count    TotalSize Class Name
114 6d3f49d8        1           24 System.Collections.Generic.List`1[[System.Byte[], mscorlib]]
115 6d4216e4        4          944 System.Byte[][]
116 6d3c5c40      103     10002006 System.Byte[]
117 Total 108 objects
View Code
            如果想看程式碼詳情,點開看吧,這裡就不展示了。
            資料大小為 100012 的專案,一種有 100 項,因為我們 For 迴圈了一百次,為什麼大小不是 100000,而是 100012 ,因為每個物件都有兩個附加成員。有了成員列表,我們可以在這個列表中,任意選擇一個項,在 Address 列,選一個地址,針對這個地址,我們使用【!gcroot】命令,就能看到我們想要的結果。
            我們選擇這個列表中最後一項,地址是:042e71a8
1 0:006> !gcroot 042e71a8
2 HandleTable:
3     00bc13ec (pinned handle)(static 底層是標記了 pinned,也就是這個 handle 持有 System.Object[] 陣列)
4     -> 03973568 System.Object[](這個物件是 CLR 建立的,持有這個 List 物件。)
5     -> 029724c8 System.Collections.Generic.List`1[[System.Byte[], mscorlib]](這個就是我們宣告的 list 型別,底層是一個 Byte 二維陣列)
6     -> 02972740 System.Byte[][](這個陣列持有 042e71a8 這個元素)
7     -> 042e71a8 System.Byte[](這個是我選擇的最後一個元素值)
8 
9 Found 1 unique roots (run '!GCRoot -all' to see all roots).

四、總結
    終於寫完了,寫作的過程是累並快樂著。學習過程真的沒那麼輕鬆,還好是自己比較喜歡這一行,否則真不知道自己能不能堅持下來。老話重談,《高階偵錯》的這本書第一遍看,真的很暈,第二遍稍微好點,不學不知道,一學嚇一跳,自己欠缺的很多。好了,不說了,不忘初心,繼續努力,希望老天不要辜負努力的人。