Windows软件调试初探-托管

碎碎念

C#和Visual Basic .NET等编程语言由公共基础设施支持,被公共类型系统CTS编译为微软中间语言MSIL(也叫公共中间语言CIL)。CIL再由公共运行时CLR或及时编译器JIT在操作系统服务上的本地代码上运行。

CIL是一种面向对象、基于栈的字节码,有200多条指令分为10组。.NET框架中有ILASM和ILDASM用于CIL指令与CIL字节码转化。

.NET将源程序中类型定义、名称等信息以元数据形式保存,并可在运行期使用。元数据与CIL字节码组合起来叫做程序集。

开发.NET程序的语言叫托管语言,用.NET技术开发的程序叫托管程序,CLR有时也叫托管运行时。.NET程序运行时,系统加载MSCOREE.dll,用于解析.NET程序中信息并为其加载合适版本的CLR。

执行引擎EE模块MSCOREE通过注册表确定所需版本CLR,加载其接口模块mscoreei.dll,要加载的CLR主模块为clr.dll。在.NET 2.0下CLR主模块桌面/工作站版本为mscorwks.dll,服务器版本为mscorwks.dll。.NET运行时经常原位更新,所以运行时目录中版本号可能不准确。

类和方法表

例如程序:

1
2
3
4
5
6
7
8
9
10
11
using System;
using System.Collections.Generic;
using System.Text;
namespace CliHello {
public class CliHello2 {
static void Main(string[] args) {
Console.WriteLine("C#/CLI Hello, World!");
Console.ReadLine();
}
}
}

