谈 WinDbg 之 InternalCall 的使用与实现‖为 InternalCall 的方法。
上就是调用被标记为 Inter
或者 Rotor 源码查看 BCL 库
如 System.String 中用于获取
nalCall 的 String.InternalLe
| 的实现时,经常会碰到一些被标记
字符串长度的 Length 属性,实现
ngth 方法:
|
| public sealed class String : ... |
[MethodImpl(MethodIm
| plOptions.InternalCall)]
|
| private int InternalLength(); |
| return this.InternalLength(); |
这些方法因为执行效率
Native Code 形式提供实现
他们无需被定义为 static
为 CLR 的诸多内部调用方
InternalCall 的函数定义
| 、安全性或者为了实现简单等不
代码。但与通过 DllImport 定
extern 方法,也无需通过单独
式之一,被封装在一个看似密不
,将函数最终使用者与函数功能
| 同原因,通过 IL 代码以外的
义的 Interoper 方法不同的是,
的 DLL 导出函数被实现。它们作
透风的盒子里面,通过一个
提供者隔离开来。
|
但实际使用中为了分析 CLR 运行机
面将从 CLR 内部使用与实现 InternalCa
| 制和调试,我们经常性需要了解和分析这类函数。下
ll 函数的不同角度,对其做一个粗略的分析。
|
作为一个 BCL 函数,被定义成 Inte
别。如同我前面《用WinDbg探索CLR世界
MethodTable 中,最初的入口地址也被指
:
| rnalCall 的函数使用上与普通 IL 函数没有任何区
[3] 跟踪椒ǖ?JIT 过程》一文中所述,它们在
向 mscorwks!PreStubWorker,可以通过 sos 查看之
|
| 0:003> !Name2EE mscorlib.dll System.String |
| -------------------------------------- |
| 0:003> !DumpMT -MD 79b7daf8 |
mdToken: 0200000f (
| e:windowsmicrosoft.net ramew
| orkv1.1.4322mscorlib.dll)
|
| MethodTable Flags : 2000000 |
| Number of elements in array: 2 |
| Number of IFaces in IFaceMap : 4 |
| -------------------------------------- |
| Entry MethodDesc JIT Name |
799917c0 79b7ebc8 PreJIT [DEF
)
| AULT] [hasThis] String System.String.ToString(
|
79b7e253 79b7e258
g.InternalLength()
| None [DEFAULT] [hasThis]
| I4 System.Strin
|
Method Name : [DEFAU
| LT] [hasThis] I4 System.Stri
| ng.InternalLength()
|
mdToken: 060000b1 (e
| :windowsmicrosoft.net ramewo
| rkv1.1.4322mscorlib.dll)
|
通过上述命令我们可以看到,String
入口地址为 79b7e253。反汇编此地址的
用 mscorwks!PreStubWorker 方法:
| .InternalLength 方法缺省没有经过 JIT 编译,其
指令,并一路追述下去可以发现,实际上最终也是调
|
| mscorlib_79980000+0x1fe253: |
79b7e253 e8287ffeff call
| mscorlib_79980000+0x1e6180 (79b66180)
|
| mscorlib_79980000+0x1e6180: |
| 79b66180 e9eb805e86 jmp 0014e270 |
| 79b66185 0000 add [eax],al |
0014e291 e8b4870879
| call mscorwks!PreS
| tubWorker (791d6a4a)
|
0014e296 897b08
| mov [ebx+0x8],edi
|
这个 PreStubWorker 函数(vm/prest
,负责编译 IL 代码以生成 Native 代码
(MethodDesc) 上:
| ub.cpp:574)可以说是所有 IL 函数进行 JIT 的入口
,并将 JIT 生成的代码入口安装到相应 MD
|
extern "C" const BYT
| E * __stdcall PreStubWorker(
| PrestubMethodFrame *pPFrame)
|
| MethodDesc *pMD = pPFrame->GetFunction(); |
| MethodTable *pDispatchingMT = NULL; |
| if (pMD->IsVtableMethod() && !pMD->GetClass()->IsValueClass()) |
OBJECTREF curobj = G
| etActiveObject(pPFrame);
|
| pDispatchingMT = curobj->GetMethodTable(); |
| return pMD->DoPrestub(pDispatchingMT); |
PreStubWorker 函数的参数是一个方
法?DoPresub 函数完成实际工作。而 M
,在进行实际代码生成时,会根据方法的
| 法帧,从中可以获取当前函数的 MD,进而调用此方
ethodDesc:oPrestub 方法(vm/prestub.cpp:590)中
类型进行各种特殊情况的处理:
|
const BYTE * MethodD
*pDispatchingMT)
| esc:[img]/images/biggrin.gif
| [/img]oPrestub(MethodTable
|
else //!IsSpecialStub() && !I
| sIL() case
|
// See if it is an F
| CALL and already "jitted", w
| hich for fcall
|
// means that its m_
| CodeOrIL is not already set.
| We explicitly
|
// check for the mcECall bit sin
| ce IsECall is really
|
// IsRuntimeGenerate
| d and so includes array also
|
if (IsJitted() && (mcECall == Ge
| tClassification()))
|
| pStub = (Stub*) GetAddrofJittedCode(); |
pStub = (Stub*) FindImplForMetho
| d(this);
|
| _ASSERTE(IsECall() || !(GetClass()->IsAnyDelegateClass())); |
| if (!fRemotingIntercepted && !(GetClass()->IsAnyDelegateClass())) |
| // backpatch the main slot. |
| pMT->GetVtable()[GetSlot()] = (SLOT) pStub; |
| bBashCall = bIsCode = TRUE; |
| inline DWORD MethodDesc::IsECall() |
return mcECall == Ge
| tClassification() || mcArray
| == GetClassification();
|
这儿 IsSpecialStub()
GetClassification() 获取
MethodImplAttribute 等标
lOptions.InternalCall 来
,以后有机会再详细介绍。
| , IsIL(), IsECall()等等方法
方法类型来进行判断的。而此方
记,在编译时写入到 Metadata
说,实际对应于 mcECall 这?br>
| ,实际上都是通过
法类型则是编译器根据
中。对 MethodImp
类型。其他的 CLR 内部调用类型
|
对于 GetClassification() 返回 mc
FindImplForMethod() 函数完成的。此函
FindECFuncForMethod 从一个全局 ECall
| ECall 这种情况,实际上时通过
数在 RVA 为 0 的情况下,会调用
注册表中查找 InternalCall 的实现代码所在。
|
void* FindImplForMethod(MethodDe
| sc* pMDofCall)
|
| DWORD_PTR rva = pMDofCall->GetRVA(); |
| ret = FindECFuncForMethod(pBaseMD); |
不过与 Rotor 的实现
工作。如前面的 DumpMD 命
只是他们指向的是一个直接
的处理方法,也因 rva 不
ECall 注册表中通过字符串
方法,生成一个对 ECall
ECall 代码时,完成字符串
JIT 代码的方式调用了。
| 不太一样的是,.NET Framework
令结果所示,CLR v1.1 中 Inte
返回的 ret 的 IL 指令。而 Fi
为 0,而从每次调用时以 FindE
匹配查找,改为通过 mscorwks!
实现代码的调用 Stub 代码。这
匹配性质的 ECall 实现代码定
| 1.1 为效率做了很多额外的优化
rnalCall 的方法也是有 RVA 的,
ndImplForMethod 对 ECall 类型
CFuncForMethod 函数在全局
ECall::EmitECallMethodStub()
样一来,只需要在第一次调用
位,就可以一劳永逸的以等同于
|
可以通过在 FindImplF
数的调用初始化工作,如:
| orMethod 方法上下断点的方式
| ,跟踪每次 InternalCall 类型函
|
| 0:000> bp mscorwks!FindImplForMethod |
eax=00000001 ebx=000
| 00001 ecx=79ba9e68 edx=c0000
| 000 esi=79ba9e68 edi=00000000
|
eip=791d8d5b esp=001
| 2f084 ebp=0012f158 iopl=0
| nv up ei pl nz na pe nc
|
cs=001b ss=0023 ds
| =0023 es=0023 fs=0038 gs=
| 0000 efl=00000202
|
| mscorwks!FindImplForMethod: |
Method Name : [DEFAULT] Void Sys
ces.Marshal.Copy(SZArray Char,I4,I4,
| tem.Runtime.InteropServi
I4)
|
mdToken: 060020d3 (e
| :windowsmicrosoft.net ramewo
| rkv1.1.4322mscorlib.dll)
|
通过 DumpMD 命令查看
pMDofCall 参数,可以知道
,再来看刚刚被处理的方法
7921b264,而这个入口地址
的 Marshal.Copy 对应的实
| FindImplForMethod 函数以 EC
目前是哪个函数被调用。在 Fin
描述,就会发现 IL RVA : 0046
,正是刚刚 EmitECallMethodSt
现代码 CopyToNative。
| X 寄存器传入的 MethodDesc*
dImplForMethod 函数运行结束后
0008 已经变成 Method VA :
ub 方法从全局 ECall 表中查询到
|
Method Name : [DEFAULT] Void Sys
ces.Marshal.Copy(SZArray Char,I4,I4,
| tem.Runtime.InteropServi
I4)
|
mdToken: 060020d3 (e
| :windowsmicrosoft.net ramewo
| rkv1.1.4322mscorlib.dll)
|
毕竟 Rotor 只是一个非商业用途的
厚非的。
| 参考实现,删去优化代码来降低程序复杂度也是无可
|
在了解了 InternalCal
1.1 中如何被动态解析后,
口的。
| l 方ㄔ?Rotor 中是如何被动
我们来看看这些调用和解析的背
| 态调用,以及在 .NET Framework
后,是如何定位方法的实现函数入
|
Rotor 中的 FindECFun
| cForMethod 函数(vm/ecall.cpp
| :1886)是最能说明问题的:
|
static ECFunc* FindECFuncForMeth
| od(MethodDesc* pMD)
|
ECFunc**cacheEntry =
| getCacheEntry(pMD);
|
| ECFunc* cur = *cacheEntry; |
| if (cur != 0 && cur->m_pMD == pMD) |
| cur = FindImplsForClass(pMD->GetMethodTable()); |
cur = GetECForIndex(FindECIndexF
| orMethod(pMD, cur), cur);
|
*cacheEntry = cur;
| // put in the cache
|
static ECFunc *GetEC
| ForIndex(USHORT index, ECFun
| c *impls)
|
| if (index == (USHORT) -1) |
ECFunc* FindImplsForClass(Method
| Table* pMT)
|
return GetImplsForIndex(FindImpl
| sIndexForClass(pMT));
|
| ECFunc* GetImplsForIndex(USHORT index) |
| if (index == (USHORT) -1) |
| return gECClasses[index].m_pECFunc; |
FindECFuncForMethod 函数首先试图
存则调用 FindImplsIndexForClass 函数
个层面寻找合适的 ECall 实现。要理解
是如何定义的:
| 从缓存中获取 pMD 参数指定的方法,如果不存在缓
和FindECIndexForMethod 函数,分别从类和方法两
这两个函数的实现,要首先看看全局 ECall 映射表
|
BOOL IsFCall()
| { return TRUE; }
|
CorInfoIntrinsics IntrinsicID(
insics(m_intrinsicID); }
| ) { return CorInfoIntr
|
| LPHARDCODEDMETASIG m_wszMethodSig; |
| LPVOID m_pImplementation; |
MethodDesc* m
| _pMD; // for r
| everse mapping
|
ECFunc* m
| _pNext; // linke
| d list for hash table
|
| static ECFunc gStringFuncs[] = |
{FCFuncElement("GetHashCode", N
| ULL, (LPVOID)COMString::GetHashCode)},
|
{FCIntrinsic("InternalLength", N
CORINFO_INTRINSIC_StringLength)},
| ULL, (LPVOID)COMString::Length,
|
首先,对于每个类的 InternalCall
的全局数组,数组每行对应于一个函数。
等。
| 方法,需要在 ECall.cpp 中定义一个 ECFunc 类型
内容包括函数的名字、Signature、实现函数入口等
|
| static ECClass gECClasses[] = |
{ECClassesElement("String", "Sys
| tem", gStringFuncs)},
|
然后,对于每个拥有 I
一个表现。每项包括类型的
死了,然后编译到程序中去
GetImplsForIndex 函数的
| nternalCall 方法的类型,需要
名称、名字空间和函数映射表。
。虽然在发行版中没有导出这个
实现代码在运行时环境中找到这
| 在一个全局 ECall 注册表中增加
这些映射表都是在编译时就预定义
符号,但我们可以通过
个映射表:
|
| 0:003> u mscorwks!GetImplsForIndex |
| mscorwks!GetImplsForIndex: |
| 791d7120 8bec mov ebp,esp |
791d7122 66837d08ff cmp
| word ptr [ebp+0x8],0xffff
|
791d7127 0f840eb30200 je
| mscorwks!GetImplsForIndex+0xb (7920243b)
|
791d712d 0fb74508
| movzx eax,word ptr
| [ebp+0x8]
|
791d7131 8d0440 lea
| eax,[eax+eax*2] // index *= 3
|
791d7134 8b048548d13
(793dd148)+eax*4]
| d79 mov eax,[mscorwks!gStr
| ingBufferFuncs+0x5cd8
|
| 791d713f 33c0 xor eax,eax |
793dd140 791d7c40 791d71c4 793e
| 0420 791d7c30
|
793dd150 791d71c4 793e03f0 791d
| 86f8 791d71c4
|
791d7c40 41 70 70 4
| 4 6f 6d 61 69-6e 00 90 90 4c
| 6f 67 00 AppDomain...Log.
|
791d71c4 53 79 73 74 65 6d 00 9
| 0-5f 5f 4b 65 79 48 61 6e System..__KeyHan
|
GetImplsForIndex 函数中的语句 gE
ADDRESS_OF(gECClasses) + index * siz
刚好对应于 mscorwks!GetImplsForIndex
gECClasses。而对此地址内容的查看证明
| CClasses[index].m_pECFunc 将被编译成
eof(ECClass) + OFFSET_OF(ECClass, m_pECFunc),
函数中的那两行,反向解析后 793dd140 地址就是
了这一点。
|
793e0420 7923db2c 0
| 0000000 7933957f 00000000
|
793e0430 000000ff 00000000 7923
| db18 00000000
|
7923db2c 43 72 65 6
| 1 74 65 42 61-73 69 63 44 6f
| 6d 61 69 CreateBasicDomai
|
7923db3c 6e 00 8d 41 14 c3 55 8
| b-ec 51 51 53 56 57 8b 7d n..A..U..QQSVW.}
|
mscorwks!AppDomainNative::Create
| BasicDomain:
|
| 进一步查看 System.AppDomain 的方法映射表也是跟我们预期相同。 |
知道了 ECall 的全局
FindECIndexForMethod 函
MethodTable 指向类型名称
数名称相同的实现函数入口
| 映射表之后,再来理解 FindImp
数就非常容易了。前者遍历 gEC
和名字空间相同的函数映射表入
。
| lsIndexForClass 函数和
Classes 表,找出与
口;后者遍历函数映射表,找出函
|
至此,InternalCall
我们可以很方便的实现一些
| 类型函数的使用和实现就大概介
无法在 IL 层面实现的底层功能
| 绍完了。通过了解这个内部原理,
。
|
上一页> 下一页>