用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

相关文章

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

504 Gateway Timeout网关超时的根源及完美解决方法

《504GatewayTimeout网关超时的根源及完美解决方法》在日常开发和运维过程中,504GatewayTimeout错误是常见的网络问题之一,尤其是在使用反向代理(如Nginx)或... 目录引言为什么会出现 504 错误?1. 探索 504 Gateway Timeout 错误的根源 1.1 后端

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

基于Redis自动过期的流处理暂停机制

《基于Redis自动过期的流处理暂停机制》基于Redis自动过期的流处理暂停机制是一种高效、可靠且易于实现的解决方案,防止延时过大的数据影响实时处理自动恢复处理,以避免积压的数据影响实时性,下面就来详... 目录核心思路代码实现1. 初始化Redis连接和键前缀2. 接收数据时检查暂停状态3. 检测到延时过

python 线程池顺序执行的方法实现

《python线程池顺序执行的方法实现》在Python中,线程池默认是并发执行任务的,但若需要实现任务的顺序执行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录方案一:强制单线程(伪顺序执行)方案二:按提交顺序获取结果方案三:任务间依赖控制方案四:队列顺序消

SpringBoot通过main方法启动web项目实践

《SpringBoot通过main方法启动web项目实践》SpringBoot通过SpringApplication.run()启动Web项目,自动推断应用类型,加载初始化器与监听器,配置Spring... 目录1. 启动入口:SpringApplication.run()2. SpringApplicat

Redis中哨兵机制和集群的区别及说明

《Redis中哨兵机制和集群的区别及说明》Redis哨兵通过主从复制实现高可用,适用于中小规模数据;集群采用分布式分片,支持动态扩展,适合大规模数据,哨兵管理简单但扩展性弱,集群性能更强但架构复杂,根... 目录一、架构设计与节点角色1. 哨兵机制(Sentinel)2. 集群(Cluster)二、数据分片