调试方法:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
0:000> sxe ld:clr //在收到clr模块加载事件时中断
0:000> g
ModLoad: 75cf0000 75d6c000 C:\Windows\SysWOW64\ADVAPI32.dll
ModLoad: 76190000 76254000 C:\Windows\SysWOW64\msvcrt.dll
ModLoad: 769e0000 76a62000 C:\Windows\SysWOW64\sechost.dll
ModLoad: 75630000 756e9000 C:\Windows\SysWOW64\RPCRT4.dll
ModLoad: 754c0000 75548000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll
ModLoad: 776a0000 776eb000 C:\Windows\SysWOW64\SHLWAPI.dll
ModLoad: 754a0000 754b3000 C:\Windows\SysWOW64\kernel.appcore.dll
ModLoad: 75490000 75498000 C:\Windows\SysWOW64\VERSION.dll
ModLoad: 74cd0000 7548a000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
eax=c0000034 ebx=0075f248 ecx=00000000 edx=00000000 esi=0000016c edi=00a5b190
eip=778f67ec esp=0075f1fc ebp=0075f240 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
ntdll!NtMapViewOfSection+0xc:
778f67ec c22800 ret 28h
0:000> bp clr!RunMain
breakpoint 0 redefined
0:000> g
ModLoad: 76260000 762fc000 C:\Windows\SysWOW64\OLEAUT32.dll
ModLoad: 77420000 7769c000 C:\Windows\SysWOW64\combase.dll
ModLoad: 74b50000 74b6a000 C:\Windows\SysWOW64\bcrypt.dll
ModLoad: 76970000 769d2000 C:\Windows\SysWOW64\bcryptprimitives.dll
ModLoad: 776f0000 7783d000 C:\Windows\SysWOW64\ole32.dll
ModLoad: 74b30000 74b45000 C:\Windows\SysWOW64\CRYPTSP.dll
ModLoad: 74b00000 74b30000 C:\Windows\SysWOW64\rsaenh.dll
ModLoad: 74af0000 74afb000 C:\Windows\SysWOW64\CRYPTBASE.dll
Breakpoint 0 hit
eax=0075f1e4 ebx=00000000 ecx=00c845dc edx=00000000 esi=00000000 edi=00c845dc
eip=74dc6bce esp=0075f1a4 ebp=0075f40c iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
clr!RunMain:
74dc6bce 68f8000000 push 0F8h
0:000> k
# ChildEBP RetAddr
00 0075f1a0 74d9c671 clr!RunMain
01 0075f40c 74d9c4e9 clr!Assembly::ExecuteMainMethod+0xf7
02 0075f8f0 74d9c8f4 clr!SystemDomain::ExecuteMainMethod+0x61c
03 0075f948 74d9c83a clr!ExecuteEXE+0x4c
04 0075f988 74ddb62c clr!_CorExeMainInternal+0xd8
05 0075f9c4 754ca36e clr!_CorExeMain+0x4d
06 0075fa00 7555fb6e mscoreei!_CorExeMain+0x100
07 0075fa10 75565708 MSCOREE!ShellShim__CorExeMain+0x9e
08 0075fa18 76057ba9 MSCOREE!_CorExeMain_Exported+0x8
09 0075fa28 778ebb9b KERNEL32!BaseThreadInitThunk+0x19
0a 0075fa80 778ebb1f ntdll!__RtlUserThreadStart+0x2b
0b 0075fa90 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> .loadby sos clr //加载托管观察扩展命令模块SOS
0:000> !name2ee CliHello.exe CliHello.CliHello2 //获取类的执行引擎信息
Module: 00c84044
Assembly: CliHello.exe
Token: 02000002
MethodTable: 00c845f0 //方法表地址
EEClass: 00c81d08 //类信息地址
Name: CliHello.CliHello2
0:000> !DumpMT -MD 00c845f0
EEClass: 00c81d08
Module: 00c84044
Name: CliHello.CliHello2
mdToken: 02000002
File: D:\Downloads\软件调试随书附件\附件\ch207\CliHello\CliHello\bin\Debug\CliHello.exe
BaseSize: 0xc
ComponentSize: 0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table //方法表
Entry MethodDe JIT Name
02810039 00fa6744 NONE System.Object.ToString()
0281003d 00fa674c NONE System.Object.Equals(System.Object)
02810049 00fa676c NONE System.Object.GetHashCode()
0281a440 00fa6784 JIT System.Object.Finalize()
0281e528 00c845e8 NONE CliHello.CliHello2..ctor()
0281e520 00c845dc NONE CliHello.CliHello2.Main(System.String[])
0:000> !DumpMD /d 00c845dc
Method Name: CliHello.CliHello2.Main(System.String[])
Class: 00c81d08
MethodTable: 00c845f0
mdToken: 06000001
Module: 00c84044
IsJitted: no
CodeAddr: ffffffff
Transparency: Not calculated
0:000> u 0281e520
0281e520 e8cb404c72 call clr!PrecodeFixupThunk (74ce25f0)
0281e525 5e pop esi
0281e526 0001 add byte ptr [ecx],al
0281e528 e8c3404c72 call clr!PrecodeFixupThunk (74ce25f0)
0281e52d 5e pop esi
0281e52e 0300 add eax,dword ptr [eax]
0281e530 dc45c8 fadd qword ptr [ebp-38h]
0281e533 0000 add byte ptr [eax],al

方法表中前4个是编译器自动赋予的基类方法,所有类必须从System.Object派生来。第5个事编译器自动产生的构造函数。

方法表共有4列。第一列为方法入口,若还未编译则为触发及时编译的桩代码。第二列为方法描述。第三列为编译状态,PreJIT表示已预编译为机器码,None还未编译,JIT已及时编译。第四列方法名。

