atexit problems on statics inside mixed assembly after moving to VS2005

A

Aek

We recently moved our large codebase over from VS7 to 8 and found that
we now get access violations in atexit calls at shutdown when
debugging the application in VS2005.
This occurs in static members / singletons (especially meyer type
singletons) which use locally declared static variables. These
variables are normally cleaned up automatically at shutdown of the
application by registering with the atexit. I break point the
destructor on a problem singleton class and notice that the destructor
of this static is called automatically *twice* with atexit. The second
call will cause the crash.

first automatic destruction (call stack):
ExecutableD.exe!GE::CShadowObjects::~CShadowObjects() Line 31 C++
ExecutableD.exe!`GE::CShadowObjects::GetInstance'::`2'::`dynamic
atexit destructor for 'g_ShadowObjects''() + 0xd bytes C++

second automatic destruction (call stack):
ExecutableD.exe!GE::CShadowObjects::~CShadowObjects() Line 31 C++
ExecutableD.exe!`GE::CShadowObjects::GetInstance'::`2'::`dynamic
atexit destructor for 'g_ShadowObjects''() + 0xd bytes C++

We notice that this singleton is implemented using a meyers singleton
that has a local static in the GetInstance() method and no specific
destroy call is needed. When I change the singleton to one that
contains a pointer to class and then call new in the GetInstance, then
it is fine. However I dont want to change tonnes of seemingly valid
code just to work around this crash.

We have no visible problems when we are running the application
outside of the IDE.

I believe it is a similar problem to this one described here:
http://groups.google.com.au/group/m...exit+crashes+static+local+VS2005&rnum=3&hl=en

I think that perhaps it has something to do with the mix managed /
unmanaged code or perhaps it has something to do with using /
clr:blush:ldSyntax and Managed Extensions for C++.

This page ( http://msdn2.microsoft.com/en-us/library/ms173268(VS.80).aspx
) suggests that the /clr option will provide support for "static
initialization of C++ variables in mixed images". Is this also true
then for /clr:blush:ldSyntax ?

We have a number of native C++ libs (no clr) that are then linked into
an executable project built with /clr:blush:ldSyntax. The executable
project contains Managed Extension C++ (hence the need for the
oldSyntax flag)
Ideally we will eventually get rid of Managed Extensions for C++ and
port to C++/CLI.
My main question is:
Could use of oldSyntax and Managed Extensions be a cause of the atexit
problems with static variables in a mixed assembly?

Any other suggestions?

Thankyou for your time
Josh
 
C

Carl Daniel [VC++ MVP]

Aek said:
My main question is:
Could use of oldSyntax and Managed Extensions be a cause of the atexit
problems with static variables in a mixed assembly?

It could be. Shouldn't be, but it's definitely the case that /clr was
tested a lot more carefully than /clr:blush:ldSyntax for VS2005.
Any other suggestions?

Contact PSS. It sounds like you're looking at something complex and subtle
that may require inside knowledge to fully diagnose and understand.

-cd
 
B

Ben Voigt [C++ MVP]

Aek said:
We recently moved our large codebase over from VS7 to 8 and found that
we now get access violations in atexit calls at shutdown when
debugging the application in VS2005.
This occurs in static members / singletons (especially meyer type
singletons) which use locally declared static variables. These
variables are normally cleaned up automatically at shutdown of the
application by registering with the atexit. I break point the

Are you doing the atexit registration in code, or the compiler is doing it
automatically?
 
B

Ben Voigt [C++ MVP]

Aek said:
The compiler is generating the atexit registration automatically.

Can you place a breakpoint on atexit and see if the cleanup code is
registered twice?

Are there multiple static objects in the same function, of which it would be
possible that only some are fully constructed due to exceptions?
 
A

Aek

The atexit registration is automatically generated by the compiler.
After some experimenting yesterday, I found that if I rebuild one of
my problematic native C++ libs with the /clr:blush:ldSyntax flag then the
problems with the statics in that lib are resolved. Problem is though
that one of the native libs is a graphics engine and it goes from real
time to slower than powerpoint with /clr enabled in the lib, so I
would like to leave the native C++ libs without clr.

Thankyou
Josh
 
A

Aek

Can you place a breakpoint on atexit and see if the cleanup code is
registered twice?

