分享
 
 
 

谈WinDbg之InternalCall的使用与实现

王朝system·作者佚名  2008-05-19
窄屏简体版  字體: |||超大  

在使用 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世界 [3] 跟踪方法的 JIT 过程》一文中所述,它们在 MethodTable 中,最初的入口地址也被指向 mscorwks!PreStubWorker,可以通过 sos 查看之:

以下为引用:

0:003 !Name2EE mscorlib.dll System.String

--------------------------------------

MethodTable: 79b7daf8

EEClass: 79b7de44

Name: System.String

0:003 !DumpMT -MD 79b7daf8

EEClass : 79b7de44

Module : 79b66000

Name: System.String

mdToken: 0200000f

(e:windowsmicrosoft.net rameworkv1.1.4322mscorlib.dll)

MethodTable Flags : 2000000

Number of elements in array: 2

Number of IFaces in IFaceMap : 4

Interface Map : 79b7de24

Slots in VTable : 190

--------------------------------------

MethodDesc Table

Entry

MethodDesc

JIT

Name

799917c0 79b7ebc8

PreJIT [DEFAULT] [hasThis] String System.String.ToString()

...

79b7e253 79b7e258

None

[DEFAULT] [hasThis] I4 System.String.InternalLength()

...

0:003 !DumpMD 79b7e258

Method Name : [DEFAULT] [hasThis] I4 System.String.InternalLength()

MethodTable 79b7daf8

Module: 79b66000

mdToken: 060000b1 (e:windowsmicrosoft.net rameworkv1.1.4322mscorlib.dll)

Flags : 1

IL RVA : 0073000b

通过上述命令我们可以看到,String.InternalLength 方法缺省没有经过 JIT 编译,其入口地址为 79b7e253。反汇编此地址的指令,并一路追述下去可以发现,实际上最终也是调用 mscorwks!PreStubWorker 方法:

以下为引用:

0:003 u 79b7e253

mscorlib_79980000+0x1fe253:

79b7e253 e8287ffeff

call

mscorlib_79980000+0x1e6180 (79b66180)

79b7e258 4d

dec

ebp

...

mscorlib_79980000+0x1e6180:

79b66180 e9eb805e86

jmp

0014e270

79b66185 0000

add

[eax],al

...

0:003 u 0014e270

0014e270 52

push

edx

...

0014e290 56

push

esi

0014e291 e8b4870879

call

mscorwks!PreStubWorker (791d6a4a)

0014e296 897b08

mov

[ebx+0x8],edi

...

这个 PreStubWorker 函数(vm/prestub.cpp:574)可以说是所有 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 != 0)

pDispatchingMT = curobj-GetMethodTable();

}

return pMD-DoPrestub(pDispatchingMT);

}

PreStubWorker 函数的参数是一个方法帧,从中可以获取当前函数的 MD,进而调用此方法的 DoPresub 函数完成实际工作。而 MethodDesc:oPrestub 方法(vm/prestub.cpp:590)中,在进行实际代码生成时,会根据方法的类型进行各种特殊情况的处理:

以下内容为程序代码:

const BYTE * MethodDesc:

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 != 0)

{

_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 为 0 的情况下,会调用 FindECFuncForMethod 从一个全局 ECall 注册表中查找 InternalCall 的实现代码所在。

以下内容为程序代码:

void* FindImplForMethod(MethodDesc* pMDofCall)

{

DWORD_PTR rva = pMDofCall-GetRVA();

// ...

if (rva == 0)

{

ret = FindECFuncForMethod(pBaseMD);

}

// ...

}

不过与 Rotor 的实现不太一样的是,.NET Framework 1.1 为效率做了很多额外的优化工作。如前面的 DumpMD 命令结果所示,CLR v1.1 中 InternalCall 的方法也是有 RVA 的,只是他们指向的是一个直接返回的 ret 的 IL 指令。而 FindImplForMethod 对 ECall 类型的处理方法,也因 rva 不为 0,而从每次调用时以 FindECFuncForMethod 函数在全局 ECall 注册表中通过字符串匹配查找,改为通过 mscorwks!ECall::EmitECallMethodStub() 方法,生成一个对 ECall 实现代码的调用 Stub 代码。这样一来,只需要在第一次调用 ECall 代码时,完成字符串匹配性质的 ECall 实现代码定位,就可以一劳永逸的以等同于 JIT 代码的方式调用了。

可以通过在 FindImplForMethod 方法上下断点的方式,跟踪每次 InternalCall 类型函数的调用初始化工作,如:

以下为引用:

0:000 bp mscorwks!FindImplForMethod

0:000 g

Breakpoint 0 hit

eax=00000001 ebx=00000001 ecx=79ba9e68 edx=c0000000 esi=79ba9e68 edi=00000000

eip=791d8d5b esp=0012f084 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:

791d8d5b 55

push

ebp

0:000 !dumpmd ecx

Method Name : [DEFAULT] Void System.Runtime.InteropServices.Marshal.Copy(SZArray Char,I4,I4,I4)

MethodTable 79ba916c

Module: 79b66000

mdToken: 060020d3 (e:windowsmicrosof

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有