简体   繁体   English

混合模式C ++ / CLI崩溃:atexit中的堆损坏(静态析构函数注册)

[英]Mixed-mode C++/CLI crashing: heap corruption in atexit (static destructor registration)

I am working on deploying a program and the codebase is a mixture of C++/CLI and C#. 我正在部署程序,并且代码库是C ++ / CLI和C#的混合体。 The C++/CLI comes in all flavors: native, mixed ( /clr ), and safe ( /clr:safe ). C ++ / CLI具有各种风格:本机,混合( /clr )和安全( /clr:safe )。 In my development environment I create a DLL of all the C++/CLI code and reference that from the C# code (EXE). 在开发环境中,我创建所有C ++ / CLI代码的DLL,并从C#代码(EXE)引用它。 This method works flawlessly. 此方法完美无缺。

For my releases that I want to release a single executable (simply stating that "why not just have a DLL and EXE separate?" is not acceptable). 对于我的版本,我想发布一个可执行文件(简单地指出“为什么不仅仅将DLL和EXE分开?”是不可接受的)。

So far I have succeeded in compiling the EXE with all the different sources. 到目前为止,我已经用所有不同的源成功地编译了EXE。 However, when I run it I get the "XXXX has stopped working" dialog with options to Check online, Close and Debug. 但是,当我运行它时,出现“ XXXX已停止工作”对话框,其中包含用于在线检查,关闭和调试的选项。 The problem details are as follows: 问题的详细信息如下:

Problem Event Name:       APPCRASH
Fault Module Name:        StackHash_8d25
Fault Module Version:     6.1.7600.16559
Fault Module Timestamp:   4ba9b29c
Exception Code:           c0000374
Exception Offset:         000cdc9b
OS Version:               6.1.7600.2.0.0.256.48
Locale ID:                1033
Additional Information 1: 8d25
Additional Information 2: 8d25552d834e8c143c43cf1d7f83abb8
Additional Information 3: 7450
Additional Information 4: 74509ce510cd821216ce477edd86119c

If I debug and send it to Visual Studio, it reports: 如果我调试并将其发送到Visual Studio,它将报告:

Unhandled exception at 0x77d2dc9b in XXX.exe: A heap has been corrupted

Choosing break results in it stopping at ntdll.dll!77d2dc9b() with no additional information. 选择中断将导致它停止在ntdll.dll!77d2dc9b()上,而没有其他信息。 If I tell Visual Studio to continue, the program starts up fine and seems to work without incident, probably since a debugger is now attached. 如果我告诉Visual Studio继续,该程序可以正常启动,并且似乎可以正常运行,这可能是因为现在已连接了调试器。

What do you make of this? 你怎么看的 How do I avoid this heap corruption? 如何避免这种堆损坏? The program seems to work fine except for this. 除此之外,该程序似乎运行良好。

My abridged compilation script is as follows (I have omitted my error checking for brevity): 我的精简编译脚本如下(为简洁起见,我省略了错误检查):

@set TARGET=x86
@set TARGETX=x86
@set OUT=%TARGETX%
@call "%VS90COMNTOOLS%\..\..\VC\vcvarsall.bat" %TARGET%

@set WIMGAPI=C:\Program Files\Windows AIK\SDKs\WIMGAPI\%TARGET%

set CL=/Zi /nologo /W4 /O2 /GS /EHa /MD /MP /D NDEBUG /D _UNICODE /D UNICODE /D INTEGRATED /Fd%OUT%\ /Fo%OUT%\
set INCLUDE=%WIMGAPI%;%INCLUDE%
set LINK=/nologo /LTCG /CLRIMAGETYPE:IJW /MANIFEST:NO /MACHINE:%TARGETX% /SUBSYSTEM:WINDOWS,6.0 /OPT:REF /OPT:ICF /DEFAULTLIB:msvcmrt.lib
set LIB=%WIMGAPI%;%LIB%
set CSC=/nologo /w:4 /d:INTEGRATED /o+ /target:module

:: Compiling resources omitted

@set CL_NATIVE=/c /FI"stdafx-native.h"
@set CL_MIXED=/c /clr /LN /FI"stdafx-mixed.h"
@set CL_PURE=/c /clr:safe /LN /GL /FI"stdafx-pure.h"

@set NATIVE=...
@set MIXED=...
@set PURE=...

cl %CL_NATIVE% %NATIVE%
cl %CL_MIXED% %MIXED%
cl %CL_PURE% %PURE%
link /LTCG /NOASSEMBLY /DLL /OUT:%OUT%\core.netmodule %OUT%\*.obj

csc %CSC% /addmodule:%OUT%\core.netmodule /out:%OUT%\GUI.netmodule /recurse:*.cs

link /FIXED /ENTRY:GUI.Program.Main /OUT:%OUT%\XXX.exe ^
/ASSEMBLYRESOURCE:%OUT%\core.resources,XXX.resources,PRIVATE /ASSEMBLYRESOURCE:%OUT%\GUI.resources,GUI.resources,PRIVATE ^
/ASSEMBLYMODULE:%OUT%\core.netmodule %OUT%\gui.res %OUT%\*.obj %OUT%\GUI.netmodule

Update 1 更新1

Upon compiling this with debug symbols and trying again, I do in fact get more information. 在使用调试符号进行编译并重试之后,实际上我确实获得了更多信息。 The call stack is: 调用堆栈为:

