介绍.NET平台下一些基础的调试技巧。
dnspy 调试
Module Breakpoint
Module Breakpoint
可以看作DLL断点,支持在程序集加载时断下。当新增程序集后,会在Assembly Explorer
出现新加载在内存中的程序集。
Class Breakpoint
类断点,将整个类的所有方法全部下断点。
Condition Breakpoint && Log BreakPoint
输入框的内容可以为任意表达式,用{}
描述表达式,如:
1
| command = {this.command}
|
输出的内容在输出中中,可以使用文件函数将其记录在日志。
这个输入框还可以读取预定义的一些关键字。
入口点
除开入口点外,<Module>.cctor
是模块的加载点。这是.NET程序的重点关注对象。
禁用.NET程序集优化
当.NET程序集以调试器方式启动时,是默认关闭程序集优化的,但当以附加调试的情况调试时。
Windbg 调试
使用Windbg 可以非常方便的进行混合模式的调试,可以理解为,Windbg作为Native
代码调试器,加载了.NET
调试的功能。
使用.loadby sos clr
加载托管调试。
在此之前,需要明白一些数据结构
.Net 数据结构的相互关系大致如下:
- 每个 .Net 应用程序都运行在一个或多个应用程序域 (Domain) 中,每个应用程序域都是一个独立的执行环境,可以加载和卸载程序集。
- 每个应用程序域都有一个基类 BaseDomain,它包含一些公共的数据结构和方法,例如 LoaderHeaps、ClassLoader、InterfaceVTableMapMgr 等。
- 每个应用程序域都有一个 SystemDomain 的引用,SystemDomain 是一个单例类,它表示 CLR 的初始化状态和全局数据结构,例如 GlobalStringLiteralMap、HandleTable、GCHeap 等。
- SystemDomain 包含一个 SharedDomain 的引用,SharedDomain 是一个单例类,它表示所有应用程序域共享的数据结构,例如共享的 LoaderHeaps、共享的程序集等。
- 每个应用程序域都有一个或多个程序集 (Assembly),每个程序集都包含一组 IL 模块和元数据。每个程序集都有一个 ClassLoader 的实例,负责加载类型和方法。
- 每个类型 (EEClass) 都有一个方法表 (MethodTable),表示该类型的所有方法和字段。方法表包含指向方法描述符 (MethodDescriptor) 的指针,方法描述符表示方法的元数据信息和实现地址。方法表还包含接口映射表 (Interface Map),表示该类型实现的接口和对应的方法表槽位。
- 每个对象实例 (ObjectInstance) 都有一个指向其类型的方法表的指针。对象实例还包含其字段值和同步块索引 (SyncBlockIndex),后者指向同步块条目 (SyncTableEntry),用于支持线程同步和垃圾回收等功能。
已知类的代码定位
对于已知类名,需要定位EEClass,对于完整的方法也可以使用这个命令。
1
| !Name2EE DllName!ClassName
|
如:
1 2 3 4 5 6 7
| 0:000> !Name2EE System.Management.Automation.dll!System.Management.Automation.PowerShell Module: 00007ff9b5791000 Assembly: System.Management.Automation.dll Token: 0000000002000342 MethodTable: 00007ff9b734f870 EEClass: 00007ff9b5c13980 Name: System.Management.Automation.PowerShell
|
和:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 0:000> !Name2EE mscorlib.dll!System.Threading.WaitHandle.WaitOne Module: 00007ffa0e721000 Assembly: mscorlib.dll Token: 0000000006003d68 MethodDesc: 00007ffa0e908d98 Name: System.Threading.WaitHandle.WaitOne(Int32, Boolean) JITTED Code Address: 00007ffa0ece9c70 ----------------------- Token: 0000000006003d69 MethodDesc: 00007ffa0e908da0 Name: System.Threading.WaitHandle.WaitOne(System.TimeSpan, Boolean) JITTED Code Address: 00007ffa0f57aed0 ----------------------- Token: 0000000006003d6a MethodDesc: 00007ffa0e908da8 Name: System.Threading.WaitHandle.WaitOne() JITTED Code Address: 00007ffa0ed0a040 ----------------------- Token: 0000000006003d6b MethodDesc: 00007ffa0e908db0 Name: System.Threading.WaitHandle.WaitOne(Int32) JITTED Code Address: 00007ffa0f57af30 ----------------------- Token: 0000000006003d6c MethodDesc: 00007ffa0e908db8 Name: System.Threading.WaitHandle.WaitOne(System.TimeSpan) JITTED Code Address: 00007ffa0f57af50 ----------------------- Token: 0000000006003d6d MethodDesc: 00007ffa0e908f50 Name: System.Threading.WaitHandle.WaitOne(Int64, Boolean) JITTED Code Address: 00007ffa0f57af70
|
点击EEClass
后,可以dump出这个类的属性
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 0:000> !DumpClass /d 00007ff9b5c13980 Class Name: System.Management.Automation.PowerShell mdToken: 0000000002000342 File: C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll Parent Class: 00007ffa0e722f68 Module: 00007ff9b5791000 Method Table: 00007ff9b734f870 Vtable Slots: 5 Total Method Slots: 16 Class Attributes: 100101 Transparency: Critical NumInstanceFields: 25 NumStaticFields: 0 MT Field Offset Type VT Attr Value Name 00007ffa0e74b698 4000edb c0 System.Boolean 1 instance isGetCommandMetadataSpecialPipeline 00007ff9b734f810 4000edc 8 ...omation.PSCommand 0 instance psCommand 00007ff9b59216b0 4000edd 10 ...ment.Automation]] 0 instance extraCommands 00007ffa0e74b698 4000ede c1 System.Boolean 1 instance runningExtraCommands ......
|
这其中有一个MT,MT是Method Table
,是这个类的方法表:
使用!DumpMT -MD MTPointer
可以打印出该表中的所有MD(Method Descriptor)
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 0:000> !DumpMT -MD 00007ff9b734f870 EEClass: 00007ff9b5c13980 Module: 00007ff9b5791000 Name: System.Management.Automation.PowerShell mdToken: 0000000002000342 File: C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll BaseSize: 0xe8 ComponentSize: 0x0 Slots in VTable: 149 Number of IFaces in IFaceMap: 1 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 00007ff9b68e70f0 00007ff9b5d30600 PreJIT System.Management.Automation.PowerShell.get_Commands() 00007ff9b6148e30 00007ff9b5d310a8 PreJIT System.Management.Automation.PowerShell.AsPSPowerShellPipeline() ......
|
MethodDesc
是非常重要的数据结构,直接点击MethodDesc,可以展示出该方法的IL代码地址,以及JIT后的地址。
Entry 即为方法的入口地址。
如:
这是直接DumpMD
的结果
1 2 3 4 5 6 7 8 9
| 0:000> !DumpMD 00007ff9b5d310a8 Method Name: System.Management.Automation.PowerShell.AsPSPowerShellPipeline() Class: 00007ff9b5c13980 MethodTable: 00007ff9b734f870 mdToken: 0000000006002eb6 Module: 00007ff9b5791000 IsJitted: yes CodeAddr: 00007ff9b68ee220 Transparency: Critical
|
直接用这个地址使用命令DumpIL
即可得到IL地址。
如:
1 2 3 4 5 6 7 8
| 0:000> !DumpIL 00007ff9b5d310a8 ilAddr = 00007ff9b629dafc IL_0000: ldstr "PS_PowerShellPipeline" IL_0005: call System.Management.Automation.InternalMISerialize::CreateCimInstance IL_000a: stloc.0 IL_000b: ldstr "InstanceId" IL_0010: ldarg.0 ......
|
对这个CodeAddr点击,这可以反汇编JIT的代码,使用U
命令
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 0:000> !U /d 00007ff9b68ee220 preJIT generated code System.Management.Automation.PowerShell.AsPSPowerShellPipeline() Begin 00007ff9b68ee220, size 442 >>> 00007ff9`b68ee220 55 push rbp 00007ff9`b68ee221 4156 push r14 00007ff9`b68ee223 57 push rdi 00007ff9`b68ee224 56 push rsi 00007ff9`b68ee225 53 push rbx 00007ff9`b68ee226 4883ec50 sub rsp,50h 00007ff9`b68ee22a 488d6c2470 lea rbp,[rsp+70h] 00007ff9`b68ee22f 488bf1 mov rsi,rcx 00007ff9`b68ee232 488d7dc0 lea rdi,[rbp-40h] 00007ff9`b68ee236 b908000000 mov ecx,8 00007ff9`b68ee23b 33c0 xor eax,eax 00007ff9`b68ee23d f3ab rep stos dword ptr [rdi] 00007ff9`b68ee23f 488bce mov rcx,rsi 00007ff9`b68ee242 488965b0 mov qword ptr [rbp-50h],rsp 00007ff9`b68ee246 488bf1 mov rsi,rcx 00007ff9`b68ee249 488b0db8ac15ff mov rcx,qword ptr [System_Management_Automation_ni+0x2b8f08 (00007ff9`b5a48f08)] (MT: Microsoft.Management.Infrastructure.CimInstance) 00007ff9`b68ee250 ff151a7072ff call qword ptr [System_Management_Automation_ni+0x885270 (00007ff9`b6015270)] ......
|
托管代码的调用堆栈
需要切换到托管线程,使用
或者
这个可以获得非托管和托管线程的代码。
如:
1 2 3 4 5 6
| 0:000> !clrstack OS Thread Id: 0x36ec (0) Child SP IP Call Site 000000a9266ed328 00007ffa36a64704 [HelperMethodFrame_1OBJ: 000000a9266ed328] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean) 000000a9266ed450 00007ffa0ece9ccc System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean) 000000a9266ed480 00007ffa0ece9c9f System.Threading.WaitHandle.WaitOne(Int32, Boolean)
|
对象查看
JIT的代码的调用规则和C代码编译的类似
rcx为this
类,对应到该类,使用!do <class addr>
1 2 3 4 5 6 7 8 9
| 0:006> !do 000001ece82fd368 Name: Microsoft.PowerShell.Commands.InvokeExpressionCommand MethodTable: 00007ff9dfe6b7d8 EEClass: 00007ff9df3bcc58 Size: 128(0x80) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.Commands.Utility\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.Commands.Utility.dll Fields: MT Field Offset Type VT Attr Value Name 00007ff9b71e9548 4002971 8 ...n.ICommandRuntime 0 instance 000001ece82fd458 commandRuntime
|
点击value即可进一步Dump类中的属性