用WinDbg探索CLR世界 [4] 方法的调用机制之静态结构

 用WinDbg探索CLR世界 [4] 方法的调用机制之静态结构

     Don Box《.NET本质论 第1卷:公共语言运行库》的第6章里,详细地解说了 CLR 中方法地调用机制的原理; qqchen在其 BLog 上也有一篇不错的介绍 CLR 中方法调用分类的文章 《CLR Drilling Down: The Overhead of Method Calls 》。但因为他们文章的目的不同,故而没有足够深入到让我满足的内部细节,呵呵,只好自己接着分析。:D

 

     我在《用WinDbg探索CLR世界 [3] 跟踪方法的 JIT 过程》一文中介绍了如何使用 WinDbg 跟踪 Don Box 所描述的 JIT 过程。本文中将使用前文所介绍的 WinDbg 功能进一步分析 CLR 中方法的调用机制。

     首先我们来看一个简单的例子,其中有两个类和一个接口的定义,并使用了几种不同的调用类型进行方法调用:
 

以下为引用:

 using System;

 

 namespace flier
 {
   public interface IFoo
   {
     void CallFromIntfBase();
     void CallFromIntfDerived();
   }

   public class Base : IFoo
   {
     public void CallFromObjBase()
     {
       System.Console.WriteLine("Base.CallFromObjBase");
     }

     public virtual void CallFromObjDerived()
     {
       System.Console.WriteLine("Base.CallFromObjDerived");
     }

     public void CallFromIntfBase()
     {
       System.Console.WriteLine("Base.IFoo.CallFromIntfBase");
     }
     public virtual void CallFromIntfDerived()
     {
       System.Console.WriteLine("Base.IFoo.CallFromIntfDerived");
     }
   }

   public class Derived : Base, IFoo
   {
     public new void CallFromObjBase()
     {
       System.Console.WriteLine("Derived.CallFromObjBase");
     }

     public override void CallFromObjDerived()
     {
       System.Console.WriteLine("Derived.CallFromObjDerived");
     }

     public override void CallFromIntfDerived()
     {
       System.Console.WriteLine("Derived.IFoo.CallFromIntfDerived");
     }
   }

  class EntryPoint
  {
   [STAThread]
   static void Main(string[] args)
   {
       Base b = new Base(),
            d = new Derived();

       b.CallFromObjBase();

       d.CallFromObjBase();
       d.CallFromObjDerived();

       IFoo i = (IFoo) b;

       i.CallFromIntfBase();

       i = (IFoo)d;

       i.CallFromIntfDerived();
   }
  }
 }
 



     将之编译成 CallIt.exe 后用 WinDbg 启动调试之。进入调试后,可以使用 sos 的 !name2ee 命令查看指定类型的当前状态,如:
 
以下为引用:

 0:000> !name2ee CallIt.exe flier.Derived
 --------------------------------------
 MethodTable: 00975288
 EEClass: 06c63414
 Name: flier.Derived
 


     使用 !dumpclass 命令进一步查看类型详细信息:
 
以下为引用:

 0:000> !dumpclass 06c63414
 Class Name : flier.Derived
 mdToken : 02000004 ()
 Parent Class : 06c6334c
 ClassLoader : 0015ee08
 Method Table : 00975288
 Vtable Slots : 9
 Total Method Slots : b
 Class Attributes : 100001 :
 Flags : 1000003
 NumInstanceFields: 0
 NumStaticFields: 0
 ThreadStaticOffset: 0
 ThreadStaticsSize: 0
 ContextStaticOffset: 0
 ContextStaticsSize: 0
 


     可以发现 Derived 类型有 11 个 Method Slot,但只有 9 个 Vtable Slot。使用 !dumpmt 进一步查看之:
 
以下为引用:

 0:000> !dumpmt -md 00975288
 EEClass : 06c63414
 Module : 00167d98
 Name: flier.Derived
 mdToken: 02000004  (D:/Temp/CallIt/CallIt/bin/Debug/CallIt.exe)
 MethodTable Flags : 80000
 Number of IFaces in IFaceMap : 1
 Interface Map : 009752e0
 Slots in VTable : 11
 --------------------------------------
 MethodDesc Table
   Entry  MethodDesc   JIT   Name
 79b7c4eb 79b7c4f0    None   [DEFAULT] [hasThis] String System.Object.ToString()
 79b7c473 79b7c478    None   [DEFAULT] [hasThis] Boolean System.Object.Equals(Object)
 79b7c48b 79b7c490    None   [DEFAULT] [hasThis] I4 System.Object.GetHashCode()
 79b7c52b 79b7c530    None   [DEFAULT] [hasThis] Void System.Object.Finalize()
 0097525b 00975260    None   [DEFAULT] [hasThis] Void flier.Derived.CallFromObjDerived()
 009751ab 009751b0    None   [DEFAULT] [hasThis] Void flier.Base.CallFromIntfBase()
 0097526b 00975270    None   [DEFAULT] [hasThis] Void flier.Derived.CallFromIntfDerived()
 // 以下开始为 IFoo 接口方法表
 009751ab 009751b0    None   [DEFAULT] [hasThis] Void flier.Base.CallFromIntfBase()
 0097526b 00975270    None   [DEFAULT] [hasThis] Void flier.Derived.CallFromIntfDerived()
 // 以下开始为非虚方法表
 0097524b 00975250    None   [DEFAULT] [hasThis] Void flier.Derived.CallFromObjBase()
 0097527b 00975280    None   [DEFAULT] [hasThis] Void flier.Derived..ctor()
 


     可以看到正如 Don Box 在书中所说,类型的方法表是分为虚方法表和非虚方法表两部分的。前面 9 个 Method Slot 组成 Derived 的 VTable,后两个 Slot 保存非虚方法。检查 Base 类的情况也是类似:
 
