Skip to content

Instantly share code, notes, and snippets.

@mgexo
Last active January 24, 2024 13:58
Show Gist options
  • Save mgexo/49c3f8c1c3b65c83dc9c35659a9fc0c1 to your computer and use it in GitHub Desktop.
Save mgexo/49c3f8c1c3b65c83dc9c35659a9fc0c1 to your computer and use it in GitHub Desktop.
AboutMixedModeDllNoEntryCrashInVS2022_17_7.md

About Mixed Mode DLL with /NOENTRY crash since Visual Studio 2022 17.7.0 and the global static initializer problem (2024-01-15)

The problem had been originally reported https://developercommunity.visualstudio.com/t/Visual-Studio-2022-1770-CRT-crash-when/10441303.but also in other posts e.g. here https://developercommunity.visualstudio.com/t/CRT-crash-when-loading-mixed-dll-from-N/10559475#T-ND10570501

After further investigation we found out, that the reason is that we used /NOENTRY in our Managed C++ DLL which worked fine with all compilers before we used VS17.7. Now with VS17.7 there seems to be a change on how the atexit table/functionality in the CRT’s utility.cpp works. The functions __scrt_initialize_onexit_tables and __scrt_dllmain_before_initialize must now be called to work correctly whereas in previous VS versions it seems that this was not the case.

The internal analysis was much more complex and took a few days and advanced debugging tools.

The whole problem is related to "global static initializers" and if and their order when they are initialized. There seems to be no guarantee in Microsoft C++ that they are actually initialized before usage i.e. the function ucrtbased.dll!_register_onexit_function crashed because the global static tables for the onexit code that is used by global static C++ class destructors and by `atexit`` functions were never initialized and the CRT wrote unitialized space.

The problem for other Visual Studio Customers seems to be, that /NOENTRY was there for a reason, because without /NOENTRY the mixed mode DLL used to crash for unknown reasons.

A recent suggested workaround has been posted here: https://developercommunity.visualstudio.com/t/CRT-crash-when-loading-mixed-dll-from-N/10559475#T-N10570501 i.e. a fix from Hans Passant, see: https://stackoverflow.com/questions/41485935/entry-point-for-c-cli-x64-windowsforms-app-vs-2015/41489950#41489950 where he suggested to change the managed entry point to the (compiler mangled): ?mainCRTStartupStrArray@@FYMHP$01EAPE$AAVString@System@@@Z (64-bit only).

Our own solution to avoid those crashes was to get rid of ALL global static initializers in our code but also in any third-party code.

To help the development community that work with VS 2022, here are some notes from me (@mgexo):

  • This is very hard to debug, even with enabled Microsoft Source Server and source codes of CRT (which by the way can be found e.g. in C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.32919\crt\src\vcruntime)
  • To find possible culprits, generate a "Map file" in the Linker process of the .dll (or native .exe) by specifying /MAP:... filename to the linker e.g. add this parameter to the linker command line in Visual Studio /MAP:yourpathandfilename.map
  • In that .map file then search for the tag "$initializer$" (dollar initializer dollar) and typically you can find all static initializers that are run by the CRT when the .dll or .exe is loaded.
  • You may be surprised in some cases about some of them accidentally happening i.e. when some programmer wrote static initializers into header files that were only included.
  • Some of the static initalizers will also creep in through third party linked libraries e.g. from a staticed linked .lib or from the Microsoft .NET framework itself.
  • Tip: You can add your own variable for testing to see if/how it appears in the map file but make sure that it is a non-trivial class e.g. create a static std::vector<std::vector<int>> g_myteststructure; at the top of a .cpp file and then search for it in the .map file.

Recommendation: Get rid of all such non-trivial global static initializers in DLLs that you can. Replace them with on-demand static initializers i.e. function calls that once calls they have a local static variable (also known as "Meyer's Singleton"). This avoids crashes and problems when a .dll (or .exe) is loaded/started and makes the startup faster in general.

Recommended Visual Studio Debugger Settings 296727689-1c854111-bf8c-45ba-b6f3-53dcfe791df5 296727774-c44b6acb-18c0-475e-9c93-880bae5e5f35

Summary: It may happen, that DLLs crash on startup with random errors. This can happen if the Microsoft CRT is not initializing correctly inside the dll when it is started, which is related to the Entry Point see /ENTRY (Entry-Point Symbol). In fact, especially for managed DLLs the entry point normally needs to be empty, to allow correct initialization of the .dll. It then uses for example the _DllMainCRTStartup and _CRT_INIT function that is automatically generated by the compiler see dll_dllmain.obj in the .map file. In some cases however, these are suddenly missing, which for example caused the issue in Visual Studio 2022 17.7.0 CRT crash when loading mixed mode DLL from .NET (regression since 17.6. and any earlier version) -- Developer Community link and the call stack trace above. If it is missing, then important initialization functions like __scrt_initialize_onexit_tables in utility.cpp of the CRT are not getting called and then standard functions like atexit that register a destructor will crash directly or randomly. This may happen if /NOENTRY is defined in the linker settings of the .dll.

Update 2024-01-23: The stories continues, see https://developercommunity.visualstudio.com/t/CRT-crash-when-loading-mixed-dll-from-N/10559475?space=21&sort=newest .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment