原文:http://www.blogcn.com/User8/flier_lu/blog/4294250.html
在上一节中曾经提到 RealProxy 在构造函数中调用 RemotingServices.CreateTransparentProxy 方法同步构造 TP 实例,而 RemotingServices.CreateTransparentProxy 方法的实现 CRemotingServices::CreateTransparentProxy 函数 (vm/remoting.cpp:318) 则最终调用 CTPMethodTable::CreateTPOfClassForRP 函数 (vm/remoting.cpp:2780) 完成 TP 类型对象的 MT 创建工作。而正如上节中提到的,这个 MT 不同于普通的 MT,是专门为 TP 定制的 CTPMethodTable (TPMT) 类型,其表内 MD 虽然与 __TransparentProxy 类型中的 MD 相同,但并非从静态 Metadata 中加载,而是在运行时构造并复制生成的。
以下将通过对 TP 实例构造的流程分析,大致了解 TP 的静态特性,为下一节的动态特性分析打下基础。
CTPMethodTable::CreateTPOfClassForRP 函数 (vm/remoting.cpp:2780) 的功能主要可以分为三部分。
1. 如果 TP 类型未初始化,则调用 InitializeFields 方法初始化其静态字段
2. 如果 TP 类型初始化成功,则确认 TP 类型的 MT 能容纳被代理类型的 MT
3. 如果确认成功,则构造 TP 类型的对象,并将 TP 对象和传入的 RP 对象互相引用,并在 TP 对象中保存被代理类型 MT
CreateTPOfClassForRP 函数的伪代码如下:
首先来看看 TP 类型是如何静态初始化自己的静态字段。
负责完成 TP 类型静态初始化的 CTPMethodTable::InitializeFields 函数 (vm/remoting.cpp:2392)
1. 载入 BCL 中预制 __TransparentProxy 类型的信息
2. 获取 MT 中 GC 信息和 MT 基本信息的大小,为后面创建动态构造 MT 做准备
3. 计算并保存 __TransparentProxy 类型中各个字段的偏移,为后面访问动态构造 MT 做准备
4. 调用 CreateTPStub 和 CreateDelegateStub 函数建立 TP 的普通函数与 delegate 的调用 stub
5. 如果 stub 建立成功则初始化 TP 类型的 MT 表,建立 Thunk 哈希表来后面访问支持动态构造 MT
6. 如果一切顺利,则最后调用 CreateTPMethodTable 函数建立所有 TP 类型共享的全局 MT 表
InitializeFields 函数的伪代码如下:
其中加载类型和计算偏移的代码较为简单,只需要进行常规的方法调用和数值计算。
CreateTPStub 和 CreateDelegateStub 函数较为复杂,而且涉及到很多执行时的动态特性,这里暂且略过,等下一节介绍 TP 的动态结构时再详细解释。需要了解的是 stub 在 CLR 中就是一段在运行时动态生成的代码片断,与后面要提到的 Thunk 类似。但不像 Thunk 那样仅完成重定向和参数转换,stub 往往还需要负担一些逻辑性的功能。而每个 stub 都由一个 Stub 类 (vm/stublink.h:325) 的对象包装起来,在其中保存了 Stub 的引用计数、标志位、代码偏移和代码程度等信息。如 CreateTPStub 函数建立的 s_pTPStub 的信息如下:
CTPMethodTable::s_pTPStub 指向的 Stub 在地址 0x00913250 处,因为当前断点没有使用过此 Stub,第一个 ULONG 字段引用计数为 0;而后的一个 ULONG 被分割为三部分:最高 4 位是标志位,被用于标志 MultiDelegate 等信息;接着的 12 位是从下一字节开始计算的 Stub 代码偏移;最后 16 位则是 Stub 代码的长度。Stub 的实现代码如下:
而这里 s_pTPStub 指向 Stub 的代码入口地址 g_dwTPStubAddr (0x00913258),正是上一节中介绍的 TP 类型 MT 表项的入口地址:
这里 TP 类型 MT 表每项入口地址指向的 push 0x?; jmp 0x00913258; 正是后面要提到的 Thunk 表,而其重定向目标则是 CTPMethodTable::s_pTPStub 指向的 TP 类型方法调用 stub。CreateDelegateStub 函数建立的 s_pDelegateStub 与之原理和用法完全相同,不要要等下一节介绍动态结构时再详细展开介绍这些 stub 的实际功能。
在建立了 Method 和 Delegate 调用的 Stub,并调用 EEThunkHashTable::Init 初始化全局 Thunk 哈希表后,InitializeFields 函数调用 CTPMethodTable::CreateTPMethodTable 函数 (vm/remoting.cpp:2536) 最终建立 TPMT 并结束创建 TP 对象的工作。
CreateTPMethodTable 函数首先为 TP 对象可能的最大大小(MT 基本信息加上最大支持 64K 个 MT 表项,并保持内存页对齐)预留连续地址空间,然后从 s_pTPMT 指向的 __TransparentProxy 类型 MT 复制 MT 基本信息,最后调用 ExtendCommitedSlots 函数为 Object 对象提交 Thunk 所需空间。因为这个 MT 表所有的表项,都类似上面 TP 类型的 MT 表项一样,入口都指向重定向到 s_pTPStub 的 stub 代码,所以所有的 TP 类型可以直接共享这一全局 MT 表,只需要在其 Thunk 代码被调用后,在 Stub 中分析传入的方法序号和方法本身信息即可,等下一节再详细解释其实现原理。
在确认 TP 类型的静态字段和全局 MT 表被正确初始化后,CreateTPOfClassForRP 函数会确认全局 MT 表中可用 Thunk 表项大于被代理类型的 MT 表项,如果不够则与前面类似处理,调用 ExtendCommitedSlots 函数扩展 TP 类型全局 MT 表。
而在 CreateTPOfClassForRP 函数最后建立的 TP 对象,则将其 MT 指向 TP 的全局 MT 表,因此无论此 TP 对象的任何方法被调用,都会被 Thunk 代码重定向到 CTPMethodTable::s_pTPStub 指向的 Stub 代码中。
通过 windbg 我们可以考察一个实际的 TP 对象,如
可以看到,虽然 TP 对象的名字也是 __TransparentProxy,但他与直接从 mscorlib.dll 中查询到的 __TransparentProxy 类型,具有不同的 MT 地址。而两个 MT 的基本信息可以说是完全一样,如
前者的 MT 表是 TP 类型的全局共享 MT 表,每一项都是在 InitializeFields 函数中建立起来的 Thunk 入口。
后者则是最为常见的 CLR 函数 JIT 入口 stub 代码,最终指向我们熟悉的 mscorwks!PreStubWorker 函数。
至此,TP 对象的静态结构就已经大致分析完毕,下一节将进一步分析 TP 的动态结构,了解 TP 是如何将用户调用从 TP 类型全局 MT 表,最终路由到合适的 RealProxy 实现代码中。
to be continue...
在上一节中曾经提到 RealProxy 在构造函数中调用 RemotingServices.CreateTransparentProxy 方法同步构造 TP 实例,而 RemotingServices.CreateTransparentProxy 方法的实现 CRemotingServices::CreateTransparentProxy 函数 (vm/remoting.cpp:318) 则最终调用 CTPMethodTable::CreateTPOfClassForRP 函数 (vm/remoting.cpp:2780) 完成 TP 类型对象的 MT 创建工作。而正如上节中提到的,这个 MT 不同于普通的 MT,是专门为 TP 定制的 CTPMethodTable (TPMT) 类型,其表内 MD 虽然与 __TransparentProxy 类型中的 MD 相同,但并非从静态 Metadata 中加载,而是在运行时构造并复制生成的。
以下将通过对 TP 实例构造的流程分析,大致了解 TP 的静态特性,为下一节的动态特性分析打下基础。
CTPMethodTable::CreateTPOfClassForRP 函数 (vm/remoting.cpp:2780) 的功能主要可以分为三部分。
1. 如果 TP 类型未初始化,则调用 InitializeFields 方法初始化其静态字段
2. 如果 TP 类型初始化成功,则确认 TP 类型的 MT 能容纳被代理类型的 MT
3. 如果确认成功,则构造 TP 类型的对象,并将 TP 对象和传入的 RP 对象互相引用,并在 TP 对象中保存被代理类型 MT
CreateTPOfClassForRP 函数的伪代码如下:
|
首先来看看 TP 类型是如何静态初始化自己的静态字段。
负责完成 TP 类型静态初始化的 CTPMethodTable::InitializeFields 函数 (vm/remoting.cpp:2392)
1. 载入 BCL 中预制 __TransparentProxy 类型的信息
2. 获取 MT 中 GC 信息和 MT 基本信息的大小,为后面创建动态构造 MT 做准备
3. 计算并保存 __TransparentProxy 类型中各个字段的偏移,为后面访问动态构造 MT 做准备
4. 调用 CreateTPStub 和 CreateDelegateStub 函数建立 TP 的普通函数与 delegate 的调用 stub
5. 如果 stub 建立成功则初始化 TP 类型的 MT 表,建立 Thunk 哈希表来后面访问支持动态构造 MT
6. 如果一切顺利,则最后调用 CreateTPMethodTable 函数建立所有 TP 类型共享的全局 MT 表
InitializeFields 函数的伪代码如下:
|
其中加载类型和计算偏移的代码较为简单,只需要进行常规的方法调用和数值计算。
CreateTPStub 和 CreateDelegateStub 函数较为复杂,而且涉及到很多执行时的动态特性,这里暂且略过,等下一节介绍 TP 的动态结构时再详细解释。需要了解的是 stub 在 CLR 中就是一段在运行时动态生成的代码片断,与后面要提到的 Thunk 类似。但不像 Thunk 那样仅完成重定向和参数转换,stub 往往还需要负担一些逻辑性的功能。而每个 stub 都由一个 Stub 类 (vm/stublink.h:325) 的对象包装起来,在其中保存了 Stub 的引用计数、标志位、代码偏移和代码程度等信息。如 CreateTPStub 函数建立的 s_pTPStub 的信息如下:
以下为引用:
0:000> dd mscorwks!CTPMethodTable::s_pTPStub
793e7a50 00913250 7ff5000c 00910000 00000000
...
0:000> dd 00913250
00913250 00000000 0000005a ff08418b c0851451
00913260 448b2a75 00a90024 75ffff00 0c418b16
...
0:000> u 00913258
00913258 8b4108 mov eax,[ecx+0x8]
0091325b ff5114 call dword ptr [ecx+0x14]
0091325e 85c0 test eax,eax
00913260 752a jnz 0091328c
00913262 8b442400 mov eax,[esp]
00913266 a90000ffff test eax,0xffff0000
0091326b 7516 jnz 00913283
0091326d 8b410c mov eax,[ecx+0xc]
...
CTPMethodTable::s_pTPStub 指向的 Stub 在地址 0x00913250 处,因为当前断点没有使用过此 Stub,第一个 ULONG 字段引用计数为 0;而后的一个 ULONG 被分割为三部分:最高 4 位是标志位,被用于标志 MultiDelegate 等信息;接着的 12 位是从下一字节开始计算的 Stub 代码偏移;最后 16 位则是 Stub 代码的长度。Stub 的实现代码如下:
|
而这里 s_pTPStub 指向 Stub 的代码入口地址 g_dwTPStubAddr (0x00913258),正是上一节中介绍的 TP 类型 MT 表项的入口地址:
以下为引用:
0:000> !DumpMT -MD 0x7ff5000c
EEClass : 79b98b44
Module : 79b7a000
Name: System.Runtime.Remoting.Proxies.__TransparentProxy
mdToken: 020004db (e:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
MethodTable Flags : 30c0000
Number of IFaces in IFaceMap : 0
Interface Map : 79b98b44
Slots in VTable : 5
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
7ff40010 7ff40015 None
7ff4001a 7ff4001f None
7ff40024 7ff40029 None
7ff4002e 7ff40033 None
7ff40038 7ff4003d None
0:000> u 7ff40010
7ff40010 6800000000 push 0x0
7ff40015 e93e329d80 jmp 00913258
7ff4001a 6801000000 push 0x1
7ff4001f e934329d80 jmp 00913258
7ff40024 6802000000 push 0x2
7ff40029 e92a329d80 jmp 00913258
7ff4002e 6803000000 push 0x3
7ff40033 e920329d80 jmp 00913258
7ff40038 6804000000 push 0x4
7ff4003d e916329d80 jmp 00913258
7ff40042 6805000000 push 0x5
7ff40047 e90c329d80 jmp 00913258
7ff4004c 6806000000 push 0x6
7ff40051 e902329d80 jmp 00913258
7ff40056 6807000000 push 0x7
7ff4005b e9f8319d80 jmp 00913258
这里 TP 类型 MT 表每项入口地址指向的 push 0x?; jmp 0x00913258; 正是后面要提到的 Thunk 表,而其重定向目标则是 CTPMethodTable::s_pTPStub 指向的 TP 类型方法调用 stub。CreateDelegateStub 函数建立的 s_pDelegateStub 与之原理和用法完全相同,不要要等下一节介绍动态结构时再详细展开介绍这些 stub 的实际功能。
在建立了 Method 和 Delegate 调用的 Stub,并调用 EEThunkHashTable::Init 初始化全局 Thunk 哈希表后,InitializeFields 函数调用 CTPMethodTable::CreateTPMethodTable 函数 (vm/remoting.cpp:2536) 最终建立 TPMT 并结束创建 TP 对象的工作。
CreateTPMethodTable 函数首先为 TP 对象可能的最大大小(MT 基本信息加上最大支持 64K 个 MT 表项,并保持内存页对齐)预留连续地址空间,然后从 s_pTPMT 指向的 __TransparentProxy 类型 MT 复制 MT 基本信息,最后调用 ExtendCommitedSlots 函数为 Object 对象提交 Thunk 所需空间。因为这个 MT 表所有的表项,都类似上面 TP 类型的 MT 表项一样,入口都指向重定向到 s_pTPStub 的 stub 代码,所以所有的 TP 类型可以直接共享这一全局 MT 表,只需要在其 Thunk 代码被调用后,在 Stub 中分析传入的方法序号和方法本身信息即可,等下一节再详细解释其实现原理。
在确认 TP 类型的静态字段和全局 MT 表被正确初始化后,CreateTPOfClassForRP 函数会确认全局 MT 表中可用 Thunk 表项大于被代理类型的 MT 表项,如果不够则与前面类似处理,调用 ExtendCommitedSlots 函数扩展 TP 类型全局 MT 表。
而在 CreateTPOfClassForRP 函数最后建立的 TP 对象,则将其 MT 指向 TP 的全局 MT 表,因此无论此 TP 对象的任何方法被调用,都会被 Thunk 代码重定向到 CTPMethodTable::s_pTPStub 指向的 Stub 代码中。
通过 windbg 我们可以考察一个实际的 TP 对象,如
以下为引用:
0:000> !DumpStackObjects
ESP/REG Object Name
ecx 00ae4960 ProxyDemo.TestProxy
0012f64c 00ae4960 ProxyDemo.TestProxy
0012f650 00ae4960 ProxyDemo.TestProxy
0012f654 00ae5894 System.Runtime.Remoting.Proxies.__TransparentProxy
...
0:000> !DumpObj 00ae5894
Name: System.Runtime.Remoting.Proxies.__TransparentProxy
MethodTable 0x7ff5000c
EEClass 0x79b98b44
Size 28(0x1c) bytes
mdToken: 020004db (e:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
FieldDesc*: 79b98ba8
MT Field Offset Type Attr Value Name
79b98b04 4001488 4 CLASS instance 00ae4960 _rp
...
0:000> !Name2EE mscorlib.dll System.Runtime.Remoting.Proxies.__TransparentProxy
--------------------------------------
MethodTable: 79b98b04
EEClass: 79b98b44
Name: System.Runtime.Remoting.Proxies.__TransparentProxy
可以看到,虽然 TP 对象的名字也是 __TransparentProxy,但他与直接从 mscorlib.dll 中查询到的 __TransparentProxy 类型,具有不同的 MT 地址。而两个 MT 的基本信息可以说是完全一样,如
以下为引用:
0:000> dd 0x7ff5000c
7ff5000c 030c0000 0000001c 79b98b44 00960000
7ff5001c 00120000 79b7a000 0004ffff 79b98b44
7ff5002c 00000000 00000005 7ff40010 7ff4001a
7ff5003c 7ff40024 7ff4002e 7ff40038 7ff40042
7ff5004c 7ff4004c 7ff40056 7ff40060 7ff4006a
7ff5005c 7ff40074 7ff4007e 7ff40088 7ff40092
0:000> dd 79b98b04
79b98b04 030c0000 0000001c 79b98b44 00960000
79b98b14 00120000 79b7a000 0004ffff 79b98b44
79b98b24 00000000 00000005 79b90fbb 79b90f43
79b98b34 79b90f5b 79b90ffb 79b98bfb 00000000
79b98b44 79b90e98 00050004 00000000 79b98654
前者的 MT 表是 TP 类型的全局共享 MT 表,每一项都是在 InitializeFields 函数中建立起来的 Thunk 入口。
以下为引用:
0:000> u 7ff40010
7ff40010 6800000000 push 0x0
7ff40015 e93e329d80 jmp 00913258
7ff4001a 6801000000 push 0x1
7ff4001f e934329d80 jmp 00913258
7ff40024 6802000000 push 0x2
7ff40029 e92a329d80 jmp 00913258
7ff4002e 6803000000 push 0x3
7ff40033 e920329d80 jmp 00913258
后者则是最为常见的 CLR 函数 JIT 入口 stub 代码,最终指向我们熟悉的 mscorwks!PreStubWorker 函数。
以下为引用:
0:000> u 79b90fbb
mscorlib_79990000+0x200fbb:
79b90fbb e8c891feff call mscorlib_79990000+0x1ea188 (79b7a188)
79b90fc0 0000 add [eax],al
0:000> u 79b7a188
mscorlib_79990000+0x1ea188:
79b7a188 e9138ed986 jmp 00912fa0
0:000> u 00912fa0
00912fa0 52 push edx
...
00912fc0 56 push esi
00912fc1 e84c478c78 call mscorwks!PreStubWorker (791d7712)
00912fc6 897b08 mov [ebx+0x8],edi
...
至此,TP 对象的静态结构就已经大致分析完毕,下一节将进一步分析 TP 的动态结构,了解 TP 是如何将用户调用从 TP 类型全局 MT 表,最终路由到合适的 RealProxy 实现代码中。
to be continue...