以下为引用:

 0:000> !name2ee CallIt.exe flier.Base
 --------------------------------------
 MethodTable: 009751d8
 EEClass: 06c6334c
 Name: flier.Base

 

 0:000> !dumpclass 06c6334c
 Class Name : flier.Base
 mdToken : 02000003 ()
 Parent Class : 79b7c3c8
 ClassLoader : 0015ee08
 Method Table : 009751d8
 Vtable Slots : 7
 Total Method Slots : 9
 Class Attributes : 100001 :
 Flags : 1000003
 NumInstanceFields: 0
 NumStaticFields: 0
 ThreadStaticOffset: 0
 ThreadStaticsSize: 0
 ContextStaticOffset: 0
 ContextStaticsSize: 0

 0:000> !dumpmt -md 009751d8
 EEClass : 06c6334c
 Module : 00167d98
 Name: flier.Base
 mdToken: 02000003  (D:/Temp/CallIt/CallIt/bin/Debug/CallIt.exe)
 MethodTable Flags : 80000
 Number of IFaces in IFaceMap : 1
 Interface Map : 00975228
 Slots in VTable : 9
 --------------------------------------
 MethodDesc Table
   Entry  MethodDesc   JIT   Name
 79b7c4eb 79b7c4f0    None   [DEFAULT] [hasThis] String System.Object.ToString()
 79b7c473 79b7c478    None   [DEFAULT] [hasThis] Boolean System.Object.Equals(Object)
 79b7c48b 79b7c490    None   [DEFAULT] [hasThis] I4 System.Object.GetHashCode()
 79b7c52b 79b7c530    None   [DEFAULT] [hasThis] Void System.Object.Finalize()
 0097519b 009751a0    None   [DEFAULT] [hasThis] Void flier.Base.CallFromObjDerived()
 // 以下开始为 IFoo 接口方法表
 009751ab 009751b0    None   [DEFAULT] [hasThis] Void flier.Base.CallFromIntfBase()
 009751bb 009751c0    None   [DEFAULT] [hasThis] Void flier.Base.CallFromIntfDerived()
 // 以下开始为非虚方法表
 0097518b 00975190    None   [DEFAULT] [hasThis] Void flier.Base.CallFromObjBase()
 009751cb 009751d0    None   [DEFAULT] [hasThis] Void flier.Base..ctor()
 



     而对于每个接口,实际上 CLR 是单独维护了一个方法表的。如 Base 类的方法表中指出,地址 0x009752e0 处有一个接口方法映射表,查看其内容如下:
 
以下为引用:

 0:000> dd 0x009752e0
 009752e0  00975138 00070001 00000000 00000000
 


     每个接口映射表表项由2个DWORD组成,头一个DWORD就是接口方法表的地址。
 
以下为引用:

 0:000> !dumpmt -md 00975138
 EEClass : 06c633b0
 Module : 00167d98
 Name: flier.IFoo
 mdToken: 02000002  (D:/Temp/CallIt/CallIt/bin/Debug/CallIt.exe)
 MethodTable Flags : 80000
 Number of IFaces in IFaceMap : 0
 Interface Map : 0097516c
 Slots in VTable : 2
 --------------------------------------
 MethodDesc Table
   Entry  MethodDesc   JIT   Name
 009750eb 009750f0    None   [DEFAULT] [hasThis] Void flier.IFoo.CallFromIntfBase()
 00975113 00975118    None   [DEFAULT] [hasThis] Void flier.IFoo.CallFromIntfDerived()
 


     比较一下就会发现,Base 和 Derived 类的接口映射表指向的接口方法表都是一样的。
 
以下为引用:

 0:000> dd 009752e0
 009752e0  00975138 00070001 00000000 00000000

 

 0:000> dd 00975228
 00975228  00975138 00050001 00000000 00000000
 



     只是接口映射表表项第2个 DWORD 的高 WORD 指名此接口在原方法表中的起始索引(Base 为 5,Derived 为 7)不同。这正符合《本质论》中167页那张图所示的接口映射表结构。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值