熱點推薦:
您现在的位置: 電腦知識網 >> 操作系統 >> Windows優化 >> 正文

談 WinDbg 之 InternalCall 的使用與實現

2022-06-13   來源: Windows優化 

  在使用 ReflectorNET 或者 Rotor 源碼查看 BCL 庫的實現時經常會碰到一些被標記為 InternalCall 的方法如 SystemString 中用於獲取字符串長度的 Length 屬性實現上就是調用被標記為 InternalCall 的 StringInternalLength 方法
  
  以下內容為程序代碼:
  namespace System
  {
  [Serializable]
  public sealed class String :
  {
  [MethodImpl(MethodImplOptionsInternalCall)]
  private int InternalLength();
  
  public int Length
  {
  get
  {
  return thisInternalLength();
  }
  }
  }
  }
  
  這些方法因為執行效率安全性或者為了實現簡單等不同原因通過 IL 代碼以外的 Native Code 形式提供實現代碼但與通過 DllImport 定義的 Interoper 方法不同的是他們無需被定義為 static extern 方法也無需通過單獨的 DLL 導出函數被實現它們作為 CLR 的諸多內部調用方式之一被封裝在一個看似密不透風的盒子裡面通過一個 InternalCall 的函數定義將函數最終使用者與函數功能提供者隔離開來
  但實際使用中為了分析 CLR 運行機制和調試我們經常性需要了解和分析這類函數下面將從 CLR 內部使用與實現 InternalCall 函數的不同角度對其做一個粗略的分析
  
  作為一個 BCL 函數被定義成 InternalCall 的函數使用上與普通 IL 函數沒有任何區別如同我前面《用WinDbg探索CLR世界 [] 跟蹤方法的 JIT 過程》一文中所述它們在 MethodTable 中最初的入口地址也被指向 mscorwks!PreStubWorker可以通過 sos 查看之
  
  以下為引用
  
  :> !NameEE mscorlibdll SystemString
  
  MethodTable: bdaf
  EEClass: bde
  Name: SystemString
  
  :> !DumpMT MD bdaf
  EEClass : bde
  Module : b
  Name: SystemString
  mdToken: f (e: rameworkvmscorlibdll)
  MethodTable Flags :
  Number of elements in array:
  Number of IFaces in IFaceMap :
  Interface Map : bde
  Slots in VTable :
  
  MethodDesc Table
  Entry MethodDesc  JIT  Name
  
  c bebc  PreJIT [DEFAULT] [hasThis] String SystemStringToString()
  
  be be  None  [DEFAULT] [hasThis] I SystemStringInternalLength()
  
  
  :> !DumpMD be
  Method Name : [DEFAULT] [hasThis] I SystemStringInternalLength()
  MethodTable bdaf
  Module: b
  mdToken: b (e: rameworkvmscorlibdll)
  Flags :
  IL RVA : b
  
  通過上述命令我們可以看到StringInternalLength 方法缺省沒有經過 JIT 編譯其入口地址為 be反匯編此地址的指令並一路追述下去可以發現實際上最終也是調用 mscorwks!PreStubWorker 方法
  
  以下為引用
  
  :> u be
  mscorlib_+xfe:
  be effeff    call  mscorlib_+xe (b)
  be d        dec   ebp
  
  
  mscorlib_+xe:
  b eebe    jmp   e
  b        add   [eax]al
  
  
  :> u e
  e         push  edx
  
  e         push  esi
  e eb    call  mscorwks!PreStubWorker (daa)
  e b      mov   [ebx+x]edi
  
  
  這個 PreStubWorker 函數(vm/prestubcpp:)可以說是所有 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/prestubcpp:)中在進行實際代碼生成時會根據方法的類型進行各種特殊情況的處理
  以下內容為程序代碼:
  
  const BYTE * MethodDesc:[img]/images/biggringif[/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 中對 MethodImplOptionsInternalCall 來說實際對應於 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=bae edx=c esi=bae edi=
  eip=ddb esp=f ebp=f iopl=     nv up ei pl nz na pe nc
  cs=b ss= ds= es= fs= gs=       efl=
  mscorwks!FindImplForMethod:
  ddb         push  ebp
  
  :> !dumpmd ecx
  Method Name : [DEFAULT] Void SystemRuntimeInteropServicesMarshalCopy(SZArray CharIII)
  MethodTable bac
  Module: b
  mdToken: d (e:windowsmicrosof
From:http://tw.wingwit.com/Article/os/youhua/201311/10912.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.