다음을 통해 공유


The Curious Case of BOOTUP_EXCEPTION_COMPLUS (0xC0020001) with mixed mode

0xC0020001 known as BOOTUP_EXCEPTION_COMPLUS ,you may see an exception with this code, or an HRESULT of this value, when trying to call into managed code. This can happen if you call in before the runtime(clr) has finished initializing, or after the runtime has started shutting down.Please refer cbrumme’s blog for more info. Al though we have it well documented as to how to code your dlls or exes to avoid such scenarios , still we may end up crashing our application . I had an opportunity to work on such a curious case .

One of our clients had a native exe which was communicating with a .net dll via c++/cli assembly . This exe was crashing whenever they were shutting down there application . Our client specifically mentioned that when they are unloading the dlls we are getting a crash.

The call stack looked something like ,

                 KERNELBASE.dll!_RaiseException@16() + 0x58 bytes
Iamc++cli!__DllMainCRTStartup(void * hDllHandle=0x60650000, unsigned long dwReason=0, void * lpreserved=0x00000001) Line 526 + 0x8 bytes C
Iamc++cli!_DllMainCRTStartup(void * hDllHandle=0x60650000, unsigned long dwReason=0, void * lpreserved=0x00000001) Line 476 + 0xe bytes C
mscoreei.dll!__CorDllMain@12() + 0x136 bytes
mscoree.dll!_ShellShim__CorDllMain@12() + 0xad bytes
ntdll.dll!_LdrpCallInitRoutine@16() + 0x14 bytes
ntdll.dll!_LdrShutdownProcess@0() + 0x141 bytes
ntdll.dll!_RtlExitUserProcess@4() + 0x74 bytes
kernel32.dll!776f79c5()
mscoreei.dll!RuntimeDesc::ShutdownAllActiveRuntimes() + 0x10e bytes
mscoreei.dll!_CorExitProcess@4() + 0x27 bytes
mscoree.dll!_ShellShim_CorExitProcess@4() + 0x94 bytes
msvcr100.dll!___crtCorExitProcess() + 0x29 bytes
msvcr100.dll!___crtExitProcess() + 0xd bytes
msvcr100.dll!__cinit() + 0xb689 bytes
msvcr100.dll!_exit() + 0x11 bytes
helloServer.exe!__tmainCRTStartup() Line 518 C
<kernel32.dll!@BaseThreadInitThunk@12>() + 0x12 bytes
ntdll.dll!___RtlUserThreadStart@8() + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes

     

Yes it does like shutdown code. While we were confident that we can resolve this issue by following certain guidelines , but I wanted to discuss some of the interesting questions that were being raised.

 

 

Why dllmain is getting called when I am already doing a freelibrary in my code ?

Ans: This is a very good question , while freelibrary by its name make us think that it is going to free the library but it is actually going decrement reference count . This is what the documentation says “The system maintains a per-process reference count for each loaded module. A module that was loaded at process initialization due to load-time dynamic linking has a reference count of one. The reference count for a module is incremented each time the module is loaded by a call to LoadLibrary. The reference count is decremented each time the FreeLibrary or FreeLibraryAndExitThread function is called for the module. When a module's reference count reaches zero or the process terminates, the system unloads the module from the address space of the process. Before unloading a library module, the system enables the module to detach from the process by calling the module's DllMain function, if it has one, with the DLL_PROCESS_DETACH value

So this answers our question as to why dllmain is getting called.
We were informed that there were certain static objects that were added to a native function after which he application started crashing .We did track down that function foo.

 

 

 

Although the crash happened after the code changes in native function , why do we need clr for executing a native function ?

Ans: To answer this question we will have to first understand,

        a. Why do we think it’s a native function?
          Because we have used #pragma managed(push, off) for the block of code which also has foo. On further digging we discovered that /clr was also used for the same file which contained foo. So whenever we use /clr for a particular file and still use #pragma managed(push, off) , per our official docs /clr supersedes #pragma managed(push, off) and there all the function would be managed even if there is a #pragma managed(push, off).

Now that we know that /clr is used the foofunction will be in fact managed and any objects inside it will also be managed.

 
        b . Ok , but those objects are not in dllmain , they are in a separate function which is not getting called , if you see the call stack? Why should we crash?
          So whenever dllmain is called there are certain tasks which are executed which include intializing and uninitializing of gloabla and static objects based on whether it is Process Shutdown or Process Intiation , in this case it is Process Shutdown. Even though they are not used in dllmain they are getting uninitialized upon it's call. Hence it will crash as we are touching manged code when clr is already unloaded .
 

I also want to separately call out some of the recommendations with the usage of #pragma managed(push, off).

  • Add the pragma preceding a function but not within a
    function body.
  • Add the pragma after #include statements (do not use these pragmas
    before #include statements).

 To connect the dots, we have a managed function which has static objects which are getting initialized when dllmain is getting called and since clr is already unloaded we are crashing!

 

 

 

Ok, How do I control loading clr or unloading clr at my will ?

Ans: Certainly this option is available, you will have to use clr hosting apis in your application.

For more info look at the below links ,

msdn.microsoft.com/en-us/library/ms404385.aspx

msdn.microsoft.com/en-us/magazine/cc163567.aspx

 

Finally our client had separated the unmanaged code in a separate file and the crash stopped .Hope this article provided you some insight into such issues. Thanks to Gaurav Patole who helped us demystify this issue.

 

Keep Coding!
Nandeesh Swami