用WinDbg探索CLR世界 [4] 方法的调用机制之动态分析 - 下

2023-12-17 04:58

本文主要是介绍用WinDbg探索CLR世界 [4] 方法的调用机制之动态分析 - 下,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

用WinDbg探索CLR世界 [4] 方法的调用机制之动态分析 - 下

    再回头看前面那个 C# 代码的例子,在 JIT 完成之后:
 
以下为引用:

 .method private hidebysig static void  Main(string[] args) cil managed
 // SIG: 00 01 01 1D 0E
 {
   .entrypoint
   .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
   // Method begins at RVA 0x2120
   // Code size       47 (0x2f)
   .maxstack  1
   .locals init ([0] class flier.Base b,
            [1] class flier.Base d,
            [2] class flier.IFoo i)
   IL_0000:  /* 73   | (06)000007       */ newobj     instance void flier.Base::.ctor()
   IL_0005:  /* 0A   |                  */ stloc.0
   IL_0006:  /* 73   | (06)00000B       */ newobj     instance void flier.Derived::.ctor()
   IL_000b:  /* 0B   |                  */ stloc.1
   IL_000c:  /* 06   |                  */ ldloc.0
   IL_000d:  /* 6F   | (06)000003       */ callvirt   instance void flier.Base::CallFromObjBase()
   IL_0012:  /* 07   |                  */ ldloc.1
   IL_0013:  /* 6F   | (06)000003       */ callvirt   instance void flier.Base::CallFromObjBase()
   IL_0018:  /* 07   |                  */ ldloc.1
   IL_0019:  /* 6F   | (06)000004       */ callvirt   instance void flier.Base::CallFromObjDerived()
   IL_001e:  /* 06   |                  */ ldloc.0
   IL_001f:  /* 0C   |                  */ stloc.2
   IL_0020:  /* 08   |                  */ ldloc.2
   IL_0021:  /* 6F   | (06)000001       */ callvirt   instance void flier.IFoo::CallFromIntfBase()
   IL_0026:  /* 07   |                  */ ldloc.1
   IL_0027:  /* 0C   |                  */ stloc.2
   IL_0028:  /* 08   |                  */ ldloc.2
   IL_0029:  /* 6F   | (06)000002       */ callvirt   instance void flier.IFoo::CallFromIntfDerived()
   IL_002e:  /* 2A   |                  */ ret
 } // end of method EntryPoint::Main

 

 0:000> !ip2md 06d900a7
 MethodDesc: 0x00975070
 Jitted by normal JIT
 Method Name : [DEFAULT] Void flier.EntryPoint.Main(SZArray String)
 MethodTable 975088
 Module: 167d98
 mdToken: 0600000c (D:/Temp/CallIt/CallIt/bin/Debug/CallIt.exe)
 Flags : 10
 Method VA : 06d90058

 0:000> u 06d90058
 06d90058 55               push    ebp
 06d90059 8bec             mov     ebp,esp
 06d9005b 83ec10           sub     esp,0x10
 06d9005e 57               push    edi
 06d9005f 56               push    esi
 06d90060 53               push    ebx
 06d90061 894dfc           mov     [ebp-0x4],ecx
 06d90064 c745f800000000   mov     dword ptr [ebp-0x8],0x0
 06d9006b 33f6             xor     esi,esi
 06d9006d 33ff             xor     edi,edi

 // newobj     instance void flier.Base::.ctor()
 06d9006f b9d8519700       mov     ecx,0x9751d8          // 类 flier.Base 的方法表
 06d90074 e89f1fbdf9       call    00962018
 06d90079 8bd8             mov     ebx,eax
 06d9007b 8bcb             mov     ecx,ebx
 06d9007d ff1520529700     call    dword ptr [00975220]  // call flier.Base::.ctor()
 06d90083 895df8           mov     [ebp-0x8],ebx         // stloc.0

 // newobj     instance void flier.Derived::.ctor()
 06d90086 b988529700       mov     ecx,0x975288          // 类 flier.Derived 的方法表
 06d9008b e8881fbdf9       call    00962018
 06d90090 8bd8             mov     ebx,eax
 06d90092 8bcb             mov     ecx,ebx
 06d90094 ff15d8529700     call    dword ptr [009752d8]  // call flier.Derived::.ctor()
 06d9009a 8bf3             mov     esi,ebx               // stloc.1

 06d9009c 8b4df8           mov     ecx,[ebp-0x8]         // ldloc.0
 06d9009f 3909             cmp     [ecx],ecx
 06d900a1 ff151c529700     call    dword ptr [0097521c]  // callvirt   instance void flier.Base::CallFromObjBase()

 06d900a7 8bce             mov     ecx,esi               // ldloc.1
 06d900a9 3909             cmp     [ecx],ecx
 06d900ab ff151c529700     call    dword ptr [0097521c]  // callvirt   instance void flier.Base::CallFromObjBase()

 06d900b1 8bce             mov     ecx,esi               // ldloc.1
 06d900b3 8b01             mov     eax,[ecx]
 06d900b5 ff5038           call    dword ptr [eax+0x38]  // callvirt   instance void flier.Base::CallFromObjDerived()

 06d900b8 8b7df8           mov     edi,[ebp-0x8]         // ldloc.0
 06d900bb 8bcf             mov     ecx,edi               // stloc.2
 06d900bd 8b01             mov     eax,[ecx]
 06d900bf 8b400c           mov     eax,[eax+0xc]
 06d900c2 8b402c           mov     eax,[eax+0x2c]
 06d900c5 ff10             call    dword ptr [eax]       // callvirt   instance void flier.IFoo::CallFromIntfBase()

 06d900c7 8bfe             mov     edi,esi               // ldloc.1
 06d900c9 8bcf             mov     ecx,edi               // stloc.2
 06d900cb 8b01             mov     eax,[ecx]
 06d900cd 8b400c           mov     eax,[eax+0xc]
 06d900d0 8b402c           mov     eax,[eax+0x2c]
 06d900d3 ff5004           call    dword ptr [eax+0x4]   // callvirt   instance void flier.IFoo::CallFromIntfDerived()

 06d900d6 90               nop
 06d900d7 5b               pop     ebx
 06d900d8 5e               pop     esi
 06d900d9 5f               pop     edi
 06d900da 8be5             mov     esp,ebp
 06d900dc 5d               pop     ebp
 06d900dd c3               ret
 


 

     除了刚刚分析过的 call 和对虚函数的 callvirt 指令外,这里又多出一种对接口虚函数进行调用的操作。
 

