zoukankan      html  css  js  c++  java
  • CLR 编译函数的两种结果的原因

    起因

    1.由于最近可能是太闲了,也可能是自己玩的无聊,于是乎就找了些代码看看。这些代码其实一直看的。发现一个问题,当coreclr 2.2.5编译一个函数的时候,它底层被编译出来的是三级只针对形式,但是在VS里面用用2.1.3编译这个函数的时候,得到的确实二级指针的形式。

    2.为了了解为啥编译的结果,跟vs运行的结果不一样,尝试了很多解决方法,比如仔细查阅源码,多次dump,以及比较fw和core 的区别等等,都没有找到答案。而这些结果千篇一律的都是二级指针,不是coreclr里面编译的三级指针结果。在网路上的大部分文章也是偏向于二级指针,有一个是三级指针形式,但是很久远了。


    3.最后灵机一动是不是clr版本不同,所以编译的结果不同,因为观察编译用的是2.2.5而vs编译是2.1.3.这两个明星不同的版本而造成了编译不同的结果呢?
    在仔细阅读代码之后,发现如果MethodDesc的m_bFlags能&2不等于0的话就会调用SetTargetInterlocked这个函数修改前置码fixupprecode来更改函数被编译前后的一个结果


    4.而当他们等于0 的时候,就会调用SetStableEntryPointInterlocked函数,这个函数有点意思的是,它会把函数编译的结果放在MethodDesc的最后面。一个内存模型基本上是 FixupPrecode地址 -》 MethodDescChunk ->MethodDesc....... 。省略号表示多个MethodDesc连接在一起,里面放的它的内容。其实很容易看到,当CLR调用C#函数的时候,他首先会初始化MethodDescChunk块,然后再初始化一个类里面的每个函数,这些函数依次跟在MethodDescChunk后面。而这些函数的前置码呢,就跟在MethodDescChunk的前面。在MethodDescChunk的前面放了这一连串前置码的地址。在初始化MethodTabble的阶段, 会调用如下代码:*pMD->GetAddrOfSlot() = addr;


    这个代码的意思是,把MethodDesc对应的前置码放在MethodDesc内容的最后面。
    放在这个最后面的意思就是,当这个函数被编译了,出现的上面所说的两种情况



    就是更改MethodDesc内容最后的地址,放入函数编译之后的地址。下一次直接获取此地址跳转

    修改前置码,并且通过jmp命令计算出跳转的地址,下一次直接jmp到此地址


    两个的结果其实都是一个意思,就是标志函数被编译,然后跳转到函数被编译的结果,而第二次就不用再来一次繁杂的编译过程。
    但是这个问题在于,coreclr 2.2.5编译的结果是修改MethodDesc内容最后的的地址,而core 2.1.3在VS里面使用被编译的是修改的前置码,那到底那一个正确呢?或者自己漏掉了什么?



    内存模型分析

    首先看一段简单的代码:
    class Program { static void Main(string[] args) { A.Method(); } } class A { public static int Method() { return 777; } }

    要分析内存结构不是一件简单的事情,尤其是从coreclr2.2.5到runtime 6.0 preview 7这个代码的改动非常大。
    如下:

    代码:hr = RunMain(pMeth, 1, &iRetVal, stringArgs); assembly.cpp(1838行)
    这个RunMain函数就是调用了程序入口点Main函数,其后又调用了所需要的分析的函数A.Method()
    因为Method函数的描述符MethodDesc的成员m_bFlgas2的值为0所以&2的话,依旧为零,如果不为零则调用SetTargetInterlocked修改其前置码,如果为零则调用SetStableEntryPointInterlocked修改其函数描述符内容最后的的8个字节。这里是调用后者,因为等于0了。

    `BOOL MethodDesc::SetStableEntryPointInterlocked(PCODE addr)
    {
    CONTRACTL {
    THROWS;
    GC_NOTRIGGER;
    } CONTRACTL_END;

    _ASSERTE(!HasPrecode());
    
    PCODE pExpected = GetTemporaryEntryPoint();
    PTR_PCODE pSlot = GetAddrOfSlot();
    EnsureWritablePages(pSlot);
    
    BOOL fResult = FastInterlockCompareExchangePointer(pSlot, addr, pExpected) == pExpected;
    
    InterlockedUpdateFlags2(enum_flag2_HasStableEntryPoint, TRUE);
    
    return fResult;
    

    }
    `
    这里的FastInterlockCompareExchangePointer就是修改MethodDesc最后的地址。
    但是我们实际上把A.Method()这段代码拿到VS下面反汇编看看结果则是:

    编译前
    0007FFEA3BE5C1D E8 8E FD FF FF call CLRStub[MethodDescPrestub]@7ffea3be59b0 (07FFEA3BE59B0h)
    00007FFEA3AE59B0 E8 FB 48 B7 5F call 00007FFF0365A2B0
    00007FFEA3AE59B5 5E pop rsi
    编译后
    00007FFEA3AE59B0 E9 CB 2E 00 00 jmp ConsoleApp12.A.Method() (07FFEA3AE8880h)
    00007FFEA3AE59B5 5F pop rdi
    注意看,07FFEA3BE59B0h这个地址一直没变,而变的是这个地址里面的值。从07FFEA3BE59B0h值的E8 5E到编译后的E9 5F 可以看出这是二级指针结构。

    而上面说了FastInterlockCompareExchangePointer是个三级指针结构
    image
    我们进到地址0x00007FFEB6976528里面看下
    image
    注意看红色两个半圆圈起来的字节,他们是一模一样的,也就是A.Method函数的前置码。这里会修改后面一个半圆圈起来的前置码,把它的结果改为函数编译后的结果。
    image
    半圆圈起来的红色八字节就是函数编译后的地址



    解决方案

    1.经过各方面尝试,最终确定是不是因为coreclr的版本不同导致的。刚好.net runtime 6.0 preview 7 发布了,不如尝试下这个版本的编译结果。

    2.首先设置好几个参数
    项目-》属性-》调试
    命令:C: untime-6.0.0-preview.7.21377.19artifactsincoreclrwindows.x64.Debugcorerun.exe
    命令参数:ConsoleApp12.dll(注意不要填写 -V ConsoleApp12.dll不能被编译通过)
    工作目录:........incoreclrwindows.x64.Debug
    环境:CORE_LIBRARIES=C:Program FilesdotnetsharedMicrosoft.NETCore.App6.0.0-preview.5.21301.5

    3.最后由于build.cmd并没有生成CoreCLR.sln.所以还得手动生成下命令如下:build.cmd -vs coreclr.sln -a x64 -c Debug
    然后就可以在目录C: untime-6.0.0-preview.7.21377.19artifactsobjcoreclrwindows.x64.Debugide下面找到CoreCLR.sln项目打开它就可以了

    4.依据上面的MethodDesc的m_bFlag2的成员变量可以看到在 runtime 6.0 Preivew 7里面这个值是3 他&2 刚好不等于0。这样的一个逻辑就导致了它会调用SetTargetInterlocked整个函数,修改前置码。这样的话,所有的东西源码编译结果都对上号了。至此基本可以确定就是版本的不同造成的。

  • 相关阅读:
    关于Oracle过程,函数的经典例子及解析
    describeType的使用
    Flash Pro CS5无法跳过注册Adobe ID的问题
    DOM的滚动
    Flex的LogLogger类
    浏览器无法打开Google服务
    as3中颜色矩阵滤镜ColorMatrixFilter的使用
    仿Google+相册的动画
    Flex中ModuleManager的一个bug
    有序的组合
  • 原文地址:https://www.cnblogs.com/tangyanzhi1111/p/15258998.html
Copyright © 2011-2022 走看看