.NET 异常处理程序在 Visual C++ 6.0 异常中导致堆栈溢出
.NET exception handler causing stack overflow on Visual C++ 6.0 exceptions
我有一个用 C++ 6.0 编写的旧应用程序的插件。文件按以下方式连接:
- 开头为:C++ 6.0 .exe(第三方应用程序)
- loads: C++ 6.0 simple loader.dll (官方插件)
- 加载:C++ 10.0 简单加载器 .dll(托管 C++/CLI)
- 加载其中之一:包含插件的 C# .NET 4.0 程序集
- 加载:C++ 6.0 .dll 为 C# 插件提供 API 与应用程序对话
问题是,一旦 .NET 4.0 被加载到 C++ 6.0 应用程序中,下次它抛出本机异常时,.NET 使用向量化异常句柄来处理异常,并且失败得很厉害。真正糟糕的部分是向量异常处理程序本身抛出一个异常,然后它尝试处理该异常,但失败了,它陷入无限循环,直到它获得堆栈溢出异常。
堆栈跟踪如下所示:
// The next 7 lines repeat until the stack overflows
clr.dll!CreateHistoryReader()
clr.dll!CreateHistoryReader()
clr.dll!GetMetaDataInternalInterfaceFromPublic()
ntdll.dll!_RtlpCallVectoredHandlers@12()
ntdll.dll!_RtlCallVectoredExceptionHanders@8()
ntdll.dll!_RtlDispatchException@8()
ntdll.dll!_KiUserExceptionDispatcher@8()
// Below is an example exception that causes this:
KernelBase.dll!RaiseException()
rpcrt4.dll!RpcRaiseException()
rpcrt4.dll!I_RpcTransConnectionFreePacket()
rpcrt4.dll!I_RpcBindingInqCurrentModifiedId()
rpcrt4.dll!NdrConformantStringMemorySize()
rpcrt4.dll!NdrComplexStructMarshall()
rpcrt4.dll!SimpleTypeMemorySize()
rpcrt4.dll!NdrClientCall2()
ole32.dll!ServerRegisterClsid(void* hRpc, void* phProcess, _RegInput* pregin, _RegOutput** ppregout unligned long* prpcstat
ole32.dll!CRpcResolver::NotifyStarted(_RegInput* pRegIn, _RegOutput** ppRegOut)
ole32.dll!CClassCache::ResumeProcessClassObjects()
实际上只有两种方法可以解决这个问题,而且都不是很好:
我发现一个简单的程序,如果我在它自己的线程上完全隔离 .NET,非 .NET 线程永远不会 运行 进入这个问题。这在实践中不起作用,因为插件 API 需要对 .NET 插件进行同步回调。
我想出的另一个方法是遍历内存中的每个地址,直到对 "RemoveVectoredExceptionHandler(HANDLE)" 的调用成功并删除 .NET 的矢量异常处理程序。 (我可以通过临时注册自己的VEH并以其句柄作为起点来加快搜索速度)。这往往会破坏本机代码的调试。
有没有更好的方法来处理这个问题?
自从我报告此问题后,CLR 的行为似乎发生了变化。由于 CLR 现在是开源的,因此可以查看幕后发生的事情。
CLR 安装自己的向量化异常处理程序。在向量异常处理期间,它会进行堆栈检查以确保有足够的 space,除非它是堆栈溢出异常。堆栈 space 检查出错,它认为它在 space 之外,但它没有,所以它抛出一个堆栈溢出异常来展开堆栈以进行实际工作。
我能够通过安装 2 个矢量异常处理程序(一个在前,一个在后)来欺骗 .NET 使应用程序不崩溃。如果是导致崩溃的异常类型,我在第一个处理程序中将异常代码更改为 Whosebug,然后在第二个处理程序中将其改回。这样 CLR 认为这是一个堆栈溢出异常并且不会尝试进行堆栈探测。
我有一个用 C++ 6.0 编写的旧应用程序的插件。文件按以下方式连接:
- 开头为:C++ 6.0 .exe(第三方应用程序)
- loads: C++ 6.0 simple loader.dll (官方插件)
- 加载:C++ 10.0 简单加载器 .dll(托管 C++/CLI)
- 加载其中之一:包含插件的 C# .NET 4.0 程序集
- 加载:C++ 6.0 .dll 为 C# 插件提供 API 与应用程序对话
问题是,一旦 .NET 4.0 被加载到 C++ 6.0 应用程序中,下次它抛出本机异常时,.NET 使用向量化异常句柄来处理异常,并且失败得很厉害。真正糟糕的部分是向量异常处理程序本身抛出一个异常,然后它尝试处理该异常,但失败了,它陷入无限循环,直到它获得堆栈溢出异常。
堆栈跟踪如下所示:
// The next 7 lines repeat until the stack overflows
clr.dll!CreateHistoryReader()
clr.dll!CreateHistoryReader()
clr.dll!GetMetaDataInternalInterfaceFromPublic()
ntdll.dll!_RtlpCallVectoredHandlers@12()
ntdll.dll!_RtlCallVectoredExceptionHanders@8()
ntdll.dll!_RtlDispatchException@8()
ntdll.dll!_KiUserExceptionDispatcher@8()
// Below is an example exception that causes this:
KernelBase.dll!RaiseException()
rpcrt4.dll!RpcRaiseException()
rpcrt4.dll!I_RpcTransConnectionFreePacket()
rpcrt4.dll!I_RpcBindingInqCurrentModifiedId()
rpcrt4.dll!NdrConformantStringMemorySize()
rpcrt4.dll!NdrComplexStructMarshall()
rpcrt4.dll!SimpleTypeMemorySize()
rpcrt4.dll!NdrClientCall2()
ole32.dll!ServerRegisterClsid(void* hRpc, void* phProcess, _RegInput* pregin, _RegOutput** ppregout unligned long* prpcstat
ole32.dll!CRpcResolver::NotifyStarted(_RegInput* pRegIn, _RegOutput** ppRegOut)
ole32.dll!CClassCache::ResumeProcessClassObjects()
实际上只有两种方法可以解决这个问题,而且都不是很好:
我发现一个简单的程序,如果我在它自己的线程上完全隔离 .NET,非 .NET 线程永远不会 运行 进入这个问题。这在实践中不起作用,因为插件 API 需要对 .NET 插件进行同步回调。
我想出的另一个方法是遍历内存中的每个地址,直到对 "RemoveVectoredExceptionHandler(HANDLE)" 的调用成功并删除 .NET 的矢量异常处理程序。 (我可以通过临时注册自己的VEH并以其句柄作为起点来加快搜索速度)。这往往会破坏本机代码的调试。
有没有更好的方法来处理这个问题?
自从我报告此问题后,CLR 的行为似乎发生了变化。由于 CLR 现在是开源的,因此可以查看幕后发生的事情。
CLR 安装自己的向量化异常处理程序。在向量异常处理期间,它会进行堆栈检查以确保有足够的 space,除非它是堆栈溢出异常。堆栈 space 检查出错,它认为它在 space 之外,但它没有,所以它抛出一个堆栈溢出异常来展开堆栈以进行实际工作。
我能够通过安装 2 个矢量异常处理程序(一个在前,一个在后)来欺骗 .NET 使应用程序不崩溃。如果是导致崩溃的异常类型,我在第一个处理程序中将异常代码更改为 Whosebug,然后在第二个处理程序中将其改回。这样 CLR 认为这是一个堆栈溢出异常并且不会尝试进行堆栈探测。