Yes I can, I just need to find where it is. Ill give it a go today.
Are there multiple static objects in the same function, of which it would be
possible that only some are fully constructed due to exceptions?

No, generally the problem is seen in functions that only have a single
static in the function. There seems to be the same problem with all
locally declared statics in native C++ libs (not just 1 case), so I do
not believe exceptions are occuring causing only some statics to be
fully constructed.

Thanks for your help so far.
Josh
 
B

Ben Voigt [C++ MVP]

Aek said:
Yes I can, I just need to find where it is. Ill give it a go today.


No, generally the problem is seen in functions that only have a single
static in the function. There seems to be the same problem with all
locally declared statics in native C++ libs (not just 1 case), so I do
not believe exceptions are occuring causing only some statics to be
fully constructed.

Ok, that was the only reason I could think of for having more than one
cleanup handler executed for the same function.

Oh... is there any possibility that the singleton is accessed from other
atexit handlers/static destructors?
 
B

Ben Voigt [C++ MVP]

Aek said:
The atexit registration is automatically generated by the compiler.
After some experimenting yesterday, I found that if I rebuild one of
my problematic native C++ libs with the /clr:blush:ldSyntax flag then the
problems with the statics in that lib are resolved. Problem is though
that one of the native libs is a graphics engine and it goes from real
time to slower than powerpoint with /clr enabled in the lib, so I
would like to leave the native C++ libs without clr.

Can you open your assembly with .NET Reflector and make sure that the
SuppressUnmanagedCodeSecurityAttribute is applied?
 
A

Aek

Can you place a breakpoint on atexit and see if the cleanup code is
registered twice?

I stepped through the local static creation and can confirm that the
atexit code for the local static is _not_ being called twice, but the
destructor is called twice on the same object.

-Josh
 
A

Aek

I stepped through the local static creation and can confirm that the
atexit code for the local static is _not_ being called twice, but the
destructor is called twice on the same object.

-Josh

Sorry I should clarify.
The atexit registration code ( __cdecl atexit ( _PVFV func ) is not
being called more than once on each local static,
but the atexit destruction is called twice on the same static objects
that have previously registered using __cdecl atexit ( _PVFV func )

thanks
Josh
 
A

Aek


I found something interesting today that I overlooked before.
The call stack of the atexit calls is showing _cexit calls coming from
different assemblies.
Sorry if this callstack cut and paste comes out a garbled mess in your
reader.. it basically shows that
ConsoleServer_d.dll is calling msvcr80d.dll!_cexit(), which cleans up
the local static... then
ExecuteD.exe calls msvcr80d.dll!_cexit() which again tries to clean up
the local static.

Interesting. ExecuteD.exe is what is run and consoleserver_d.dll is a
dll that it uses. At least now I have somewhere to focus my attention.

Feel free to drop in any thoughts you have about this now, with this
new information.
ExecuteD.exe!OSMonitor::~OSMonitor() Line 35 C++
ExecuteD.exe!`OSMonitor::GetInstance'::`2'::`dynamic atexit
destructor for 'instance''() + 0xd bytes C++
msvcr80d.dll!doexit(int code=0, int quick=0, int retcaller=1) Line
553 C
msvcr80d.dll!_cexit() Line 413 + 0xb bytes C
[External Code]
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void*
cookie = <undefined value>) Line 713 C++
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain()
Line 729 + 0x8 bytes C++
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^
source = 0x048ef89c, System::EventArgs^ arguments = <undefined value>)
Line 762 C++
msvcm80d.dll!
<CrtImplementationDetails>::ModuleUninitializer::SingletonDomainUnload(System::Object^
source = 0x048ef8bc, System::EventArgs^ arguments = <undefined value>)
Line 303 + 0x57 bytes C++
[Frames below may be incorrect and/or missing, no symbols loaded for
mscorwks.dll]
ntdll.dll!7c90e57c()
kernel32.dll!7c80a027()
kernel32.dll!7c80b683()