msvcr90d.dll!_msize_dbg(void * pUserData, int nBlockUse)  Line 1511 + 0x30 bytes
msvcr90d.dll!_dllonexit_nolock(int (void)* func, void (void)* * * pbegin, void (void)* * * pend)  Line 295 + 0xd bytes
msvcr90d.dll!__dllonexit(int (void)* func, void (void)* * * pbegin, void (void)* * * pend)  Line 273 + 0x11 bytes
XXX.exe!_onexit(int (void)* func)  Line 110 + 0x1b bytes
XXX.exe!atexit(void (void)* func)  Line 127 + 0x9 bytes
XXX.exe!`dynamic initializer for 'Bytes::Null''()  Line 7 + 0xa bytes
mscorwks.dll!6cbd1b5c()
[Frames below may be incorrect and/or missing, no symbols loaded for mscorwks.dll]
...

The line of my code that 'causes' this (dynamic initializer for Bytes::Null ) is: 我的代码“导致”此行为( Bytes::Null动态初始化器)的行是:

Bytes Bytes::Null;

In the header that is declared as: 在标为的标头中:

class Bytes { public: static Bytes Null; }

I also tried doing a global extern in the header like so: 我还尝试在标头中执行全局外部操作,如下所示:

extern Bytes Null; // header
Bytes Null; // cpp file

Which failed in the same way. 以同样的方式失败了。

It seems that the CRT atexit function is responsible, being inadvertently required due to the static initializer. 似乎CRT atexit函数是负责的,由于静态初始化程序而无意中需要它。


Fix 固定

As Ben Voigt pointed out the use of any CRT functions (including native static initializers) requires proper initialization of the CRT (which happens in mainCRTStartup , WinMainCRTStartup , or _DllMainCRTStartup ). 正如Ben Voigt指出的那样,使用任何CRT函数(包括本机静态初始化器)都需要对CRT进行适当的初始化(这发生在mainCRTStartupWinMainCRTStartup_DllMainCRTStartup )。 I have added a mixed C++/CLI file that has a C++ main or WinMain : 我添加了一个具有C ++ mainWinMain的混合C ++ / CLI文件:

using namespace System;
[STAThread] // required if using an STA COM objects (such as drag-n-drop or file dialogs)
int main() { // or "int __stdcall WinMain(void*, void*, wchar_t**, int)" for GUI applications
    array<String^> ^args_orig = Environment::GetCommandLineArgs();
    int l = args_orig->Length - 1; // required to remove first argument (program name)
    array<String^> ^args = gcnew array<String^>(l);
    if (l > 0) Array::Copy(args_orig, 1, args, 0, l);
    return XXX::CUI::Program::Main(args); // return XXX::GUI::Program::Main(args);
}

After doing this, the program now gets a little further, but still has issues (which will be addressed elsewhere): 完成此操作后,该程序现在会进一步扩展,但是仍然存在问题(将在其他地方解决):

  • When the program is solely in C# it works fine, along with whenever it is just calling C++/CLI methods, getting C++/CLI properties, and creating managed C++/CLI objects 当程序仅使用C#时,它以及调用C ++ / CLI方法,获取C ++ / CLI属性以及创建托管C ++ / CLI对象的任何时候都可以正常工作
  • Events added by C# into the C++/CLI code never fire (even though they should) C#添加到C ++ / CLI代码中的事件不会触发(即使它们应该触发)
  • One other weird error is that an exception happens is a InvalidCastException saying can't cast from X to X (where X is the same as X...) 另一个奇怪的错误是发生了异常,这是一个InvalidCastException,它说不能从X转换为X(其中X与X相同...)

However since the heap corruption is fixed (by getting the CRT initialized) the question is done. 但是,由于修复了堆损坏(通过初始化CRT),因此问题就解决了。

EDIT: Spotted the problem, leaving the suggested debugging steps below in case they help anyone in the future. 编辑:发现了问题,建议的调试步骤在下面,以防将来对任何人有所帮助。

The problem is that you've changed the entry point. 问题是您已经更改了入口点。 You should be using the C++/CLI standard library-provided entry point, which sets up internal resources like the onexit list. 您应该使用C ++ / CLI标准库提供的入口点,该入口点设置诸如onexit列表之类的内部资源。

Remove the /ENTRY switch and write a simple main function that calls your desired startup routine. 删除/ENTRY开关,并编写一个简单的main函数来调用所需的启动例程。


Although using a separate EXE and DLL may not be acceptable for the end product, it would be good to test this simpler configuration and see if you get the same problem. 尽管最终产品可能无法使用单独的EXE和DLL,但是最好测试这种更简单的配置,看看是否遇到相同的问题。

If you can reproduce the heap corruption with a separate .DLL, you know it's somewhere in your native C++ code and it will be much easier to debug without the C# mixed into the same file. 如果您可以使用单独的.DLL重现堆损坏,则可以知道它在本机C ++代码中的某个位置,并且无需将C#混合到同一文件中,调试起来会容易得多。

If you can't reproduce the problem with separate DLL and EXE, then it could be related to the integration process (or it could just be less evident because the layout changes depending on what gets linked). 如果无法用单独的DLL和EXE重现该问题,则可能与集成过程有关(或者可能不太明显,因为布局取决于链接的内容而改变)。

After you find and squash the heap corruption bug, then you can go back to the single .EXE. 找到并压扁堆损坏错误之后,可以返回到单个.EXE。

Another approach would be to build the debug database so you can get better stack traces when it does crash. 另一种方法是构建调试数据库,以便在崩溃时获得更好的堆栈跟踪。 Even release builds (or maybe especially release builds) should be built with debugging info. 甚至发布版本(或尤其是发布版本)也应使用调试信息来构建。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM