2009年10月27日

phoenix的FlowGraph源代码之(1):InstructionKind类型介绍

最近终于把Phoenix的FlowGraph的源代码研究透了,现在发一些笔记上来,有机会再发上来一起讨论源代码。

Member name

Description

SsaPhiInstruction

Instruction kind is SsaPhiInstruction    11

DataInstruction

Instruction kind is DataInstruction      10

LabelInstruction

Instruction kind is LabelInstruction     9

PragmaInstruction

Instruction kind is PragmaInstruction    8

OutlineInstruction

Instruction kind is OutlineInstruction   7

SwitchInstruction

Instruction kind is SwitchInstruction    5

BranchInstruction

Instruction kind is BranchInstruction    4

ValueInstruction

Instruction kind is ValueInstruction     2

UpperSentinel

Max number Instruction kinds             8

CallInstruction

Instruction kind is CallInstruction      6

CompareInstruction

Instruction kind is CompareInstruction   3

Instruction

Instruction kind is Instruction          1

IllegalSentinel

Instruction kind is illegal              0

后面的数字是这个枚举类型的bytes值,一想到这里就要超级鄙视一下写phoenix框架的那些家伙,变量的命名一点都不规范,充满了num1、num2这样的变量名!!

posted @ 2009-10-27 21:25 John Connor 阅读(83) 评论(0) 编辑

2009年10月25日

rotor2.0的runtimetype之谜

不是说Rotor是公开的源码吗?怎么找不到RumtimeType这个源文件啊。

最近在研究它的reflection机制。在一句代码

Type t = cust.GetType();//cust是一个对象
MethodInfo method = t.GetMethod("DoSomething");

我寻根究底找到Type的源文件("C:\sscli20\clr\src\bcl\system"目录下),发现它是个abstrace的类,

并且GetMethod方法调用了GetMethodImpl方法。而GetMethodImpl方法也是一个abstrace。

所以找到它的派生类typedelegator("C:\sscli20\clr\src\bcl\system\reflection"目录下)。我就迷糊了

protected Type typeImpl;

public TypeDelegator(Type delegatingType) {
            if (delegatingType == null)
                throw new ArgumentNullException("delegatingType");
               
            typeImpl = delegatingType;
        }

protected override MethodInfo GetMethodImpl(String name,BindingFlags bindingAttr,Binder binder,
                CallingConventions callConvention, Type[] types,ParameterModifier[] modifiers)
        {
            // This is interesting there are two paths into the impl.  One that validates
            //  type as non-null and one where type may be null.
            if (types == null)
                return typeImpl.GetMethod(name,bindingAttr);
            else
                return typeImpl.GetMethod(name,bindingAttr,binder,callConvention,types,modifiers);
        }

怎么又调用了Type类的GetMethod啊??????why???????????

后来我在rotor里运行了一下我的试验程序。

set COMPlus_JitTrace=1

clix Program.exe

发现

 GetMethodImpl是由RuntimeType类运行的。顺藤摸瓜去找那个类的源文件,发现找不到。反编译mscorlib里面也没有,msdn上也没有。到底是什么回事啊????????

 

后来我无意之中心血来潮,看了了下rotor1.1版本的源文件。我居然发现了runtimetype!why????

posted @ 2009-10-25 11:57 John Connor 阅读(68) 评论(0) 编辑

2009年10月14日

[转]从源代码看.net下exe的加载过程

在看雪学院看到一篇好文章,发上来大家共享共享。
http://bbs.pediy.com/showthread.php?threadid=31799

这里的源代码自然不是指.net Framework的源码,不过微软公开了一个代号为rotor的open source cli的源码,你可以把它看为轻量级的.net framework。最关键的是,它俩的运行机理大致相同。今天,我们就从rotor的源码中看看做为程序调试最基本的exe文件的动态加载。同样,先给出参考文献,免得有人说我抄袭。《inside the rotor cli》,另一本是《shared source cli》,只不过网上搞不到。当然,还要从MSDN的网站下载sscli2.0压缩包。
    和win32下一样,系统会提供一个loader将exe读入,sscli中提供了另一个loader的例子:clix.exe。我们暂且把它看为系统默认的loader,来看源码(clix.cpp),注意红色的代码