ExecuteD.exe!OSMonitor::~OSMonitor() Line 35 C++
ExecuteD.exe!`OSMonitor::GetInstance'::`2'::`dynamic atexit
destructor for 'instance''() + 0xd bytes C++
msvcr80d.dll!doexit(int code=0, int quick=0, int retcaller=1) Line
553 C
msvcr80d.dll!_cexit() Line 413 + 0xb bytes C
[External Code]
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void*
cookie = <undefined value>) Line 713 C++
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain()
Line 729 + 0x8 bytes C++
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^
source = 0x048ef89c, System::EventArgs^ arguments = <undefined value>)
Line 762 C++
msvcm80d.dll!
<CrtImplementationDetails>::ModuleUninitializer::SingletonDomainUnload(System::Object^
source = 0x048ef8bc, System::EventArgs^ arguments = <undefined value>)
Line 303 + 0x57 bytes C++
[Frames below may be incorrect and/or missing, no symbols loaded for
mscorwks.dll]
ntdll.dll!7c90e57c()
kernel32.dll!7c80a027()
kernel32.dll!7c80b683()

Regards,
Josh
 
A

Aek

Can you open your assembly with .NET Reflector and make sure that the
SuppressUnmanagedCodeSecurityAttribute is applied?

The SuppressUnmanagedCodeSecurityAttribute was not applied to the dll
(ConsoleServerD.dll) or the executable (ExecuteD.exe)
I have applied it now and will investigate the results.

Thanks
Josh
 
B

Ben Voigt [C++ MVP]

Aek said:
I found something interesting today that I overlooked before.
The call stack of the atexit calls is showing _cexit calls coming from
different assemblies.
Sorry if this callstack cut and paste comes out a garbled mess in your
reader.. it basically shows that
ConsoleServer_d.dll is calling msvcr80d.dll!_cexit(), which cleans up
the local static... then
ExecuteD.exe calls msvcr80d.dll!_cexit() which again tries to clean up
the local static.

This seems very important.

I think I'm actually running into a related problem.... the "attempted to
initialize CRT more than once" runtime error. I was looking through the
source code where that error message was generated, and found comments
talking about process-wide state which looked very module-local to me (there
was no __declspec dllimport). Let me see if I can find the particular spot
again.
Interesting. ExecuteD.exe is what is run and consoleserver_d.dll is a
dll that it uses. At least now I have somewhere to focus my attention.

Feel free to drop in any thoughts you have about this now, with this
new information.
ExecuteD.exe!OSMonitor::~OSMonitor() Line 35 C++
ExecuteD.exe!`OSMonitor::GetInstance'::`2'::`dynamic atexit
destructor for 'instance''() + 0xd bytes C++
msvcr80d.dll!doexit(int code=0, int quick=0, int retcaller=1) Line
553 C
msvcr80d.dll!_cexit() Line 413 + 0xb bytes C
[External Code]
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void*
cookie = <undefined value>) Line 713 C++
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain()
Line 729 + 0x8 bytes C++
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^
source = 0x048ef89c, System::EventArgs^ arguments = <undefined value>)
Line 762 C++
msvcm80d.dll!
<CrtImplementationDetails>::ModuleUninitializer::SingletonDomainUnload(System::Object^
source = 0x048ef8bc, System::EventArgs^ arguments = <undefined value>)
Line 303 + 0x57 bytes C++
[Frames below may be incorrect and/or missing, no symbols loaded for
mscorwks.dll]
ntdll.dll!7c90e57c()
kernel32.dll!7c80a027()
kernel32.dll!7c80b683()

ExecuteD.exe!OSMonitor::~OSMonitor() Line 35 C++
ExecuteD.exe!`OSMonitor::GetInstance'::`2'::`dynamic atexit
destructor for 'instance''() + 0xd bytes C++
msvcr80d.dll!doexit(int code=0, int quick=0, int retcaller=1) Line
553 C
msvcr80d.dll!_cexit() Line 413 + 0xb bytes C
[External Code]
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void*
cookie = <undefined value>) Line 713 C++
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain()
Line 729 + 0x8 bytes C++
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^
source = 0x048ef89c, System::EventArgs^ arguments = <undefined value>)
Line 762 C++
msvcm80d.dll!
<CrtImplementationDetails>::ModuleUninitializer::SingletonDomainUnload(System::Object^
source = 0x048ef8bc, System::EventArgs^ arguments = <undefined value>)
Line 303 + 0x57 bytes C++
[Frames below may be incorrect and/or missing, no symbols loaded for
mscorwks.dll]
ntdll.dll!7c90e57c()
kernel32.dll!7c80a027()
kernel32.dll!7c80b683()