例如Main方法反编译后有clr!PrecodeEixupThunk即为桩代码。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
0:000> sxe ld:clrjit
0:000> g
ModLoad: 75cf0000 75d6c000 C:\Windows\SysWOW64\ADVAPI32.dll
ModLoad: 76190000 76254000 C:\Windows\SysWOW64\msvcrt.dll
ModLoad: 769e0000 76a62000 C:\Windows\SysWOW64\sechost.dll
ModLoad: 75630000 756e9000 C:\Windows\SysWOW64\RPCRT4.dll
ModLoad: 754c0000 75548000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll
ModLoad: 776a0000 776eb000 C:\Windows\SysWOW64\SHLWAPI.dll
ModLoad: 754a0000 754b3000 C:\Windows\SysWOW64\kernel.appcore.dll
ModLoad: 75490000 75498000 C:\Windows\SysWOW64\VERSION.dll
ModLoad: 74cd0000 7548a000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
ModLoad: 75810000 759b6000 C:\Windows\SysWOW64\USER32.dll
ModLoad: 76950000 7696a000 C:\Windows\SysWOW64\win32u.dll
ModLoad: 75ca0000 75cc2000 C:\Windows\SysWOW64\GDI32.dll
ModLoad: 74cb0000 74cc5000 C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll
ModLoad: 770d0000 771ad000 C:\Windows\SysWOW64\gdi32full.dll
ModLoad: 755b0000 75629000 C:\Windows\SysWOW64\msvcp_win.dll
ModLoad: 00ec0000 00f73000 C:\Windows\SysWOW64\ucrtbase_clr0400.dll
ModLoad: 74bf0000 74ca3000 C:\Windows\SysWOW64\ucrtbase_clr0400.dll
ModLoad: 75a80000 75b92000 C:\Windows\SysWOW64\ucrtbase.dll
ModLoad: 77840000 77866000 C:\Windows\SysWOW64\IMM32.DLL
ModLoad: 77420000 7769c000 C:\Windows\SysWOW64\combase.dll
(5d88.5630): Unknown exception - code 04242420 (first chance)
ModLoad: 04e50000 053b8000 mscorlib.dll
ModLoad: 053c0000 05928000 mscorlib.dll
ModLoad: 74b70000 74bee000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
eax=c0000034 ebx=008fdee8 ecx=00000000 edx=00000000 esi=00000264 edi=00c63800
eip=778f67ec esp=008fde9c ebp=008fdee0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
ntdll!NtMapViewOfSection+0xc:
778f67ec c22800 ret 28h
0:000> k
# ChildEBP RetAddr
00 008fde98 7790d03c ntdll!NtMapViewOfSection+0xc
01 008fdee8 778c10ea ntdll!LdrpMapViewOfSection+0x77
02 008fdf30 778c0de6 ntdll!LdrpMinimalMapModule+0xe4
03 008fdf58 778bdd53 ntdll!LdrpMapDllWithSectionHandle+0x15
04 008fdfc0 778ba262 ntdll!LdrpMapDllNtFileName+0x13e
05 008fe0f0 778c76bd ntdll!LdrpMapDllFullPath+0xb8
06 008fe138 7790b7a9 ntdll!LdrpProcessWork+0x6e
07 008fe184 778c75aa ntdll!LdrpLoadDllInternal+0x1f2
08 008fe328 772ce203 ntdll!LdrLoadDll+0x14a
09 008fe368 754ccc4e KERNELBASE!LoadLibraryExW+0x153
0a 008fe4c0 754d2601 mscoreei!RuntimeDesc::LoadLibrary+0xa5
0b 008fe524 74e2e07c mscoreei!CLRRuntimeInfoImpl::LoadLibrary+0xda
0c 008fe5b8 74e2e238 clr!LoadAndInitializeJIT+0x7e
0d 008fe5f8 74d2f102 clr!EEJitManager::LoadJIT+0x9c
0e 008fe9a0 74d2eef5 clr!UnsafeJitFunction+0xc6
0f 008fea9c 74d2e9e1 clr!MethodDesc::MakeJitWorker+0x48c
10 008feb0c 74d0673c clr!MethodDesc::DoPrestub+0x596
11 008feb84 74ce299b clr!PreStubWorker+0xef
12 008feba8 74ce2516 clr!ThePreStub+0x11
13 008febc4 74cee549 clr!CallDescrWorkerInternal+0x34
14 008fec18 74cef217 clr!CallDescrWorkerWithHandler+0x6b
15 008fec94 74ddc01e clr!MethodDescCallSite::CallTargetWorker+0x170
16 008fed8c 74de1c8a clr!AppDomain::InitializeDomainContext+0x1f4
17 008ff218 74d9c15a clr!SystemDomain::InitializeDefaultDomain+0x288
18 008ff6f8 74d9c8f4 clr!SystemDomain::ExecuteMainMethod+0x1e2
19 008ff750 74d9c83a clr!ExecuteEXE+0x4c
1a 008ff790 74ddb62c clr!_CorExeMainInternal+0xd8
1b 008ff7cc 754ca36e clr!_CorExeMain+0x4d
1c 008ff808 7555fb6e mscoreei!_CorExeMain+0x100
1d 008ff818 75565708 MSCOREE!ShellShim__CorExeMain+0x9e
1e 008ff820 76057ba9 MSCOREE!_CorExeMain_Exported+0x8
1f 008ff830 778ebb9b KERNEL32!BaseThreadInitThunk+0x19
20 008ff888 778ebb1f ntdll!__RtlUserThreadStart+0x2b
21 008ff898 00000000 ntdll!_RtlUserThreadStart+0x1b

