分类

  • 软件天地

  • 谈 WinDbg 之 InternalCall 的使用与实现‖
    为 InternalCall 的方法。
    上就是调用被标记为 Inter
    或者 Rotor 源码查看 BCL 库
    如 System.String 中用于获取
    nalCall 的 String.InternalLe
    的实现时,经常会碰到一些被标记
    字符串长度的 Length 属性,实现
    ngth 方法:

      以下内容为程序代码:

      namespace System                                              
      {                                                                            
      [Serializable]                                                  
      public sealed class String : ...              
      {                                                                            

      [MethodImpl(MethodIm
    plOptions.InternalCall)]
      private int InternalLength();                    

      public int Length                                            
      {                                                                            
      get                                                                        
      {                                                                            
      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

      --------------------------------------  
      MethodTable: 79b7daf8                                    
      EEClass: 79b7de44                                            
      Name: System.String                                        

      0:003> !DumpMT -MD 79b7daf8

      EEClass : 79b7de44                                          
      Module : 79b66000                                            
      Name: System.String                                        
      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              
      Interface Map : 79b7de24                              
      Slots in VTable : 190                                    
      --------------------------------------  
      MethodDesc Table                                              
      Entry MethodDesc  JIT  Name                        

      799917c0 79b7ebc8  PreJIT [DEF
    )
    AULT] [hasThis] String System.String.ToString(

      ...                                                                        
      79b7e253 79b7e258 
    g.InternalLength()
     None  [DEFAULT] [hasThis]

    I4 System.Strin

      ...                                                                        

      0:003> !DumpMD 79b7e258

      Method Name : [DEFAU
    LT] [hasThis] I4 System.Stri
    ng.InternalLength()
      MethodTable 79b7daf8                                      
      Module: 79b66000                                              
      mdToken: 060000b1 (e
    :windowsmicrosoft.net ramewo
    rkv1.1.4322mscorlib.dll)
      Flags : 1                                                            
      IL RVA : 0073000b                                            

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


      以下为引用:                                                                  

      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!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);
      if (curobj != 0)                                              
      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

      {                                                                            
      Stub *pStub = NULL;                                        

      //...                                                                    

      if (IsSpecialStub())                                      
      {                                                                            
      //...                                                                    
      }                                                                            
      else if (IsIL())                                              
      {                                                                            
      //...                                                                    
      }                                                                            
      else  //!IsSpecialStub() && !I
    sIL() case
      {                                                                            
      if (IsECall())                                                  
      {                                                                            
      // 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();  
      else                                                                      
      pStub = (Stub*) FindImplForMetho
    d(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 == 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();


      // ...                                                                  

      if (rva == 0)                                                    
      {                                                                            
      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


      0:000> g

      Breakpoint 0 hit                                              
      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:                        
      791d8d5b 55        push  ebp                      

      0:000> !dumpmd ecx

      Method Name : [DEFAULT] Void Sys
    ces.Marshal.Copy(SZArray Char,I4,I4,
    tem.Runtime.InteropServi
    I4)
      MethodTable 79ba916c                                      
      Module: 79b66000                                              
      mdToken: 060020d3 (e
    :windowsmicrosoft.net ramewo
    rkv1.1.4322mscorlib.dll)
      Flags : 11                                                          
      IL RVA : 00460008                                            

      通过 DumpMD 命令查看
    pMDofCall 参数,可以知道
    ,再来看刚刚被处理的方法
    7921b264,而这个入口地址
    的 Marshal.Copy 对应的实
    FindImplForMethod 函数以 EC
    目前是哪个函数被调用。在 Fin
    描述,就会发现 IL RVA : 0046
    ,正是刚刚 EmitECallMethodSt
    现代码 CopyToNative。
    X 寄存器传入的 MethodDesc*
    dImplForMethod 函数运行结束后
    0008 已经变成 Method VA :
    ub 方法从全局 ECall 表中查询到


      以下为引用:                                                                  

      0:000> !dumpmd 79ba9e68

      Method Name : [DEFAULT] Void Sys
    ces.Marshal.Copy(SZArray Char,I4,I4,
    tem.Runtime.InteropServi
    I4)
      MethodTable 79ba916c                                      
      Module: 79b66000                                              
      mdToken: 060020d3 (e
    :windowsmicrosoft.net ramewo
    rkv1.1.4322mscorlib.dll)
      Flags : 11                                                          
      Method VA : 7921b264                                      

      0:000> u 7921b264

      mscorwks!CopyToNative:                                  
      7921b264 55        push  ebp                      
      ...                                                                        

      毕竟 Rotor 只是一个非商业用途的
    厚非的。
    参考实现,删去优化代码来降低程序复杂度也是无可


      在了解了 InternalCal
    1.1 中如何被动态解析后,
    口的。
    l 方ㄔ?Rotor 中是如何被动
    我们来看看这些调用和解析的背

    态调用,以及在 .NET Framework
    后,是如何定位方法的实现函数入

      Rotor 中的 FindECFun
    cForMethod 函数(vm/ecall.cpp
    :1886)是最能说明问题的:

      以下为引用:                                                                  

      static ECFunc* FindECFuncForMeth
    od(MethodDesc* pMD)
      {                                                                            
      // check the cache                                          

      ECFunc**cacheEntry =
    getCacheEntry(pMD);
      ECFunc* cur = *cacheEntry;                          
      if (cur != 0 && cur->m_pMD == pMD)

      return(cur);                                                      

      cur = FindImplsForClass(pMD->GetMethodTable());

      if (cur == 0)                                                    
      return(0);                                                          

      cur = GetECForIndex(FindECIndexF
    orMethod(pMD, cur), cur);
      if (cur == 0)                                                    
      return(0);                                                          

      *cacheEntry = cur;       
          // put in the cache
      return cur;                                                        
      }                                                                            

      static ECFunc *GetEC
    ForIndex(USHORT index, ECFun
    c *impls)
      {                                                                            
      if (index == (USHORT) -1)                            
      return NULL;                                                      
      else                                                                      
      return impls + index;                                    
      }                                                                            

      ECFunc* FindImplsForClass(Method
    Table* pMT)
      {                                                                            
      return GetImplsForIndex(FindImpl
    sIndexForClass(pMT));
      }                                                                            

      ECFunc* GetImplsForIndex(USHORT index)  
      {                                                                            
      if (index == (USHORT) -1)                            
      return NULL;                                                      
      else                                                                      
      return gECClasses[index].m_pECFunc;        
      }                                                                            

      FindECFuncForMethod 函数首先试图
    存则调用 FindImplsIndexForClass 函数
    个层面寻找合适的 ECall 实现。要理解
    是如何定义的:
    从缓存中获取 pMD 参数指定的方法,如果不存在缓
    和FindECIndexForMethod 函数,分别从类和方法两
    这两个函数的实现,要首先看看全局 ECall 映射表


      以下为引用:

      struct ECFunc {                                                
      BOOL IsFCall()         
      { return TRUE; }
      CorInfoIntrinsics  IntrinsicID(
    insics(m_intrinsicID); }
    )  { return CorInfoIntr


      LPCUTF8      m_wszMethodName;                    
      LPHARDCODEDMETASIG m_wszMethodSig;          
      LPVOID       m_pImplementation;                
      MethodDesc*    m
    _pMD;        // for r
    everse mapping

      int        m_intrinsicID : 8;                    

      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、实现函数入口等


      以下为引用:                                                                  

      struct ECClass                                                  
      {                                                                            
      LPCUTF8   m_wszClassName;                            
      LPCUTF8   m_wszNameSpace;                            
      ECFunc   *m_pECFunc;                                      
      };                                                                          

      static ECClass gECClasses[] =                    
      {                                                                            
      ...                                                                        
      {ECClassesElement("String", "Sys
    tem", gStringFuncs)},
      ...                                                                        
      };                                                                          

      然后,对于每个拥有 I
    一个表现。每项包括类型的
    死了,然后编译到程序中去
    GetImplsForIndex 函数的
    nternalCall 方法的类型,需要
    名称、名字空间和函数映射表。
    。虽然在发行版中没有导出这个
    实现代码在运行时环境中找到这
    在一个全局 ECall 注册表中增加
    这些映射表都是在编译时就预定义
    符号,但我们可以通过
    个映射表:

      以下为引用:

      0:003> u mscorwks!GetImplsForIndex

      mscorwks!GetImplsForIndex:                          
      791d711f 55        push  ebp                      
      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

      791d713b 5d        pop   ebp                      
      791d713c c20400      ret   0x4                  
      791d713f 33c0       xor   eax,eax            
      791d7141 40        inc   eax                      
      791d7142 5e        pop   esi                      
      791d7143 c3        ret                                  

      0:008> dd 793dd140

      793dd140 791d7c40 791d71c4 793e
    0420 791d7c30
      793dd150 791d71c4 793e03f0 791d
    86f8 791d71c4
      ...                                                                        

      0:008> db 791d7c40

      791d7c40 41 70 70 4
    4 6f 6d 61 69-6e 00 90 90 4c
    6f 67 00 AppDomain...Log.
      ...                                                                        

      0:008> db 791d71c4

      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 地址就是
    了这一点。

      以下为引用:                                                                  

      0:008> dd 793e0420


      793e0420 7923db2c 0
    0000000 7933957f 00000000
      793e0430 000000ff 00000000 7923
    db18 00000000

      ...                                                                        

      0:008> db 7923db2c

      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.}

      ...                                                                        

      0:008> u 7933957f

      mscorwks!AppDomainNative::Create
    BasicDomain:
      7933957f 55        push  ebp                      

      进一步查看 System.AppDomain 的方法映射表也是跟我们预期相同。

      知道了 ECall 的全局
    FindECIndexForMethod 函
    MethodTable 指向类型名称
    数名称相同的实现函数入口
    映射表之后,再来理解 FindImp
    数就非常容易了。前者遍历 gEC
    和名字空间相同的函数映射表入

    lsIndexForClass 函数和
    Classes 表,找出与
    口;后者遍历函数映射表,找出函


      至此,InternalCall
    我们可以很方便的实现一些
    类型函数的使用和实现就大概介
    无法在 IL 层面实现的底层功能
    绍完了。通过了解这个内部原理,



    上一页 下一页




    map