Regards,
Josh
 
B

Ben Voigt [C++ MVP]

Aek said:
I found something interesting today that I overlooked before.
The call stack of the atexit calls is showing _cexit calls coming from
different assemblies.
Sorry if this callstack cut and paste comes out a garbled mess in your
reader.. it basically shows that
ConsoleServer_d.dll is calling msvcr80d.dll!_cexit(), which cleans up
the local static... then
ExecuteD.exe calls msvcr80d.dll!_cexit() which again tries to clean up
the local static.

Interesting. ExecuteD.exe is what is run and consoleserver_d.dll is a
dll that it uses. At least now I have somewhere to focus my attention.

Feel free to drop in any thoughts you have about this now, with this
new information.


These areas may be relevant:

C:\Program Files (x86)\Microsoft Visual Studio
8\VC\crt\src\cmsgs.h(74):#define _RT_CRT_INIT_CONFLICT_TXT "R6031" EOL "-
Attempt to initialize the CRT more than once.\n" \
C:\Program Files (x86)\Microsoft Visual Studio 8\VC\crt\src\crt0msg.c(86):
{ _RT_CRT_INIT_CONFLICT, _RT_CRT_INIT_CONFLICT_TXT},
C:\Program Files (x86)\Microsoft Visual Studio 8\VC\crt\src\crtdll.c(293):
_amsg_exit( _RT_CRT_INIT_CONFLICT);
C:\Program Files (x86)\Microsoft Visual Studio 8\VC\crt\src\crtdll.c(398):
_amsg_exit( _RT_CRT_INIT_CONFLICT);
C:\Program Files (x86)\Microsoft Visual Studio 8\VC\crt\src\crtexe.c(488):
_amsg_exit( _RT_CRT_INIT_CONFLICT);
C:\Program Files (x86)\Microsoft Visual Studio
8\VC\crt\src\rterr.h(83):#define _RT_CRT_INIT_CONFLICT 31 /*
global initialization order conflict */

ExecuteD.exe!OSMonitor::~OSMonitor() Line 35 C++
ExecuteD.exe!`OSMonitor::GetInstance'::`2'::`dynamic atexit
destructor for 'instance''() + 0xd bytes C++
msvcr80d.dll!doexit(int code=0, int quick=0, int retcaller=1) Line
553 C
msvcr80d.dll!_cexit() Line 413 + 0xb bytes C
[External Code]
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void*
cookie = <undefined value>) Line 713 C++
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain()
Line 729 + 0x8 bytes C++
ConsoleServer_d.dll!
<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^
source = 0x048ef89c, System::EventArgs^ arguments = <undefined value>)
Line 762 C++
msvcm80d.dll!
<CrtImplementationDetails>::ModuleUninitializer::SingletonDomainUnload(System::Object^
source = 0x048ef8bc, System::EventArgs^ arguments = <undefined value>)
Line 303 + 0x57 bytes C++
[Frames below may be incorrect and/or missing, no symbols loaded for
mscorwks.dll]
ntdll.dll!7c90e57c()
kernel32.dll!7c80a027()
kernel32.dll!7c80b683()

ExecuteD.exe!OSMonitor::~OSMonitor() Line 35 C++
ExecuteD.exe!`OSMonitor::GetInstance'::`2'::`dynamic atexit
destructor for 'instance''() + 0xd bytes C++
msvcr80d.dll!doexit(int code=0, int quick=0, int retcaller=1) Line
553 C
msvcr80d.dll!_cexit() Line 413 + 0xb bytes C
[External Code]
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void*
cookie = <undefined value>) Line 713 C++
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain()
Line 729 + 0x8 bytes C++
ExecuteD.exe!
<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^
source = 0x048ef89c, System::EventArgs^ arguments = <undefined value>)
Line 762 C++
msvcm80d.dll!
<CrtImplementationDetails>::ModuleUninitializer::SingletonDomainUnload(System::Object^
source = 0x048ef8bc, System::EventArgs^ arguments = <undefined value>)
Line 303 + 0x57 bytes C++
[Frames below may be incorrect and/or missing, no symbols loaded for
mscorwks.dll]
ntdll.dll!7c90e57c()
kernel32.dll!7c80a027()
kernel32.dll!7c80b683()