以下为引用:

 06d900bb 8bcf             mov     ecx,edi               // stloc.2
 06d900bd 8b01             mov     eax,[ecx]             // 载入对象地址指向对象结构头部(04aa1b4c)字段指向的类型信息地址
 06d900bf 8b400c           mov     eax,[eax+0xc]         // 载入全局接口偏移量表基址
 06d900c2 8b402c           mov     eax,[eax+0x2c]        // 获取 IFoo 接口映射表偏移量
 06d900c5 ff10             call    dword ptr [eax]       // callvirt   instance void flier.IFoo::CallFromIntfBase()
 


     使用 WinDbg 动态跟踪到上述指令处
 
以下为引用:

 0:000> !dumpstackobjects
 ESP/REG  Object   Name
 ebx      04aa1b74 flier.Derived
 ecx      04aa2804 System.IO.TextWriter/SyncTextWriter
 esi      04aa1b74 flier.Derived
 edi      04aa1b68 flier.Base
 0012f6a0 04aa1b68 flier.Base
 0012f6a4 04aa1b4c System.Object[]
 0012f6d8 04aa1b4c System.Object[]
 0012f928 04aa1b4c System.Object[]
 0012f92c 04aa1b4c System.Object[]
 


     edi 指向 flier.Base 类型的对象实例(0x04aa1b68)
 
以下为引用:

 0:000> !dumpobj 04aa1b68
 Name: flier.Base
 MethodTable 0x009751d8
 EEClass 0x06c6334c
 Size 12(0xc) bytes
 mdToken: 02000003  (D:/Temp/CallIt/CallIt/bin/Debug/CallIt.exe)

 

 0:000> dd 04aa1b68
 04aa1b68  009751d8 00000000 00000000 00975288
 04aa1b78  00000000 80000000 79b7daf8 00000015
 



     而此对象的偏移 0 处保存着此对象的类型信息地址(0x009751d8)
 
以下为引用:

 0:000> !dumpmt 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

 

 0:000> dd 009751d8
 009751d8  00080000 0000000c 06c6334c 0097bff0
 009751e8  00120001 00167d98 0008ffff 00975228
 


 

     类型信息的 0xC 偏移处是全局接口偏移量表的入口基址 (0x0097bff0)
 

以下为引用:

 0:000> dd 0097bff0
 0097bff0  ???????? ???????? ???????? ????????
 0097c000  00000000 0097c000 00004000 00000000
 0097c010  00000000 000003e8 00000001 00975214
 0097c020  009752cc 00000000 00000000 00000000
 


     而 IFoo 接口的物理地址就在此偏移量表的 0x2C 偏移处(0x00975214)。这个地址是直接指向 flier.Base 类的虚方法表。
 
