用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

相关文章

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

oracle DBMS_SQL.PARSE的使用方法和示例

《oracleDBMS_SQL.PARSE的使用方法和示例》DBMS_SQL是Oracle数据库中的一个强大包,用于动态构建和执行SQL语句,DBMS_SQL.PARSE过程解析SQL语句或PL/S... 目录语法示例注意事项DBMS_SQL 是 oracle 数据库中的一个强大包,它允许动态地构建和执行

pip install jupyterlab失败的原因问题及探索

《pipinstalljupyterlab失败的原因问题及探索》在学习Yolo模型时,尝试安装JupyterLab但遇到错误,错误提示缺少Rust和Cargo编译环境,因为pywinpty包需要它... 目录背景问题解决方案总结背景最近在学习Yolo模型,然后其中要下载jupyter(有点LSVmu像一个

Ubuntu固定虚拟机ip地址的方法教程

《Ubuntu固定虚拟机ip地址的方法教程》本文详细介绍了如何在Ubuntu虚拟机中固定IP地址,包括检查和编辑`/etc/apt/sources.list`文件、更新网络配置文件以及使用Networ... 1、由于虚拟机网络是桥接,所以ip地址会不停地变化,接下来我们就讲述ip如何固定 2、如果apt安

Go路由注册方法详解

《Go路由注册方法详解》Go语言中,http.NewServeMux()和http.HandleFunc()是两种不同的路由注册方式,前者创建独立的ServeMux实例,适合模块化和分层路由,灵活性高... 目录Go路由注册方法1. 路由注册的方式2. 路由器的独立性3. 灵活性4. 启动服务器的方式5.

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

一分钟带你上手Python调用DeepSeek的API

《一分钟带你上手Python调用DeepSeek的API》最近DeepSeek非常火,作为一枚对前言技术非常关注的程序员来说,自然都想对接DeepSeek的API来体验一把,下面小编就来为大家介绍一下... 目录前言免费体验API-Key申请首次调用API基本概念最小单元推理模型智能体自定义界面总结前言最

Idea实现接口的方法上无法添加@Override注解的解决方案

《Idea实现接口的方法上无法添加@Override注解的解决方案》文章介绍了在IDEA中实现接口方法时无法添加@Override注解的问题及其解决方法,主要步骤包括更改项目结构中的Languagel... 目录Idea实现接China编程口的方法上无法添加@javascriptOverride注解错误原因解决方