Regards,
Josh
 
B

Ben Voigt [C++ MVP]

Aek said:
I found something interesting today that I overlooked before.
The call stack of the atexit calls is showing _cexit calls coming from
different assemblies.
Sorry if this callstack cut and paste comes out a garbled mess in your
reader.. it basically shows that
ConsoleServer_d.dll is calling msvcr80d.dll!_cexit(), which cleans up
the local static... then
ExecuteD.exe calls msvcr80d.dll!_cexit() which again tries to clean up
the local static.

Interesting. ExecuteD.exe is what is run and consoleserver_d.dll is a
dll that it uses. At least now I have somewhere to focus my attention.

You are linking both with /MDd, right?
 
A

Aek

Yeah they are both /MDd

Josh

We came to a resolution on this issue. We found that once the
offending C++ DLL that used managed extensions was removed from the
main application, then the problem went away.
Luckily the managed dll didnt do that much, and was mostly prototype
code for a legacy task, so removing it was the best option. We have
other managed dlls that we continue to use, but there was something
specific about how this one was built or used that caused the problem.

We still have not found the root cause of the issue. There is likely
someway that we are building/configuring or using this managed C++ dll
that is causing it to automatically cleanup native types registered
with atexit in a different assembly.

Why the dll calls the CRT cleanup code and the fact that the main
executable does it again is odd, considering that there are a number
of checks in place around that automatic cleanup code to avoid this
problem.

I will spend a few hours today trying to create a small reproducible
case and if the root cause is not clear, perhaps I can post it here
for examination.

Thanks for all your help.
Regards,
Josh Stewart
 
A

Aek

We came to a resolution on this issue. We found that once the
offending C++ DLL that used managed extensions was removed from the
main application, then the problem went away.
Luckily the managed dll didnt do that much, and was mostly prototype
code for a legacy task, so removing it was the best option. We have
other managed dlls that we continue to use, but there was something
specific about how this one was built or used that caused the problem.

We still have not found the root cause of the issue. There is likely
someway that we are building/configuring or using this managed C++ dll
that is causing it to automatically cleanup native types registered
with atexit in a different assembly.

Why the dll calls the CRT cleanup code and the fact that the main
executable does it again is odd, considering that there are a number
of checks in place around that automatic cleanup code to avoid this
problem.

I will spend a few hours today trying to create a small reproducible
case and if the root cause is not clear, perhaps I can post it here
for examination.

Thanks for all your help.
Regards,
Josh Stewart

For anyone that is interested in have a look at a small example of the
problem.. I have created a small reproducible example.
The offending DLL has had all of its managed code removed, so I do not
think it is the managed extensions C++ that was causing the problems
with that DLL.
The issue seems to be related to mixing a /clr built executable + dll
with a native C++ (no clr) library.

You can breakpoint in the destructor of the meyer singleton in the
native C++ lib to see it destroy twice.

Here is the example solution:
http://aek.bur.st/work/Example_AtExit_Issue.zip

If anyone has an idea about what the issue here is, please let me
know :)
Cheers,
Josh Stewart
 
A

Aek

For anyone that is interested in have a look at a small example of the
problem.. I have created a small reproducible example.
The offending DLL has had all of its managed code removed, so I do not
think it is the managed extensions C++ that was causing the problems
with that DLL.
The issue seems to be related to mixing a /clr built executable + dll
with a native C++ (no clr) library.

You can breakpoint in the destructor of the meyer singleton in the
native C++ lib to see it destroy twice.

Here is the example solution:http://aek.bur.st/work/Example_AtExit_Issue.zip

If anyone has an idea about what the issue here is, please let me
know :)
Cheers,
Josh Stewart

Someone at work found the solution..

In the managed DLL project, remove /NOENTRY from Linker -> Command
Line -> Additional Options
Note that this is the same as setting "No Entry Point" to "yes" in
Linker -> Advanced.

Seems that the /NOENTRY was left in after converting from 2003 to 2005
project and should not be used in this case.

Still not sure why this affects the local statics in the native
library though.

Cheers,
Josh
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top