引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
一、簡介
這是我的《Advanced .Net Debugging》這個系列的第七篇文章。這篇文章的內容是原書的第二部分的【調試實戰】的第五章,這一章主要講的是從根本上認識托管堆和垃圾回收。軟體系統的記憶體管理方式有兩種,第一種是手動管理記憶體,這種方式容易產生一些問題產生,比如:懸空指針、重覆釋放,或者記憶體泄漏等;第二種是自動記憶體管理,比如:java 平臺、.NET 平臺。儘管 GC 能幫助開發人員簡化開發工作,讓他們更關註系統的業務功能實現。如果我們對 GC 運作原理瞭解更深入一些,也可以讓我們避免在垃圾回收環境中出現的問題。高級調試會涉及很多方面的內容,你對 .NET 基礎知識掌握越全面、細節越底層,調試成功的幾率越大,當我們遇到各種奇葩問題的時候才不會手足無措。
如果在沒有說明的情況下,所有代碼的測試環境都是 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源碼:源碼下載
在此說明:我使用了兩種調試工具,第一種:Windbg Preivew,圖形界面,使用方便,操作順手,不用擔心干擾;第二種是:NTSD,該調試器是命令行的,命令使用上和 Windbg 沒有任何區別,之所以增加了它的調試過程,不過是我的個人愛好,想多瞭解一些,看看他們有什麼區別,為了學習而使用的。如果在工作中,我推薦使用 Windbg Preview,更好用,更方便,也不會出現奇怪問題(我在使用 NTSD 調試斷點的時候,不能斷住,提示記憶體不可讀,Windbg preview 就沒有任何問題)。
如果大家想瞭解調試過程,二選一即可,當然,我推薦查看【Windbg Preview 調試】。
二、調試源碼
廢話不多說,本節是調試的源碼部分,沒有代碼,當然就談不上測試了,調試必須有載體。
2.1、ExampleCore_5_1
1 namespace ExampleCore_5_1 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 Name? name; 8 9 Console.WriteLine("Press any key to allocate memory"); 10 Console.ReadKey(); 11 12 name = new Name("Mario", "Hewardt"); 13 14 Console.WriteLine("Press any key to Exit"); 15 Console.ReadKey(); 16 } 17 } 18 19 internal class Name 20 { 21 private string _first; 22 private string _last; 23 24 public string First { get { return _first; } } 25 26 public string Last { get { return _last; } } 27 28 public Name(string first, string last) 29 { 30 _first = first; 31 _last = last; 32 } 33 } 34 }View Code
2.2、ExampleCore_5_2
1 namespace ExampleCore_5_2 2 { 3 internal class Name 4 { 5 private string _first; 6 private string _last; 7 8 public string First { get { return _first; } } 9 10 public string Last { get { return _last; } } 11 12 public Name(string first, string last) 13 { 14 _first = first; 15 _last = last; 16 } 17 } 18 19 internal class Program 20 { 21 static void Main(string[] args) 22 { 23 Name? n1 = new Name("Mario", "Hewardt"); 24 Name? n2 = new Name("Gemma", "Hewardt"); 25 26 Console.WriteLine("allocated objects"); 27 28 Console.WriteLine("Press any key to invoke GC"); 29 Console.ReadKey(); 30 31 n1 = null; 32 GC.Collect(); 33 34 Console.WriteLine("Press any key to invoke GC"); 35 Console.ReadKey(); 36 37 GC.Collect(); 38 39 Console.WriteLine("Press any key to Exit"); 40 Console.ReadKey(); 41 } 42 } 43 }View Code
2.3、ExampleCore_5_3
1 namespace ExampleCore_5_3 2 { 3 internal class Name 4 { 5 private string _first; 6 private string _last; 7 8 public string First { get { return _first; } } 9 10 public string Last { get { return _last; } } 11 12 public Name(string first, string last) 13 { 14 _first = first; 15 _last = last; 16 } 17 } 18 19 internal class Program 20 { 21 public static Name CompleteName = new Name("First", "Last"); 22 23 private Thread? thread; 24 private bool shouldExit; 25 static void Main(string[] args) 26 { 27 Program p = new Program(); 28 p.Run(); 29 } 30 31 public void Run() 32 { 33 shouldExit = false; 34 Name n1 = CompleteName; 35 36 thread = new Thread(Worker); 37 thread.Start(n1); 38 39 Thread.Sleep(1000); 40 41 Console.WriteLine("Press any key to Exit"); 42 Console.ReadKey(); 43 44 shouldExit = true; 45 } 46 47 public void Worker(object? o) 48 { 49 var n1 = (Name)o!; 50 Console.WriteLine("Thread started {0},{1}", n1.First, n1.Last); 51 52 while (true) 53 { 54 Thread.Sleep(500); 55 if (shouldExit) 56 { 57 break; 58 } 59 } 60 } 61 } 62 }View Code
2.4、ExampleCore_5_4
1 using System.Runtime.InteropServices; 2 3 namespace ExampleCore_5_4 4 { 5 internal class NativeEvent 6 { 7 private IntPtr _nativeHandle; 8 public IntPtr NativeHandle { get { return _nativeHandle; } } 9 10 public NativeEvent(string name) 11 { 12 _nativeHandle = CreateEvent(IntPtr.Zero, false, true, name); 13 } 14 15 ~NativeEvent() 16 { 17 if (_nativeHandle != IntPtr.Zero) 18 { 19 CloseHandle(_nativeHandle); 20 _nativeHandle = IntPtr.Zero; 21 } 22 } 23 24 [DllImport("kernel32.dll")] 25 public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool manualReset, bool initialState, string lpName); 26 27 [DllImport("kernel32.dll")] 28 public static extern IntPtr CloseHandle(IntPtr lpEvent); 29 } 30 31 internal class Program 32 { 33 static void Main(string[] args) 34 { 35 Program p = new Program(); 36 p.Run(); 37 } 38 39 public void Run() 40 { 41 NativeEvent nativeEvent= new NativeEvent("MyNewEvent"); 42 43 Console.WriteLine("Press any key to GC1!"); 44 Console.ReadKey(); 45 46 GC.Collect(); 47 48 Console.WriteLine("Press any key to GC2!"); 49 Console.ReadKey(); 50 51 GC.Collect(); 52 53 Console.WriteLine("Press any key to Exit!"); 54 Console.ReadKey(); 55 } 56 } 57 }View Code
2.5、ExampleCore_5_5
1 using System.Diagnostics; 2 3 namespace ExampleCore_5_5 4 { 5 internal class Program 6 { 7 static void Main(string[] args) 8 { 9 Test(); 10 11 Console.WriteLine("1、對象已經分配,請查看托管堆!"); 12 Debugger.Break(); 13 GC.Collect(); 14 15 Console.WriteLine("2、GC 已經觸發,請查看托管堆中的 byte2"); 16 Debugger.Break(); 17 18 Console.WriteLine("3、已分配 byte4,查看是否 Free 塊中"); 19 var byte4 = new byte[280000]; 20 Debugger.Break(); 21 } 22 23 public static byte[]? byte1; 24 public static byte[]? byte3; 25 26 private static void Test() 27 { 28 byte1 = new byte[185000]; 29 var byte2 = new byte[285000]; 30 byte3 = new byte[385000]; 31 } 32 } 33 }View Code
2.7、ExampleCore_5_6
1 using System.Runtime.InteropServices; 2 3 namespace ExampleCore_5_6 4 { 5 internal class Program 6 { 7 static void Main(string[] args) 8 { 9 Program program = new Program(); 10 program.Run(); 11 12 Console.WriteLine("Press any key to Exit!"); 13 Console.ReadKey(); 14 } 15 16 public void Run() 17 { 18 sbyte[] b1; 19 sbyte[] b2; 20 sbyte[] b3; 21 22 Console.WriteLine("Press any key to Alloc And Pinned!"); 23 Console.ReadKey(); 24 25 b1 = new sbyte[100]; 26 b2 = new sbyte[200]; 27 b3 = new sbyte[300]; 28 29 GCHandle h1 = GCHandle.Alloc(b1, GCHandleType.Pinned); 30 GCHandle h2 = GCHandle.Alloc(b2, GCHandleType.Pinned); 31 GCHandle h3 = GCHandle.Alloc(b3, GCHandleType.Pinned); 32 33 Console.WriteLine("Press any key to GC1!"); 34 Console.ReadKey(); 35 36 GC.Collect(); 37 38 Console.WriteLine("Press any key to Free!"); 39 Console.ReadKey(); 40 41 h1.Free(); 42 h2.Free(); 43 h3.Free(); 44 45 Console.WriteLine("Press any key to GC2!"); 46 Console.ReadKey(); 47 48 GC.Collect(); 49 } 50 } 51 }View Code
2.8、ExampleCore_5_7
1 using System.Runtime.InteropServices; 2 3 namespace ExampleCore_5_7 4 { 5 internal class Program 6 { 7 [DllImport("ExampleCore_5_8.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] 8 public extern static int InitChars(char[] chars); 9 10 static void Main(string[] args) 11 { 12 char[] c = { 'a', 'b', 'c' }; 13 var len = InitChars(c); 14 15 GC.Collect(); 16 17 Console.WriteLine($"len={len}"); 18 for (int i = 0; i < c.Count(); i++) 19 { 20 Console.WriteLine($"{i}={c[i]}"); 21 } 22 Console.Read(); 23 } 24 } 25 }View Code
2.9、ExampleCore_5_8(C++)
1 // ExampleCore_5_8.cpp : 此文件包含 "main" 函數。程式執行將在此處開始並結束。 2 // 3 4 extern "C" 5 { 6 _declspec(dllexport) int InitChars(wchar_t c[]); 7 } 8 9 #include <iostream> 10 using namespace std; 11 12 int InitChars(wchar_t* c) 13 { 14 for (size_t i = 0; i < 100; i++) 15 { 16 c[i] = L'a'; 17 } 18 return sizeof(wchar_t) * 100; 19 }View Code
2.10、ExampleCore_5_9
1 using System.Runtime.InteropServices; 2 3 namespace ExampleCore_5_9 4 { 5 internal class Program 6 { 7 static void Main(string[] args) 8 { 9 Program program = new Program(); 10 program.Run(); 11 } 12 13 public void Run() 14 { 15 Console.WriteLine("<alloc size>"); 16 var sizeValue = Console.ReadLine(); 17 if (!int.TryParse(sizeValue, out int size)) 18 { 19 Console.WriteLine("參數:<alloc size>!"); 20 return; 21 } 22 23 Console.WriteLine("<max mem in MB>"); 24 var maxMemValue = Console.ReadLine(); 25 if (!int.TryParse(maxMemValue, out int maxMem)) 26 { 27 Console.WriteLine("參數:<max mem in MB>!"); 28 return; 29 } 30 31 byte[][]? nonPinned = null; 32 byte[][]? pinned = null; 33 GCHandle[]? pinnedHandles = null; 34 35 int numAllocs = maxMem * 1000000 / size; 36 pinnedHandles = new GCHandle[numAllocs]; 37 38 pinned =