用SOS扩展的bpmd命令对Main方法下端点,因其未编译,下的是未决端点。命中该未决端点后,异常代码0xe0444141即.DAC,表示数据访问组件DAC。DAC为.NET框架中供各种工具访问.NET内部的组件

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
0:000> .loadby sos clr
0:000> !name2ee CliHello.exe CliHello.CliHello2
Module: 00f84044
Assembly: CliHello.exe
Token: 02000002
MethodTable: 00f845f0
EEClass: 00f81d08
Name: CliHello.CliHello2
0:000> !DumpMT -MD 00f845f0
EEClass: 00f81d08
Module: 00f84044
Name: CliHello.CliHello2
mdToken: 02000002
File: D:\Downloads\软件调试随书附件\附件\ch207\CliHello\CliHello\bin\Debug\CliHello.exe
BaseSize: 0xc
ComponentSize: 0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDe JIT Name
02e20039 01226744 NONE System.Object.ToString()
02e2003d 0122674c NONE System.Object.Equals(System.Object)
02e20049 0122676c NONE System.Object.GetHashCode()
02e2a440 01226784 JIT System.Object.Finalize()
02e2e528 00f845e8 NONE CliHello.CliHello2..ctor()
02e2e520 00f845dc NONE CliHello.CliHello2.Main(System.String[])
0:000> !bpmd -MD 00f845dc
MethodDesc = 00f845dc
Adding pending breakpoints...
//可对Main进行命中 Main变为JIT
0:000> !DumpMT -MD 00f845f0
EEClass: 00f81d08
Module: 00f84044
Name: CliHello.CliHello2
mdToken: 02000002
File: D:\Downloads\软件调试随书附件\附件\ch207\CliHello\CliHello\bin\Debug\CliHello.exe
BaseSize: 0xc
ComponentSize: 0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDe JIT Name
02e20039 01226744 NONE System.Object.ToString()
02e2003d 0122674c NONE System.Object.Equals(System.Object)
02e20049 0122676c NONE System.Object.GetHashCode()
02e2a440 01226784 JIT System.Object.Finalize()
02e2e528 00f845e8 NONE CliHello.CliHello2..ctor()
02e2f940 00f845dc JIT CliHello.CliHello2.Main(System.String[])
0:000> u 02e2f940 L14
02e2f940 55 push ebp
02e2f941 8bec mov ebp,esp
02e2f943 83ec08 sub esp,8
02e2f946 33c0 xor eax,eax
02e2f948 8945f8 mov dword ptr [ebp-8],eax
02e2f94b 894dfc mov dword ptr [ebp-4],ecx
02e2f94e 833df042f80000 cmp dword ptr ds:[0F842F0h],0
02e2f955 7405 je 02e2f95c
02e2f957 e844041e72 call clr!JIT_DbgIsJustMyCode (7500fda0)
02e2f95c 90 nop
02e2f95d 8b0db023ef03 mov ecx,dword ptr ds:[3EF23B0h]
02e2f963 ff156050e802 call dword ptr ds:[2E85060h]
02e2f969 90 nop
02e2f96a ff15b84fe802 call dword ptr ds:[2E84FB8h]
02e2f970 8945f8 mov dword ptr [ebp-8],eax
02e2f973 90 nop
02e2f974 90 nop
02e2f975 8be5 mov esp,ebp
02e2f977 5d pop ebp
02e2f978 c3 ret

