Hexo
dotNet调试技巧
发布于: 2023-06-01 更新于: 2023-06-02 分类于: 

介绍.NET平台下一些基础的调试技巧。

dnspy 调试

Module Breakpoint

Module Breakpoint 可以看作DLL断点,支持在程序集加载时断下。当新增程序集后,会在Assembly Explorer出现新加载在内存中的程序集。

image-20230601170151971

Class Breakpoint

类断点,将整个类的所有方法全部下断点。

image-20230601170831306

Condition Breakpoint && Log BreakPoint

输入框的内容可以为任意表达式,用{}描述表达式,如:

1
command = {this.command}

image-20230602153208454

输出的内容在输出中中,可以使用文件函数将其记录在日志。

image-20230602153559594

这个输入框还可以读取预定义的一些关键字。

image-20230602171331115

入口点

除开入口点外,<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
!clrstack

或者

1
!DumpStack

这个可以获得非托管和托管线程的代码。

如:

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类中的属性

--- 我是有底线的 ---