LiteLDev/LiteLoader.NET

添加捕获C++异常的方法

LazuliKao opened this issue · 9 comments

在.net framework 可以通过HandleProcessCorruptedStateExceptions标注,使得代码中的AccessViolationException和SEHException得以捕获,从而避免崩服
可惜.net6上,这项外星科技被移除了,急需一个能安全执行的方法

需要一个叫做TryCatch的方法,传入一个.net的托管Action对象(委托),或者传入函数指针,在C++中安全的执行,并翻译AccessViolationException和SEHException给.net托管代码

可惜.net6上,这项外星科技被移除了

毕竟是Windows平台限定私货(

Pd233 commented

有时间再做翻译和映射,写不动了(

image
image
要命,PInvoke调用C++库还是炸

测试代码

LLNET.Hook.NativeAPI.TryCatch(() =>
{
    MC.Player p = new(IntPtr.Zero);
    p.TalkAs("test");
}, out _);

还是没能捕获System.AccessViolationException

测试PInvoke在C++库抛出
TESTPlugin.zip

不过纯托管指针操作套上LLNET.Hook.NativeAPI.TryCatch已经安全了

Pd233 commented

感觉如果是经过PInvoke的都捉不到异常,估计是被clr直接拦下来了
想要实现这种东西估计得用点黑魔法(

已找到解决方法

首先Hookcoreclr.dll的LogInfoForFatalError方法

PublicSymbol    ?LogInfoForFatalError@@YAXIPEB_W00@Z
RelativeVirtualAddress  2480668

(这个方法在遭受致命错误后会被clr调用,随即会终止运行时)
在该方法内直接throw 一个C++的异常,就可以在外边抓到异常,而不是走clr的逻辑终止程序

对于如何得到该方法的基址
有以下方法:
1.微软符号服务器获取pdb,解析pdb获取地址(麻烦,首次运行还得下载50MB的pdb
2.提前根据不同编译的coreclr获取地址,通过已有数据做个通用的内存特征码,通过特征码进行内存寻址

打算通过内存寻址实现,部分参考Horion,在fix/CLRFatalError分支

对于如何得到该方法的基址 有以下方法: 1.微软符号服务器获取pdb,解析pdb获取地址(麻烦,首次运行还得下载50MB的pdb 2.提前根据不同编译的coreclr获取地址,通过已有数据做个通用的内存特征码,通过特征码进行内存寻址
打算通过内存寻址实现,部分参考Horion,在FixCLRFatalError分支

或者直接fork一个coreclr?( https://github.com/dotnet/runtime/tree/v6.0.8/src/coreclr

相关函数在这 https://github.com/dotnet/runtime/blob/v6.0.8/src/coreclr/vm/eepolicy.cpp#L345

void LogInfoForFatalError(UINT exitCode, LPCWSTR pszMessage, LPCWSTR errorSource, LPCWSTR argExceptionString)
{
    WRAPPER_NO_CONTRACT;

    static Thread *const FatalErrorNotSeenYet = nullptr;
    static Thread *const FatalErrorLoggingFinished = reinterpret_cast<Thread *>(1);

    static Thread *volatile s_pCrashingThread = FatalErrorNotSeenYet;

    Thread *pThread = GetThreadNULLOk();
    Thread *pPreviousThread = InterlockedCompareExchangeT<Thread *>(&s_pCrashingThread, pThread, FatalErrorNotSeenYet);

    if (pPreviousThread == pThread)
    {
        PrintToStdErrA("Fatal error while logging another fatal error.\n");
        return;
    }
    else if (pPreviousThread != nullptr)
    {
        while (s_pCrashingThread != FatalErrorLoggingFinished)
        {
            ClrSleepEx(50, /*bAlertable*/ FALSE);
        }
        return;
    }

    EX_TRY
    {
        if (exitCode == (UINT)COR_E_FAILFAST)
        {
            PrintToStdErrA("Process terminated. ");
        }
        else
        {
            PrintToStdErrA("Fatal error. ");
        }

        if (errorSource != NULL)
        {
            PrintToStdErrW(errorSource);
            PrintToStdErrA("\n");
        }

        if (pszMessage != NULL)
        {
            PrintToStdErrW(pszMessage);
        }
        else
        {
            // If no message was passed in, generate it from the exitCode
            SString exitCodeMessage;
            GetHRMsg(exitCode, exitCodeMessage);
            PrintToStdErrW((LPCWSTR)exitCodeMessage);
        }

        PrintToStdErrA("\n");

        if (pThread && errorSource == NULL)
        {
            LogCallstackForLogWorker(GetThread());

            if (argExceptionString != NULL) {
                PrintToStdErrW(argExceptionString);
            }
        }
    }
    EX_CATCH
    {
    }
    EX_END_CATCH(SwallowAllExceptions)
    
    InterlockedCompareExchangeT<Thread *>(&s_pCrashingThread, FatalErrorLoggingFinished, pThread);
}

是的就是这个,