代码:
DWORD Launch(WCHAR* pFileName, WCHAR* pCmdLine)
{
    WCHAR exeFileName[MAX_PATH + 1];
    DWORD dwAttrs;
    DWORD dwError;
    DWORD nExitCode;

...
//这里进行一系列文件的属性检查
...
 
    if (dwError != ERROR_SUCCESS) {
        // We can't find the file, or there's some other problem. Exit with an error.
        fwprintf(stderr, L"%s: ", pFileName);
        DisplayMessageFromSystem(dwError);
        return 1;   // error
    }
    nExitCode = _CorExeMain2(NULL, 0, pFileName, NULL, pCmdLine);

    // _CorExeMain2 never returns with success
    _ASSERTE(nExitCode != 0);

    DisplayMessageFromSystem(::GetLastError());

    return nExitCode;
}
    这里我们看到了著名的CorExeMain,还记得用PE编辑文件打开.netPE文件,只引入了一个函数吗?mscoree.dll!_CorExeMain。奇怪,怎么不是_CorExeMain2呢?这只是rotor和商业版的framework的一点区别而已。你可以用IDApro逆一下mscoree.dll,就可以看到_CorExeMain()只不过是一个中转,代码如下

代码:
.text:79011B47                 push    offset a_corexemain ; "_CorExeMain"
.text:79011B4C                 push    [ebp+hModule]   ; hModule
.text:79011B4F                 call    ds:__imp__GetProcAddress@8 ; GetProcAddress(x,x)
.text:79011B55                 test    eax, eax
.text:79011B57                 jz      loc_79019B46
.text:79011B5D                 call    eax
进入后马上就调用了mscorwks.dll的_CorExeMain。而这个函数和rotor中刚才提到的_CorExeMain2提供的功能差不多,就开始exe载入的初始化了。这些都可以从反汇编代码与源代码比较看出来。继续回到sscli中,来看_CorExeMain2()的代码(ceemain.cpp)

