@@ -114,20 +114,72 @@ int ExceptionRecursions = -1;
114114DynamicVectorClass<ThreadInfoType*> ThreadList;
115115
116116/*
117- ** Critical section to protect ThreadList from concurrent access.
118- ** This prevents race conditions when threads register/unregister while
119- ** another thread is accessing the list (e.g., during exception handling or shutdown).
117+ ** Returns the CRITICAL_SECTION used to protect ThreadList.
120118**
121- ** Intentionally heap-allocated and never freed: threads may call Unregister_Thread_ID
122- ** after C++ global destructors have run, so a static-lifetime object would already be
123- ** destroyed by that point, causing a use-after-free crash.
119+ ** Allocated from the Windows process heap (not the CRT heap) and never freed.
120+ ** The CRT heap can be torn down during application shutdown before all threads
121+ ** have exited. If a thread calls Unregister_Thread_ID after the CRT heap is
122+ ** destroyed, any CRITICAL_SECTION allocated via _aligned_malloc (which uses
123+ ** the CRT heap) would already be invalid memory, causing an access violation
124+ ** when EnterCriticalSection dereferences it.
125+ **
126+ ** The Windows process heap (GetProcessHeap) outlives the CRT heap and is only
127+ ** reclaimed when the process terminates, so this CRITICAL_SECTION remains valid
128+ ** for the entire process lifetime regardless of CRT shutdown order.
129+ **
130+ ** Thread-safe one-time initialization is achieved via
131+ ** InterlockedCompareExchangePointer, which works on all supported Windows versions.
124132*/
125- static CriticalSectionClass& GetThreadListLock ()
133+ static CRITICAL_SECTION * GetThreadListCS ()
126134{
127- static CriticalSectionClass* lock = new CriticalSectionClass ();
128- return *lock;
135+ static CRITICAL_SECTION * volatile s_cs = nullptr ;
136+
137+ if (s_cs == nullptr ) {
138+ CRITICAL_SECTION * cs = reinterpret_cast <CRITICAL_SECTION *>(
139+ HeapAlloc (GetProcessHeap (), HEAP_ZERO_MEMORY , sizeof (CRITICAL_SECTION )));
140+ if (cs != nullptr ) {
141+ InitializeCriticalSection (cs);
142+ }
143+ // Race-free handoff: only one thread's allocation wins; the loser is discarded.
144+ if (InterlockedCompareExchangePointer (
145+ reinterpret_cast <volatile PVOID *>(&s_cs), cs, nullptr ) != nullptr ) {
146+ // Another thread initialized it first; discard our copy.
147+ if (cs != nullptr ) {
148+ DeleteCriticalSection (cs);
149+ HeapFree (GetProcessHeap (), 0 , cs);
150+ }
151+ }
152+ }
153+
154+ return s_cs;
129155}
130156
157+ /*
158+ ** RAII lock guard for the raw ThreadList CRITICAL_SECTION.
159+ ** Replaces CriticalSectionClass::LockClass for the thread-list lock so that
160+ ** the CriticalSectionClass wrapper (and its _aligned_malloc-based handle) is
161+ ** never used for this particular, shutdown-sensitive critical path.
162+ */
163+ struct ScopedThreadListLock
164+ {
165+ explicit ScopedThreadListLock (CRITICAL_SECTION * cs) : cs_(cs)
166+ {
167+ if (cs_ != nullptr ) {
168+ EnterCriticalSection (cs_);
169+ }
170+ }
171+ ~ScopedThreadListLock ()
172+ {
173+ if (cs_ != nullptr ) {
174+ LeaveCriticalSection (cs_);
175+ }
176+ }
177+ private:
178+ CRITICAL_SECTION * cs_;
179+ ScopedThreadListLock (const ScopedThreadListLock&);
180+ ScopedThreadListLock& operator =(const ScopedThreadListLock&);
181+ };
182+
131183/*
132184** Definitions to allow run-time linking to the Imagehlp.dll functions.
133185**
@@ -905,7 +957,7 @@ void Register_Thread_ID(unsigned long thread_id, char *thread_name, bool main_th
905957{
906958 WWMEMLOG (MEM_GAMEDATA );
907959 if (thread_name) {
908- CriticalSectionClass::LockClass lock (GetThreadListLock ());
960+ ScopedThreadListLock lock (GetThreadListCS ());
909961
910962 /*
911963 ** See if we already know about this thread. Maybe just the thread_id changed.
@@ -1016,7 +1068,7 @@ HANDLE Get_Thread_Handle(int thread_index)
10161068 *=============================================================================================*/
10171069void Unregister_Thread_ID (unsigned long thread_id, char *thread_name)
10181070{
1019- CriticalSectionClass::LockClass lock (GetThreadListLock ());
1071+ ScopedThreadListLock lock (GetThreadListCS ());
10201072
10211073 for (int i=0 ; i<ThreadList.Count () ; i++) {
10221074 if (strcmp (thread_name, ThreadList[i]->ThreadName ) == 0 ) {
@@ -1046,7 +1098,7 @@ void Unregister_Thread_ID(unsigned long thread_id, char *thread_name)
10461098 *=============================================================================================*/
10471099unsigned long Get_Main_Thread_ID ()
10481100{
1049- CriticalSectionClass::LockClass lock (GetThreadListLock ());
1101+ ScopedThreadListLock lock (GetThreadListCS ());
10501102
10511103 for (int i=0 ; i<ThreadList.Count () ; i++) {
10521104 if (ThreadList[i]->Main ) {
0 commit comments