当你把一个简单的类.

public sealed class C {
    public static void M() {
    }
}

变成https://sharplab.io/

它翻译成(带我的注释):(source)

C.M()
    L0000: push ebp      /////////////////////
    L0001: mov ebp, esp  // function frame initialization
    L0003: push edi      /////////////////////
    L0004: cmp dword ptr [0x281dc19c], 0 // if (0 == ???)
    L000b: je short L0012  // then: jump to actual method body
    L000d: call 0x727a8790 // else: call ??? what ???
    L0012: nop           // the actual method body
    L0013: nop           // the actual method body
    L0014: pop edi       /////////////////////
    L0015: pop ebp       // function frame teardown/exit
    L0016: ret           /////////////////////

L0004到L000d的用途是什么?

    L0004: cmp dword ptr [0x281dc19c], 0 // if (0 == ???)
    L000b: je short L0012  // then: jump to actual method body
    L000d: call 0x727a8790 // else: call ??? what ??

What is the called function?
Is it terminating the process? Why does C# JIT put this in every method?

我以为它可能与继承有关,但我密封了类并使方法成为静态的,以消除该选项.

这是某种理智的判断吗?就像支票一样:

  • 方法重载
  • 代码损坏
  • 堆栈溢出
  • 分段故障

IL没有给出任何线索:

    .method public hidebysig static 
        void M () cil managed 
    {
        // Method begins at RVA 0x2069
        // Code size 2 (0x2)
        .maxstack 8

        IL_0000: nop
        IL_0001: ret
    }

最新情况:

Thx@dai,将其设置为Release将删除代码.为了确保完全删除不是由空方法主体引起的,我添加了一条简单的语句

    public static void M() {
        System.Console.WriteLine(7);
    }

发布模式下的JIT结果为(source):

C.M()
    L0000: mov ecx, 7
    L0005: call dword ptr [0x10a25768]
    L000b: ret

就像@Dai说的那样,没有提到cmp-je

推荐答案

让我们来玩一个有趣的游戏"这在我的代码中做了什么?"

步骤1:在本地编译

我用csc /define:DEBUG; /debug+ /debug:portable(和其他选项)编译了这个结果.

我将阻塞调用添加到Console.ReadLine(),这样我们就不需要在调试器中设置断点了.

using System;

namespace JitHmm
{
    class Program
    {
        static void Main( string[] args )
        {
            C.M();
        }
    }

    public static class C
    {
        public static void M()
        {
            Console.WriteLine( "Foo" );
            _ = Console.ReadLine();
        }
    }
}