代码:
__int32 STDMETHODCALLTYPE _CorExeMain2( // Executable exit code.
    PBYTE   pUnmappedPE,                // -> memory mapped code
    DWORD   cUnmappedPE,                // Size of memory mapped code
    __in LPWSTR  pImageNameIn,          // -> Executable Name
    __in LPWSTR  pLoadersFileName,      // -> Loaders Name
    __in LPWSTR  pCmdLine)              // -> Command Line
{

    // This entry point is used by clix
    BOOL bRetVal = 0;

    //BEGIN_ENTRYPOINT_VOIDRET;

    // Before we initialize the EE, make sure we've snooped for all EE-specific
    // command line arguments that might guide our startup.
    HRESULT result = CorCommandLine::SetArgvW(pCmdLine);

    if (!CacheCommandLine(pCmdLine, CorCommandLine::GetArgvW(NULL))) {
        LOG((LF_STARTUP, LL_INFO10, "Program exiting - CacheCommandLine failed\n"));
        bRetVal = -1;
        goto exit;
    }

    if (SUCCEEDED(result))
        result = CoInitializeEE(COINITEE_DEFAULT | COINITEE_MAIN);

    if (FAILED(result)) {
        VMDumpCOMErrors(result);
        SetLatchedExitCode (-1);
        goto exit;
    }

    // This is here to get the ZAPMONITOR working correctly
    INSTALL_UNWIND_AND_CONTINUE_HANDLER;


    // Load the executable
    bRetVal = ExecuteEXE(pImageNameIn);

...
...
    
    
    大多数代码都可以略过,关键的就两个,一个是初始化ee(execute engine),初始化成功后就调用ExecuteEXE,参数是文件名。这里可以清楚地看到_CorExeMain()的传入参数是什么。ExecuteEXE()的代码不多,也是个跳板:


代码:
BOOL STDMETHODCALLTYPE ExecuteEXE(HMODULE hMod)
{
    STATIC_CONTRACT_GC_TRIGGERS;

    _ASSERTE(hMod);
    if (!hMod)
        return FALSE;

    ETWTraceStartup::TraceEvent(ETW_TYPE_STARTUP_EXEC_EXE);
    TIMELINE_START(STARTUP, ("ExecuteExe"));

    EX_TRY_NOCATCH
    {
        // Executables are part of the system domain
        SystemDomain::ExecuteMainMethod(hMod);
    }
    EX_END_NOCATCH;

    ETWTraceStartup::TraceEvent(ETW_TYPE_STARTUP_EXEC_EXE+1);
    TIMELINE_END(STARTUP, ("ExecuteExe"));

    return TRUE;
}       
 

    同样,关键的代码只有一行,SystemDomain::ExecuteMainMethod(hMod)。其中,字面上看ExecuteMainMethod是将传入的文件作为了一个module,在.net中,如果要以包含关系算的话,assembly > module > class > method。也就是说每一个assembly可能包含多个module,且至少有一个module有且只有一个MainMethod,就是入口方法。
    
    下面转到SystemDomain::ExecuteMainMethod()的代码中(assembly.cpp)
    
代码:
    
INT32 Assembly::ExecuteMainMethod(PTRARRAYREF *stringArgs)
{
    CONTRACTL
    {
        INSTANCE_CHECK;
        THROWS;
        GC_TRIGGERS;
        MODE_ANY;
        ENTRY_POINT;
        INJECT_FAULT(COMPlusThrowOM());
    }
    CONTRACTL_END;

    HRESULT hr = S_OK;
    INT32   iRetVal = 0;

    BEGIN_ENTRYPOINT_THROWS;

    Thread *pThread = GetThread();
    MethodDesc *pMeth;
    {
        // This thread looks like it wandered in -- but actually we rely on it to keep the process alive.
        pThread->SetBackground(FALSE);
    
        GCX_COOP();

        pMeth = GetEntryPoint();
        if (pMeth) {
            RunMainPre();
            hr = ClassLoader::RunMain(pMeth, 1, &iRetVal, stringArgs);
        }
    }

    //RunMainPost is supposed to be called on the main thread of an EXE,
    //after that thread has finished doing useful work.  It contains logic
    //to decide when the process should get torn down.  So, don't call it from
    // AppDomain.ExecuteAssembly()
    if (pMeth) {
        if (stringArgs == NULL)
            RunMainPost();
    }
    else {
        StackSString displayName;
        GetDisplayName(displayName);
        COMPlusThrowHR(COR_E_MISSINGMETHOD, IDS_EE_FAILED_TO_FIND_MAIN, displayName);
    }

    if (FAILED(hr))
        ThrowHR(hr);
    END_ENTRYPOINT_THROWS;
    return iRetVal;
}   
 

    关键的步骤还是两个,准备好线程环境,然后运行Main方法。下面来到clsload.cpp中看ClassLoader::RunMain,这也是这次我们的最后一站。
    
代码:
HRESULT ClassLoader::RunMain(MethodDesc *pFD ,
                             short numSkipArgs,
                             INT32 *piRetVal,
                             PTRARRAYREF *stringArgs /*=NULL*/)
{
    STATIC_CONTRACT_THROWS;
    _ASSERTE(piRetVal);

    DWORD       cCommandArgs = 0;  // count of args on command line
    DWORD       arg = 0;
    LPWSTR      *wzArgs = NULL; // command line args
    HRESULT     hr = S_OK;

    *piRetVal = -1;

    // The exit code for the process is communicated in one of two ways.  If the
    // entrypoint returns an 'int' we take that.  Otherwise we take a latched
    // process exit code.  This can be modified by the app via setting
    // Environment's ExitCode property.
    if (stringArgs == NULL)
        SetLatchedExitCode(0);

    if (!pFD) {
        _ASSERTE(!"Must have a function to call!");
        return E_FAIL;
    }

    CorEntryPointType EntryType = EntryManagedMain;
    ValidateMainMethod(pFD, &EntryType);

    if ((EntryType == EntryManagedMain) &&
        (stringArgs == NULL)) {
        // If you look at the DIFF on this code then you will see a major change which is that we
        // no longer accept all the different types of data arguments to main.  We now only accept
        // an array of strings.

        wzArgs = CorCommandLine::GetArgvW(&cCommandArgs);
        // In the WindowsCE case where the app has additional args the count will come back zero.
        if (cCommandArgs > 0) {
            if (!wzArgs)
                return E_INVALIDARG;
        }
    }


    ETWTraceStartup::TraceEvent(ETW_TYPE_STARTUP_MAIN);
    TIMELINE_START(STARTUP, ("RunMain"));

    EX_TRY_NOCATCH
    {
        MethodDescCallSite  threadStart(pFD);
        
        PTRARRAYREF StrArgArray = NULL;
        GCPROTECT_BEGIN(StrArgArray);

        // Build the parameter array and invoke the method.
        if (EntryType == EntryManagedMain) {
            if (stringArgs == NULL) {
                // Allocate a COM Array object with enough slots for cCommandArgs - 1
                StrArgArray = (PTRARRAYREF) AllocateObjectArray((cCommandArgs - numSkipArgs), g_pStringClass);

                // Create Stringrefs for each of the args
                for( arg = numSkipArgs; arg < cCommandArgs; arg++) {
                    STRINGREF sref = COMString::NewString(wzArgs[arg]);
                    StrArgArray->SetAt(arg-numSkipArgs, (OBJECTREF) sref);
                }
            }
            else
                StrArgArray = *stringArgs;
        }

#ifdef STRESS_THREAD
        OBJECTHANDLE argHandle = (StrArgArray != NULL) ? CreateGlobalStrongHandle (StrArgArray) : NULL;
        Stress_Thread_Param Param = {pFD, argHandle, numSkipArgs, EntryType, 0};
        Stress_Thread_Start (&Param);
#endif

        ARG_SLOT stackVar = ObjToArgSlot(StrArgArray);

        if (pFD->IsVoid()) 
        {
            // Set the return value to 0 instead of returning random junk
            *piRetVal = 0;
            threadStart.Call(&stackVar);
        }
        else 
        {
            *piRetVal = (INT32)threadStart.Call_RetArgSlot(&stackVar);
            if (stringArgs == NULL) 
            {
                SetLatchedExitCode(*piRetVal);
            }
        }

        GCPROTECT_END();

        fflush(stdout);
        fflush(stderr);
    }
    EX_END_NOCATCH

    ETWTraceStartup::TraceEvent(ETW_TYPE_STARTUP_MAIN+1);
    TIMELINE_END(STARTUP, ("RunMain"));

    return hr;
}
    
    这些代码主要是进行方法最终运行前的一些准备,然后运行。分两种,有返回值的和void()的。下面的运行情况就是深入到framework的核心中了,改天看了再写吧。代码中运用了许多COM下的定义,也可见.net和COM关系的密切。就像.net下的Debugger和Profiler甚至直接调用了COM接口来编译。只是我对COM了解不深,无法就此问题深入。

posted @ 2009-10-14 14:13 John Connor 阅读(261) 评论(1) 编辑

第一个SSCLI范例:echo的调试问题

 

今天好不容易把sslic2.0调试好。Hello world!!也终于面试。激动之余,开始了我的sscli之旅。

由于调试sscli的那几天,我没有停歇,直接看sscli2.0的书籍,都是英文版的。烦啊。所以就直接开始了第一个程序echo.cs。看过《Shared Source CLI 2.0 Internals》的朋友都应该知道这个程序吧,是第一个教学程序。Ctrl+cctrl+v。进行调试,运行csc /target:library /debug echo.cs。很简单的啊,我完全没想到这都会报错:

    error CS0583:  Internal Compiler Error (0x80000003 at address 7C921230): likely culprit is 'PARSE'.

    无语啊。后来我想到我进入sscli是运行env的时候用的checked,会不会是这个问题呢?我重新进入,env free;然后再csc /target:library /debug echo.cs。果然成功。看来是freecheckedfastchecked的问题了。

    现在来看看书上对这三个模式的讲解:

One of three different build variants can be established using command-line arguments to the script.

 In the checked build, symbols are generated for debugging and no compiler optimizations are used when building code. Some extra instrumentation is also built into the CLI execution engine. This mode is slow but very useful when debugging.

Free mode, in contrast, is built without debugging instrumentation. It is also built using compiler optimizations so that it can be as fast as possible and will have the best performance of the three variants.

Fastchecked is a compromise between the free and checked: it preserves debug symbols and instrumentation but also uses some compiler optimizations.

 

本人英语比较差,自己翻译了一点点,还是不拿出来献丑了,大家自己看吧。

posted @ 2009-10-14 08:40 John Connor 阅读(67) 评论(1) 编辑

仅列出标题  

导航

统计

公告