以下为引用:

 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()
 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()

 

 0:000> dd 009751d8
 009751d8  00080000 0000000c 06c6334c 0097bff0
 009751e8  00120001 00167d98 0008ffff 00975228
 009751f8  00000000 00000009 79b7c4eb 79b7c473
 00975208  79b7c48b 79b7c52b 0097519b 009751ab
 00975218  009751bb 0097518b 009751cb 00000000
 00975228  00975138 00050001 00000000 00000000
 00975238  00975288 00000000 00000003 00000000
 00975248  e8000008 ff7d9110 00000009 c00020c4
 


 

     0x0097519b 就是最后 flier.Base.CallFromObjDerived() 函数的入口地址。因此对于接口进行调用的 callvirt 指令,实际上是遵循以下的 dispatch 路线完成调用的:

     ObjectPtr -> Object -> Class -> Global Interface Map Table -> Class Method Table

     具体的结构图请参考《本质论》167面的图 (6.5 - 0.1), -_-b

     至此,CLR 中最常见的三种函数调用方式就大致分析完毕,以后有机会在继续分析其他的如jmp、间接调用和 tail call等方式的实现。

 btw: BLogCN限制一篇帖子只能12K实在太过分了,害得我一篇文章拆成三盘发 :( 
         这也就算了,居然还说我的文章里面有敏感词语,真是 $#@%$#        

这篇关于用WinDbg探索CLR世界 [4] 方法的调用机制之动态分析 - 下的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/503131

相关文章

PyTorch核心方法之state_dict()、parameters()参数打印与应用案例

《PyTorch核心方法之state_dict()、parameters()参数打印与应用案例》PyTorch是一个流行的开源深度学习框架,提供了灵活且高效的方式来训练和部署神经网络,这篇文章主要介绍... 目录前言模型案例A. state_dict()方法验证B. parameters()C. 模型结构冻

Django调用外部Python程序的完整项目实战

《Django调用外部Python程序的完整项目实战》Django是一个强大的PythonWeb框架,它的设计理念简洁优雅,:本文主要介绍Django调用外部Python程序的完整项目实战,文中通... 目录一、为什么 Django 需要调用外部 python 程序二、三种常见的调用方式方式 1:直接 im

Python字符串处理方法超全攻略

《Python字符串处理方法超全攻略》字符串可以看作多个字符的按照先后顺序组合,相当于就是序列结构,意味着可以对它进行遍历、切片,:本文主要介绍Python字符串处理方法的相关资料,文中通过代码介... 目录一、基础知识:字符串的“不可变”特性与创建方式二、常用操作:80%场景的“万能工具箱”三、格式化方法

springboot+redis实现订单过期(超时取消)功能的方法详解

《springboot+redis实现订单过期(超时取消)功能的方法详解》在SpringBoot中使用Redis实现订单过期(超时取消)功能,有多种成熟方案,本文为大家整理了几个详细方法,文中的示例代... 目录一、Redis键过期回调方案(推荐)1. 配置Redis监听器2. 监听键过期事件3. Redi

基于SpringBoot实现分布式锁的三种方法

《基于SpringBoot实现分布式锁的三种方法》这篇文章主要为大家详细介绍了基于SpringBoot实现分布式锁的三种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、基于Redis原生命令实现分布式锁1. 基础版Redis分布式锁2. 可重入锁实现二、使用Redisso

自定义注解SpringBoot防重复提交AOP方法详解

《自定义注解SpringBoot防重复提交AOP方法详解》该文章描述了一个防止重复提交的流程,通过HttpServletRequest对象获取请求信息,生成唯一标识,使用Redis分布式锁判断请求是否... 目录防重复提交流程引入依赖properties配置自定义注解切面Redis工具类controller

Java调用DeepSeek API的8个高频坑与解决方法

《Java调用DeepSeekAPI的8个高频坑与解决方法》现在大模型开发特别火,DeepSeek因为中文理解好、反应快、还便宜,不少Java开发者都用它,本文整理了最常踩的8个坑,希望对... 目录引言一、坑 1:Token 过期未处理,鉴权异常引发服务中断问题本质典型错误代码解决方案:实现 Token

Nginx 访问控制的多种方法

《Nginx访问控制的多种方法》本文系统介绍了Nginx实现Web访问控制的多种方法,包括IP黑白名单、路径/方法/参数控制、HTTP基本认证、防盗链机制、客户端证书校验、限速限流、地理位置控制等基... 目录一、IP 白名单与黑名单1. 允许/拒绝指定IP2. 全局黑名单二、基于路径、方法、参数的访问控制

Python中Request的安装以及简单的使用方法图文教程

《Python中Request的安装以及简单的使用方法图文教程》python里的request库经常被用于进行网络爬虫,想要学习网络爬虫的同学必须得安装request这个第三方库,:本文主要介绍P... 目录1.Requests 安装cmd 窗口安装为pycharm安装在pycharm设置中为项目安装req

nginx跨域访问配置的几种方法实现

《nginx跨域访问配置的几种方法实现》本文详细介绍了Nginx跨域配置方法,包括基本配置、只允许指定域名、携带Cookie的跨域、动态设置允许的Origin、支持不同路径的跨域控制、静态资源跨域以及... 目录一、基本跨域配置二、只允许指定域名跨域三、完整示例四、配置后重载 nginx五、注意事项六、支持