注意这里触发的断点应该在02e2f95c处,其前面代码判断一个全局表示,为0则用clr!JIT_DbgIsJustMyCode调用调试接口,否则在02e2f95c断下。下面栈帧#01~#03即为CLR通过“方法描述”来调用.NET方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0:000> k
# ChildEBP RetAddr
00 00eff114 74cee549 clr!CallDescrWorkerInternal
01 00eff168 74cef217 clr!CallDescrWorkerWithHandler+0x6b
02 00eff1d4 74d9c573 clr!MethodDescCallSite::CallTargetWorker+0x170
03 00eff2f8 74d9c671 clr!RunMain+0x1c6
04 00eff564 74d9c4e9 clr!Assembly::ExecuteMainMethod+0xf7
05 00effa48 74d9c8f4 clr!SystemDomain::ExecuteMainMethod+0x61c
06 00effaa0 74d9c83a clr!ExecuteEXE+0x4c
07 00effae0 74ddb62c clr!_CorExeMainInternal+0xd8
08 00effb1c 754ca36e clr!_CorExeMain+0x4d
09 00effb58 7555fb6e mscoreei!_CorExeMain+0x100
0a 00effb68 75565708 MSCOREE!ShellShim__CorExeMain+0x9e
0b 00effb70 76057ba9 MSCOREE!_CorExeMain_Exported+0x8
0c 00effb80 778ebb9b KERNEL32!BaseThreadInitThunk+0x19
0d 00effbd8 778ebb1f ntdll!__RtlUserThreadStart+0x2b
0e 00effbe8 00000000 ntdll!_RtlUserThreadStart+0x1b

在SOS模块中用!clrstack获取.NET中栈,用!dumpstack获取.NET方法和非托管函数。

辅助调试线程

在托管程序初始化时,.NET运行时创建两个工作线程。一个是负责内存回收机制的终结器进程,另一个是支持调试的调试辅助线程RCThread。

在被调试进程中,依次有:非托管代码、托管代码、托管调试API 运行时控制器。在调试器进程,依次有:非托管调试器、托管调试器、托管调试API接口。.NET下调试托管程序有3种模式,WinDBG为第1种,Visual Studio默认用第2种,第3种需在“调试”中启用“启用本地代码调试”。

  • 用非托管调试器调试托管程序中非托管代码。既可调试托管进程中本地模块,也可调试及时编译后的托管代码。需要借助扩展插件才能观察托管中名称和数据结构。灵活性大但不容易观察顶层托管语义,适合调试复杂问题。
  • 纯托管调试。用托管调试器通过托管调试API 接口与托管调试API运行时控制器通信,来访问托管代码和数据,并调试托管代码。只能跟踪托管代码,不能跟踪本地代码。最常用,适合开发阶段源代码级调试。
  • 混合调试。同时调试托管代码和非托管代码。此时Enc不可使用,单步跟踪时速度慢,通常用于托管代码与非托管代码交互调用。

对于RCThread调试:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
0:006> ~*k //该进程所有线程栈回溯