第二步:通过WinDbg+SOS启动程序:


  1. 当程序运行时,它将把"foo"输出到stdout,然后在Console.ReadLine()内等待.

  2. 如果您告诉WinDbg中断,它将显示.NET主线程(apphost)在NtReadFile内等待(因为Console.ReadLine正在try 从标准输入读取).

  3. 别忘了加载符号,所以WinDbg的反汇编窗口将显示call条指令的已解析函数名,而不是原始内存地址.

  4. 打开堆栈跟踪窗口并向上移动到表示M()函数的帧-这将是紧靠在第一个System_Console帧之前的帧.

    • WinDbg似乎没有使用CLR符号在Stack窗口中显示C#方法名称,但如果您运行!DumpStack -EE,那么您将在主命令输出窗口的转储堆栈中看到C.M().
  5. 导航到C.M()函数,反汇编窗口应该显示与Sharplab大致相同的内容:

    00007ffe`9a615eff 005548           add     byte ptr [rbp+48h], dl
    00007ffe`9a615f02 83ec30           sub     esp, 30h
    00007ffe`9a615f05 488d6c2430       lea     rbp, [rsp+30h]
    00007ffe`9a615f0a 33c0             xor     eax, eax
    00007ffe`9a615f0c 8945fc           mov     dword ptr [rbp-4], eax
    00007ffe`9a615f0f 488945f0         mov     qword ptr [rbp-10h], rax
    00007ffe`9a615f13 833d16cb090000   cmp     dword ptr [7FFE9A6B2A30h], 0
    00007ffe`9a615f1a 7405             je      00007FFE9A615F21
    00007ffe`9a615f1c e8cf26c85f       call    00007FFEFA2985F0
    00007ffe`9a615f21 90               nop     
    00007ffe`9a615f22 33c9             xor     ecx, ecx
    00007ffe`9a615f24 894dfc           mov     dword ptr [rbp-4], ecx
    00007ffe`9a615f27 488b0c258030701a mov     rcx, qword ptr [1A703080h]
    00007ffe`9a615f2f e8acffffff       call    00007FFE9A615EE0
    00007ffe`9a615f34 90               nop     
    00007ffe`9a615f35 e836ffffff       call    00007FFE9A615E70
    00007ffe`9a615f3a 488945f0         mov     qword ptr [rbp-10h], rax
    
  6. 指示WinDbg加载符号,在短暂等待之后,您将看到反汇编窗口中的第call 00007FFEFA2985F0行变为call coreclr!JIT_DbgIsJustMyCode (7ffefa2985f0)

  7. 那么,JIT_DbgIsJustMyCode到底是什么呢?

  8. 运行命令!u 00007ffe`9a615f13-它将打印00007ffe`9a615f13处的函数的带注释反汇编,这将向我显示以下内容:

    Normal JIT generated code
    JitHmm.C.M()
    ilAddr is 0000000000592064 pImport is 0000000002BFD460
    Begin 00007FFE9A615F00, size 46
    
    C:\git\_bollocks\JitHmm\Program.cs @ 16:
    00007ffe`9a615f00 55              push    rbp
    00007ffe`9a615f01 4883ec30        sub     rsp,30h
    00007ffe`9a615f05 488d6c2430      lea     rbp,[rsp+30h]
    00007ffe`9a615f0a 33c0            xor     eax,eax
    00007ffe`9a615f0c 8945fc          mov     dword ptr [rbp-4],eax
    00007ffe`9a615f0f 488945f0        mov     qword ptr [rbp-10h],rax
    >>> 00007ffe`9a615f13 833d16cb090000  cmp     dword ptr [00007ffe`9a6b2a30],0
    00007ffe`9a615f1a 7405            je      00007ffe`9a615f21
    00007ffe`9a615f1c e8cf26c85f      call    coreclr!GetCLRRuntimeHost+0x82700 (00007ffe`fa2985f0) (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
    00007ffe`9a615f21 90              nop
    
    C:\git\_bollocks\JitHmm\Program.cs @ 17:
    00007ffe`9a615f22 33c9            xor     ecx,ecx
    00007ffe`9a615f24 894dfc          mov     dword ptr [rbp-4],ecx
    
    C:\git\_bollocks\JitHmm\Program.cs @ 19:
    00007ffe`9a615f27 488b0c258030701a mov     rcx,qword ptr [1A703080h] ("Foo")
    00007ffe`9a615f2f e8acffffff      call    00007ffe`9a615ee0
    00007ffe`9a615f34 90              nop
    
    C:\git\_bollocks\JitHmm\Program.cs @ 20:
    00007ffe`9a615f35 e836ffffff      call    00007ffe`9a615e70
    00007ffe`9a615f3a 488945f0        mov     qword ptr [rbp-10h],rax
    00007ffe`9a615f3e 90              nop
    
    C:\git\_bollocks\JitHmm\Program.cs @ 21:
    00007ffe`9a615f3f 90              nop
    00007ffe`9a615f40 488d6500        lea     rsp,[rbp]
    00007ffe`9a615f44 5d              pop     rbp
    00007ffe`9a615f45 c3              ret
    
  9. 请注意第(JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)部分-that's something we can search on .NET's main GitHub repo

  10. ...它解析为CLR本身内置的这个实际函数:void JIT_DbgIsJustMyCode()

  11. ...which is documented in debug/ee/debugger.hwell as coreclr/vm/jithelpers.cpp:

    // The jit injects probes in debuggable managed methods that look like:
    // if (*pFlag != 0) call JIT_DbgIsJustMyCode.
    // pFlag is unique per-method constant determined by GetJMCFlagAddr.
    // JIT_DbgIsJustMyCode will get the ip & fp and call OnMethodEnter.
    // pIP is an ip within the method, right after the prolog.
    
    // Callback for Just-My-Code probe
    // Probe looks like:
    //  if (*pFlag != 0) call JIT_DbgIsJustMyCode
    // So this is only called if the flag (obtained by GetJMCFlagAddr) is
    //  non-zero.
    
  12. 因此,神秘的cmpje指令对应于if(*pFlag != 0)发出的指令.

  13. 实际的GetJMCFlagAddr功能不是由CLR提供的.


So, what does it do?

它允许您的调试器在程序的执行点到达用户函数(而不是库函数)时得到通知,这就是Visual Studio中的"只是我的代码"调试器选项的工作方式.

虽然我不确定为什么他们这样做,而不是(例如)只使用PDB符号.

Csharp相关问答推荐

在ASP.NET中为数据注释 Select 合适的语言

如何从顶部提取发票号作为单词发票后的第一个匹配

.NET框架4.7.2项目如何引用.NET Core 2.2库?

更改对象的旋转方向

使用yaml将Azure函数代码部署到FunctionApp插槽时出现问题(zip未找到)

图形API基于appid列表检索多个应用程序

MAUI查询参数单一字符串项将不起作用

Quartz调度程序不调用作业(job)类

在ASP.NET Core Web API项目中通过中间件修改`Request.Path`不会更改使用的控制器/操作

交替的奇数

为什么Docker中没有转发该端口?

Blazor Server/.NET 8/在初始加载时调用异步代码是否冻结屏幕,直到第一次异步调用完成?

在C#中,当输入一个方法/局部函数时,我的IEnumerator被重置/重新创建.为什么?

如何在C#.NET桌面应用程序中动态更改焦点工具上的后退 colored颜色

为什么我的属性即使没有显式地设置任何[必需]属性,也会显示验证?

如何更改Datagridview行标题

同时通过多个IEumable<;T&>枚举

Windows 10上埃及标准时间的时区偏移不正确

SendInput无法在C#中正确模拟键盘

C# -根据列表所包含的值的数量组织列表- C#