.Net 7函数Ctor与CCtor使用及区别详解

目录
  • 楔子
  • 目的非手段
  • .Ctor目的
  • .CCtor目的

楔子

有小伙伴被面试官问到这个问题,本篇彻底解析下这个问题。

为了彻底点,注意本篇是最底层的.Net 7 RC CLR运行模型(汇编)为基础进行全局剖析,局部业务分析。

如有疏漏,请斧正。

目的非手段

这两个函数比较特殊的存在,.Ctor是非静态默认实例化。.CCtor是静态默认实例化。这两个函数伴随着.Net任何对象的实例化都自动存在于这个对象当中。

跟踪.CCtor可以在全局静态对象下断点,观察它的里面运行。跟踪.Ctor可以通过!name2ee模块 模块.类名..Ctor找到JITTED Code Address,观察它的运行。正如本段题所说,这只是手段,不是目的。所以下面看目的。

.Ctor目的

先来看下非静态默认构造函数.Ctor。上一段代码:

    internal class Program
    {
        public class ABC
        {
        }
        static void Main(string[] args)
        {
            ABC abc = new ABC();
            Console.ReadLine();
        }
    }

直接给它反编译:

00007FFDF2FA03B0 55                   push        rbp
00007FFDF2FA03B1 48 83 EC 40          sub         rsp,40h
00007FFDF2FA03B5 48 8D 6C 24 40       lea         rbp,[rsp+40h]
00007FFDF2FA03BA C5 D8 57 E4          vxorps      xmm4,xmm4,xmm4
00007FFDF2FA03BE C5 FA 7F 65 E8       vmovdqu     xmmword ptr [rbp-18h],xmm4
00007FFDF2FA03C3 33 C0                xor         eax,eax
00007FFDF2FA03C5 48 89 45 F8          mov         qword ptr [rbp-8],rax
00007FFDF2FA03C9 48 89 4D 10          mov         qword ptr [rbp+10h],rcx
00007FFDF2FA03CD 83 3D BC E9 19 00 00 cmp         dword ptr [7FFDF313ED90h],0
00007FFDF2FA03D4 74 05                je          00007FFDF2FA03DB
00007FFDF2FA03D6 E8 B5 BF 79 5E       call        JIT_DbgIsJustMyCode (07FFE5173C390h)
00007FFDF2FA03DB 90                   nop
00007FFDF2FA03DC 48 B9 30 F6 5B F3 FD 7F 00 00 mov         rcx,7FFDF35BF630h
00007FFDF2FA03E6 E8 75 7C C1 5E       call        JIT_TrialAllocSFastMP_InlineGetThread (07FFE51BB8060h)
00007FFDF2FA03EB 48 89 45 F0          mov         qword ptr [rbp-10h],rax
00007FFDF2FA03EF 48 8B 4D F0          mov         rcx,qword ptr [rbp-10h]
// 这个地方是调用了.Ctor
00007FFDF2FA03F3 FF 15 0F 8D 60 00    call        qword ptr [7FFDF35A9108h]
00007FFDF2FA03F9 48 8B 45 F0          mov         rax,qword ptr [rbp-10h]
00007FFDF2FA03FD 48 89 45 F8          mov         qword ptr [rbp-8],rax
00007FFDF2FA0401 FF 15 A9 93 60 00    call        qword ptr [7FFDF35A97B0h]
00007FFDF2FA0407 48 89 45 E8          mov         qword ptr [rbp-18h],rax
00007FFDF2FA040B 90                   nop
00007FFDF2FA040C 90                   nop
00007FFDF2FA040D 48 83 C4 40          add         rsp,40h
00007FFDF2FA0411 5D                   pop         rbp
00007FFDF2FA0412 C3                   ret

调用.Ctor的地方注释了下,如果直接进入会调用到PrecodeFixupThunk。所以这里需要在PreStubWorker下断点。一路跟踪下去发现这个.Ctor是利用预备的IL代码,让RyuJIt对它进行一个编译