0 Id: 1f38.3920 Suspend: 1 Teb: 00e79000 Unfrozen
# ChildEBP RetAddr
00 00dfee6c 750129ec ntdll!NtReadFile+0xc
01 00dfeed0 057c0edb KERNELBASE!ReadFile+0x5c
WARNING: Frame IP not in any known module. Following frames may be wrong.
02 00dfef40 057cb540 0x57c0edb
03 00dfef68 057cb3e9 0x57cb540
04 00dfef94 057cb2c3 0x57cb3e9
05 00dfefac 057cb088 0x57cb2c3
06 00dfefc8 057cb039 0x57cb088
07 00dfefd8 057ca77b 0x57cb039
08 00dfefe0 0575f9a9 0x57ca77b
09 00dff028 73d02516 0x575f9a9
0a 00dff034 73d0e549 clr!CallDescrWorkerInternal+0x34
0b 00dff088 73d0f217 clr!CallDescrWorkerWithHandler+0x6b
0c 00dff0f8 73dbc573 clr!MethodDescCallSite::CallTargetWorker+0x170
0d 00dff21c 73dbc671 clr!RunMain+0x1c6
0e 00dff488 73dbc4e9 clr!Assembly::ExecuteMainMethod+0xf7
0f 00dff96c 73dbc8f4 clr!SystemDomain::ExecuteMainMethod+0x61c
10 00dff9c4 73dbc83a clr!ExecuteEXE+0x4c
11 00dffa04 73dfb62c clr!_CorExeMainInternal+0xd8
12 00dffa40 744ea36e clr!_CorExeMain+0x4d
13 00dffa7c 74d1fb6e mscoreei!_CorExeMain+0x100
14 00dffa8c 74d25708 MSCOREE!ShellShim__CorExeMain+0x9e
15 00dffa94 75c77ba9 MSCOREE!_CorExeMain_Exported+0x8
16 00dffaa4 770abb9b KERNEL32!BaseThreadInitThunk+0x19
17 00dffafc 770abb1f ntdll!__RtlUserThreadStart+0x2b
18 00dffb0c 00000000 ntdll!_RtlUserThreadStart+0x1b

1 Id: 1f38.26ac Suspend: 1 Teb: 00e7d000 Unfrozen
# ChildEBP RetAddr
00 015ef800 7707ee48 ntdll!NtWaitForWorkViaWorkerFactory+0xc
01 015ef9c8 75c77ba9 ntdll!TppWorkerThread+0x338
02 015ef9d8 770abb9b KERNEL32!BaseThreadInitThunk+0x19
03 015efa30 770abb1f ntdll!__RtlUserThreadStart+0x2b
04 015efa40 00000000 ntdll!_RtlUserThreadStart+0x1b

2 Id: 1f38.4780 Suspend: 1 Teb: 00e81000 Unfrozen
# ChildEBP RetAddr
00 0171fa44 7707ee48 ntdll!NtWaitForWorkViaWorkerFactory+0xc
01 0171fc0c 75c77ba9 ntdll!TppWorkerThread+0x338
02 0171fc1c 770abb9b KERNEL32!BaseThreadInitThunk+0x19
03 0171fc74 770abb1f ntdll!__RtlUserThreadStart+0x2b
04 0171fc84 00000000 ntdll!_RtlUserThreadStart+0x1b

3 Id: 1f38.2960 Suspend: 1 Teb: 00e85000 Unfrozen
# ChildEBP RetAddr
00 0185f79c 7707ee48 ntdll!NtWaitForWorkViaWorkerFactory+0xc
01 0185f964 75c77ba9 ntdll!TppWorkerThread+0x338
02 0185f974 770abb9b KERNEL32!BaseThreadInitThunk+0x19
03 0185f9cc 770abb1f ntdll!__RtlUserThreadStart+0x2b
04 0185f9dc 00000000 ntdll!_RtlUserThreadStart+0x1b

4 Id: 1f38.da4 Suspend: 1 Teb: 00e89000 Unfrozen
# ChildEBP RetAddr
00 0328fb5c 75011aaf ntdll!NtWaitForMultipleObjects+0xc
01 0328fcec 73df9a7f KERNELBASE!WaitForMultipleObjectsEx+0x18f
02 0328fd58 73df99d1 clr!DebuggerRCThread::MainLoop+0x9c
03 0328fd88 73df990d clr!DebuggerRCThread::ThreadProc+0xd0
04 0328fdb4 75c77ba9 clr!DebuggerRCThread::ThreadProcStatic+0x6c
05 0328fdc4 770abb9b KERNEL32!BaseThreadInitThunk+0x19
06 0328fe1c 770abb1f ntdll!__RtlUserThreadStart+0x2b
07 0328fe2c 00000000 ntdll!_RtlUserThreadStart+0x1b

