诡异的bug异常,(__debugbreak()语句或类似调用)

楔子

项目里面出现一种奇怪的Bug ,直接Debug的时候能正常运行,编译的可执行文件也完全没有问题。当逐步断点调试到某一行到时候,却出现了错误。这个奇怪的错误,搞笑的吗?.

异常地方如下

诡异的bug异常,(__debugbreak()语句或类似调用)

堆栈信息如下

 	KernelBase.dll!wil::details::DebugBreak(void)	未知
>	coreclr.dll!CHECK::Setup(const char * message, const char * condition, const char * file, int line) 行 198	C++
 	coreclr.dll!CLRVectoredExceptionHandlerPhase3(_EXCEPTION_POINTERS * pExceptionInfo) 行 7135	C++
 	coreclr.dll!CLRVectoredExceptionHandlerPhase2(_EXCEPTION_POINTERS * pExceptionInfo) 行 6889	C++
 	coreclr.dll!CLRVectoredExceptionHandler(_EXCEPTION_POINTERS * pExceptionInfo) 行 6856	C++
 	coreclr.dll!CLRVectoredExceptionHandlerShim(_EXCEPTION_POINTERS * pExceptionInfo) 行 7547	C++
 	ntdll.dll!RtlpCallVectoredHandlers()	未知
 	ntdll.dll!RtlDispatchException()	未知
 	ntdll.dll!KiUserExceptionDispatch()	未知
 	coreclr.dll!_hpCodeHdr::GetNumberOfUnwindInfos() 行 331	C++
 	coreclr.dll!_hpCodeHdr::GetUnwindInfo(unsigned int iUnwindInfo) 行 341	C++
 	coreclr.dll!CEEJitInfo::WriteCode(EEJitManager * jitMgr) 行 10883	C++
 	coreclr.dll!UnsafeJitFunction(PrepareCodeConfig * config, COR_ILMETHOD_DECODER * ILHeader, CORJIT_FLAGS flags, unsigned long * pSizeOfCode) 行 12918	C++
 	coreclr.dll!MethodDesc::JitCompileCodeLocked(PrepareCodeConfig * pConfig, ListLockEntryBase<NativeCodeVersion> * pEntry, unsigned long * pSizeOfCode, CORJIT_FLAGS * pFlags) 行 952	C++
 	coreclr.dll!MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig * pConfig, ListLockEntryBase<NativeCodeVersion> * pEntry) 行 823	C++
 	coreclr.dll!MethodDesc::JitCompileCode(PrepareCodeConfig * pConfig) 行 763	C++
 	coreclr.dll!MethodDesc::PrepareILBasedCode(PrepareCodeConfig * pConfig) 行 426	C++
 	coreclr.dll!MethodDesc::PrepareCode(PrepareCodeConfig * pConfig) 行 323	C++
 	coreclr.dll!CodeVersionManager::PublishVersionableCodeIfNecessary(MethodDesc * pMethodDesc, CallerGCMode callerGCMode, bool * doBackpatchRef, bool * doFullBackpatchRef) 行 1698	C++
 	coreclr.dll!MethodDesc::DoPrestub(MethodTable * pDispatchingMT, CallerGCMode callerGCMode) 行 2109	C++
 	coreclr.dll!PreStubWorker(TransitionBlock * pTransitionBlock, MethodDesc * pMD) 行 1938	C++
 	coreclr.dll!ThePreStub()	未知 	00007ffca072040b()	未知 	000001d312125d88()	未知 	000001d312125d48()	未知 	cccccccccccccccc()	未知 	cccccccccccccccc()	未知 	0000006f03f7d6e8()	未知

很明显错误是出现coreclr.dll!_hpCodeHdr::GetNumberOfUnwindInfos()这一段代码,这段代码函数内容如下:

  PTR_RUNTIME_FUNCTION    GetUnwindInfo(UINT iUnwindInfo)
    {
        SUPPORTS_DAC;        _ASSERTE(iUnwindInfo < GetNumberOfUnwindInfos());
        return dac_cast<PTR_RUNTIME_FUNCTION>(
            PTR_TO_MEMBER_TADDR(RealCodeHeader, pRealCodeHeader, unwindInfos) + iUnwindInfo * sizeof(T_RUNTIME_FUNCTION));
    }#if defined(FEATURE_EH_FUNCLETS)
    UINT                    GetNumberOfUnwindInfos()
    {
        SUPPORTS_DAC;
        return pRealCodeHeader->nUnwindInfos;
    }

GetUnwindInfo函数调用了GetNumberOfUnwindInfos,后者里面报了异常。原因在于pRealCodeHeader的值为零。所以它报了异常。
这个pRealCodeHeader来自何处呢?是谁给他赋值的呢?导致了运行没有错误,逐步调试却报错。


 

推究

通过跟踪得知,调用GetUnwindInfo函数的是WriteCode函数,在WriteCode调用GetUnwindInfo函数之前调用了WriteCodeBytes函数。其代码如下:

void CEEJitInfo::WriteCodeBytes(){
    LIMITED_METHOD_CONTRACT;#ifdef USE_INDIRECT_CODEHEADER
    if (m_pRealCodeHeader != NULL)
    {        // Restore the read only version of the real code header
        m_CodeHeaderRW->SetRealCodeHeader(m_pRealCodeHeader);
        m_pRealCodeHeader = NULL;
    }#endif // USE_INDIRECT_CODEHEADER

    if (m_CodeHeaderRW != m_CodeHeader)
    {        ExecutableWriterHolder<void> codeWriterHolder((void *)m_CodeHeader, m_codeWriteBufferSize);        memcpy(codeWriterHolder.GetRW(), m_CodeHeaderRW, m_codeWriteBufferSize);
    }
}

这里面非常简单,主要做了两件事情,第一件事是实例化一个ExecutableWriterHolder的实例codeWriterHolder。注意看实例化codeWriterHolder的参数m_CodeHeader,它里面正是包含了上面异常空值pRealCodeHeader字段。只要知道m_CodeHeader是谁赋值的,就可以找出这个空值的源头了。

codeWriterHolder实例化里面做的事情是调用了MapViewOfFile,这个函数是把文件映射到内存。具体的就是通过一个内存地址(此处称为地址一),转换出另外一个内存地址(此处称为地址二),此后如果地址二的内存地址的值发生了变化,那么地址一内存地址值也会发生变化。

memcpy(codeWriterHolder.GetRW(), m_CodeHeaderRW, m_codeWriteBufferSize);codeWriterHolder里面包含了两个字段,第一个字段是m_CodeHeader的地址,第二个字段是m_CodeHeader被MapViewOfFile转换后的地址。这句代码主要是把m_CodeHeaderRW也就是被RyuJit编译之后的机器码拷贝到被转换的内存地址地方进行存储,同时也会改变前者。

问题就在于,当运行程序单步Debug的时候,这个被转换后的地址里面是有值的,但是未被转换的则是空值,所以异常出现了。

为什么会出现这种情况,其实现在还是没有解决,就当记录下吧。