.Ctor调用堆栈:

    coreclr.dll!MethodDesc::JitCompileCodeLocked 行 952	C++
 	coreclr.dll!MethodDesc::JitCompileCodeLockedEventWrapper 行 823	C++
 	coreclr.dll!MethodDesc::JitCompileCode 行 763	C++
 	coreclr.dll!MethodDesc::PrepareILBasedCode 行 426	C++
 	coreclr.dll!MethodDesc::PrepareCode 行 323	C++
 	coreclr.dll!CodeVersionManager::PublishVersionableCodeIfNecessary 行 1698	C++
 	coreclr.dll!MethodDesc::DoPrestub 行 2109	C++
 	coreclr.dll!PreStubWorker 行 1938
 	coreclr.dll!ThePreStub(

JitCompileCodeLocked里面调用了UnsafeJitFunction为止,因为后面都是RyuJit的复杂编译过程,此处不述。

我们来看下UnsafeJitFunction返回的pCode地址处的汇编代码:

00007FFDF2F80430 55                   push        rbp
00007FFDF2F80431 57                   push        rdi
00007FFDF2F80432 48 83 EC 28          sub         rsp,28h
00007FFDF2F80436 48 8D 6C 24 30       lea         rbp,[rsp+30h]
00007FFDF2F8043B 48 89 4D 10          mov         qword ptr [rbp+10h],rcx
00007FFDF2F8043F 83 3D 4A E9 19 00 00 cmp         dword ptr [7FFDF311ED90h],0
00007FFDF2F80446 74 05                je          00007FFDF2F8044D
00007FFDF2F80448 E8 43 BF 7B 5E       call        JIT_DbgIsJustMyCode (07FFE5173C390h)
00007FFDF2F8044D 48 8B 4D 10          mov         rcx,qword ptr [rbp+10h]
00007FFDF2F80451 FF 15 D9 0B E5 FF    call        qword ptr [7FFDF2DD1030h]
00007FFDF2F80457 90                   nop
00007FFDF2F80458 90                   nop
00007FFDF2F80459 48 83 C4 28          add         rsp,28h
00007FFDF2F8045D 5F                   pop         rdi
00007FFDF2F8045E 5D                   pop         rbp
00007FFDF2F8045F C3                   ret

它里面就调用了一个Call,也就是这句话:

call  qword ptr [7FFDF2DD1030h]

这个十六进制的7FFDF2DD1030h是个啥呢?继续跟进下:0x00007FFDF2DD1030 00007ffe50357230它里面包含了一个地址00007ffe50357230看下这个地址的汇编代码:

00007FFE50357230 C3   ret

它直接返回了。

所以这得出了一个什么结论呢?也就是说在当前这个例子中,.Ctor啥都没做。

.CCtor目的

来看下静态的默认构造函数干了些啥。先上代码:

    internal class Program
    {
        static string a ="abcd";
        static void Main(string[] args)
        {
            string i = a;
            Console.WriteLine(a);
            Console.ReadLine();
        }
    }

同样反编译下:

00007FFDF01903B0 55                   push        rbp
00007FFDF01903B1 57                   push        rdi
00007FFDF01903B2 48 83 EC 28          sub         rsp,28h
00007FFDF01903B6 48 8D 6C 24 30       lea         rbp,[rsp+30h]
00007FFDF01903BB 33 C0                xor         eax,eax
00007FFDF01903BD 48 89 45 F0          mov         qword ptr [rbp-10h],rax
00007FFDF01903C1 48 89 4D 10          mov         qword ptr [rbp+10h],rcx
00007FFDF01903C5 83 3D C4 E9 19 00 00 cmp         dword ptr [7FFDF032ED90h],0
00007FFDF01903CC 74 05                je          00007FFDF01903D3
00007FFDF01903CE E8 BD BF 7D 5E       call        JIT_DbgIsJustMyCode (07FFE4E96C390h)
00007FFDF01903D3 90                   nop
00007FFDF01903D4 48 B9 60 EF 32 F0 FD 7F 00 00 mov         rcx,7FFDF032EF60h
00007FFDF01903DE BA 04 00 00 00       mov         edx,4
// 可以看到这个 string 静态对象并没有调用.CCtor。
// 那是否说明上面的说法不对呢?注意看,他实际调用了
// JIT_GetSharedNonGCStaticBase_SingleAppDomain,
// 而这个就是关键所在
00007FFDF01903E3 E8 48 7E C5 5E       call        JIT_GetSharedNonGCStaticBase_SingleAppDomain (07FFE4EDE8230h)
00007FFDF01903E8 8B 0D AA EB 19 00    mov         ecx,dword ptr [7FFDF032EF98h]
00007FFDF01903EE FF 15 7C 94 60 00    call        qword ptr [7FFDF0799870h]
00007FFDF01903F4 90                   nop
00007FFDF01903F5 FF 15 9D 93 60 00    call        qword ptr [7FFDF0799798h]
00007FFDF01903FB 48 89 45 F0          mov         qword ptr [rbp-10h],rax
00007FFDF01903FF 90                   nop
00007FFDF0190400 90                   nop
00007FFDF0190401 48 83 C4 28          add         rsp,28h
00007FFDF0190405 5F                   pop         rdi
00007FFDF0190406 5D                   pop         rbp
00007FFDF0190407 C3                   ret
00007FFDF0190408 19 06                sbb         dword ptr [rsi],eax

看这段代码上面的注释,这段代码里面并没有.CCtor被调用的痕迹。而它的奥秘在JIT_GetSharedNonGCStaticBase_SingleAppDomain函数里面。

JIT_GetSharedNonGCStaticBase_SingleAppDomain又调用了JIT_GetSharedNonGCStaticBase_Helper

看下堆栈

>	coreclr.dll!MethodTable::RunClassInitEx 行 3591	C++
 	coreclr.dll!MethodTable::DoRunClassInitThrowing 行 3792	C++
 	coreclr.dll!MethodTable::CheckRunClassInitThrowing 行 3929	C++
 	coreclr.dll!JIT_GetSharedNonGCStaticBase_Helper 行 1401	C++

函数RunClassInitEx代码如下:

BOOL MethodTable::RunClassInitEx(OBJECTREF *pThrowable)
{
            //为了方便观看 此处省略部分代码
            PCODE pCctorCode = pCanonMT->GetSlot(pCanonMT->GetClassConstructorSlot());
            //为了方便观看 此处省略部分代码
            PREPARE_NONVIRTUAL_CALLSITE_USING_CODE(pCctorCode);
            DECLARE_ARGHOLDER_ARRAY(args, 0);
            CATCH_HANDLER_FOUND_NOTIFICATION_CALLSITE;
            CALL_MANAGED_METHOD_NORET(args);
	    //为了方便观看 此处省略部分代码

变量pCctorCode就是.CCtor的函数头地址。而后面的一堆的宏定义实际上是调用了函数DispatchCallSimple,而DispatchCallSimple又调用了CallDescrWorkerWithHandler然后又调用了PrecodeFixupThunk下面调用了PreStubWorker

PreStubWorker通过call rax命令跳转到调用的函数的函数头地址,比如本例的.CCtor函数头的地址。

00007FFE8BB289C0 E8 DB FE 8F FF       call        PreStubWorker (07FFE8B4288A0h)
00007FFE8BB289C5 66 0F 6F 44 24 20    movdqa      xmm0,xmmword ptr [rsp+20h]
00007FFE8BB289CB 66 0F 6F 4C 24 30    movdqa      xmm1,xmmword ptr [rsp+30h]
00007FFE8BB289D1 66 0F 6F 54 24 40    movdqa      xmm2,xmmword ptr [rsp+40h]
00007FFE8BB289D7 66 0F 6F 5C 24 50    movdqa      xmm3,xmmword ptr [rsp+50h]
00007FFE8BB289DD 48 8B 8C 24 B0 00 00 00 mov         rcx,qword ptr [rsp+0B0h]
00007FFE8BB289E5 48 8B 94 24 B8 00 00 00 mov         rdx,qword ptr [rsp+0B8h]
00007FFE8BB289ED 4C 8B 84 24 C0 00 00 00 mov         r8,qword ptr [rsp+0C0h]
00007FFE8BB289F5 4C 8B 8C 24 C8 00 00 00 mov         r9,qword ptr [rsp+0C8h]
00007FFE8BB289FD 48 83 C4 68          add         rsp,68h
00007FFE8BB28A01 5F                   pop         rdi
00007FFE8BB28A02 5E                   pop         rsi
00007FFE8BB28A03 5B                   pop         rbx
00007FFE8BB28A04 5D                   pop         rbp
00007FFE8BB28A05 41 5C                pop         r12
00007FFE8BB28A07 41 5D                pop         r13
00007FFE8BB28A09 41 5E                pop         r14
00007FFE8BB28A0B 41 5F                pop         r15
// 这个rax 就是 .CCtor的函数头的地址
00007FFE8BB28A0D 48 FF E0             jmp  rax

jmp rax跳转到了如下:

00007FFE2CFE8888 FF 25 FA 0F 00 00 jmp qword ptr [7FFE2CFE9888h]

7FFE2CFE9888h地址的值是00007FFE8A50C7A0

注意这句代码

static string a ="abcd";

它实际上被编译成了一个函数,当运行到.CCtor的时候,会调用它,然后对它进行赋值abcd

>>> 00007ffe`06ac29e0 55              push    rbp
00007ffe`06ac29e1 4883ec20        sub     rsp,20h
00007ffe`06ac29e5 488d6c2420      lea     rbp,[rsp+20h]
00007ffe`06ac29ea 833d9f410c0000  cmp     dword ptr [00007ffe`06b86b90],0
00007ffe`06ac29f1 7405            je      ConsoleApp3!ConsoleApp3.Program..cctor+0x18 (00007ffe`06ac29f8)
00007ffe`06ac29f3 e8e8a4cd5f      call    coreclr!JIT_DbgIsJustMyCode (00007ffe`6679cee0)
00007ffe`06ac29f8 48bad83000186c020000 mov rdx,26C180030D8h
00007ffe`06ac2a02 488b12          mov     rdx,qword ptr [rdx]
00007ffe`06ac2a05 48b9902e00186c020000 mov rcx,26C18002E90h
00007ffe`06ac2a0f e8fc85bb5f      call    coreclr!JIT_CheckedWriteBarrier (00007ffe`6667b010)
00007ffe`06ac2a14 90              nop
00007ffe`06ac2a15 4883c420        add     rsp,20h
00007ffe`06ac2a19 5d              pop     rbp
00007ffe`06ac2a1a c3              ret

JIT_CheckedWriteBarrier的原型如下:

extern "C" HCIMPL2_RAW(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref)

很明显,他这就是把ref指向的object完整的传递给dst。也就是赋值给静态字符串a。寄存器rcx表示dstrdx表示ref。此处可以通过!dumpobj rdx来查被看对象。

那么总结下,.CCtor的作用就是把静态的全局变量对象进行一个初始化,这个结果也说明,静态全局变量不是在CLR初始化的时候初始化,而是在当前类的.CCtor里面初始化的。

以上就是.Net 7函数Ctor与CCtor使用及区别详解的详细内容,更多关于.Net 7函数Ctor CCtor的资料请关注我们其它相关文章!

(0)

相关推荐

  • .NET中函数Main的使用技巧

    引言 最近在使用pandoc这个文档转换软件,能够对各种文档进行完美的转换,比如从markdown文件转为doc,ppt,tex,odt等等各种,感兴趣的可以从Pandoc下载,对于pandoc的的文档转换,都是使用cmd中的命令来进行操作的.现在我需要把d盘的1123.md文件转换为docx文档,我只需要在cmd中输入下面的命令即可实现. 当然在安装pandoc的时候就已经把pandoc添加到环境变量中了,现在我们来分析一下这个命令pandoc 1123.md -o 1123.doc,将这个命

  • 在ASP.NET 2.0中操作数据之七十三:用Managed Code创建存储过程和用户自定义函数(上部分)

    导言: 数据库,比如Microsoft's SQL Server 2005使用Transact-Structured Query Language (T-SQL)来插入.修改.检索数据.绝大多数数据库系统都包含constructs来对一系列的SQL statements进行分组,这些statements可以作为单独的单元来执行.存储过程就是一个例子,另一个例子是用户自定义函数(UDFs), 我们将在第9步进行详细的探讨. SQL是设计来处理一系列数据的. SELECT,UPDATE,和DELET

  • Asp.Net实现的通用分页函数

    本文实例讲述了Asp.Net实现的通用分页函数.分享给大家供大家参考,具体如下: 功能: 1.每页设置显示9页,超过9页,点5页后的+1页显示(可以随便修改) 2.CSS样式自己可以设置 3.无任何咋代码产生,利于搜索引擎优化 分页程序 objPDS = new PagedDataSource(); objPDS.DataSource = dtTable.DefaultView;//绑定数据源 objPDS.AllowPaging = true; objPDS.PageSize =10;//分页

  • C# ODP.NET 调用Oracle函数返回值时报错的一个解决方案

    有人在社区问到:C#调用Oracle中自定义函数的返回值时,无法正常调用.但在PL/SQL中正常调用返回. 于是动手一试: 1.准备函数(Oralce 11g.2.0.0.4) CREATE OR REPLACE FUNCTION F_Update_Grade(v_UserID in Number) return nvarchar2 is V_Grade nVARCHAR2(20); begin V_Grade := '1205'; update TESTDB3 set Grade = V_Gr

  • 在ASP.NET 2.0中操作数据之七十四:用Managed Code创建存储过程和用户自定义函数(下部分)

    第八步:从表现层调用Managed Stored Procedures 当对数据访问层和业务逻辑层进行扩充以支持调用GetDiscontinuedProducts 和 GetProductsWithPriceLessThan这2种managed stored procedures后,我们可以在一个ASP.NET页面里展示这些存储过程的结果了. 打开AdvancedDAL文件夹里的ManagedFunctionsAndSprocs.aspx页面,从工具箱拖一个GridView控件到设计器,设其ID

  • 在阿里云函数计算上部署.NET Core 3.1的方法

    使用阿里云ECS或者其他常见的VPS服务部署应用的时候,需要手动配置环境,并且监测ECS的行为,做补丁之类的,搞得有点复杂.好在很多云厂商(阿里云.Azure等)提供了Serverless服务,借助于Serverless,开发人员可以更加专注于代码的开发,减少运维的成本. Azure的部署直接集成在了VS中,非常方便,本文主要介绍一下使用ASP.NET CORE 3.1部署在阿里云Serverless(函数计算)的内容. 准备 阿里云的函数计算提供了很多运行库,对.NET的支持现在到ASP.NE

  • .Net 7函数Ctor与CCtor使用及区别详解

    目录 楔子 目的非手段 .Ctor目的 .CCtor目的 楔子 有小伙伴被面试官问到这个问题,本篇彻底解析下这个问题. 为了彻底点,注意本篇是最底层的.Net 7 RC CLR运行模型(汇编)为基础进行全局剖析,局部业务分析. 如有疏漏,请斧正. 目的非手段 这两个函数比较特殊的存在,.Ctor是非静态默认实例化..CCtor是静态默认实例化.这两个函数伴随着.Net任何对象的实例化都自动存在于这个对象当中. 跟踪.CCtor可以在全局静态对象下断点,观察它的里面运行.跟踪.Ctor可以通过!n

  • JQuery的ready函数与JS的onload的区别详解

    JQuery的ready函数与JS的onload的区别:1.执行时间window.onload必须等到页面内包括图片的所有元素加载完毕后才能执行.$(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕. 2.编写个数不同window.onload不能同时编写多个,如果有多个window.onload方法,只会执行一个$(document).ready()可以同时编写多个,并且都可以得到执行 3.简化写法window.onload没有简化写法$(document).r

  • js中字符串编码函数escape()、encodeURI()、encodeURIComponent()区别详解

    JavaScript中有三个可以对字符串编码的函数,分别是: escape,encodeURI,encodeURIComponent,相应3个解码函数: unescape,decodeURI,decodeURIComponent . 下面简单介绍一下它们的区别 1 escape()函数 定义和用法 escape() 函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串. 语法 escape(string) 参数 描述 string 必需.要被转义或编码的字符串. 返回值 已编码的 st

  • Python中正则表达式match()、search()函数及match()和search()的区别详解

    match()和search()都是python中的正则匹配函数,那这两个函数有何区别呢? match()函数只检测RE是不是在string的开始位置匹配, search()会扫描整个string查找匹配, 也就是说match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回none 例如: #! /usr/bin/env python # -*- coding=utf-8 -*- import re text = 'pythontab' m = re.ma

  • Python中函数eval和ast.literal_eval的区别详解

    前言 众所周知在Python中,如果要将字符串型的list,tuple,dict转变成原有的类型呢? 这个时候你自然会想到eval. eval函数在python中做数据类型的转换还是很有用的.它的作用就是把数据还原成它本身或者是能够转化成的数据类型.下面来看看示例代码: string <==> list string <==> tuple string <==> dict 也就是说,使用eval可以实现从元祖,列表,字典型的字符串到元祖,列表,字典的转换,此外,eval

  • oracle中函数 trunc(),round(),ceil(),floor的使用详解

    1.round函数(四舍五入) 描述 : 传回一个数值,该数值是按照指定的小数位元数进行四舍五入运算的结果 参数: number : 欲处理之数值 decimal_places : 四舍五入 , 小数取几位 ( 预设为 0 ) select round(123.456, 0) from dual: 返回123 select round(123.456, 1) from dual; 返回123.5 select round(-123.456, 2) from dual; 返回-123.46 2.c

  • C++ 类中有虚函数(虚函数表)时 内存分布详解

    虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,保证其容真实反应实际的函数.这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数. 这里我们着重看一下这张虚函数表.C++的编译器应该是

  • 对python中不同模块(函数、类、变量)的调用详解

    首先,先介绍两种引入模块的方法. 法一:将整个文件引入 import 文件名 文件名.函数名( ) / 文件名.类名 通过这个方法可以运行另外一个文件里的函数 法二:只引入某个文件中一个类/函数/变量 需要从某个文件中引入多个函数或变量时,用逗号隔开即可 from 文件名 import 函数名,类名,变量名 接下来,通过一个具体的例子说明引入 模块的具体方法: 假设新建一个python包test2,里边有一个名为run.py的python文件,run.py文件里有一个名为running()的函数

  • python函数声明和调用定义及原理详解

    这篇文章主要介绍了python函数声明和调用定义及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 函数是指代码片段,可以重复调用,比如我们前面文章接触到的type()/len()等等都是函数,这些函数是python的内置函数,python底层封装后用于实现某些功能. 一.函数的定义 在Python中,定义一个函数要使用def语句,依次写出函数名.括号.括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回:

  • C语言rewind与fseek函数之随机读写文件的用法详解

    前面介绍的文件读写函数都是顺序读写,即读写文件只能从头开始,依次读写各个数据.但在实际开发中经常需要读写文件的中间部分,要解决这个问题,就得先移动文件内部的位置指针,再进行读写.这种读写方式称为随机读写,也就是说从文件的任意位置开始读写. 实现随机读写的关键是要按要求移动位置指针,这称为文件的定位. 文件定位函数rewind和fseek 移动文件内部位置指针的函数主要有两个,即 rewind() 和 fseek(). rewind() 用来将位置指针移动到文件开头,前面已经多次使用过,它的原型为

随机推荐