前言

.Net运行模型,无非就两个过程。一个是调用入口函数,另外一个就是编译入口函数。前者主调用,后者主编译。


概括

一:入口函数:RunMainInternal
所有的.Net程序,包括控制台,Web,窗体等等入口都是Main。而调用这个Main的就是RunMainInternal,从这个函数的名字就可以看出,它的Run就表示是运行Main函数的,而后面的Internal表示是内部的,而非外部所能调用,当然你用一些非常规手段,这个函数还是可以外部的调用的,那是后话。


二:通过LLDB看下它的堆栈

* thread #1, name = 'corerun', stop reason = step over
  * frame #0: 0x00007ffff6d53cdf libcoreclr.so`RunMainInternal
    frame #1: 0x00007ffff6d53c99 libcoreclr.so`RunMain
    frame #2: 0x00007ffff6d4ec76 libcoreclr.so`RunMain
    frame #3: 0x00007ffff6d4ea22 libcoreclr.so`RunMain
    frame #4: 0x00007ffff6d4f030 libcoreclr.so`Assembly::ExecuteMainMethod
    frame #5: 0x00007ffff6dc2b9c libcoreclr.so`CorHost2::ExecuteAssembly
    frame #6: 0x00007ffff6d1a6c1 libcoreclr.so`::coreclr_execute_assembly
    frame #7: 0x000055555556f9f9 corerun`run(config=0x00007fffffffdbc0)
    frame #9: 0x00007ffff7829d90 libc.so.6`__libc_start_call_main
    frame #10: 0x00007ffff7829e40 libc.so.6`__libc_start_main_impl
    frame #11: 0x000055555556c0e5 corerun`_start + 37

可以看到它在Linux平台下面,是通过Glibc OR Newlibc调用的。而它的前面一个函数就是RunMain,这个通过它名字也可以看到它就是运行Main函数的。


三:编译函数PreStubWorker
这个函数顾名思义,预桩工作。就是插桩的意思。每次编译一个函数,都会经过它。它会调用RyuJIT把函数编译成机器码,然后再返回到这个被编译函数的函数头地址运行。
部分代码:

extern "C" PCODE STDCALL PreStubWorker(TransitionBlock* pTransitionBlock, MethodDesc* pMD)
{
    PCODE pbRetVal = NULL;

    BEGIN_PRESERVE_LAST_ERROR;

    STATIC_CONTRACT_THROWS;
    STATIC_CONTRACT_GC_TRIGGERS;
    STATIC_CONTRACT_MODE_ANY;
    STATIC_CONTRACT_ENTRY_POINT;
    // 此处省略一万字
}

可以看到它只是带了一个MethodDesc,也就是函数的描述结构体。通过它可以找到Module,MSIL,然后进行一个编译。


四:看下它的堆栈

 thread #1, name = 'corerun', stop reason = breakpoint 2.1
  * frame #0: 0x00007ffff6ef55d0 libcoreclr.so`::PreStubWorker
    frame #2: 0x00007ffff731e14f libcoreclr.so`CallDescrWorkerInternal
    frame #3: 0x00007ffff6faae98 libcoreclr.so`CallDescrWorkerWithHandler
    frame #4: 0x00007ffff6fabb8d libcoreclr.so`MethodDescCallSite::CallTargetWorker
    frame #5: 0x00007ffff6d2e4b3 libcoreclr.so`MethodDescCallSite::Call
    frame #6: 0x00007ffff6d53f6f libcoreclr.so`RunMainInternal
    frame #7: 0x00007ffff6d53c99 libcoreclr.so`RunMain
    frame #8: 0x00007ffff6d4ec76 libcoreclr.so`RunMain
    frame #9: 0x00007ffff6d4ea22 libcoreclr.so`RunMain
    frame #10: 0x00007ffff6d4f030 libcoreclr.so`Assembly::ExecuteMainMethod
    frame #11: 0x00007ffff6dc2b9c libcoreclr.so`CorHost2::ExecuteAssembly
    frame #12: 0x00007ffff6d1a6c1 libcoreclr.so`::coreclr_execute_assembly
    frame #13: 0x000055555556f9f9 corerun`run(config=0x00007fffffffdbc0) argv=0x00007fffffffddb8) at corerun.cpp:624:21
    frame #15: 0x00007ffff7829d90 libc.so.6`__libc_start_call_main
    frame #16: 0x00007ffff7829e40 libc.so.6`__libc_start_main_impl
    frame #17: 0x000055555556c0e5 corerun`_start + 37

它刚好在RunMainInternal的后面,说明它是在被调用之后,才会进行编译。



结尾

作者:江湖评谈
.Net7 CLR的调用函数和编译函数-LMLPHP

03-17 10:06