5 Id: 1f38.fc4 Suspend: 1 Teb: 00e8d000 Unfrozen
# ChildEBP RetAddr
00 053cf69c 75011aaf ntdll!NtWaitForMultipleObjects+0xc
01 053cf82c 73d5abf9 KERNELBASE!WaitForMultipleObjectsEx+0x18f
02 053cf858 73d5ceaf clr!FinalizerThread::WaitForFinalizerEvent+0x86
03 053cf884 73d13b74 clr!FinalizerThread::FinalizerThreadWorker+0x40
04 053cf89c 73d13beb clr!ManagedThreadBase_DispatchInner+0x71
05 053cf924 73d13c9b clr!ManagedThreadBase_DispatchMiddle+0x8f
06 053cf980 73dbca48 clr!ManagedThreadBase_DispatchOuter+0x6d
07 053cf9a8 73dbcb1d clr!ManagedThreadBase::FinalizerBase+0x33
08 053cf9e4 73d4ab44 clr!FinalizerThread::FinalizerThreadStart+0xe2
09 053cfa80 75c77ba9 clr!Thread::intermediateThreadProc+0x58
0a 053cfa90 770abb9b KERNEL32!BaseThreadInitThunk+0x19
0b 053cfae8 770abb1f ntdll!__RtlUserThreadStart+0x2b
0c 053cfaf8 00000000 ntdll!_RtlUserThreadStart+0x1b

# 6 Id: 1f38.3ad8 Suspend: 1 Teb: 00e91000 Unfrozen
# ChildEBP RetAddr
00 0597fcb0 770f38d9 ntdll!DbgBreakPoint
01 0597fce0 75c77ba9 ntdll!DbgUiRemoteBreakin+0x39
02 0597fcf0 770abb9b KERNEL32!BaseThreadInitThunk+0x19
03 0597fd48 770abb1f ntdll!__RtlUserThreadStart+0x2b
04 0597fd58 00000000 ntdll!_RtlUserThreadStart+0x1b

其中包含DebuggerRCThread的线程4即为RCThread,可用~l4将其挂起。RCThread以及被调试进程内部的其他内部类和函数被称为调试器左端LS,位于调试器进程中的通信和接口函数被称为调试器右端RS。调试器LS与RS之间通过进程间通信IPC机制协作,其使用了以下几个命名事件对象和内存映射对象,其中“%d”表示对应进程ID。微软公开了CLI源程序,即SSCLI,又称ROTOR,源代码包含了RCThread和IPC通信机制源代码。如ipcman目录包含IPC使用的共享内存块有关源文件,rcthread.cpp包含辅助线程的实现,还有调试器CorDbg。

1
2
3
4
5
6
7
8
9
10
11
被调试进程:
CorDBIPCSetupSyncEvent_%d
CorDBIPCLISEventAvailName_%d
CorDBIPCLSEventReadName_%d
CorDBDebuggerAttacheedEvent_%d

调试进程:
Cor_Private_IPCBlock_%d
Cor_Public_IPCBlock_%d
CLR_PRIVATE_RS_IPCBlock_%d
CLR_PUBLIC_IPCBlock_%d

调试时LS和RCThread需要访问托管程序中资源,访问前可能需要先获取该资源保护锁。普通线程需由RCThread唤醒后才可释放锁,因此可能导致死锁。RCThread创建刺探线程,来了解哪些情况需要获取锁,哪些情况下不要去冒险。刺探线程的栈回溯中出现clr!HelperCanary::ThreadProc等。

CLR4调试模型重构

.NET 4.0引入CLR4调试模型,调试器进程与被调试进程直接通过NT内核的用户态调试子系统DbgK协调。调试器附加到被调试托管进程时,用本地用户态调试APIkernel32!DebugActiveProcess建立调试会话。

SOS扩展

.NET 4.0的没搞懂先不写了。