[Ace-users] Re: [ace-bugs] ACE_Service_Config TSS usage: Improper
TSS usage/memory management
Andrew Keleher
Andrew.Keleher at ca.thalesgroup.com
Fri Jun 22 15:37:37 CDT 2007
Hi Patrick,
I found this same issue back in April and it is being tracked in ACE
bugzilla as bug 2915. Aleksandar Vukajlovic came up with a simple test
program that allows the problem to be reproduced (see below). Aleksandar
also came up with a proposed solution, however the assumption was that
the keys were leaked, and not due to pointer indirection. ACE has yet to
validate Aleksandar's proposed patch.
I have tried to debug the ACE code myself and had no luck...
Aleksandar's analysis is as follows:
1.First not released key is used internaly in ACE_TSS_Cleanup called
"in_use_" key.
Instead of empty destructor
ACE_TSS_Cleanup::~ACE_TSS_Cleanup ()
{
}
there must be
ACE_TSS_Cleanup::~ACE_TSS_Cleanup (void)
{
if (this->in_use_ != ACE_OS::NULL_key)
{
this->free_key (this->in_use_);
}
}
2. Second not released key is ACE_Log_Msg key. According to comments in
function:
void ACE_Log_Msg::close (void) somebody expected that ACE_TSS_Cleanup
will
remove key of ACE_Log_Msg on closing,
but that is not the case (because ACE_TSS_Cleanup singleton maintains
per
thread tls object destruction not a global one )
We need to call ACE_Thread::keyfree (*(log_msg_tss_key ())); after
if (key_created_ == 1)
{
.....
ACE_Thread::keyfree (*(log_msg_tss_key ())); //missing line
}
Sample code to reproduce issue:
#include "ace/ACE.h"
#include "ace/OS_NS_Thread.h"
#include "ace/streams.h"
#define N 1000
void doTest ()
{
for (int i = 0; i < ACE_DEFAULT_THREAD_KEYS + N; ++i)
{
try
{
ACE::init ();
}
catch (...)
{
std::cout << "Unknown exception in ACE::init () - iteration " << i
<< std::endl;
//just to be sure
DWORD out_of_key = ::TlsAlloc ();
if (out_of_key == TLS_OUT_OF_INDEXES)
std::cout << "ACE::init out of TLS " << std::endl;
return;
}
DWORD next_available = ::TlsAlloc ();
::TlsFree (next_available);
std::cout << "Last available TLS key after init = " << next_available
<< " (iteration = "<< i << ")" << std::endl;
try
{
ACE::fini ();
}
catch (...)
{
std::cout << "Unknown exception in ACE::fini () - iteration " << i
<< std::endl;
return;
}
}
}
int main (int argc, char* argv[])
{
doTest ();
return 0;
}
Cheers,
Andrew Keleher
>>> "Bennett, Patrick" <Patrick.Bennett at inin.com> 6/22/2007 3:55 PM
>>>
ACE VERSION: 5.5.7
HOST MACHINE and OPERATING SYSTEM: Windows XP/Vista
COMPILER NAME AND VERSION (AND PATCHLEVEL): VC++ 7.1 SP1
THE $ACE_ROOT/ace/config.h FILE:
#define ACE_DEFINES_DEFAULT_WIN32_SECURITY_ATTRIBUTES
#define ACE_DEFAULT_THREAD_KEYS 128
#include "ace/config-win32.h"
AREA/CLASS/EXAMPLE AFFECTED: ACE_Service_Config and its use of TSS for
pointers to itself (which ACE_TSS can then destroy erroneously)
DOES THE PROBLEM AFFECT:
COMPILATION? No
LINKING? No
EXECUTION? Yes
SYNOPSIS: When ACE is used within a DLL dynamically loaded into another
process that doesn't use ACE, the use of TLS in ACE_Service_Config
causes
the heap to be corrupted the heap during thread-detach cleanup.
DESCRIPTION: ACE_Service_Config uses ACE_TSS to store pointers to
itself
yet ACE_TSS is designed to own all objects assigned to it, deleting
them
as threads are destroyed (via the ACE_OS::cleanup_tss (0); call in
DllMain's DLL_THREAD_DETACH in OS_NS_stdio.cpp).
Since ACE_Service_Config was actually created as an aggregate member
within ACE_Singleton ('new' was called on
ACE_Singleton<ACE_Service_Config> not ACE_Service_Config!), having the
cleanup method in ACE_TSS call delete on the pointer assigned to it
causes
it to try to delete the ACE_Service_Config instance pointer even
though
it
wasn't what was allocated from the heap! With VC++ at least, this
means
the heap is corrupted as a pointer incorrectly offset by 4 bytes is
passed
to delete (it's passing the pointer to the aggregated member instead of
the containing class that was actually allocated off the heap).
ACE_Service_Config has a hack in its destructor to set the
tss_.ts_object(0) value to 0 so that ACE_TSS won't clean it up, but
this
assumes that the ACE_Service_Config created via a static variable at
startup was actually created on the main thread!
Worse yet, it assumes the destructor (object manager cleanup) is
called
on the same thread that called the constructor. But then again, that
all
pales compared to deleting an aggregated instance anyway...
For normal console applications, the destructor will be called
before the thread it was created within actually goes away so the
destructor hack resetting the TSS object will work. However,
if ACE is used by a DLL loaded dynamically by another process, the
ACE_Service_Config global isn't necessarily destroyed before the
thread
it
was created on goes away, so the ts_object isn't reset and
ACE_TSS::cleanup ends up deleting the 'this' pointer that
ACE_Service_Config's constructor set to itself.
Unfortunately, this took way too long to track down. :(
REPEAT BY:
This was 100% repeatable with a test program, but its not something
that
can easily be posted. It was basically an Internet Explorer add-in
loaded
dynamically by IE, and the crash would come as IE terminated the thread
ACE was loaded on.
SAMPLE FIX/WORKAROUND:
The TSS usage within ACE_Service_Config is completely broken.
Not only does it assume that the thread which created the
ACE_Service_Config is the same one that will later access it (if not,
the tss info will be 0), it places pointers to its *aggregated* self
within a class that assumes ownership and will delete them!
Perhaps a wrapper class should be allocated/stored in ACE_TSS instead
that
simply contains a pointer to an ACE_Service_Gestalt and whose
destructor
does nothing. The current() method in ACE_Service_Config should
also be changed to return the 'global' singleton instance if the
tss_ object is 0.
Thanks,
Patrick Bennett
_______________________________________________
ace-bugs mailing list
ace-bugs at mail.cse.wustl.edu
http://mail.cse.wustl.edu/mailman/listinfo/ace-bugs
More information about the Ace-users
mailing list