1 using System; 2 using System.Threading; 3 4 namespace Example_14_1_1 5 { 6 internal class Program 7 { 8 static void Main(string[] args) 9 { 10 var thread = new Thread(() => 11 { 12 Console.WriteLine($"tid={Environment.CurrentManagedThreadId}"); 13 Console.ReadLine(); 14 }); 15 16 thread.Start(); 17 18 Console.ReadLine(); 19 } 20 } 21 }
1 using System; 2 using System.Diagnostics; 3 using System.Threading; 4 5 namespace Example_14_1_2 6 { 7 internal class Program 8 { 9 public static ManualResetEvent mre = new ManualResetEvent(false); 10 11 static void Main(string[] args) 12 { 13 Console.WriteLine($"mre 預設為 false,即等待狀態,請檢視!"); 14 Debugger.Break(); 15 16 mre.Set(); 17 Console.WriteLine($"mre 預設為 true,即放行狀態,請檢視!"); 18 Debugger.Break(); 19 20 mre.Reset(); 21 Console.WriteLine($"mre Reset後為 false,即等待狀態,請檢視!"); 22 Debugger.Break(); 23 } 24 } 25 }
1 using System; 2 using System.Diagnostics; 3 using System.Threading; 4 5 namespace Example_14_1_3 6 { 7 internal class Program 8 { 9 public static Semaphore sem = new Semaphore(1, 10); 10 static void Main(string[] args) 11 { 12 for (int i = 0; i < int.MaxValue; i++) 13 { 14 sem.Release(); 15 Console.WriteLine("檢視當前的 sem 值。"); 16 Debugger.Break(); 17 } 18 } 19 } 20 }
1 using System.Diagnostics; 2 3 namespace Example_14_1_4_Core 4 { 5 internal class Program 6 { 7 public static Person person = new Person(); 8 9 static void Main(string[] args) 10 { 11 Task.Run(() => 12 { 13 lock (person) 14 { 15 Console.WriteLine($"{Environment.CurrentManagedThreadId} 已進入 Person 鎖中111111"); 16 Debugger.Break(); 17 } 18 }); 19 Task.Run(() => 20 { 21 lock (person) 22 { 23 Console.WriteLine($"{Environment.CurrentManagedThreadId} 已進入 Person 鎖中222222"); 24 Debugger.Break(); 25 } 26 }); 27 Console.ReadLine(); 28 } 29 } 30 31 public class Person 32 { 33 } 34 }
1 using System; 2 using System.Diagnostics; 3 using System.Threading.Tasks; 4 5 namespace Example_14_1_5 6 { 7 internal class Program 8 { 9 public static Person person = new Person(); 10 11 static void Main(string[] args) 12 { 13 Task.Run(() => 14 { 15 lock (person) 16 { 17 Console.WriteLine($"{Environment.CurrentManagedThreadId} 已進入 Person 鎖中"); 18 Debugger.Break(); 19 } 20 }); 21 Console.ReadLine(); 22 } 23 } 24 25 public class Person 26 { 27 } 28 }
程式執行起來如下:
接著,我們在過【通過名稱過濾(Filter by name)】中輸入我們專案的名稱:Example_14_1_1,來程序查詢。效果如圖:
我們在找到的程序上雙擊破,開啟新視窗,如圖:
我們找到了我們專案程序的主鍵執行緒編號,然後就可以使用 Windbg 檢視核心態的執行緒表示了。我們主執行緒的編號是:1204,這個是十進位制的,要注意。
然後,我們開啟 Windbg,點選【File】-->【Attach to kernel(附加核心態)】,在右側選擇【local】,就是本機的核心態,點選【ok】按鈕,進入偵錯介面。然後,我們使用【process】命令查詢一下我們的專案。
1 lkd> !process 0 2 Example_14_1_1.exe 2 PROCESS ffff9004b47eb080 3 SessionId: 1 Cid: 3a0c Peb: 00322000 ParentCid: 24bc 4 DirBase: 36353c002 ObjectTable: ffffc6096ce7b180 HandleCount: 194. 5 Image: Example_14_1_1.exe 6 7 THREAD ffff9004b64d2080 Cid 3a0c.04b4 Teb: 0000000000324000 Win32Thread: ffff9004b7232db0 WAIT: (Executive) KernelMode Alertable 8 ffff9004b7310e68 NotificationEvent 9 10 THREAD ffff9004b42e70c0 Cid 3a0c.0fb8 Teb: 000000000032d000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable 11 ffff9004b7352ae0 SynchronizationEvent 12 ffff9004b7352760 SynchronizationEvent 13 ffff9004b73524e0 SynchronizationEvent 14 15 THREAD ffff9004b6b4f100 Cid 3a0c.3ab8 Teb: 0000000000330000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable 16 ffff9004a72b2b20 NotificationEvent 17 ffff9004b7352660 SynchronizationEvent 18 ffff9004b4d35a90 SynchronizationEvent 19 20 THREAD ffff9004b63ea080 Cid 3a0c.318c Teb: 0000000000333000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Alertable 21 ffff9004b7353560 SynchronizationEvent
他會把這個程序中的所有執行緒找出來。然後,我們點選【break】按鈕,我們通過【ProcessExploler】看到我們專案的主執行緒是:1204,這個值是十進位制的,我們看看十六進位制是多少。
1 lkd> ?0n1204 2 Evaluate expression: 1204 = 00000000`000004b4
然後,我們使用04b4查詢一下,效果如圖:
ffff9004b64d2080 這個值就是執行緒的核心態的資料結構,我們可以繼續使用【dt】命令檢視一下詳情。
1 lkd> dt nt!_KThread ffff9004b64d2080 2 +0x000 Header : _DISPATCHER_HEADER 3 +0x018 SListFaultAddress : (null) 4 +0x020 QuantumTarget : 0x9c1aedd 5 +0x028 InitialStack : 0xfffff48b`1c777c50 Void 6 +0x030 StackLimit : 0xfffff48b`1c771000 Void 7 +0x038 StackBase : 0xfffff48b`1c778000 Void 8 +0x040 ThreadLock : 0 9 +0x048 CycleTime : 0x766fe16 10 +0x050 CurrentRunTime : 0 11 +0x054 ExpectedRunTime : 0x589722 12 +0x058 KernelStack : 0xfffff48b`1c777570 Void 13 +0x060 StateSaveArea : 0xfffff48b`1c777c80 _XSAVE_FORMAT 14 +0x068 SchedulingGroup : (null) 15 +0x070 WaitRegister : _KWAIT_STATUS_REGISTER 16 +0x071 Running : 0 '' 17 +0x072 Alerted : [2] "" 18 +0x074 AutoBoostActive : 0y1 19 +0x074 ReadyTransition : 0y0 20 +0x074 WaitNext : 0y0 21 +0x074 SystemAffinityActive : 0y0 22 +0x074 Alertable : 0y1 23 +0x074 UserStackWalkActive : 0y0 24 +0x074 ApcInterruptRequest : 0y0 25 +0x074 QuantumEndMigrate : 0y0 26 +0x074 UmsDirectedSwitchEnable : 0y0 27 +0x074 TimerActive : 0y0 28 +0x074 SystemThread : 0y0 29 +0x074 ProcessDetachActive : 0y0 30 +0x074 CalloutActive : 0y0 31 +0x074 ScbReadyQueue : 0y0 32 +0x074 ApcQueueable : 0y1 33 +0x074 ReservedStackInUse : 0y0 34 +0x074 UmsPerformingSyscall : 0y0 35 +0x074 TimerSuspended : 0y0 36 +0x074 SuspendedWaitMode : 0y0 37 +0x074 SuspendSchedulerApcWait : 0y0 38 +0x074 CetUserShadowStack : 0y0 39 +0x074 BypassProcessFreeze : 0y0 40 +0x074 Reserved : 0y0000000000 (0) 41 +0x074 MiscFlags : 0n16401 42 +0x078 ThreadFlagsSpare : 0y00 43 +0x078 AutoAlignment : 0y1 44 +0x078 DisableBoost : 0y0 45 +0x078 AlertedByThreadId : 0y0 46 +0x078 QuantumDonation : 0y1 47 +0x078 EnableStackSwap : 0y1 48 +0x078 GuiThread : 0y1 49 +0x078 DisableQuantum : 0y0 50 +0x078 ChargeOnlySchedulingGroup : 0y0 51 +0x078 DeferPreemption : 0y0 52 +0x078 QueueDeferPreemption : 0y0 53 +0x078 ForceDeferSchedule : 0y0 54 +0x078 SharedReadyQueueAffinity : 0y1 55 +0x078 FreezeCount : 0y0 56 +0x078 TerminationApcRequest : 0y0 57 +0x078 AutoBoostEntriesExhausted : 0y1 58 +0x078 KernelStackResident : 0y1 59 +0x078 TerminateRequestReason : 0y00 60 +0x078 ProcessStackCountDecremented : 0y0 61 +0x078 RestrictedGuiThread : 0y0 62 +0x078 VpBackingThread : 0y0 63 +0x078 ThreadFlagsSpare2 : 0y0 64 +0x078 EtwStackTraceApcInserted : 0y00000000 (0) 65 +0x078 ThreadFlags : 0n205028 66 +0x07c Tag : 0 '' 67 +0x07d SystemHeteroCpuPolicy : 0 '' 68 +0x07e UserHeteroCpuPolicy : 0y0001000 (0x8) 69 +0x07e ExplicitSystemHeteroCpuPolicy : 0y0 70 +0x07f RunningNonRetpolineCode : 0y0 71 +0x07f SpecCtrlSpare : 0y0000000 (0) 72 +0x07f SpecCtrl : 0 '' 73 +0x080 SystemCallNumber : 0x1a0006 74 +0x084 ReadyTime : 1 75 +0x088 FirstArgument : 0x00000000`00000094 Void 76 +0x090 TrapFrame : 0xfffff48b`1c777ac0 _KTRAP_FRAME 77 +0x098 ApcState : _KAPC_STATE 78 +0x098 ApcStateFill : [43] "???" 79 +0x0c3 Priority : 9 '' 80 +0x0c4 UserIdealProcessor : 2 81 +0x0c8 WaitStatus : 0n256 82 +0x0d0 WaitBlockList : 0xffff9004`b64d21c0 _KWAIT_BLOCK 83 +0x0d8 WaitListEntry : _LIST_ENTRY [ 0x00000000`00000000 - 0xffff9004`b1e95158 ] 84 +0x0d8 SwapListEntry : _SINGLE_LIST_ENTRY 85 +0x0e8 Queue : (null) 86 +0x0f0 Teb : 0x00000000`00324000 Void 87 +0x0f8 RelativeTimerBias : 0 88 +0x100 Timer : _KTIMER 89 +0x140 WaitBlock : [4] _KWAIT_BLOCK 90 +0x140 WaitBlockFill4 : [20] "p???" 91 +0x154 ContextSwitches : 0xee 92 +0x140 WaitBlockFill5 : [68] "p???" 93 +0x184 State : 0x5 '' 94 +0x185 Spare13 : 0 '' 95 +0x186 WaitIrql : 0 '' 96 +0x187 WaitMode : 0 '' 97 +0x140 WaitBlockFill6 : [116] "p???" 98 +0x1b4 WaitTime : 0x780fc 99 +0x140 WaitBlockFill7 : [164] "p???" 100 +0x1e4 KernelApcDisable : 0n-1 101 +0x1e6 SpecialApcDisable : 0n0 102 +0x1e4 CombinedApcDisable : 0xffff 103 +0x140 WaitBlockFill8 : [40] "p???" 104 +0x168 ThreadCounters : (null) 105 +0x140 WaitBlockFill9 : [88] "p???" 106 +0x198 XStateSave : (null) 107 +0x140 WaitBlockFill10 : [136] "p???" 108 +0x1c8 Win32Thread : 0xffff9004`b7232db0 Void 109 +0x140 WaitBlockFill11 : [176] "p???" 110 +0x1f0 Ucb : (null) 111 +0x1f8 Uch : (null) 112 +0x200 ThreadFlags2 : 0n0 113 +0x200 BamQosLevel : 0y00000000 (0) 114 +0x200 ThreadFlags2Reserved : 0y000000000000000000000000 (0) 115 +0x204 Spare21 : 0 116 +0x208 QueueListEntry : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ] 117 +0x218 NextProcessor : 0 118 +0x218 NextProcessorNumber : 0y0000000000000000000000000000000 (0) 119 +0x218 SharedReadyQueue : 0y0 120 +0x21c QueuePriority : 0n0 121 +0x220 Process : 0xffff9004`b47eb080 _KPROCESS 122 +0x228 UserAffinity : _GROUP_AFFINITY 123 +0x228 UserAffinityFill : [10] "???" 124 +0x232 PreviousMode : 1 '' 125 +0x233 BasePriority : 8 '' 126 +0x234 PriorityDecrement : 0 '' 127 +0x234 ForegroundBoost : 0y0000 128 +0x234 UnusualBoost : 0y0000 129 +0x235 Preempted : 0 '' 130 +0x236 AdjustReason : 0 '' 131 +0x237 AdjustIncrement : 0 '' 132 +0x238 AffinityVersion : 0x28 133 +0x240 Affinity : _GROUP_AFFINITY 134 +0x240 AffinityFill : [10] "???" 135 +0x24a ApcStateIndex : 0 '' 136 +0x24b WaitBlockCount : 0x1 '' 137 +0x24c IdealProcessor : 2 138 +0x250 NpxState : 5 139 +0x258 SavedApcState : _KAPC_STATE 140 +0x258 SavedApcStateFill : [43] "???" 141 +0x283 WaitReason : 0 '' 142 +0x284 SuspendCount : 0 '' 143 +0x285 Saturation : 0 '' 144 +0x286 SListFaultCount : 0 145 +0x288 SchedulerApc : _KAPC 146 +0x288 SchedulerApcFill0 : [1] "??????" 147 +0x289 ResourceIndex : 0x1 '' 148 +0x288 SchedulerApcFill1 : [3] "???" 149 +0x28b QuantumReset : 0x6 '' 150 +0x288 SchedulerApcFill2 : [4] "???" 151 +0x28c KernelTime : 3 152 +0x288 SchedulerApcFill3 : [64] "???" 153 +0x2c8 WaitPrcb : (null) 154 +0x288 SchedulerApcFill4 : [72] "???" 155 +0x2d0 LegoData : (null) 156 +0x288 SchedulerApcFill5 : [83] "???" 157 +0x2db CallbackNestingLevel : 0 '' 158 +0x2dc UserTime : 0 159 +0x2e0 SuspendEvent : _KEVENT 160 +0x2f8 ThreadListEntry : _LIST_ENTRY [ 0xffff9004`b42e73b8 - 0xffff9004`b47eb0b0 ] 161 +0x308 MutantListHead : _LIST_ENTRY [ 0xffff9004`b64d2388 - 0xffff9004`b64d2388 ] 162 +0x318 AbEntrySummary : 0x3e '>' 163 +0x319 AbWaitEntryCount : 0 '' 164 +0x31a AbAllocationRegionCount : 0 '' 165 +0x31b SystemPriority : 0 '' 166 +0x31c SecureThreadCookie : 0 167 +0x320 LockEntries : 0xffff9004`b64d26d0 _KLOCK_ENTRY 168 +0x328 PropagateBoostsEntry : _SINGLE_LIST_ENTRY 169 +0x330 IoSelfBoostsEntry : _SINGLE_LIST_ENTRY 170 +0x338 PriorityFloorCounts : [16] "" 171 +0x348 PriorityFloorCountsReserved : [16] "" 172 +0x358 PriorityFloorSummary : 0 173 +0x35c AbCompletedIoBoostCount : 0n0 174 +0x360 AbCompletedIoQoSBoostCount : 0n0 175 +0x364 KeReferenceCount : 0n0 176 +0x366 AbOrphanedEntrySummary : 0 '' 177 +0x367 AbOwnedEntryCount : 0x1 '' 178 +0x368 ForegroundLossTime : 0 179 +0x370 GlobalForegroundListEntry : _LIST_ENTRY [ 0x00000000`00000001 - 0x00000000`00000000 ] 180 +0x370 ForegroundDpcStackListEntry : _SINGLE_LIST_ENTRY 181 +0x378 InGlobalForegroundList : 0 182 +0x380 ReadOperationCount : 0n12 183 +0x388 WriteOperationCount : 0n0 184 +0x390 OtherOperationCount : 0n293 185 +0x398 ReadTransferCount : 0n27743 186 +0x3a0 WriteTransferCount : 0n0 187 +0x3a8 OtherTransferCount : 0n9406 188 +0x3b0 QueuedScb : (null) 189 +0x3b8 ThreadTimerDelay : 0 190 +0x3bc ThreadFlags3 : 0n0 191 +0x3bc ThreadFlags3Reserved : 0y00000000 (0) 192 +0x3bc PpmPolicy : 0y00 193 +0x3bc ThreadFlags3Reserved2 : 0y0000000000000000000000 (0) 194 +0x3c0 TracingPrivate : [1] 0 195 +0x3c8 SchedulerAssist : (null) 196 +0x3d0 AbWaitObject : (null) 197 +0x3d8 ReservedPreviousReadyTimeValue : 0 198 +0x3e0 KernelWaitTime : 0xe 199 +0x3e8 UserWaitTime : 0 200 +0x3f0 GlobalUpdateVpThreadPriorityListEntry : _LIST_ENTRY [ 0x00000000`00000001 - 0x00000000`00000000 ] 201 +0x3f0 UpdateVpThreadPriorityDpcStackListEntry : _SINGLE_LIST_ENTRY 202 +0x3f8 InGlobalUpdateVpThreadPriorityList : 0 203 +0x400 SchedulerAssistPriorityFloor : 0n0 204 +0x404 Spare28 : 0 205 +0x408 EndPadding : [5] 0
大家感興趣的,可以開啟看看,內容還是不少的。
當然,我們也可以通過 Windbg 直接檢視了,我們的專案正在執行中,所以我們可以通過【Attach to process】進入偵錯介面,然後,通過【!t】或者【!threads】命令,檢視執行緒三者的對應關係。
1 0:004> !t 2 ThreadCount: 3 3 UnstartedThread: 0 4 BackgroundThread: 1 5 PendingThread: 0 6 DeadThread: 0 7 Hosted Runtime: no 8 Lock 9 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 10 0 1 4b4 00696b10 2a020 Preemptive 02506254:00000000 006903d0 1 MTA 11 2 2 3ab8 00698df8 2b220 Preemptive 00000000:00000000 006903d0 0 MTA (Finalizer) 12 3 3 318c 006ee308 202b020 Preemptive 0250501C:00000000 006903d0 0 MTA 13 0:004> !threads 14 ThreadCount: 3 15 UnstartedThread: 0 16 BackgroundThread: 1 17 PendingThread: 0 18 DeadThread: 0 19 Hosted Runtime: no 20 Lock 21 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 22 0 1 4b4 00696b10 2a020 Preemptive 02506254:00000000 006903d0 1 MTA 23 2 2 3ab8 00698df8 2b220 Preemptive 00000000:00000000 006903d0 0 MTA (Finalizer) 24 3 3 318c 006ee308 202b020 Preemptive 0250501C:00000000 006903d0 0 MTA
ID是1就是C#的託管執行緒編號, OSID的值是4b4就是作業系統層面的執行緒的資料結構,ThreadOBJ 就是CLR 層面的執行緒。
1 1:000> !dumpheap -type ManualResetEvent 2 Address MT Size 3 033e24d4 6d53d578 24 4 5 Statistics: 6 MT Count TotalSize Class Name 7 6d53d578 1 24 System.Threading.ManualResetEvent 8 Total 1 objects
紅色標註的地址就是我們要找的 ManualResetEvent 的範例。我們繼續使用【!do】命令檢視詳情。
1 1:000> !do 033e24d4 2 Name: System.Threading.ManualResetEvent 3 MethodTable: 6d53d578 4 EEClass: 6d6114d0 5 Size: 24(0x18) bytes 6 File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 Fields: 8 MT Field Offset Type VT Attr Value Name 9 6d4f2734 40005ba 4 System.Object 0 instance 00000000 __identity 10 6d4f7b18 4001990 c System.IntPtr 1 instance 2f8 waitHandle 11 6d4f6688 4001991 8 ...es.SafeWaitHandle 0 instance 033e2504 safeWaitHandle 12 6d4f878c 4001992 10 System.Boolean 1 instance 0 hasThreadAffinity 13 6d4f7b18 4001993 ec8 System.IntPtr 1 shared static InvalidHandle 14 >> Domain:Value 016adb18:ffffffff <<
紅色標註的是一個 handle 物件,我們可以使用【!handle 2f8 f】命令繼續檢視,必須具有 f 引數。
1 1:000> !handle 2f8 f 2 Handle 2f8 3 Type Event 4 Attributes 0 5 GrantedAccess 0x1f0003: 6 Delete,ReadControl,WriteDac,WriteOwner,Synch 7 QueryState,ModifyState 8 HandleCount 2 9 PointerCount 32769 10 Name <none> 11 Object Specific Information 12 Event Type Manual Reset(事件型別是 ManualResetEvent) 13 Event is Waiting(當前是等待狀態)
說明 false 是等待的狀態,然後,我們繼續【g】執行一下,等我們的控制檯專案輸出:mre 預設為 true,即放行狀態,請檢視!我們繼續執行【!handle 2f8 f】命令檢視。
1 1:000> !handle 2f8 f 2 Handle 2f8 3 Type Event 4 Attributes 0 5 GrantedAccess 0x1f0003: 6 Delete,ReadControl,WriteDac,WriteOwner,Synch 7 QueryState,ModifyState 8 HandleCount 2 9 PointerCount 65536 10 Name <none> 11 Object Specific Information 12 Event Type Manual Reset 13 Event is Set
然後,我們繼續【g】執行一下,等我們的控制檯專案輸出:mre Reset後為 false,即等待狀態,請檢視!我們繼續執行【!handle 2f8 f】命令檢視。
1 1:000> !handle 2f8 f 2 Handle 2f8 3 Type Event 4 Attributes 0 5 GrantedAccess 0x1f0003: 6 Delete,ReadControl,WriteDac,WriteOwner,Synch 7 QueryState,ModifyState 8 HandleCount 2 9 PointerCount 65535 10 Name <none> 11 Object Specific Information 12 Event Type Manual Reset 13 Event is Waiting
我們都知道 AutoResetEvent 和 ManulResetEvent 的功能就是 Windows 底層的功能,說白了就是 C# 只是使用了 Windows 核心提供的事件,C# 不過是對其進行了包裝,如果你想要檢視記憶體地址,必須到核心態去看。
2.3、如何到核心態去檢視 AutoResetEvent 和 ManulResetEvent 地址。0:000> !dumpheap -type ManualResetEvent Address MT Size 033224d4 6d53d578 24 Statistics: MT Count TotalSize Class Name 6d53d578 1 24 System.Threading.ManualResetEvent Total 1 objects
紅色標註的就是【ManualResetEvent】物件地址,我們可以使用【!dumpobj /d 033224d4】命令檢視 ManualResetEvent 範例物件。
1 0:000> !dumpobj /d 033224d4 2 Name: System.Threading.ManualResetEvent 3 MethodTable: 6d53d578 4 EEClass: 6d6114d0 5 Size: 24(0x18) bytes 6 File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 Fields: 8 MT Field Offset Type VT Attr Value Name 9 6d4f2734 40005ba 4 System.Object 0 instance 00000000 __identity 10 6d4f7b18 4001990 c System.IntPtr 1 instance 2dc waitHandle(我們要查詢的事件控制程式碼) 11 6d4f6688 4001991 8 ...es.SafeWaitHandle 0 instance 03322504 safeWaitHandle 12 6d4f878c 4001992 10 System.Boolean 1 instance 0 hasThreadAffinity 13 6d4f7b18 4001993 ec8 System.IntPtr 1 shared static InvalidHandle 14 >> Domain:Value 01532430:ffffffff <<
我們再開啟一個 Windbg,檢視核心態,點選【File】-->【Attach to Kernel】,右側選擇【local】,點選【ok】進入偵錯程式介面。2dc是一個控制程式碼,就像一個編號,我們還需要藉助【Process Explorer】工具,我們開啟這個工具,然後在【Filter by name】輸入專案名稱Example_14_1_2,結果如圖:
我們在 0X000002dc行雙擊,開啟新視窗,效果如圖:
我們就找到了核心地址了。然後,我們到 Windbg 的核心態中去檢視一下這個地址,使用【dp】命令。當前值:0(00000000)
1 lkd> dp 0xFFFF9004B7B916E0 l1 2 ffff9004`b7b916e0 00000000`00060000
然後我們【g】一下使用者態的 Windbg,控制檯輸出:mre 預設為 true,即放行狀態,請檢視!當前值:1(00000001),然後切換到【核心態】的Windbg,繼續使用【dp】命令,檢視一下。
1 lkd> dp 0xFFFF9004B7B916E0 l1 2 ffff9004`b7b916e0 00000001`00060000
然後,我們再【g】一下【使用者態】的Windbg,控制檯輸出:mre Reset後為 false,即等待狀態,請檢視!當前值:0(00000000),然後切換到【核心態】的Windbg,繼續使用【dp】命令,檢視一下。
1 lkd> dp 0xFFFF9004B7B916E0 l1 2 ffff9004`b7b916e0 00000000`00060000
我們就看到了,狀態是0和1相互切換的。
1 0:000> !dumpheap -type Semaphore 2 Address MT Size 3 02f924d4 6d59611c 24 4 5 Statistics: 6 MT Count TotalSize Class Name 7 6d59611c 1 24 System.Threading.Semaphore 8 Total 1 objects
紅色標註的地址 02f924d4 就是 Semaphore 物件,然後,我們可以使用【!do 02f924d4】或者【!dumpobj /d 02f924d4】檢視 Semaphore 物件的詳情,兩個命令執行的結果都是一樣的。
1 0:000> !do 02f924d4 2 Name: System.Threading.Semaphore 3 MethodTable: 6d59611c 4 EEClass: 6d5ccfa0 5 Size: 24(0x18) bytes 6 File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll 7 Fields: 8 MT Field Offset Type VT Attr Value Name 9 6ec52734 40005ba 4 System.Object 0 instance 00000000 __identity 10 6ec57b18 4001990 c System.IntPtr 1 instance 314 waitHandle 11 6ec56688 4001991 8 ...es.SafeWaitHandle 0 instance 02f92504 safeWaitHandle 12 6ec5878c 4001992 10 System.Boolean 1 instance 0 hasThreadAffinity 13 6ec57b18 4001993 ec8 System.IntPtr 1 shared static InvalidHandle 14 >> Domain:Value 010dd880:ffffffff << 15 16 17 0:000> !dumpobj /d 02f924d4 18 Name: System.Threading.Semaphore 19 MethodTable: 6d59611c 20 EEClass: 6d5ccfa0 21 Size: 24(0x18) bytes 22 File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll 23 Fields: 24 MT Field Offset Type VT Attr Value Name 25 6ec52734 40005ba 4 System.Object 0 instance 00000000 __identity 26 6ec57b18 4001990 c System.IntPtr 1 instance 314 waitHandle 27 6ec56688 4001991 8 ...es.SafeWaitHandle 0 instance 02f92504 safeWaitHandle 28 6ec5878c 4001992 10 System.Boolean 1 instance 0 hasThreadAffinity 29 6ec57b18 4001993 ec8 System.IntPtr 1 shared static InvalidHandle 30 >> Domain:Value 010dd880:ffffffff <<
Semaphore 其實也是一個 waitHandle,我們有了 handle 地址,就可以使用【!handle】命令了。
1 Handle 314 2 Type Semaphore 3 Attributes 0 4 GrantedAccess 0x1f0003: 5 Delete,ReadControl,WriteDac,WriteOwner,Synch 6 QueryState,ModifyState 7 HandleCount 2 8 PointerCount 65536 9 Name <none> 10 Object Specific Information 11 Semaphore Count 2(Semaphore sem = new Semaphore(1, 10),我們初始值是1,當前值是2) 12 Semaphore Limit 10
我們繼續【g】,然後再次執行【!handle 314 f】命令,再次檢視,Semaphore Count 的值就是3。
1 0:000> !handle 314 f 2 Handle 314 3 Type Semaphore 4 Attributes 0 5 GrantedAccess 0x1f0003: 6 Delete,ReadControl,WriteDac,WriteOwner,Synch 7 QueryState,ModifyState 8 HandleCount 2 9 PointerCount 65535 10 Name <none> 11 Object Specific Information 12 Semaphore Count 3(現在值是:3,每執行一次,該值就增加1,呼叫 Release()函數一次,值就增加一次。) 13 Semaphore Limit 10(這個值是極限值)
我們繼續【g】,然後再次執行【!handle 314 f】命令,再次檢視,Semaphore Count 值肯定就是4了。
1 0:000> !handle 314 f 2 Handle 314 3 Type Semaphore 4 Attributes 0 5 GrantedAccess 0x1f0003: 6 Delete,ReadControl,WriteDac,WriteOwner,Synch 7 QueryState,ModifyState 8 HandleCount 2 9 PointerCount 65534 10 Name <none> 11 Object Specific Information 12 Semaphore Count 4(又增加了一次) 13 Semaphore Limit 10
這個變化的 Count 值的記憶體地址在哪裡呢?其實它的功能都是有核心態提供的,如果想看 Count 的記憶體地址,必須找到核心態的地址。這裡我們還是需要借用【Process Explorer】工具,我使用的64位元版本,自己可以根據自己系統的特點選擇。開啟工具,過濾我們的專案【Example_14_1_3】。效果如圖:
雙擊【Semaphore】條目打卡屬性視窗,就能看到它的核心態的記憶體地址了。
我們有了核心態的記憶體地址,就需要再開啟一個 Windbg,點選【File】-->【Attach to Kernel】,在右側視窗選擇【local】點選【ok】開啟偵錯程式。然後,我們就可以使用【dp】命令檢視具體的值了,當前值是:4(00000004)。
1 lkd> dp 0xFFFFCE09E477EA60 l1 2 ffffce09`e477ea60 00000004`00080005
我們切換到第一個 Windbg 視窗,【g】繼續執行,然後再切換回來這個 Windbg,再次執行【dp】命令,當前的值應該就是:5(00000005)。
1 lkd> dp 0xFFFFCE09E477EA60 l1 2 ffffce09`e477ea60 00000005`00080005
其實,我們可以在第一個 Windbg 視窗中,使用【!handle 314 f】也可以看到結果值,肯定也是5。
1 0:000> !handle 314 f 2 Handle 314 3 Type Semaphore 4 Attributes 0 5 GrantedAccess 0x1f0003: 6 Delete,ReadControl,WriteDac,WriteOwner,Synch 7 QueryState,ModifyState 8 HandleCount 2 9 PointerCount 65532 10 Name <none> 11 Object Specific Information 12 Semaphore Count 5 13 Semaphore Limit 10
Semaphore 是有極限值的,如果超過極限值,CLR 會丟擲異常。
1 0:000> g 2 ModLoad: 058f0000 059ea000 image058f0000 3 ModLoad: 059f0000 05aea000 image059f0000 4 (52c.2990): CLR exception - code e0434352 (first chance) 5 ModLoad: 66bf0000 66cf5000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\diasymreader.dll 6 ModLoad: 6cd30000 6d548000 C:\Windows\assembly\NativeImages_v4.0.30319_32\System.Core\System.Core.ni.dll 7 ModLoad: 73630000 73643000 C:\Windows\SysWOW64\CRYPTSP.dll 8 ModLoad: 73600000 7362f000 C:\Windows\SysWOW64\rsaenh.dll 9 ModLoad: 75ce0000 75cf9000 C:\Windows\SysWOW64\bcrypt.dll 10 ModLoad: 73690000 7369a000 C:\Windows\SysWOW64\CRYPTBASE.dll 11 (52c.2990): CLR exception - code e0434352 (!!! second chance !!!) 12 *** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\\System.ni.dll 13 eax=00f3ec58 ebx=00000005 ecx=00000005 edx=00000000 esi=00f3ed1c edi=00000001 14 eip=75969862 esp=00f3ec58 ebp=00f3ecb4 iopl=0 nv up ei pl nz ac po nc 15 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 16 KERNELBASE!RaiseException+0x62: 17 75969862 8b4c2454 mov ecx,dword ptr [esp+54h] ss:002b:00f3ecac=c4689540
2.5、我們使用 Windbg 檢視 Monitor 的實現,該專案是 Net 7.0,因為Net Framework 是閉源的,沒有辦法看到原始碼。
偵錯原始碼:Example_14_1_4
我們編譯專案,開啟 Windbg,點選【檔案】----》【launch executable】附加程式,開啟偵錯程式的介面,程式已經處於中斷狀態。我們需要使用【g】命令,繼續執行程式,當我們的控制檯程式輸出:4 已進入 Person 鎖中111111(這裡不一定是這個,我的輸出是這個),這個過程的時間有點長,Windbg執行框處在【busy】狀態, 因為正在下載 coreclr.pdb,下載完畢就可以,操作完成,Windbg 有一個 int 3 中斷,就可以偵錯程式了。
然後,我們使用【!syncblk】命令,檢視一下同步塊。
1 0:007> !syncblk 2 Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner 3 9 0000026C988CE368 3 1 000002AD2EE66800 18b0 7 0000026c9cc0cb88 Example_14_1_4_Core.Person 4 ----------------------------- 5 Total 12 6 CCW 0 7 RCW 0 8 ComClassFactory 0 9 Free 0
我們說過 Monitor 的底層實現就是 AwareLock,這個標紅 0000026C988CE368 地址就是指向 AwareLock。我們使用【dt】命令檢視一番。
1 0:007> dt coreclr!AwareLock 0000026C988CE368 2 +0x000 m_lockState : AwareLock::LockState(底層的 awarelock) 3 +0x004 m_Recursion : 1(遞迴次數1) 4 +0x008 m_HoldingThread : 0x000002ad`2ee66800 Thread(持有的執行緒,和 Owning Thread Info 值一樣) 5 +0x010 m_TransientPrecious : 0n1 6 +0x014 m_dwSyncIndex : 0x80000009(這個就是同步塊索引,是9) 7 +0x018 m_SemEvent : CLREvent(底層還是使用的 Event 實現同步) 8 +0x028 m_waiterStarvationStartTimeMs : 0xb6cb0b
我們繼續使用【dx】命令檢視 m_SemEvent 是什麼。
1 0:007> dx -r1 (*((coreclr!CLREvent *)0x26c988ce380)) 2 (*((coreclr!CLREvent *)0x26c988ce380)) [Type: CLREvent] 3 [+0x000] m_handle : 0x2d0 [Type: void *](這裡是一個控制程式碼) 4 [+0x008] m_dwFlags : 0xd [Type: Volatile<unsigned long>]
既然是一個 handle,我們就使用【!handle】命令檢視一下就知道了。
1 0:007> !handle 0x2d0 f 2 Handle 2d0 3 Type Event 4 Attributes 0 5 GrantedAccess 0x1f0003: 6 Delete,ReadControl,WriteDac,WriteOwner,Synch 7 QueryState,ModifyState 8 HandleCount 2 9 PointerCount 65537 10 Name <none> 11 Object Specific Information 12 Event Type Auto Reset(其實就是 AutoResetEvent) 13 Event is Waiting
2.6、我們看看 ThinLock 鎖的實現邏輯。
偵錯原始碼:Example_14_1_5
我們編譯專案,開啟 Windbg,點選【檔案】----》【launch executable】附加程式,開啟偵錯程式的介面,程式已經處於中斷狀態。我們需要使用【g】命令,繼續執行程式,我們的控制檯會輸出:3 已進入 Person 鎖中。此時,我們的 Windbg 處於 int 3 中斷的狀態,就可以偵錯程式了。
我們還是先使用【!syncblk】命令,檢視一下同步塊。
1 0:009> !syncblk 2 Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner 3 ----------------------------- 4 Total 6 5 CCW 1 6 RCW 2 7 ComClassFactory 0 8 Free 0
沒有同步塊,這就說明雖然用到了鎖,但是沒有用到同步塊。我們既然想要檢視這個Person物件的物件,那我們就現在託管堆中找到這個物件,可以使用【!dumpheap -type Person】命令,完成這個操作。
1 0:009> !dumpheap -type Person 2 Address MT Size 3 02c224d4 01224e0c 12 4 5 Statistics: 6 MT Count TotalSize Class Name 7 01224e0c 1 12 Example_14_1_5.Person 8 Total 1 objects
紅色標註的地址就是 Person 物件的地址。我們可以使用【!dp】命令來檢視。
1 0:009> dp 02c224d4-0x4 l4 2 02c224d0 00000003 01224e0c 00000000 00000000
同步塊索引的值是3(00000003),這個3 就是持有鎖的執行緒 id 值。我們可以使用【!t】或者【!threads】命令檢視一下當前的執行緒。
1 0:009> !t 2 ThreadCount: 4 3 UnstartedThread: 0 4 BackgroundThread: 3 5 PendingThread: 0 6 DeadThread: 0 7 Hosted Runtime: no 8 Lock 9 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 10 0 1 3b00 00cb9760 2a020 Preemptive 02C2A044:00000000 00c823e8 1 MTA 11 5 2 174c 00c893b8 2b220 Preemptive 00000000:00000000 00c823e8 0 MTA (Finalizer) 12 9 3 29fc 00cf0fe8 1029220 Preemptive 02C2742C:00000000 00c823e8 1 MTA (Threadpool Worker) (這個就是持有鎖的執行緒,id=3) 13 11 4 37a8 00cf4af8 1029220 Preemptive 02C281E8:00000000 00c823e8 0 MTA (Threadpool Worker)
我們知道了執行緒 id,我們就可以切換到該執行緒上去看看那呼叫棧是什麼樣子的。
1 0:003> ~~[29fc]s 2 eax=0567f124 ebx=00000000 ecx=00cf0fe8 edx=0567f55c esi=02c27244 edi=0567f168 3 eip=7599f262 esp=0567f0bc ebp=0567f148 iopl=0 nv up ei pl zr na pe nc 4 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000244 5 KERNELBASE!wil::details::DebugBreak+0x2: 6 7599f262 cc int 3
然後,我們看看執行緒棧。
1 0:009> !clrstack 2 OS Thread Id: 0x29fc (9) 3 Child SP IP Call Site 4 0567f0d4 7599f262 [HelperMethodFrame: 0567f0d4] System.Diagnostics.Debugger.BreakInternal() 5 0567f150 6f7cf195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91] 6 0567f178 012b0a90 Example_14_1_5.Program+c.b__1_0() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_14_1_5\Program.cs @ 18] 7 0567f1c0 6f09d4bb System.Threading.Tasks.Task.InnerInvoke() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2884] 8 0567f1cc 6f09b731 System.Threading.Tasks.Task.Execute() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2498] 9 0567f1f0 6f09b6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2861] 10 0567f1f4 6f038604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 980] 11 0567f260 6f038537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 928] 12 0567f274 6f09b4b2 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2827] 13 0567f2d8 6f09b357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2767] 14 0567f2e8 6f09b29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2704] 15 0567f2ec 6f00eb7d System.Threading.ThreadPoolWorkQueue.Dispatch() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 820] 16 0567f33c 6f00e9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 1161] 17 0567f55c 70def036 [DebuggerU2MCatchHandlerFrame: 0567f55c]
紅色標註的就是我們程式暫停的位置,VisualStudio 所對應的程式碼行數。
其實,我們獲取到了物件地址,可以【!do】一下,也可以看到一些資訊。
1 0:009> !do 02c224d4 2 Name: Example_14_1_5.Person 3 MethodTable: 01224e0c 4 EEClass: 0122135c 5 Size: 12(0xc) bytes 6 File: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_14_1_5\bin\Debug\Example_14_1_5.exe 7 Fields: 8 None 9 ThinLock owner 3 (00cf0fe8), Recursive 0
四、總結
終於寫完了。還是老話,雖然很忙,寫作過程也挺累的,但是看到了自己的成長,心裡還是挺快樂的。學習過程真的沒那麼輕鬆,還好是自己比較喜歡這一行,否則真不知道自己能不能堅持下來。老話重談,《高階偵錯》的這本書第一遍看,真的很暈,第二遍稍微好點,不學不知道,一學嚇一跳,自己欠缺的很多。好了,不說了,不忘初心,繼續努力,希望老天不要辜負努力的人。