用WinDbg进行CLI探险(2) 调试本机代码,RuntimeType
先看下面这样一段代码:
1 static void Main(string[] args)
2 {
3 Console.WriteLine("Start .....");
4 Test();
5 }
6
7 static void Test()
8 {
9 Console.WriteLine("Test ...");
10 Console.ReadLine();
11 Regiest(typeof(implementclass));
12 }
13
14 static void Regiest(Type type)
15 {
16 }
我们现在要做的是用windbgDebug到Test函数里面,然后看看发生了什么,运行代码:
Windbg下载地址: http://www.microsoft.com/whdc/devtools/debugging/default.mspx
然后我们可以运行一个叫做: Process Explorer的软件看到当前所有运行的进程:
下载地址:http://technet.microsoft.com/en-us/sysinternals/bb896653
打开Winddbg,
打开菜单栏中的debug菜单,关闭SourceMode选项,进入汇编调试模式,
Attach到进程号为3516的进程中去,然后运行
.loadby sos clr命令加载符号集。
sos的部分指令集可以参看:
http://msdn.microsoft.com/zh-cn/library/bb190764(vs.80).aspx
0:004> !name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.Test
Module: 00232e9c
Assembly: ConsoleApplication1.exe
Token: 06000002
MethodDesc: 002337fc
Name: ConsoleApplication1.Program.Test()
JITTED Code Address: 004600b0
我们可以看到 MethodDesc: 002537fc
于是看这个地址的IL代码:
0:004> !dumpil 002337fc
ilAddr = 00232064
IL_0000: nop
IL_0001: ldstr "Test ..."
IL_0006: call System.Console::WriteLine
IL_000b: nop
IL_000c: call System.Console::ReadLine
IL_0011: pop
IL_0012: ldtoken ExtensionProject.implementclass
IL_0017: call System.Type::GetTypeFromHandle
IL_001c: call ConsoleApplication1.Program::Regiest
IL_0021: nop
IL_0022: ret
这个显然正是我们要找的,再看JIT产生的汇编本地代码:
0:004> !u 002337fc
Normal JIT generated code
ConsoleApplication1.Program.Test()
Begin 004600b0, size 55
F:\Lab\ClientBin\TestCompressedFile\ConsoleApplication1\Program.cs @ 19:
004600b0 55 push ebp
004600b1 8bec mov ebp,esp
004600b3 83ec08 sub esp,8
004600b6 33c0 xor eax,eax
004600b8 8945fc mov dword ptr [ebp-4],eax
004600bb 833d3c31230000 cmp dword ptr ds:[23313Ch],0
004600c2 7405 je 004600c9
004600c4 e87e670869 call clr!JIT_DbgIsJustMyCode (694e6847)
004600c9 90 nop
F:\Lab\ClientBin\TestCompressedFile\ConsoleApplication1\Program.cs @ 20:
004600ca 8b0d3420a402 mov ecx,dword ptr ds:[2A42034h] ("Test ...")
004600d0 e8d76f1e5e call mscorlib_ni+0x2570ac (5e6470ac) (System.Console.WriteLine(System.String), mdToken: 06000954)
004600d5 90 nop
F:\Lab\ClientBin\TestCompressedFile\ConsoleApplication1\Program.cs @ 21:
004600d6 e8dda67b5e call mscorlib_ni+0x82a7b8 (5ec1a7b8) (System.Console.ReadLine(), mdToken: 06000946)
004600db 90 nop
F:\Lab\ClientBin\TestCompressedFile\ConsoleApplication1\Program.cs @ 22:
004600dc 8d4dfc lea ecx,[ebp-4]
004600df ba88432300 mov edx,234388h (MT: ExtensionProject.implementclass)
004600e4 e8d4910869 call clr!JIT_GetRuntimeTypeHandle (694e92bd)
004600e9 8d45fc lea eax,[ebp-4]
004600ec ff30 push dword ptr [eax]
004600ee e8d1d91169 call clr!RuntimeTypeHandle::GetTypeFromHandle (6957dac4)
004600f3 8945f8 mov dword ptr [ebp-8],eax
004600f6 8b4df8 mov ecx,dword ptr [ebp-8]
004600f9 ff1510382300 call dword ptr ds:[233810h] (ConsoleApplication1.Program.Regiest(System.Type), mdToken: 06000003)
004600ff 90 nop
F:\Lab\ClientBin\TestCompressedFile\ConsoleApplication1\Program.cs @ 23:
00460100 90 nop
00460101 8be5 mov esp,ebp
00460103 5d pop ebp
00460104 c3 ret
汇编代码的开始出正好是MethodAddress中的,JITTED Code Address: 004600b0
然后点击菜单栏中的View,打开Desassembly窗口。
我们的关键代码正是这一行:
F:\Lab\ClientBin\TestCompressedFile\ConsoleApplication1\Program.cs @ 22:
用bp 004600dc在该处设置断点。我们可以看到红色的断点:
输入g继续运行,然后键入回车走过Console.ReadLine,可以看到代码在断点处停下。
然后一直执行到Call GetType(004600ee)指令结束。
此时寄存器的值为:
我们找到栈顶指针esp-4 22f18c处能看到01a4f0e4这个值,也就是eax中的值
再对该处所存地址执行!do命令
0:000> !do 01a4f0e4
Name: System.RuntimeType
MethodTable: 5e70f5c0
EEClass: 5e448974
Size: 24(0x18) bytes
Type Name: ExtensionProject.implementclass
Type MT: 00234388
File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
5e70d814 4000532 e4 ...tion.MemberFilter 0 shared static FilterAttribute
>> Domain:Value 002b4248:NotInit <<
5e70d814 4000533 e8 ...tion.MemberFilter 0 shared static FilterName
>> Domain:Value 002b4248:NotInit <<
5e70d814 4000534 ec ...tion.MemberFilter 0 shared static FilterNameIgnoreCase
>> Domain:Value 002b4248:NotInit <<
5e70f568 4000535 f0 System.Object 0 shared static Missing
>> Domain:Value 002b4248:NotInit <<
5e711d48 4000536 b60 System.Char 1 shared static Delimiter
>> Domain:Value 002b4248:NotInit <<
5e6c6ba8 4000537 f4 System.Object[] 0 shared static EmptyTypes
>> Domain:Value 002b4248:NotInit <<
5e700b64 4000538 f8 ...Reflection.Binder 0 shared static defaultBinder
>> Domain:Value 002b4248:NotInit <<
5e7012ec 4000542 4 ...che.InternalCache 0 instance 00000000 m_cachedData
5e70f568 4000543 8 System.Object 0 instance 00000000 m_keepalive
5e70a9ac 4000544 c System.IntPtr 1 instance 0 m_cache
5e70a9ac 4000545 10 System.IntPtr 1 instance 234388 m_handle
5e70e320 4000546 fc ...pe+TypeCacheQueue 0 shared static s_typeCache
>> Domain:Value 002b4248:00000000 <<
5e70f5c0 4000547 100 System.RuntimeType 0 shared static ValueType
>> Domain:Value 002b4248:01a43470 <<
5e70f5c0 4000548 104 System.RuntimeType 0 shared static EnumType
>> Domain:Value 002b4248:01a43488 <<
5e70f5c0 4000549 108 System.RuntimeType 0 shared static ObjectType
>> Domain:Value 002b4248:01a434a0 <<
5e70f5c0 400054a 10c System.RuntimeType 0 shared static StringType
>> Domain:Value 002b4248:01a434b8 <<
5e70f5c0 400054b 110 System.RuntimeType 0 shared static DelegateType
>> Domain:Value 002b4248:01a434d0 <<
5e70f5c0 400054c 114 System.RuntimeType 0 shared static s_typedRef
>> Domain:Value 002b4248:01a434e8 <<
5e70ee58 400054d 118 ...pe+ActivatorCache 0 shared static s_ActivatorCache
>> Domain:Value 002b4248:00000000 <<
5e71a69c 400054e 11c System.OleAutBinder 0 shared static s_ForwardCallBinder
>> Domain:Value 002b4248:00000000 <<
我们注意到 RuntimeType的内容中有很多Domain:Value 这样的显示,让我们不禁考虑到RuntimeType是否是可以跨Domain的,不过这一点今天就不讨论了。
补充:要说明的是,我最开始以为这个RuntimeType的Object是004600ee这里的GetTypeFromHandle方法生成的,但实际上是在前面004600e4行的JIT_GetRunTypeHandle方法生成的。而且我们也知道esp-4这个位置实际上已经出栈了。在我后来的实验中我发现早在调用JIT_GetRunTypeHandle这个方法之后就已经在[ebp-4]处,也就是栈底第一个元素处生成了指向RuntimeType的Object的指针。这个时候执行!do就已经可以看到上面的结果了。那么第二个函数JIT_GetRunTypeHandle到底是做什么的呢。看下面这个截图:
我对clr!RuntimeTypeHandle::GetTypeFromHandle之前和之后的RuntimeType进行了对比,没有找到表面上的区别,但是从上面的截图中我推测GetTypeFromHandle这个语句可能是对虚函数表进行了一些操作,绑定准确类型重载的方法,但这也只是个推测而已。
最后,由于这个函数在被我们Attatch的时候已经被调用,所以JIT已经生成了本地代码,我们可以直接用bp在本地代码中设置断点,如果我们想在本地代码没有生成时设置断点应该怎么办呢?
0:004> !name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.Test
Module: 00182e9c
Assembly: ConsoleApplication1.exe
Token: 06000002
MethodDesc: 001837fc
Name: ConsoleApplication1.Program.Test()
Not JITTED yet. Use !bpmd -md 001837fc to break on run.
最后一行显示可以Not JITTED yet表示尚未生成本地代码,并提示我们可以用!bpmd -md 001837fc来设置断点。
但是其实还有另一种方法,它涉及到CLI内部的存储结构,这个留待以后再介绍。