前言:
此时看官们对“netframework46是什么”可能比较珍视,兄弟们都想要学习一些“netframework46是什么”的相关内容。那么小编同时在网摘上网罗了一些对于“netframework46是什么””的相关文章,希望你们能喜欢,各位老铁们快快来了解一下吧!一、简介
今天是《Net 高级调试》的第七篇文章。上一篇文章我们说了值类型,引用类型,数组等的内存表现形式。有了这个基础,我们可以更好的了解我们的程序在运行时的状态,内存里有什么东西,它们的结构组成是什么样子的,对我们调试程序是更有帮助的。今天,我们要说一些和线程有关的话题,虽然和线程相关,但是不是多线程的知识,不是线程安全的知识。今天我们讨论的是如何查看线程,它的表现形式,以及线程的调用栈,调用栈,又分为托管线程的调用栈和非托管线程的调用栈,这些也是我们高级调试必须掌握的。有了这些基础,我们就知道了程序的开始端点,调试的起点我们就找到了。虽然这些都是基础,如果这些掌握不好,以后的高级调试的道路,也不好走。当然了,第一次看视频或者看书,是很迷糊的,不知道如何操作,还是那句老话,一遍不行,那就再来一遍,还不行,那就再来一遍,俗话说的好,书读千遍,其意自现。
如果在没有说明的情况下,所有代码的测试环境都是 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、简介
在高级调试的过程中,很难不和 线程、线程栈 打交道,所以好好的学习并掌握有关线程操作的命令是很有必要的。
2、获取 线程列表 的命令
2.1、查看线程列表。
可以使用【!t】命令获取所有的托管线程。
2.2、使用 Net 7.0 查看线程列表
可以使用【!t】命令获取所有的托管线程,如果我们想查看 CLR 的线程对象的结构就只能使用开源版本了,这里使用Net 7.0。
3、查看非托管线程栈
Windbg 是随 Windows 成长起来的非托管调试器,它自带的命令只能查看非托管调用栈,因为 C# 中的线程栈是托管函数,而托管函数是一种运行时编译的,所以这个命令往往看不到托管部分。
3.1、使用 k 命令查看非托管线程栈
【k】命令可以查看线程栈,但是么有办法展示托管函数,作为补充,可以使用【!clrstack】命令。
3.2、使用 kb 命令查看线程栈。
很多时候我们需要获取非托管函数的参数,这个时候我们可以使用【kb】命令。
3.3、查看托管函数栈
SOS 提供了一个专门查看托管函数调用栈的命令,毕竟只有 JIT更熟悉托管函数,也知道编译后的机器码放在什么位置。
这个命令就是【!clrstack】。
!clrstack -a:这个命令表示将线程栈中的所有局部变量和参数全部输出。
!clrstack -p:这个命令表示将线程栈中的参数全部输出。
!clrstack -l:这个命令表示将线程栈中的所有局部变量全部输出。
我们还可以查看所有托管线程栈,可以使用【~*e】命令。
3.4、查看托管和非托管合体
使用【!dumpstack】命令查看托管和非托管的线程栈。
3.5、执行所有线程的 DumpStack。
如果我们想查看所有线程的线程栈,可以使用【!EEStack】,也可以使用【~*e !dumpstack】,结果是一样的。
三、测试过程
废话不多说,这一节是具体的调试操作的过程,又可以说是眼见为实的过程,在开始之前,我还是要啰嗦两句,这一节分为两个部分,第一部分是测试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。第二部分就是根据具体的代码来证实我们学到的知识,是具体的眼见为实。
1、测试源码
1.1、Example_7_1_1
1 namespace Example_7_1_1 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 int a = 10; 8 int b = 11; 9 Test(12);10 Console.ReadLine();11 }12 13 private static void Test(int c)14 {15 Task.Run(() => { Run1(); });16 Task.Run(() => { Run2(); });17 Task.Run(() => { Run3(); });18 }19 20 private static void Run1()21 {22 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run1 正在运行");23 Console.ReadLine();24 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run1 结束运行");25 }26 27 private static void Run2()28 {29 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run2 正在运行");30 Console.ReadLine();31 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run2 结束运行");32 }33 34 private static void Run3()35 {36 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run3 正在运行");37 Console.ReadLine();38 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run3 结束运行");39 }40 }41 }
1.2、Example_7_1_2(特别说明,这个项目是 Net 7.0)
1 namespace Example_7_1_2 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 Console.WriteLine("Hello, World!"); 8 Debugger.Break(); 9 }10 }11 }
2、眼见为实
项目的所有操作都是一样的,所以就在这里说明一下,但是每个测试例子,都需要重新启动,并加载相应的应用程序,加载方法都是一样的。流程如下:我们编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们需要使用【g】命令,继续运行程序,然后到达指定地点停止后,我们可以点击【break】按钮,就可以调试程序了。有时候可能需要切换到主线程,可以使用【~0s】命令。
2.1、使用 【!t】命令查看进程中有多少个托管线程。
测试源码:Example_7_1_1
1 0:012> !t 2 ThreadCount: 6 3 UnstartedThread: 0 4 BackgroundThread: 5 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 Exception10 0 1 2ef4 00c73930 2a020 Preemptive 02B94F38:00000000 00c6d758 1 MTA 11 5 2 39c0 00cb0728 2b220 Preemptive 00000000:00000000 00c6d758 0 MTA (Finalizer) 12 9 3 17dc 00cdc010 3029220 Preemptive 02B9C6C8:00000000 00c6d758 0 MTA (Threadpool Worker) 13 10 4 a18 00cdc9b8 3029220 Preemptive 02B98350:00000000 00c6d758 0 MTA (Threadpool Worker) 14 11 5 31d0 00cdd360 3029220 Preemptive 02B9A350:00000000 00c6d758 0 MTA (Threadpool Worker) 15 13 6 9a0 00ce05c0 1029220 Preemptive 02B9E1E8:00000000 00c6d758 0 MTA (Threadpool Worker)
ThreadCount:有多少个托管线程,这个进程里面有6个。
UnstartedThread:已经创建,但是还没有使用的线程,当前数量是0。
BackgroundThread:后台线程的数量,这个进程里面有5个。
PendingThread:阻塞的线程的数量,当前这个进程是0个阻塞的线程。
DeadThread:当一个线程完成任务,但是还没有被回收,这个阶段的线程就是死线程,也就是说这个线程对象底层的数据结构的 OSID 已经销毁。
以上就是介绍的主要名称,下面接着介绍,列表的每一项。我们使用 C# Thread 类型声明一个线程的时候,其实在操作系统和CLR都有一个数据结构相对应,有了 OSID我们才可以在任务管理器中看到线程对象。
第一列,没有标题,是 WindbgThreadId,Windbg 自己的给 Thread 打了一个标记,便于以后使用,也可以区分托管线程和非托管线程。
第二列 ID:是托管线程,也就是我们 Thread 类型的标识符 Id,就是 这段代码的值:Environment.CurrentManagedThreadId
第三列 OSID:操作系统线程的 ID,
第四列 ThreadOBJ:CLR 底层的 Thread 线程对象,可以使用【dp】命令观察其中的内容,我们查看托管线程 ID=3 的【00cdc010】内容,红色标注就是托管线程的ID,就是3。
1 0:012> dp 00cdc0102 00cdc010 72074bd4 03029220 00000000 055ff6803 00cdc020 00000000 00c6d758 00000000 000000034 00cdc030 00cdc034 00cdc034 00cdc034 000000005 00cdc040 00000000 baad0000 00c6ed50 0094f0006 00cdc050 02b9c6c8 02b9dfe8 00003d10 000000007 00cdc060 00000000 00000000 00000000 000000008 00cdc070 00000000 baadf00d 70ad2c60 00cdc8509 00cdc080 00000000 00000000 00000000 00000000
第五列 State:CLR 层面的线程状态,03029220 就是托管线程的状态。我们可以点进去或者使用【!threadstate】命令查看线程状态详情,当前就是托管线程是3的状态。
1 0:012> !ThreadState 30292202 Legal to Join(可以执行 join 操作)3 Background(是后台线程)4 CLR Owns(是CLR 拥有的线程)5 In Multi Threaded Apartment(是MTA模式)6 Fully initialized(已经完全初始化)7 Thread Pool Worker Thread(是线程池线程)8 Interruptible(可执行中断操作)
第六列 GC Mode:表示当前的线程有没有操作托管堆的权限。
第七列 GC Alloc Context:缓冲区的开始节点和结束节点,每个线程在托管堆中分配一个对象,都有一个缓冲区,这个就是确定了缓冲区起始和结束。
第八列 Domain:表示当前线程所属于的域。
第九列 Lock Count:表示当前的线程有多少个托管锁。
第十列 Apt:表示线程是 STA(线程串行模式,WPF、WinForm) 模式还是 MTA (多线程并行模式)模式。
第十一列 Exception:在当前线程上发生了异常,会把这个异常和这个线程关联起来。
Finalizer 表示当前是终结器线程,Threadpool Worker 表示线程是线程池的线程。
2.2、如何查看 CLR Thread 的结构,也就是查看 ThreadOBJ 的结构。
测试源码:Example_7_1_2(Net 7.0项目)
如果我们想在 Net Framework 环境下查看 CLR 线程对象的结构是很难的,因为他是不开源的,所以我们只能新建 Net 7.0 的项目。
输出所有的线程列表。
1 0:000> !t 2 ThreadCount: 3 3 UnstartedThread: 0 4 BackgroundThread: 2 5 PendingThread: 0 6 DeadThread: 0 7 Hosted Runtime: no 8 Lock 9 DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception10 0 1 624 00000199DFD0CAE0 2a020 Preemptive 00000199E440AD60:00000199E440C750 00000199dfd02dd0 -00001 MTA 11 5 2 1344 00000199DFD51DD0 21220 Preemptive 0000000000000000:0000000000000000 00000199dfd02dd0 -00001 Ukn (Finalizer) 12 6 3 3b40 000001DA763B15E0 2b220 Preemptive 0000000000000000:0000000000000000 00000199dfd02dd0 -00001 MTA
然后,我们使用【dt】命令,查看线程的结构。
1 0:000> dt coreclr!Thread 00000199DFD0CAE0 2 +0x000 m_stackLocalAllocator : (null) 3 =00007ffd`027a8af0 m_DetachCount : 0n0 4 =00007ffd`027a8af4 m_ActiveDetachCount : 0n0 5 +0x008 m_State : Volatile<enum Thread::ThreadState> 6 +0x00c m_fPreemptiveGCDisabled : Volatile<unsigned long> 7 +0x010 m_pFrame : 0x0000008a`24f7e478 Frame 8 +0x018 m_pDomain : 0x00000199`dfd02dd0 AppDomain 9 +0x020 m_ThreadId : 1 10 +0x028 m_pHead : 0x00000199`dfd0cb10 LockEntry 11 +0x030 m_embeddedEntry : LockEntry 12 +0x050 m_pBlockingLock : VolatilePtr<DeadlockAwareLock,DeadlockAwareLock *> 13 +0x058 m_alloc_context : gc_alloc_context 14 +0x090 m_thAllocContextObj : TypeHandle 15 +0x098 m_pTEB : 0x0000008a`24c20000 _NT_TIB 16 +0x0a0 m_pRCWStack : 0x00000199`dfd0d710 RCWStackHeader 17 +0x0a8 m_ThreadTasks : 0 (No matching name) 18 +0x0ac m_StateNC : 100 ( TSNC_ExistInThreadStore ) 19 +0x0b0 m_dwForbidSuspendThread : Volatile<long> 20 +0x0b4 m_dwHashCodeSeed : 0xdfca504a 21 +0x0b8 m_pLoadLimiter : (null) 22 +0x0c0 m_AbortType : 0 23 +0x0c8 m_AbortEndTime : 0xffffffff`ffffffff 24 +0x0d0 m_RudeAbortEndTime : 0xffffffff`ffffffff 25 +0x0d8 m_fRudeAbortInitiated : 0n0 26 +0x0dc m_AbortController : 0n0 27 +0x0e0 m_AbortRequestLock : 0n0 28 +0x0e4 m_ThrewControlForThread : 0 29 +0x0e8 m_OSContext : 0x00000199`dfd0d210 _CONTEXT 30 +0x0f0 m_pPendingTypeLoad : (null) 31 +0x0f8 m_Link : SLink 32 +0x100 m_dwLastError : 0 33 +0x108 m_CacheStackBase : 0x0000008a`24f80000 Void 34 +0x110 m_CacheStackLimit : 0x0000008a`24e00000 Void 35 +0x118 m_CacheStackSufficientExecutionLimit : 0x0000008a`24e20000 36 +0x120 m_CacheStackStackAllocNonRiskyExecutionLimit : 0x0000008a`24e80000 37 +0x128 m_pvHJRetAddr : 0xcccccccc`cccccccc Void 38 +0x130 m_ppvHJRetAddrPtr : 0xcccccccc`cccccccc -> ???? 39 +0x138 m_HijackedFunction : 0xbaadf00d`baadf00d MethodDesc 40 +0x140 m_UserInterrupt : 0n0 41 +0x148 m_DebugSuspendEvent : CLREvent 42 +0x158 m_EventWait : CLREvent 43 +0x168 m_WaitEventLink : WaitEventLink 44 +0x198 m_ThreadHandle : 0x00000000`000001e0 Void 45 +0x1a0 m_ThreadHandleForClose : 0xffffffff`ffffffff Void 46 +0x1a8 m_ThreadHandleForResume : 0xffffffff`ffffffff Void 47 +0x1b0 m_WeOwnThreadHandle : 0n1 48 +0x1b8 m_OSThreadId : 0x624 49 +0x1c0 m_ExposedObject : 0x00000199`dfc411f8 OBJECTHANDLE__ 50 +0x1c8 m_StrongHndToExposedObject : 0x00000199`dfc413f8 OBJECTHANDLE__ 51 +0x1d0 m_Priority : 0x80000000 52 +0x1d4 m_ExternalRefCount : 1 53 +0x1d8 m_TraceCallCount : 0n0 54 +0x1e0 m_LastThrownObjectHandle : (null) 55 +0x1e8 m_ltoIsUnhandled : 0n0 56 +0x1f0 m_ExceptionState : ThreadExceptionState 57 +0x398 m_debuggerFilterContext : (null) 58 +0x3a0 m_pProfilerFilterContext : (null) 59 +0x3a8 m_hijackLock : Volatile<long> 60 +0x3b0 m_hCurrNotification : 0xbaadf00d`baadf00d OBJECTHANDLE__ 61 +0x3b8 m_fInteropDebuggingHijacked : 0n0 62 +0x3bc m_profilerCallbackState : 0 63 +0x3c0 m_dwProfilerEvacuationCounters : [33] Volatile<unsigned long> 64 +0x444 m_workerThreadPoolCompletionCount : 0 65 =00007ffd`027b3cc0 s_workerThreadPoolCompletionCountOverflow : 0 66 +0x448 m_ioThreadPoolCompletionCount : 0 67 =00007ffd`027b3cc8 s_ioThreadPoolCompletionCountOverflow : 0 68 +0x44c m_monitorLockContentionCount : 0 69 =00007ffd`027a8ad8 s_monitorLockContentionCountOverflow : 0 70 +0x450 m_PreventAsync : 0n0 71 =00007ffd`027a6204 m_DebugWillSyncCount : 0n-1 72 +0x458 m_pSavedRedirectContext : (null) 73 +0x460 m_pOSContextBuffer : (null) 74 +0x468 m_ThreadLocalBlock : ThreadLocalBlock 75 +0x490 m_tailCallTls : TailCallTls 76 +0x4a0 m_dwAVInRuntimeImplOkayCount : 0 77 +0x4a8 m_pExceptionDuringStartup : (null) 78 +0x4b0 m_debuggerActivePatchSkipper : VolatilePtr<DebuggerPatchSkip,DebuggerPatchSkip *> 79 +0x4b8 m_fAllowProfilerCallbacks : 0n1 80 +0x4c0 m_pIOCompletionContext : 0x00000199`dfd0da40 Void 81 +0x4c8 m_dwThreadHandleBeingUsed : Volatile<long> 82 =00007ffd`027a8ad0 s_fCleanFinalizedThread : 0n0 83 +0x4d0 m_pCreatingThrowableForException : (null) 84 +0x4d8 m_dwIndexClauseForCatch : 0 85 +0x4e0 m_sfEstablisherOfActualHandlerFrame : StackFrame 86 +0x4e8 DebugBlockingInfo : ThreadDebugBlockingInfo 87 +0x4f0 m_fDisableComObjectEagerCleanup : 0 88 +0x4f1 m_fHasDeadThreadBeenConsideredForGCTrigger : 0 89 +0x4f4 m_random : CLRRandom 90 +0x5e0 m_uliInitializeSpyCookie : _ULARGE_INTEGER 0x0 91 +0x5e8 m_fInitializeSpyRegistered : 0 92 +0x5f0 m_pLastSTACtxCookie : (null) 93 +0x5f8 m_fGCSpecial : 0 94 +0x600 m_pGCFrame : 0x0000008a`24f7e790 GCFrame 95 +0x608 m_wCPUGroup : 0 96 +0x610 m_pAffinityMask : 0 97 +0x618 m_pAllLoggedTypes : (null) 98 +0x620 m_gcModeOnSuspension : Volatile<unsigned long> 99 +0x624 m_activityId : _GUID {00000000-0000-0000-0000-000000000000}100 +0x634 m_HijackReturnKind : ff ( RT_Illegal )101 =00007ffd`027a8b40 dead_threads_non_alloc_bytes : 0102 +0x638 m_currentPrepareCodeConfig : (null) 103 +0x640 m_isInForbidSuspendForDebuggerRegion : 0104 =00007ffd`027ba3d0 s_pfnQueueUserAPC2Proc : (null) 105 +0x641 m_hasPendingActivation : 0
+0x020 m_ThreadId : 1表示托管线程的ID。因为我打印的是 ThreadOBJ 为00000199DFD0CAE0 线程的结构。m 表示Management 托管的。
我们可以继续看看 GC Alloc Context,在输出的结构中有这样一行代码:+0x058 m_alloc_context : gc_alloc_context,蓝色的字体,可以点击,相当于执行【dx】命令。
1 0:000> dx -r1 (*((coreclr!gc_alloc_context *)0x199dfd0cb38))2 (*((coreclr!gc_alloc_context *)0x199dfd0cb38)) [Type: gc_alloc_context]3 [+0x000] alloc_ptr : 0x199e440ad60 : 0x0 [Type: unsigned char *]4 [+0x008] alloc_limit : 0x199e440c750 : 0x0 [Type: unsigned char *]5 [+0x010] alloc_bytes : 50920 [Type: __int64](小对象堆的大小)6 [+0x018] alloc_bytes_uoh : 17416 [Type: __int64](大对象堆的大小)7 [+0x020] gc_reserved_1 : 0x0 [Type: void *]8 [+0x028] gc_reserved_2 : 0x0 [Type: void *]9 [+0x030] alloc_count : 0 [Type: int]
2.3、使用 Windbg 的【k】命令查看非托管函数。
测试源码:Example_7_1_1
这里操作需要切换到主线程上,执行命令【~0s】,然后使用【k】命令。
1 0:010> ~0s 2 eax=00000000 ebx=00000090 ecx=00000000 edx=00000000 esi=00f3f444 edi=00000000 3 eip=77c310fc esp=00f3f32c ebp=00f3f38c iopl=0 nv up ei pl nz na po nc 4 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 5 ntdll!NtReadFile+0xc: 6 77c310fc c22400 ret 24h 7 8 9 0:000> k10 # ChildEBP RetAddr 11 00 00f3f38c 75dbf25c ntdll!NtReadFile+0xc12 01 00f3f38c 70f79b71 KERNELBASE!ReadFile+0xec13 02 00f3f3fc 716ab275 mscorlib_ni+0x4b9b7114 03 00f3f428 716ab17b ...15 WARNING: Frame IP not in any known module. Following frames may be wrong.(无法显示托管函数)16 09 00f3f4b8 71faf036 0x2e9088e17 0a 00f3f4c4 71fb22da clr!CallDescrWorkerInternal+0x3418 0b 00f3f518 71fb859b clr!CallDescrWorkerWithHandler+0x6b19 0c 00f3f588 7215b11b clr!MethodDescCallSite::CallTargetWorker+0x16a20 0d 00f3f6ac 7215b7fa clr!RunMain+0x1b321 0e 00f3f918 7215b727 clr!Assembly::ExecuteMainMethod+0xf722 0f 00f3fdfc 7215b8a8 clr!SystemDomain::ExecuteMainMethod+0x5ef23 10 00f3fe54 7215b9ce clr!ExecuteEXE+0x4c24 11 00f3fe94 72157305 clr!_CorExeMainInternal+0xdc25 12 00f3fed0 7275fa84 clr!_CorExeMain+0x4d26 13 00f3ff08 7285e81e mscoreei!_CorExeMain+0xd627 14 00f3ff18 72864338 MSCOREE!ShellShim__CorExeMain+0x9e28 15 00f3ff30 7636f989 MSCOREE!_CorExeMain_Exported+0x829 16 00f3ff30 77c27084 KERNEL32!BaseThreadInitThunk+0x1930 17 00f3ff8c 77c27054 ntdll!__RtlUserThreadStart+0x2f31 18 00f3ff9c 00000000 ntdll!_RtlUserThreadStart+0x1b
但是我们可以使用 SOS的【!clrstack】命令显示托管函数。
1 0:000> !clrstack 2 OS Thread Id: 0xc08 (0) 3 Child SP IP Call Site 4 00f3f3ac 77c310fc [InlinedCallFrame: 00f3f3ac] 5 00f3f3a8 70f79b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 6 00f3f3ac 716ab275 [InlinedCallFrame: 00f3f3ac] Microsoft.Win32.Win32Native.ReadFile(..., Int32, Int32 ByRef, IntPtr) 7 00f3f410 716ab275 System.IO.__ConsoleStream.ReadFileNative(.., Int32 ByRef) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205] 8 00f3f444 716ab17b System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134] 9 00f3f464 70f5e6a3 System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595]10 00f3f474 70f5eb5b System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748]11 00f3f490 717f3786 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363]12 00f3f4a0 71651845 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]13 00f3f4a8 02e9088e Example_7_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\...\Example_7_1_1\Program.cs @ 13]14 00f3f62c 71faf036 [GCFrame: 00f3f62c]
2.4、使用【kb】命令查看线程栈,提取 ntdll!NtReadFile 的第一个参数。
测试源码:Example_7_1_1
1 0:000> kb 2 # ChildEBP RetAddr Args to Child 3 00 00f3f38c 75dbf25c 00000090 00000000 00000000 ntdll!NtReadFile+0xc 4 01 00f3f38c 70f79b71 00000090 02f35d18 00000100 KERNELBASE!ReadFile+0xec 5 02 00f3f3fc 716ab275 00000000 00f3f444 00000100 mscorlib_ni+0x4b9b71 6 03 00f3f428 716ab17b 00f3f444 00000000 00000001 mscorlib_ni!System.IO.__ConsoleStream.ReadFileNative+0x89 [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205] 7 04 00f3f454 70f5e6a3 00000100 00000000 02f3a210 mscorlib_ni!System.IO.__ConsoleStream.Read+0x9f [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134] 8 05 00f3f46c 70f5eb5b 00000001 00000000 00f3f55c mscorlib_ni!System.IO.StreamReader.ReadBuffer+0x33 [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595] 9 06 00f3f488 717f3786 00000000 00f3f4d0 00f3f4a0 mscorlib_ni!System.IO.StreamReader.ReadLine+0xe3 [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748] 10 07 00f3f498 71651845 00f3f4b8 02e9088e 00000000 mscorlib_ni!System.IO.TextReader.SyncTextReader.ReadLine+0x1a [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363] 11 08 00f3f4a0 02e9088e 00000000 0000000b 0000000a 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 00f3f4b8 71faf036 0116a628 00f3f518 71fb22da 0x2e9088e14 0a 00f3f4c4 71fb22da 00f3f55c 00f3f508 720a23d0 clr!CallDescrWorkerInternal+0x3415 0b 00f3f518 71fb859b 00f3f570 02f324bc 00000000 clr!CallDescrWorkerWithHandler+0x6b16 0c 00f3f588 7215b11b 00f3f664 6a38df42 02d04d78 clr!MethodDescCallSite::CallTargetWorker+0x16a17 0d 00f3f6ac 7215b7fa 00f3f6f0 00000000 6a38d0f6 clr!RunMain+0x1b318 0e 00f3f918 7215b727 00000000 6a38d412 00b70000 clr!Assembly::ExecuteMainMethod+0xf719 0f 00f3fdfc 7215b8a8 6a38d7ba 00000000 00000000 clr!SystemDomain::ExecuteMainMethod+0x5ef20 10 00f3fe54 7215b9ce 6a38d77a 00000000 721572e0 clr!ExecuteEXE+0x4c21 11 00f3fe94 72157305 6a38d73e 00000000 721572e0 clr!_CorExeMainInternal+0xdc22 12 00f3fed0 7275fa84 8ee8ef1f 72864330 7275fa20 clr!_CorExeMain+0x4d23 13 00f3ff08 7285e81e 72864330 72750000 00f3ff30 mscoreei!_CorExeMain+0xd624 14 00f3ff18 72864338 72864330 7636f989 00c95000 MSCOREE!ShellShim__CorExeMain+0x9e25 15 00f3ff30 7636f989 00c95000 7636f970 00f3ff8c MSCOREE!_CorExeMain_Exported+0x826 16 00f3ff30 77c27084 00c95000 281a60a3 00000000 KERNEL32!BaseThreadInitThunk+0x1927 17 00f3ff8c 77c27054 ffffffff 77c462ae 00000000 ntdll!__RtlUserThreadStart+0x2f28 18 00f3ff9c 00000000 00000000 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b
ntdll!NtReadFile 方法的第一个参数是一个 Handle,我们可以使用【!handle】命令+ f 参数显示详情。Console.ReadLine()方法底层就是 File 的句柄。
1 0:000> !handle 00000090 f 2 Handle 90 3 Type File 4 Attributes 0 5 GrantedAccess 0x12019f: 6 ReadControl,Synch 7 Read/List,Write/Add,Append/SubDir/CreatePipe,ReadEA,WriteEA,ReadAttr,WriteAttr 8 HandleCount 2 9 PointerCount 6553110 No Object Specific Information available
2.5、我们使用【!clrstack】命令查看托管函数。
测试源码:Example_7_1_1
!clrstack 执行效果
1 0:000> !clrstack 2 OS Thread Id: 0xc08 (0) 3 Child SP IP Call Site 4 00f3f3ac 77c310fc [InlinedCallFrame: 00f3f3ac] 5 00f3f3a8 70f79b71 DomainNeutralILStubClass.IL_STUB_PInvoke(..., Byte*, Int32, Int32 ByRef, IntPtr) 6 00f3f3ac 716ab275 [InlinedCallFrame: 00f3f3ac] Microsoft.Win32.Win32Native.ReadFile(... Byte*, Int32, Int32 ByRef, IntPtr) 7 00f3f410 716ab275 System.IO.__ConsoleStream.ReadFileNative(.. Int32 ByRef) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205] 8 00f3f444 716ab17b System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134] 9 00f3f464 70f5e6a3 System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595]10 00f3f474 70f5eb5b System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748]11 00f3f490 717f3786 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363]12 00f3f4a0 71651845 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]13 00f3f4a8 02e9088e Example_7_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\...\Example_7_1_1\Program.cs @ 13]14 00f3f62c 71faf036 [GCFrame: 00f3f62c]
!clrstack -a:显示所有参数和局部变量,关注红色标注的。
1 0:000> !clrstack -a 2 OS Thread Id: 0xc08 (0) 3 Child SP IP Call Site 4 00f3f3ac 77c310fc [InlinedCallFrame: 00f3f3ac] 5 00f3f3a8 70f79b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 6 PARAMETERS: 7 <no data> 8 <no data> 9 <no data>10 <no data>11 <no data>12 ...(内容太多,就省略了,也没用)13 14 00f3f4a8 02e9088e Example_7_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\..\Example_7_1_1\Program.cs @ 13]15 PARAMETERS:(参数)16 args (0x00f3f4b4) = 0x02f324bc(参数)17 LOCALS:(局部变量)18 0x00f3f4b0 = 0x0000000a19 0x00f3f4ac = 0x0000000b20 21 00f3f62c 71faf036 [GCFrame: 00f3f62c]
!clrstack -l:显示所有局部变量,关注红色标注的。
1 0:000> !clrstack -l 2 OS Thread Id: 0xc08 (0) 3 Child SP IP Call Site 4 00f3f3ac 77c310fc [InlinedCallFrame: 00f3f3ac] 5 00f3f3a8 70f79b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 6 ...(内容太多,就省略了,也没用) 7 8 00f3f4a8 02e9088e Example_7_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\...\Example_7_1_1\Program.cs @ 13] 9 LOCALS:(只有局部变量)10 0x00f3f4b0 = 0x0000000a11 0x00f3f4ac = 0x0000000b12 13 00f3f62c 71faf036 [GCFrame: 00f3f62c]
!clrstack -p:显示所有参数,关注红色标注的。
1 0:000> !clrstack -p 2 OS Thread Id: 0xc08 (0) 3 Child SP IP Call Site 4 00f3f3ac 77c310fc [InlinedCallFrame: 00f3f3ac] 5 00f3f3a8 70f79b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 6 PARAMETERS: 7 <no data> 8 <no data> 9 <no data>10 <no data>11 <no data>12 13 ...(内容太多,就省略了,也没用)14 15 00f3f4a8 02e9088e Example_7_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\...\Example_7_1_1\Program.cs @ 13]16 PARAMETERS:(只有参数)17 args (0x00f3f4b4) = 0x02f324bc18 19 00f3f62c 71faf036 [GCFrame: 00f3f62c]
2.6、使用【~*e】查看所有托管线程栈。
测试源码:Example_7_1_1
~ 命令输出所有线程列表。
1 0:000> ~ 2 . 0 Id: 2074.c08 Suspend: 1 Teb: 00c98000 Unfrozen 3 1 Id: 2074.2cdc Suspend: 1 Teb: 00c9b000 Unfrozen 4 2 Id: 2074.3bec Suspend: 1 Teb: 00c9e000 Unfrozen 5 3 Id: 2074.1a34 Suspend: 1 Teb: 00ca1000 Unfrozen 6 4 Id: 2074.3858 Suspend: 1 Teb: 00ca4000 Unfrozen 7 5 Id: 2074.379c Suspend: 1 Teb: 00ca7000 Unfrozen 8 6 Id: 2074.3088 Suspend: 1 Teb: 00caa000 Unfrozen 9 7 Id: 2074.2c54 Suspend: 1 Teb: 00cad000 Unfrozen10 8 Id: 2074.20dc Suspend: 1 Teb: 00cb0000 Unfrozen11 9 Id: 2074.2014 Suspend: 1 Teb: 00cb3000 Unfrozen12 # 10 Id: 2074.187c Suspend: 1 Teb: 00cc2000 Unfrozen13 11 Id: 2074.2b64 Suspend: 1 Teb: 00cb9000 Unfrozen14 12 Id: 2074.1358 Suspend: 1 Teb: 00cbc000 Unfrozen15 13 Id: 2074.6e8 Suspend: 1 Teb: 00cbf000 Unfrozen
~* 命令,显示所有线程列表和入口函数。
1 0:000> ~* 2 . 0 Id: 2074.c08 Suspend: 1 Teb: 00c98000 Unfrozen 3 Start: 00402bca 4 Priority: 0 Priority class: 32 Affinity: f 5 1 Id: 2074.2cdc Suspend: 1 Teb: 00c9b000 Unfrozen 6 Start: ntdll!TppWorkerThread (77c10c90) 7 Priority: 0 Priority class: 32 Affinity: f 8 2 Id: 2074.3bec Suspend: 1 Teb: 00c9e000 Unfrozen 9 Start: ntdll!TppWorkerThread (77c10c90)10 Priority: 0 Priority class: 32 Affinity: f11 3 Id: 2074.1a34 Suspend: 1 Teb: 00ca1000 Unfrozen12 Start: ntdll!TppWorkerThread (77c10c90)13 Priority: 0 Priority class: 32 Affinity: f14 4 Id: 2074.3858 Suspend: 1 Teb: 00ca4000 Unfrozen15 Start: clr!DebuggerRCThread::ThreadProcStatic (721565d0)16 Priority: 0 Priority class: 32 Affinity: f17 5 Id: 2074.379c Suspend: 1 Teb: 00ca7000 Unfrozen18 Start: clr!Thread::intermediateThreadProc (72074b60)19 Priority: 2 Priority class: 32 Affinity: f20 6 Id: 2074.3088 Suspend: 1 Teb: 00caa000 Unfrozen21 Start: combase!CRpcThreadCache::RpcWorkerThreadEntry (7731bcc0)22 Priority: 0 Priority class: 32 Affinity: f23 7 Id: 2074.2c54 Suspend: 1 Teb: 00cad000 Unfrozen24 Start: ntdll!TppWorkerThread (77c10c90)25 Priority: 0 Priority class: 32 Affinity: f26 8 Id: 2074.20dc Suspend: 1 Teb: 00cb0000 Unfrozen27 Start: ntdll!TppWorkerThread (77c10c90)28 Priority: 0 Priority class: 32 Affinity: f29 9 Id: 2074.2014 Suspend: 1 Teb: 00cb3000 Unfrozen30 Start: clr!Thread::intermediateThreadProc (72074b60)31 Priority: 0 Priority class: 32 Affinity: f32 # 10 Id: 2074.187c Suspend: 1 Teb: 00cc2000 Unfrozen33 Start: ntdll!DbgUiRemoteBreakin (77c6cee0)34 Priority: 0 Priority class: 32 Affinity: f35 11 Id: 2074.2b64 Suspend: 1 Teb: 00cb9000 Unfrozen36 Start: clr!Thread::intermediateThreadProc (72074b60)37 Priority: 0 Priority class: 32 Affinity: f38 12 Id: 2074.1358 Suspend: 1 Teb: 00cbc000 Unfrozen39 Start: clr!Thread::intermediateThreadProc (72074b60)40 Priority: 0 Priority class: 32 Affinity: f41 13 Id: 2074.6e8 Suspend: 1 Teb: 00cbf000 Unfrozen42 Start: clr!Thread::intermediateThreadProc (72074b60)43 Priority: 0 Priority class: 32 Affinity: f
~*e !clrstack 命令执行的结果,内容太多,折叠了。
View Code
2.7、使用【!dumpstack】命令查看托管和非托管的线程栈。
测试源码:Example_7_1_1
需要切换到主线程,然后执行命令【!dumpstack】
1 0:000> !dumpstack 2 OS Thread Id: 0x710 (0) 3 Current frame: ntdll!NtReadFile+0xc 4 ChildEBP RetAddr Caller, Callee 5 00daef38 75dbf25c KERNELBASE!ReadFile+0xec, calling ntdll!NtReadFile ......66 00daf7d0 71fb9704 clr!Alloc+0x142, calling clr!_EH_epilog367 00daf7d8 71fc3cbc clr!HndLogSetEvent+0x15, calling clr!GCEventEnabledSetGCHandle68 00daf9b0 77c052fe ntdll!RtlAllocateHeap+0x3e, calling ntdll!RtlpAllocateHeapInternal69 00daf9d0 7213ca2c clr!EEStartupHelper+0xabb, calling clr!_EH_epilog370 00daf9d4 7213b55b clr!EEStartup+0xb8, calling clr!_SEH_epilog471 00dafa0c 7215b8a8 clr!ExecuteEXE+0x4c, calling clr!SystemDomain::ExecuteMainMethod72 00dafa64 7215b9ce clr!_CorExeMainInternal+0xdc, calling clr!ExecuteEXE73 00dafaa4 72157305 clr!_CorExeMain+0x4d, calling clr!_CorExeMainInternal74 00dafae0 7275fa84 mscoreei!_CorExeMain+0xd675 00dafafc 7636f4c4 KERNEL32!GetProcAddressStub+0x14, calling KERNELBASE!GetProcAddressForCaller76 00dafb18 7285e81e MSCOREE!ShellShim__CorExeMain+0x9e77 00dafb28 72864338 MSCOREE!_CorExeMain_Exported+0x8, calling MSCOREE!ShellShim__CorExeMain78 00dafb30 7636f989 KERNEL32!BaseThreadInitThunk+0x1979 00dafb40 77c27084 ntdll!__RtlUserThreadStart+0x2f80 00dafb9c 77c27054 ntdll!_RtlUserThreadStart+0x1b, calling ntdll!__RtlUserThreadStart
我们可以使用【!dumpstack -ee】只查看托管栈。
1 0:000> !dumpstack -ee 2 OS Thread Id: 0x710 (0) 3 Current frame: 4 ChildEBP RetAddr Caller, Callee 5 00daef9c 70f79b71 (MethodDesc 70c438c4 +0x69 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)) 6 00daefc8 70f79b71 (MethodDesc 70c438c4 +0x69 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)) 7 00daf00c 716ab275 (MethodDesc 70cf7a24 +0x89 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef)) 8 00daf038 716ab17b (MethodDesc 70cf7a4c +0x9f System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)) 9 00daf064 70f5e6a3 (MethodDesc 70c2d964 +0x33 System.IO.StreamReader.ReadBuffer())10 00daf07c 70f5eb5b (MethodDesc 70c2d96c +0xe3 System.IO.StreamReader.ReadLine())11 00daf098 717f3786 (MethodDesc 70d22100 +0x1a System.IO.TextReader+SyncTextReader.ReadLine())12 00daf0a8 71651845 (MethodDesc 70c19e00 +0x15 System.Console.ReadLine())13 00daf0b0 0144088e (MethodDesc 01184d78 +0x46 Example_7_1_1.Program.Main(System.String[]))
2.8、使用【!eestack】和【~*e !dumpstack】查看所有的线程栈。
测试源码:Example_7_1_1
1 0:000> !eestack2 ---------------------------------------------3 Thread 04 Current frame: ntdll!NtReadFile+0xc5 ChildEBP RetAddr Caller, Callee6 。。。。7 内容太多,省略了。
~*e !dumpstack执行效果
1 0:000> ~*e !dumpstack2 OS Thread Id: 0x710 (0)3 Current frame: ntdll!NtReadFile+0xc4 ChildEBP RetAddr Caller, Callee5 00daef38 75dbf25c KERNELBASE!ReadFile+0xec, calling ntdll!NtReadFile6 ......7 内容太多,省略了。
eestack 命令,可以增加参数,比如:-short,我们可以使用 SOS 的帮助命令查看某个命令的解释,比如:eestack。
1 0:010> !sos.help eestack 2 ------------------------------------------------------------------------------- 3 !EEStack [-short] [-EE] 4 5 This command runs !DumpStack on all threads in the process. The -EE option is 6 passed directly to !DumpStack. The -short option tries to narrow down the 7 output to "interesting" threads only, which is defined by 8 9 1) The thread has taken a lock.(可以显示具有锁的线程)10 2) The thread has been "hijacked" in order to allow a garbage collection.(可以显示被“劫持”的线程)11 3) The thread is currently in managed code.(可以显示托管线程)12 13 See the documentation for !DumpStack for more info.
四、总结
终于写完了,今天这篇文章是第六篇。我们了解了线程和线程调用栈,那调试起来我们就知道如何开始了,知道如何查找参数和局部变量,知道线程栈地址,哦我们可以输出线程栈的内容,对值类型和引用类型了解的也更深入了,当然对线程了解也更深入了,终于做到知其一也知其二了。其实这些知识是相互作用的,不是独立的,任何一个环节的调试,都需要很多技巧。好了,不说了,不忘初心,继续努力,希望老天不要辜负努力的人。
标签: #netframework46是什么