在.NET中Newtonsoft.Json(Json.NET)是我們常用來進行Json序列化與反序列化的庫。 而在使用中常會遇到反序列化Json時,遇到不規則的Json數據解構而拋出異常。 Newtonsoft.Json 支持序列化和反序列化過程中的錯誤處理。 允許您捕獲錯誤並選擇是處理它並繼續序列 ...
一、介紹
這是我的《Advanced .Net Debugging》這個系列的第五篇文章。今天這篇文章的標題雖然叫做“基本調試任務”,但是這章的內容還是挺多的。上一篇我們瞭解了一些調.NET 框架中必要的概念,比如:記憶體轉儲、值類型轉儲、引用類型轉儲、數組轉儲和異常轉儲等,我們既能做到知其然,又能做到眼見為實,知其所以然,對我們分析.NET 程式有很大的幫助。今天這篇文章主要涉及的內容是線程的操作、代碼的審查和診斷命令等。SOSEX擴展的內容我就省略了,因為我這個系列的是基於 .NET 8 版本來寫的,SOSEX是基於 .NET Framework 版本的,如果大家想瞭解其內容,可以查看我的【高級調試】系列(我當前寫的是《Advanced .Net Debugging》系列,是不一樣的),當然,也可以看原書。【高級調試】系列主要是集中在 .NET Framework 版本的。如果我們想成為一名合格程式員,這些調試技巧都是必須要掌握的。
如果在沒有說明的情況下,所有代碼的測試環境都是 Net 8.0,如果有變動,我會在項目章節里進行說明。好了,廢話不多說,開始我們今天的調試工作。
調試環境我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
操作系統:Windows Professional 10
調試工具:Windbg Preview(Debugger Client:1.2306.1401.0,Debugger engine:10.0.25877.1004)和 NTSD(10.0.22621.2428 AMD64)
下載地址:可以去Microsoft Store 去下載
開發工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
Net 版本:.Net 8.0
CoreCLR源碼:源碼下載
二、調試源碼
廢話不多說,本節是調試的源碼部分,沒有代碼,當然就談不上測試了,調試必須有載體。
2.1、ExampleCore_3_1_9
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 namespace ExampleCore_3_1_9 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 int a = 10; 8 int b = 11; 9 Console.WriteLine("X={0},Y={1}", a, b); 10 Test(12); 11 Console.ReadKey(); 12 } 13 14 private static void Test(int c) 15 { 16 Task.Run(Run1); 17 Task.Run(Run2); 18 Task.Run(Run3); 19 } 20 21 private static void Run1() 22 { 23 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run1 正在運行"); 24 Console.ReadLine(); 25 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run1 結束運行"); 26 } 27 28 private static void Run2() 29 { 30 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run2 正在運行"); 31 Console.ReadLine(); 32 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run2 結束運行"); 33 } 34 35 private static void Run3() 36 { 37 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run3 正在運行"); 38 Console.ReadLine(); 39 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run3 結束運行"); 40 } 41 } 42 }View Code
2.2、ExampleCore_3_1_10
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 namespace ExampleCore_3_1_10 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
2.3、ExampleCore_3_1_11
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 namespace ExampleCore_3_1_11 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.4、ExampleCore_3_1_12
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 namespace ExampleCore_3_1_12 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 Console.WriteLine("請輸入一個除數:"); 8 var num = Console.ReadLine(); 9 var result = 10 / Convert.ToInt32(num); 10 11 Console.ReadLine(); 12 } 13 } 14 }View Code
三、基礎知識
3.1、線程的操作
在非托管代碼調試中,所有與線程相關的調試器命令都是以非托管 Windows 線程為基礎的,換個說法就是,用於調試非托管代碼的調試器命令使用的就是非托管 Windows 線程,調試器命令知道如何轉儲我們要轉儲的對象。但是,托管代碼就不一樣了,它的線程有自己的結構,調試器本身是無法對調用棧進行遍歷的。
我們知道,CLR 能對托管代碼進行動態轉換,並且 JIT 編譯器可以將生成的機器代碼放在它認為合適的地方。非托管調試器是不知道 JIT 編譯器的任何知識,它也不知道生成的代碼放在何處,因此就不能正確的顯示棧回溯。
3.1.1、ClrStack
A、基礎知識
SOS 調試器擴展提供了一個專門查看托管函數調用棧的命令,畢竟只有 JIT 編譯器更熟悉托管函數,也知道編譯後的機器碼放在什麼位置。
這個命令就是【!clrstack】。
!clrstack -a(all):這個命令表示將線程棧中的所有局部變數和參數全部輸出。
!clrstack -p(parameter):這個命令表示將線程棧中的參數全部輸出。
!clrstack -l(locals):這個命令表示將線程棧中的所有局部變數全部輸出。
我們還可以查看所有托管線程棧,可以使用【~*e】命令。
請註意:如果 ClrStack 命令在非托管線程的上下文中運行,將會顯示一個錯誤:
1 0:006> !clrstack 2 OS Thread Id: 0x335c (6) 3 Unable to walk the managed stack. The current thread is likely not a 4 managed thread. You can run !clrthreads to get a list of managed threads in 5 the process 6 Failed to start stack walk: 80070057
B、眼見為實
調試源碼:ExampleCore_3_1_9
調試任務:ClrStack 命令使用
1)、NTSD 調試
編譯項目,打開【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_9\bin\Debug\net8.0\ExampleCore_3_1_9.exe】。
![](https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240311133100758-1998649146.png)
打開【NTSD】調試器視窗。
使用【g】命令運行調試器,知道調試器有如圖輸出,調試器會卡住暫停。
按組合鍵【ctrl+c】進入中斷模式,在所有操作之前,我們先切換到托管線程,執行命令【~0s】。
1 0:003> ~0s 2 coreclr!CorSigUncompressElementType_EndPtr+0x18 [inlined in coreclr!MetaSig::CompareElementType+0x1d0]: 3 00007ffb`98b68e40 48ffc0 inc rax
我們先使用【!clrstack】命令,後面不跟任何命令開關。
1 0:000> !clrstack 2 OS Thread Id: 0x3de4 (0) 3 Child SP IP Call Site 4 000000F08ABCDFF8 00007ffb98b68e40 [ExternalMethodFrame: 000000f08abcdff8] 5 000000F08ABCE5D0 00007FFC5382F269 System.Text.DecoderDBCS.GetChars(Byte[], Int32, Int32, Char[], Int32, Boolean) 6 000000F08ABCE660 00007FFB92A28A51 System.IO.StreamReader.ReadBuffer() 7 000000F08ABCE6B0 00007FFB92A290A4 System.IO.StreamReader.ReadLine() 8 000000F08ABCE760 00007FFC5383005D System.IO.SyncTextReader.ReadLine() 9 000000F08ABCE7B0 00007FFC53829319 System.Console.ReadLine() 10 000000F08ABCE7E0 00007FFB391219CF ExampleCore_3_1_9.Program.Main(System.String[])
它羅列出了調用棧。如果想查看每個棧幀的參數,我們可以使用【!clrstack -p】命令,很簡單,就不多說了。
1 0:000> !clrstack -p 2 OS Thread Id: 0x3de4 (0) 3 Child SP IP Call Site 4 000000F08ABCDFF8 00007ffb98b68e40 [ExternalMethodFrame: 000000f08abcdff8] 5 。。。。。。(省略了) 6 000000F08ABCE7E0 00007FFB391219CF ExampleCore_3_1_9.Program.Main(System.String[]) 7 PARAMETERS:(這裡就是參數所在地) 8 args (0x000000F08ABCE830) = 0x000002664e008ea0
如果想查看每個棧幀的局部變數,可以使用【!clrstack -l】命令。
1 0:000> !clrstack -l 2 OS Thread Id: 0x3de4 (0) 3 Child SP IP Call Site 4 000000F08ABCDFF8 00007ffb98b68e40 [ExternalMethodFrame: 000000f08abcdff8] 5 。。。。。。(省略了) 6 000000F08ABCE7E0 00007FFB391219CF ExampleCore_3_1_9.Program.Main(System.String[]) 7 LOCALS:(以下就是局部變數) 8 0x000000F08ABCE81C = 0x000000000000000a 9 0x000000F08ABCE818 = 0x000000000000000b
如果想查看每一棧幀的局部變數和參數,可以使用【!clrstack -a】命令。
1 0:000> !clrstack -a 2 OS Thread Id: 0x3de4 (0) 3 Child SP IP Call Site 4 000000F08ABCDFF8 00007ffb98b68e40 [ExternalMethodFrame: 000000f08abcdff8] 5 。。。。。。(省略了) 6 000000F08ABCE7E0 00007FFB391219CF ExampleCore_3_1_9.Program.Main(System.String[]) 7 PARAMETERS:(這裡是參數區域) 8 args (0x000000F08ABCE830) = 0x000002664e008ea0 9 LOCALS:(這裡是局部變數區域) 10 0x000000F08ABCE81C = 0x000000000000000a 11 0x000000F08ABCE818 = 0x000000000000000b
很簡單,就不多數了。
還有一個命令,我們要熟悉一下,那就是~*e 命令。
~ 命令輸出所有線程列表。
1 0:000> ~ 2 . 0 Id: 3f64.3de4 Suspend: 1 Teb: 000000f0`8acb8000 Unfrozen 3 1 Id: 3f64.ab4 Suspend: 1 Teb: 000000f0`8acd6000 Unfrozen ".NET Tiered Compilation Worker" 4 2 Id: 3f64.b08 Suspend: 1 Teb: 000000f0`8acd8000 Unfrozen 5 # 3 Id: 3f64.6d0 Suspend: 1 Teb: 000000f0`8acda000 Unfrozen 6 4 Id: 3f64.220c Suspend: 1 Teb: 000000f0`8acc0000 Unfrozen ".NET EventPipe" 7 5 Id: 3f64.1830 Suspend: 1 Teb: 000000f0`8acc2000 Unfrozen ".NET Debugger" 8 6 Id: 3f64.3c34 Suspend: 1 Teb: 000000f0`8acc4000 Unfrozen ".NET Finalizer" 9 8 Id: 3f64.10d0 Suspend: 1 Teb: 000000f0`8acca000 Unfrozen ".NET TP Worker" 10 9 Id: 3f64.3e9c Suspend: 1 Teb: 000000f0`8accc000 Unfrozen ".NET TP Gate" 11 10 Id: 3f64.3d90 Suspend: 1 Teb: 000000f0`8acce000 Unfrozen ".NET TP Worker" 12 11 Id: 3f64.1e48 Suspend: 1 Teb: 000000f0`8acd0000 Unfrozen ".NET TP Worker"
~* 命令,顯示所有線程列表和入口函數。
1 0:000> ~* 2 . 0 Id: 3f64.3de4 Suspend: 1 Teb: 000000f0`8acb8000 Unfrozen 3 Start: apphost!wmainCRTStartup (00007ff7`8a2b1360) 4 Priority: 0 Priority class: 32 Affinity: f 5 1 Id: 3f64.ab4 Suspend: 1 Teb: 000000f0`8acd6000 Unfrozen ".NET Tiered Compilation Worker" 6 Start: coreclr!TieredCompilationManager::BackgroundWorkerBootstrapper0 (00007ffb`98c833f0) 7 Priority: 0 Priority class: 32 Affinity: f 8 2 Id: 3f64.b08 Suspend: 1 Teb: 000000f0`8acd8000 Unfrozen 9 Start: KERNELBASE!CtrlRoutine (00007ffc`651d9c80) 10 Priority: 2 Priority class: 32 Affinity: f 11 # 3 Id: 3f64.6d0 Suspend: 1 Teb: 000000f0`8acda000 Unfrozen 12 Start: ntdll!DbgUiRemoteBreakin (00007ffc`6791ba10) 13 Priority: 0 Priority class: 32 Affinity: f 14 4 Id: 3f64.220c Suspend: 1 Teb: 000000f0`8acc0000 Unfrozen ".NET EventPipe" 15 Start: coreclr!server_thread (00007ffb`98c6b530) 16 Priority: 0 Priority class: 32 Affinity: f 17 5 Id: 3f64.1830 Suspend: 1 Teb: 000000f0`8acc2000 Unfrozen ".NET Debugger" 18 Start: coreclr!DebuggerRCThread::ThreadProcStatic (00007ffb`98c667f0) 19 Priority: 0 Priority class: 32 Affinity: f 20 6 Id: 3f64.3c34 Suspend: 1 Teb: 000000f0`8acc4000 Unfrozen ".NET Finalizer" 21 Start: coreclr!FinalizerThread::FinalizerThreadStart (00007ffb`98c4e370) 22 Priority: 2 Priority class: 32 Affinity: f 23 8 Id: 3f64.10d0 Suspend: 1 Teb: 000000f0`8acca000 Unfrozen ".NET TP Worker" 24 Start: coreclr!ThreadNative::KickOffThread (00007ffb`98c07340) 25 Priority: 0 Priority class: 32 Affinity: f 26 9 Id: 3f64.3e9c Suspend: 1 Teb: 000000f0`8accc000 Unfrozen ".NET TP Gate" 27 Start: coreclr!ThreadNative::KickOffThread (00007ffb`98c07340) 28 Priority: 0 Priority class: 32 Affinity: f 29 10 Id: 3f64.3d90 Suspend: 1 Teb: 000000f0`8acce000 Unfrozen ".NET TP Worker" 30 Start: coreclr!ThreadNative::KickOffThread (00007ffb`98c07340) 31 Priority: 0 Priority class: 32 Affinity: f 32 11 Id: 3f64.1e48 Suspend: 1 Teb: 000000f0`8acd0000 Unfrozen ".NET TP Worker" 33 Start: coreclr!ThreadNative::KickOffThread (00007ffb`98c07340) 34 Priority: 0 Priority class: 32 Affinity: f
2)、Windbg Preview 調試
編譯項目,打開【Windbg Preview】,依次點擊【文件】--->【Launch executable】,載入我們的項目文件:ExampleCore_3_1_9.exe,進入調試器。繼續使用【g】命令,運行調試器。我們的控制台程式輸出如下內容,如圖:
![](https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240311113729639-1977028775.png)
點擊【break】按鈕,中斷調試器的執行,由於,我們手動中斷程式執行,需要將線程切換到托管線程上,執行命令【~0s】。
1 0:001> ~0s 2 ntdll!NtReadFile+0x14: 3 00007ffc`678eae54 c3 ret
我們繼續使用【!clrstack】命令,輸出托管線程的調用棧。
1 0:000> !clrstack 2 OS Thread Id: 0x3b40 (0)【底層操作系統的ID】 3 Child SP IP Call Site 4 000000D3A5F7E560 00007ffc678eae54 [InlinedCallFrame: 000000d3a5f7e560] 5 000000D3A5F7E560 00007ffc0bf676eb [InlinedCallFrame: 000000d3a5f7e560] 6 000000D3A5F7E530 00007ffc0bf676eb Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr) [/_/.../LibraryImports.g.cs @ 412] 7 000000D3A5F7E620 00007ffc0bf6c9c0 System.ConsolePal+WindowsConsoleStream..../System/ConsolePal.Windows.cs @ 1150] 8 000000D3A5F7E680 00007ffc0bf6c8bb System.ConsolePal+WindowsConsoleStream.Read(System.Span`1) [/_/src/libraries/....Windows.cs @ 1108] 9 000000D3A5F7E6C0 00007ffc0bf6fb84 System.IO.ConsoleStream.Read(Byte[], Int32, Int32) [/_/src/libraries/.../ConsoleStream.cs @ 34] 10 000000D3A5F7E730 00007ffb8d1589c1 System.IO.StreamReader.ReadBuffer() [/_/src/libraries/System.Private.CoreLib/.../IO/StreamReader.cs @ 613] 11 000000D3A5F7E780 00007ffb8d1590a4 System.IO.StreamReader.ReadLine() [/_/src/libraries/System.Private.CoreLib/.../StreamReader.cs @ 802] 12 000000D3A5F7E830 00007ffc0bf7005d System.IO.SyncTextReader.ReadLine() [/_/src/libraries/System.Console/src/System/IO/SyncTextReader.cs @ 77] 13 000000D3A5F7E880 00007ffc0bf69319 System.Console.ReadLine() [/_/src/libraries/System.Console/src/System/Console.cs @ 752] 14 000000D3A5F7E8B0 00007ffb2e3419cf ExampleCore_3_1_9.Program.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_9\Program.cs @ 11]
我們可以使用【!clrstack -l】,查看托管線程調用棧,並顯示每個棧幀的局部變數。
1 0:000> !clrstack -l 2 OS Thread Id: 0x3b40 (0) 3 Child SP IP Call Site 4 000000D3A5F7E560 00007ffc678eae54 [InlinedCallFrame: 000000d3a5f7e560] 5 000000D3A5F7E560 00007ffc0bf676eb [InlinedCallFrame: 000000d3a5f7e560] 。。。。。。(省略了)49 50 000000D3A5F7E8B0 00007ffb2e3419cf ExampleCore_3_1_9.Program.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_9\Program.cs @ 11] 51 LOCALS: 52 0x000000D3A5F7E8EC = 0x000000000000000a 53 0x000000D3A5F7E8E8 = 0x000000000000000b 這就是我們的局部變數
如果我們想查看托管調用棧的參數,可以使用【!clrstack -p】命令,p(parameter)參數。
1 0:000> !clrstack -p 2 OS Thread Id: 0x3b40 (0) 3 Child SP IP Call Site 4 000000D3A5F7E560 00007ffc678eae54 [InlinedCallFrame: 000000d3a5f7e560] 5 000000D3A5F7E560 00007ffc0bf676eb [InlinedCallFrame: 000000d3a5f7e560] 。。。。。。(省略了)47 48 000000D3A5F7E8B0 00007ffb2e3419cf ExampleCore_3_1_9.Program.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_9\Program.cs @ 11] 49 PARAMETERS: 50 args (0x000000D3A5F7E900) = 0x000001efb4408ea0(這個就是 Main 方法的 args 參數)
0x000001efb4408ea0 這個地址就是 Program.Main 方法的 args 參數,args 是字元串數組,是引用類型,我們可以直接使用【!dumpobj】來確認。
1 0:000> !dumpobj 0x000001efb4408ea0 2 Name: System.String[] 3 MethodTable: 00007ffb2e3cfab0 4 EEClass: 00007ffb2e27c440 5 Tracked Type: false 6 Size: 24(0x18) bytes 7 Array: Rank 1, Number of elements 0, Type CLASS (Print Array) 8 Fields: 9 None
如果想同時查看局部變數和參數,可以使用【!clrstack -a】命令。
1 0:000> !clrstack -a 2 OS Thread Id: 0x3b40 (0) 3 Child SP IP Call Site 4 000000D3A5F7E560 00007ffc678eae54 [InlinedCallFrame: 000000d3a5f7e560] 5 000000D3A5F7E560 00007ffc0bf676eb [InlinedCallFrame: 000000d3a5f7e560] 6 。。。。。。(省略了) 7 8 000000D3A5F7E8B0 00007ffb2e3419cf ExampleCore_3_1_9.Program.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_9\Program.cs @ 11] 9 PARAMETERS: 10 args (0x000000D3A5F7E900) = 0x000001efb4408ea0 (這是參數) 11 LOCALS: 12 0x000000D3A5F7E8EC = 0x000000000000000a (這是局部變數) 13 0x000000D3A5F7E8E8 = 0x000000000000000b (這是局部變數)
3.1.2、Threads
A、基礎知識
如何你想枚舉出進程中所有的托管代碼線程,可以使用【!threads】命令,或者簡寫形式【!t】。當然,這個命令也有一些開關設置,-live 只羅列出那些處於活躍狀態的線程的信息,-special 只輸出進程中所有“特殊的”線程,比如:垃圾收集線程、調試器線程、線程池定時器線程等。
B、眼見為實
調試源碼:ExampleCore_3_1_9
調試任務:Threads 命令的使用
1)、NTSD 調試
編譯項目,打開【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_9\bin\Debug\net8.0\ExampleCore_3_1_9.exe】,打開調試器視窗。
![](https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240312130733314-1204641246.png)
使用【g】命令,繼續運行調試器,等到調試器輸出以下內容:
按【ctrl+c】組合鍵進入調試模式。
1 0:013> !threads 2 ThreadCount: 8 3 UnstartedThread: 1 4 BackgroundThread: 5 5 PendingThread: 1 6 DeadThread: 1 7 Hosted Runtime: no