在使用 Reflector
NET 或者 Rotor 源碼查看 BCL 庫的實現時
經常會碰到一些被標記為 InternalCall 的方法
如 System
String 中用於獲取字符串長度的 Length 屬性
實現上就是調用被標記為 InternalCall 的 String
InternalLength 方法
以下內容為程序代碼: namespace System
{
[Serializable]
public sealed class String :
{
[MethodImpl(MethodImplOptions
InternalCall)]
private int InternalLength();
public int Length
{
get
{
return this
InternalLength();
}
}
}
}
這些方法因為執行效率
安全性或者為了實現簡單等不同原因
通過 IL 代碼以外的 Native Code 形式提供實現代碼
但與通過 DllImport 定義的 Interoper 方法不同的是
他們無需被定義為 static extern 方法
也無需通過單獨的 DLL 導出函數被實現
它們作為 CLR 的諸多內部調用方式之一
被封裝在一個看似密不透風的盒子裡面
通過一個 InternalCall 的函數定義
將函數最終使用者與函數功能提供者隔離開來
但實際使用中為了分析 CLR 運行機制和調試
我們經常性需要了解和分析這類函數
下面將從 CLR 內部使用與實現 InternalCall 函數的不同角度
對其做一個粗略的分析
作為一個 BCL 函數
被定義成 InternalCall 的函數使用上與普通 IL 函數沒有任何區別
如同我前面《用WinDbg探索CLR世界 [
] 跟蹤方法的 JIT 過程》一文中所述
它們在 MethodTable 中
最初的入口地址也被指向 mscorwks!PreStubWorker
可以通過 sos 查看之
以下為引用
:
> !Name
EE mscorlib
dll System
String
MethodTable:
b
daf
EEClass:
b
de
Name: System
String
:
> !DumpMT
MD
b
daf
EEClass :
b
de
Module :
b
Name: System
String
mdToken:
f (e: rameworkv
mscorlib
dll)
MethodTable Flags :
Number of elements in array:
Number of IFaces in IFaceMap :
Interface Map :
b
de
Slots in VTable :
MethodDesc Table
Entry MethodDesc JIT Name
c
b
ebc
PreJIT [DEFAULT] [hasThis] String System
String
ToString()
b
e
b
e
None [DEFAULT] [hasThis] I
System
String
InternalLength()
:
> !DumpMD
b
e
Method Name : [DEFAULT] [hasThis] I
System
String
InternalLength()
MethodTable
b
daf
Module:
b
mdToken:
b
(e: rameworkv
mscorlib
dll)
Flags :
IL RVA :
b
通過上述命令我們可以看到
String
InternalLength 方法缺省沒有經過 JIT 編譯
其入口地址為
b
e
反匯編此地址的指令
並一路追述下去可以發現
實際上最終也是調用 mscorwks!PreStubWorker 方法
以下為引用
:
> u
b
e
mscorlib_
+
x
fe
:
b
e
e
ffeff call mscorlib_
+
x
e
(
b
)
b
e
d dec ebp
mscorlib_
+
x
e
:
b
e
eb
e
jmp
e
b
add [eax]
al
:
> u
e
e
push edx
e
push esi
e
e
b
call mscorwks!PreStubWorker (
d
a
a)
e
b
mov [ebx+
x
]
edi
這個 PreStubWorker 函數(vm/prestub
cpp:
)可以說是所有 IL 函數進行 JIT 的入口
負責編譯 IL 代碼以生成 Native 代碼
並將 JIT 生成的代碼入口安裝到相應 MD (MethodDesc) 上
以下內容為程序代碼:
extern
C
const BYTE * __stdcall PreStubWorker(PrestubMethodFrame *pPFrame)
{
MethodDesc *pMD = pPFrame
>GetFunction();
MethodTable *pDispatchingMT = NULL;
if (pMD
>IsVtableMethod() && !pMD
>GetClass()
>IsValueClass())
{
OBJECTREF curobj = GetActiveObject(pPFrame);
if (curobj !=
)
pDispatchingMT = curobj
>GetMethodTable();
}
return pMD
>DoPrestub(pDispatchingMT);
}
PreStubWorker 函數的參數是一個方法幀
從中可以獲取當前函數的 MD
進而調用此方法的 DoPresub 函數完成實際工作
而 MethodDesc:oPrestub 方法(vm/prestub
cpp:
)中
在進行實際代碼生成時
會根據方法的類型進行各種特殊情況的處理
以下內容為程序代碼:
const BYTE * MethodDesc:[img]/images/biggrin
gif[/img]oPrestub(MethodTable *pDispatchingMT)
{
Stub *pStub = NULL;
//
if (IsSpecialStub())
{
//
}
else if (IsIL())
{
//
}
else //!IsSpecialStub() && !IsIL() case
{
if (IsECall())
{
// See if it is an FCALL and already
jitted
which for fcall
// means that its m_CodeOrIL is not already set
We explicitly
// check for the mcECall bit since IsECall is really
// IsRuntimeGenerated and so includes array also
if (IsJitted() && (mcECall == GetClassification()))
pStub = (Stub*) GetAddrofJittedCode();
else
pStub = (Stub*) FindImplForMethod(this);
}
if (pStub !=
)
{
_ASSERTE(IsECall() || !(GetClass()
>IsAnyDelegateClass()));
if (!fRemotingIntercepted && !(GetClass()
>IsAnyDelegateClass()))
{
// backpatch the main slot
pMT
>GetVtable()[GetSlot()] = (SLOT) pStub;
}
bBashCall = bIsCode = TRUE;
}
else
{
//
}
}
}
}
inline DWORD MethodDesc::IsECall()
{
return mcECall == GetClassification() || mcArray == GetClassification();
}
這兒 IsSpecialStub()
IsIL()
IsECall()等等方法
實際上都是通過 GetClassification() 獲取方法類型來進行判斷的
而此方法類型則是編譯器根據 MethodImplAttribute 等標記
在編譯時寫入到 Metadata 中
對 MethodImplOptions
InternalCall 來說
實際對應於 mcECall 這種類型
其他的 CLR 內部調用類型
以後有機會再詳細介紹
對於 GetClassification() 返回 mcECall 這種情況
實際上時通過 FindImplForMethod() 函數完成的
此函數在 RVA 為
的情況下
會調用 FindECFuncForMethod 從一個全局 ECall 注冊表中查找 InternalCall 的實現代碼所在
以下內容為程序代碼:
void* FindImplForMethod(MethodDesc* pMDofCall)
{
DWORD_PTR rva = pMDofCall
>GetRVA();
//
if (rva ==
)
{
ret = FindECFuncForMethod(pBaseMD);
}
//
}
不過與 Rotor 的實現不太一樣的是
NET Framework
為效率做了很多額外的優化工作
如前面的 DumpMD 命令結果所示
CLR v
中 InternalCall 的方法也是有 RVA 的
只是他們指向的是一個直接返回的 ret 的 IL 指令
而 FindImplForMethod 對 ECall 類型的處理方法
也因 rva 不為
而從每次調用時以 FindECFuncForMethod 函數在全局 ECall 注冊表中通過字符串匹配查找
改為通過 mscorwks!ECall::EmitECallMethodStub() 方法
生成一個對 ECall 實現代碼的調用 Stub 代碼
這樣一來
只需要在第一次調用 ECall 代碼時
完成字符串匹配性質的 ECall 實現代碼定位
就可以一勞永逸的以等同於 JIT 代碼的方式調用了
可以通過在 FindImplForMethod 方法上下斷點的方式
跟蹤每次 InternalCall 類型函數的調用初始化工作
如
以下為引用
:
> bp mscorwks!FindImplForMethod
:
> g
Breakpoint
hit
eax=
ebx=
ecx=
ba
e
edx=c
esi=
ba
e
edi=
eip=
d
d
b esp=
f
ebp=
f
iopl=
nv up ei pl nz na pe nc
cs=
b ss=
ds=
es=
fs=
gs=
efl=
mscorwks!FindImplForMethod:
d
d
b
push ebp
:
> !dumpmd ecx
Method Name : [DEFAULT] Void System
Runtime
InteropServices
Marshal
Copy(SZArray Char
I
I
I
)
MethodTable
ba
c
Module:
b
mdToken:
d
(e:windowsmicrosof
From:http://tw.wingwit.com/Article/os/youhua/201311/10912.html