/*++ Copyright (c) 1989-1993 Microsoft Corporation Module Name: resource.c Abstract: This module implements the executive functions to acquire and release a shared resource. Author: Mark Lucovsky (markl) 04-Aug-1989 Environment: These routines are statically linked in the caller's executable and are callable only from user mode. They make use of Nt system services. Revision History: --*/ #include "ldrp.h" #include #include #include #include #include "wmiumkm.h" #include "NtdllTrc.h" // // Define the desired access for semaphores. // #define DESIRED_EVENT_ACCESS \ (EVENT_QUERY_STATE | EVENT_MODIFY_STATE | SYNCHRONIZE) #define DESIRED_SEMAPHORE_ACCESS \ (SEMAPHORE_QUERY_STATE | SEMAPHORE_MODIFY_STATE | SYNCHRONIZE) VOID RtlDumpResource( IN PRTL_RESOURCE Resource ); extern BOOLEAN LdrpShutdownInProgress; extern HANDLE LdrpShutdownThreadId; RTL_CRITICAL_SECTION DeferredCriticalSection; HANDLE GlobalKeyedEventHandle=NULL; //#define RTLP_USE_GLOBAL_KEYED_EVENT 1 #define RtlpIsKeyedEvent(xxHandle) ((((ULONG_PTR)xxHandle)&1) != 0) #define RtlpSetKeyedEventHandle(xxHandle) ((HANDLE)(((ULONG_PTR)xxHandle)|1)) #define MAX_SLIST_DEPTH 10 BOOLEAN ProtectHandle ( IN HANDLE hObject ) { NTSTATUS Status; OBJECT_HANDLE_FLAG_INFORMATION HandleInfo; Status = NtQueryObject (hObject, ObjectHandleFlagInformation, &HandleInfo, sizeof (HandleInfo), NULL); if (NT_SUCCESS(Status)) { HandleInfo.ProtectFromClose = TRUE; Status = NtSetInformationObject (hObject, ObjectHandleFlagInformation, &HandleInfo, sizeof (HandleInfo)); if (NT_SUCCESS( Status )) { return TRUE; } } return FALSE; } BOOLEAN UnProtectHandle ( IN HANDLE hObject ) { NTSTATUS Status; OBJECT_HANDLE_FLAG_INFORMATION HandleInfo; Status = NtQueryObject (hObject, ObjectHandleFlagInformation, &HandleInfo, sizeof (HandleInfo), NULL); if (NT_SUCCESS(Status)) { HandleInfo.ProtectFromClose = FALSE; Status = NtSetInformationObject (hObject, ObjectHandleFlagInformation, &HandleInfo, sizeof (HandleInfo)); if (NT_SUCCESS(Status)) { return TRUE; } } return FALSE; } SLIST_HEADER RtlCriticalSectionDebugSList; RTL_CRITICAL_SECTION_DEBUG RtlpStaticDebugInfo[64]; PRTL_CRITICAL_SECTION_DEBUG RtlpStaticDebugInfoEnd; PVOID RtlpAllocateDebugInfo ( VOID ) { PVOID p; p = RtlInterlockedPopEntrySList (&RtlCriticalSectionDebugSList); if (p == NULL) { p = RtlAllocateHeap (NtCurrentPeb()->ProcessHeap, 0, sizeof(RTL_CRITICAL_SECTION_DEBUG)); if (p == NULL) { KdPrint(( "NTDLL: Unable to allocate debug information from heap\n")); } } return p; } VOID RtlpFreeDebugInfo ( IN PRTL_CRITICAL_SECTION_DEBUG DebugInfo ) { ASSERT (RtlpStaticDebugInfoEnd != NULL); if ((RtlQueryDepthSList (&RtlCriticalSectionDebugSList) < MAX_SLIST_DEPTH) || ((DebugInfo >= RtlpStaticDebugInfo) && (DebugInfo < RtlpStaticDebugInfoEnd))) { RtlInterlockedPushEntrySList (&RtlCriticalSectionDebugSList, (PSLIST_ENTRY) DebugInfo); } else { // // Free it to the heap. // RtlFreeHeap(NtCurrentPeb()->ProcessHeap, 0, DebugInfo); } return; } NTSTATUS RtlpInitDeferredCriticalSection ( VOID ) { ULONG Size; NTSTATUS Status; PRTL_CRITICAL_SECTION_DEBUG p; OBJECT_ATTRIBUTES oa; UNICODE_STRING Name; HANDLE Handle; // // Open the global out of memory keyed event if it's not already set up. // Name.Buffer = L"\\KernelObjects\\CritSecOutOfMemoryEvent"; Name.Length = sizeof (L"\\KernelObjects\\CritSecOutOfMemoryEvent") - sizeof (WCHAR); InitializeObjectAttributes (&oa, &Name, 0, NULL, NULL); Status = NtOpenKeyedEvent (&Handle, KEYEDEVENT_WAKE|KEYEDEVENT_WAIT, &oa); if (!NT_SUCCESS (Status)) { return Status; } #if DBG ProtectHandle (Handle); #endif // DBG GlobalKeyedEventHandle = RtlpSetKeyedEventHandle (Handle); InitializeListHead (&RtlCriticalSectionList); // // Create the critical section SLIST. Populate it with initial entries // so it can be used by ntdll routines before the first heap is created. // RtlInitializeSListHead (&RtlCriticalSectionDebugSList); Size = sizeof (RtlpStaticDebugInfo) / sizeof (RtlpStaticDebugInfo[0]); RtlpStaticDebugInfoEnd = RtlpStaticDebugInfo + Size; // // Chain all the free blocks together and then push them as a set. // for (p = RtlpStaticDebugInfo; p <= RtlpStaticDebugInfo + Size - 2; p++) { ((PSLIST_ENTRY) p)->Next = (PSLIST_ENTRY)(p + 1); } ((PSLIST_ENTRY) p)->Next = NULL; InterlockedPushListSList (&RtlCriticalSectionDebugSList, (PSLIST_ENTRY) &RtlpStaticDebugInfo[0], (PSLIST_ENTRY) &RtlpStaticDebugInfo[Size - 1], Size); // // Initialize the critical section lock & the deferred critical section. // ASSERT (sizeof (RTL_CRITICAL_SECTION_DEBUG) == sizeof (RTL_RESOURCE_DEBUG)); Status = RtlInitializeCriticalSectionAndSpinCount (&RtlCriticalSectionLock, 1000); if (NT_SUCCESS (Status)) { Status = RtlInitializeCriticalSectionAndSpinCount (&DeferredCriticalSection, 1000); } return Status; } BOOLEAN NtdllOkayToLockRoutine( IN PVOID Lock ) { UNREFERENCED_PARAMETER (Lock); return TRUE; } VOID RtlInitializeResource ( IN PRTL_RESOURCE Resource ) /*++ Routine Description: This routine initializes the input resource variable Arguments: Resource - Supplies the resource variable being initialized Return Value: None --*/ { NTSTATUS Status; PRTL_RESOURCE_DEBUG ResourceDebugInfo; ULONG SpinCount; // // Initialize the lock fields, the count indicates how many are waiting // to enter or are in the critical section, LockSemaphore is the object // to wait on when entering the critical section. SpinLock is used // for the add interlock instruction. // SpinCount = 1024 * (NtCurrentPeb()->NumberOfProcessors - 1); if (SpinCount > 12000) { SpinCount = 12000; } Status = RtlInitializeCriticalSectionAndSpinCount (&Resource->CriticalSection, SpinCount); if (!NT_SUCCESS (Status)) { RtlRaiseStatus(Status); } Resource->CriticalSection.DebugInfo->Type = RTL_RESOURCE_TYPE; ResourceDebugInfo = (PRTL_RESOURCE_DEBUG) RtlpAllocateDebugInfo(); if (ResourceDebugInfo == NULL) { RtlDeleteCriticalSection (&Resource->CriticalSection); RtlRaiseStatus(STATUS_NO_MEMORY); } // // NOTICE-2002/03/21-ELi // Only the ResourceDebugInfo->ContentionCount is used by the resource // code, so the other fields are not initialized // ResourceDebugInfo->ContentionCount = 0; Resource->DebugInfo = ResourceDebugInfo; // // Initialize flags so there is a default value. // (Some apps may set RTL_RESOURCE_FLAGS_LONG_TERM to affect timeouts.) // Resource->Flags = 0; // // Initialize the shared and exclusive waiting counters and semaphore. // The counters indicate how many are waiting for access to the resource // and the semaphores are used to wait on the resource. Note that // the semaphores can also indicate the number waiting for a resource // however there is a race condition in the alogrithm on the acquire // side if count if not updated before the critical section is exited. // Status = NtCreateSemaphore (&Resource->SharedSemaphore, DESIRED_SEMAPHORE_ACCESS, NULL, 0, MAXLONG); if (!NT_SUCCESS(Status)) { RtlDeleteCriticalSection (&Resource->CriticalSection); RtlpFreeDebugInfo (Resource->DebugInfo); RtlRaiseStatus (Status); } Resource->NumberOfWaitingShared = 0; Status = NtCreateSemaphore (&Resource->ExclusiveSemaphore, DESIRED_SEMAPHORE_ACCESS, NULL, 0, MAXLONG); if (!NT_SUCCESS(Status)) { RtlDeleteCriticalSection (&Resource->CriticalSection); NtClose (Resource->SharedSemaphore); RtlpFreeDebugInfo (Resource->DebugInfo); RtlRaiseStatus (Status); } Resource->NumberOfWaitingExclusive = 0; // // Initialize the current state of the resource // Resource->NumberOfActive = 0; Resource->ExclusiveOwnerThread = NULL; return; } VOID RtlpPossibleDeadlock ( IN PVOID ResourceOrCritSect ) { PIMAGE_NT_HEADERS NtHeaders; EXCEPTION_RECORD ExceptionRecord; NtHeaders = RtlImageNtHeader (NtCurrentPeb()->ImageBaseAddress); // // If the image is a Win32 image, then raise an exception // and try to get to the uae popup. // try { if (NtHeaders != NULL && (NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI || NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI)) { ExceptionRecord.ExceptionCode = STATUS_POSSIBLE_DEADLOCK; ExceptionRecord.ExceptionFlags = 0; ExceptionRecord.ExceptionRecord = NULL; ExceptionRecord.ExceptionAddress = (PVOID)(ULONG_PTR) RtlRaiseException; ExceptionRecord.NumberParameters = 1; ExceptionRecord.ExceptionInformation[0] = (ULONG_PTR)ResourceOrCritSect; RtlRaiseException(&ExceptionRecord); } else { DbgBreakPoint(); } } finally { ASSERT (!AbnormalTermination ()); #if !DBG if (AbnormalTermination ()) { // // Somebody tried to handle this exception. // This is illegal as it will corrupt the critical section. // DbgBreakPoint (); } #endif } return; } BOOLEAN RtlAcquireResourceShared ( IN PRTL_RESOURCE Resource, IN BOOLEAN Wait ) /*++ Routine Description: The routine acquires the resource for shared access. Upon return from the procedure the resource is acquired for shared access. Arguments: Resource - Supplies the resource to acquire Wait - Indicates if the call is allowed to wait for the resource to become available or must return immediately Return Value: BOOLEAN - TRUE if the resource is acquired and FALSE otherwise --*/ { NTSTATUS Status; ULONG TimeoutCount; PLARGE_INTEGER TimeoutTime; // // Enter the critical section // RtlEnterCriticalSection (&Resource->CriticalSection); // // If it is not currently acquired for exclusive use then we can acquire // the resource for shared access. Note that this can potentially // starve an exclusive waiter however, this is necessary given the // ability to recursively acquire the resource shared. Otherwise we // might/will reach a deadlock situation where a thread tries to acquire // the resource recursively shared but is blocked by an exclusive waiter. // // The test to reenable not starving an exclusive waiter is: // // if ((Resource->NumberOfWaitingExclusive == 0) && // (Resource->NumberOfActive >= 0)) { // if (Resource->NumberOfActive >= 0) { // // The resource is ours, so indicate that we have it and // exit the critical section // Resource->NumberOfActive += 1; RtlLeaveCriticalSection(&Resource->CriticalSection); // // Otherwise check to see if this thread is the one currently holding // exclusive access to the resource. And if it is then we change // this shared request to an exclusive recursive request and grant // access to the resource. // } else if (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread) { // // The resource is ours (recursively) so indicate that we have it // and exit the critical section // Resource->NumberOfActive -= 1; RtlLeaveCriticalSection(&Resource->CriticalSection); // // Otherwise we'll have to wait for access. // } else { // // Check if we are allowed to wait or must return immediately, and // indicate that we didn't acquire the resource // if (!Wait) { RtlLeaveCriticalSection(&Resource->CriticalSection); return FALSE; } // // Otherwise we need to wait to acquire the resource. // To wait we will increment the number of waiting shared, // release the lock, and wait on the shared semaphore // Resource->NumberOfWaitingShared += 1; Resource->DebugInfo->ContentionCount += 1; RtlLeaveCriticalSection(&Resource->CriticalSection); TimeoutCount = 0; rewait: if (Resource->Flags & RTL_RESOURCE_FLAG_LONG_TERM) { TimeoutTime = NULL; } else { TimeoutTime = &RtlpTimeout; } Status = NtWaitForSingleObject (Resource->SharedSemaphore, FALSE, TimeoutTime); if (Status == STATUS_TIMEOUT) { DbgPrint("RTL: Acquire Shared Sem Timeout %d(%I64u secs)\n", TimeoutCount, TimeoutTime->QuadPart / (-10000000)); DbgPrint("RTL: Resource at %p\n",Resource); TimeoutCount += 1; if (TimeoutCount > 2) { // // If the image is a Win32 image, then raise an exception // and try to get to the uae popup // RtlpPossibleDeadlock ((PVOID) Resource); } DbgPrint("RTL: Re-Waiting\n"); goto rewait; } if (!NT_SUCCESS(Status)) { RtlRaiseStatus(Status); } } // // Now the resource is ours, for shared access // return TRUE; } BOOLEAN RtlAcquireResourceExclusive ( IN PRTL_RESOURCE Resource, IN BOOLEAN Wait ) /*++ Routine Description: The routine acquires the resource for exclusive access. Upon return from the procedure the resource is acquired for exclusive access. Arguments: Resource - Supplies the resource to acquire Wait - Indicates if the call is allowed to wait for the resource to become available or must return immediately Return Value: BOOLEAN - TRUE if the resource is acquired and FALSE otherwise --*/ { NTSTATUS Status; ULONG TimeoutCount; PLARGE_INTEGER TimeoutTime; HANDLE Thread; Thread = NtCurrentTeb()->ClientId.UniqueThread; // // Loop until the resource is ours or exit if we cannot wait. // do { // // Enter the critical section // RtlEnterCriticalSection (&Resource->CriticalSection); // // If there are no shared users and it is not currently acquired for // exclusive use then we can acquire the resource for exclusive // access. We also can acquire it if the resource indicates exclusive // access but there isn't currently an owner. // if ((Resource->NumberOfActive == 0) || ((Resource->NumberOfActive == -1) && (Resource->ExclusiveOwnerThread == NULL))) { // // The resource is ours, so indicate that we have it and // exit the critical section // Resource->NumberOfActive = -1; Resource->ExclusiveOwnerThread = Thread; RtlLeaveCriticalSection(&Resource->CriticalSection); return TRUE; } // // Otherwise check to see if we already have exclusive access to the // resource and can simply recursively acquire it again. // if (Resource->ExclusiveOwnerThread == Thread) { // // The resource is ours (recursively) so indicate that we have it // and exit the critical section // Resource->NumberOfActive -= 1; RtlLeaveCriticalSection(&Resource->CriticalSection); return TRUE; } // // Check if we are allowed to wait or must return immediately, and // indicate that we didn't acquire the resource // if (!Wait) { RtlLeaveCriticalSection(&Resource->CriticalSection); return FALSE; } // // Otherwise we need to wait to acquire the resource. // To wait we will increment the number of waiting exclusive, // release the lock, and wait on the exclusive semaphore // Resource->NumberOfWaitingExclusive += 1; Resource->DebugInfo->ContentionCount += 1; RtlLeaveCriticalSection (&Resource->CriticalSection); TimeoutCount = 0; rewait: if (Resource->Flags & RTL_RESOURCE_FLAG_LONG_TERM) { TimeoutTime = NULL; } else { TimeoutTime = &RtlpTimeout; } Status = NtWaitForSingleObject (Resource->ExclusiveSemaphore, FALSE, TimeoutTime); if (Status == STATUS_TIMEOUT) { DbgPrint("RTL: Acquire Exclusive Sem Timeout %d (%I64u secs)\n", TimeoutCount, TimeoutTime->QuadPart / (-10000000)); DbgPrint("RTL: Resource at %p\n",Resource); TimeoutCount += 1; if (TimeoutCount > 2) { // // If the image is a Win32 image, then raise an exception // and try to get to the uae popup. // RtlpPossibleDeadlock ((PVOID) Resource); } DbgPrint("RTL: Re-Waiting\n"); goto rewait; } if (!NT_SUCCESS(Status)) { RtlRaiseStatus(Status); } } while (TRUE); } VOID RtlReleaseResource ( IN PRTL_RESOURCE Resource ) /*++ Routine Description: This routine release the input resource. The resource can have been acquired for either shared or exclusive access. Arguments: Resource - Supplies the resource to release Return Value: None. --*/ { NTSTATUS Status; LONG PreviousCount; // // Enter the critical section // RtlEnterCriticalSection (&Resource->CriticalSection); // // Test if the resource is acquired for shared or exclusive access // if (Resource->NumberOfActive > 0) { // // Releasing shared access to the resource, so decrement // the number of shared users // Resource->NumberOfActive -= 1; // // If the resource is now available and there is a waiting // exclusive user then give the resource to the waiting thread // if ((Resource->NumberOfActive == 0) && (Resource->NumberOfWaitingExclusive > 0)) { // // Set the resource state to exclusive (but not owned), // decrement the number of waiting exclusive, and release // one exclusive waiter // Resource->NumberOfActive = -1; Resource->ExclusiveOwnerThread = NULL; Resource->NumberOfWaitingExclusive -= 1; Status = NtReleaseSemaphore (Resource->ExclusiveSemaphore, 1, &PreviousCount); if (!NT_SUCCESS(Status)) { RtlLeaveCriticalSection (&Resource->CriticalSection); RtlRaiseStatus(Status); } } } else if (Resource->NumberOfActive < 0) { // // Releasing exclusive access to the resource, so increment the // number of active by one. And continue testing only // if the resource is now available. // Resource->NumberOfActive += 1; if (Resource->NumberOfActive == 0) { // // The resource is now available. Remove ourselves as the // owner thread // Resource->ExclusiveOwnerThread = NULL; // // If there is another waiting exclusive then give the resource // to it. // if (Resource->NumberOfWaitingExclusive > 0) { // // Set the resource to exclusive, and its owner undefined. // Decrement the number of waiting exclusive and release one // exclusive waiter // Resource->NumberOfActive = -1; Resource->NumberOfWaitingExclusive -= 1; Status = NtReleaseSemaphore (Resource->ExclusiveSemaphore, 1, &PreviousCount); if ( !NT_SUCCESS(Status) ) { RtlLeaveCriticalSection (&Resource->CriticalSection); RtlRaiseStatus(Status); } // // Check to see if there are waiting shared, who should now get // the resource // } else if (Resource->NumberOfWaitingShared > 0) { // // Set the new state to indicate that all of the shared // requesters have access and there are no more waiting // shared requesters, and then release all of the shared // requsters // Resource->NumberOfActive = Resource->NumberOfWaitingShared; Resource->NumberOfWaitingShared = 0; Status = NtReleaseSemaphore (Resource->SharedSemaphore, Resource->NumberOfActive, &PreviousCount); if (!NT_SUCCESS(Status)) { RtlLeaveCriticalSection (&Resource->CriticalSection); RtlRaiseStatus(Status); } } } #if DBG } else { // // The resource isn't currently acquired, there is nothing to release // so tell the user the mistake // DbgPrint("NTDLL - Resource released too many times %lx\n", Resource); DbgBreakPoint(); #endif } // // Exit the critical section, and return to the caller // RtlLeaveCriticalSection (&Resource->CriticalSection); return; } VOID RtlConvertSharedToExclusive ( IN PRTL_RESOURCE Resource ) /*++ Routine Description: This routine converts a resource acquired for shared access into one acquired for exclusive access. Upon return from the procedure the resource is acquired for exclusive access Arguments: Resource - Supplies the resource to acquire for shared access, it must already be acquired for shared access Return Value: None --*/ { NTSTATUS Status; ULONG TimeoutCount; HANDLE Thread; Thread = NtCurrentTeb()->ClientId.UniqueThread; // // Enter the critical section // RtlEnterCriticalSection(&Resource->CriticalSection); // // If there is only one shared user (it's us) and we can acquire the // resource for exclusive access. // if (Resource->NumberOfActive == 1) { // // The resource is ours, so indicate that we have it and // exit the critical section, and return // Resource->NumberOfActive = -1; Resource->ExclusiveOwnerThread = Thread; RtlLeaveCriticalSection(&Resource->CriticalSection); return; } // // If the resource is currently acquired exclusive and it's us then // we already have exclusive access // if ((Resource->NumberOfActive < 0) && (Resource->ExclusiveOwnerThread == Thread)) { // // We already have exclusive access to the resource so we'll just // exit the critical section and return // RtlLeaveCriticalSection(&Resource->CriticalSection); return; } // // If the resource is acquired by more than one shared then we need // to wait to get exclusive access to the resource // if (Resource->NumberOfActive > 1) { TimeoutCount = 0; // // To wait we will decrement the fact that we have the resource for // shared, and then loop waiting on the exclusive lock, and then // testing to see if we can get exclusive access to the resource // Resource->NumberOfActive -= 1; while (TRUE) { // // Increment the number of waiting exclusive, exit and critical // section and wait on the exclusive semaphore // Resource->NumberOfWaitingExclusive += 1; Resource->DebugInfo->ContentionCount += 1; RtlLeaveCriticalSection(&Resource->CriticalSection); do { Status = NtWaitForSingleObject (Resource->ExclusiveSemaphore, FALSE, &RtlpTimeout); if (NT_SUCCESS(Status)) { if (Status != STATUS_TIMEOUT) { // // Success ! // break; } DbgPrint("RTL: Convert Exclusive Sem Timeout %d (%I64u secs)\n", TimeoutCount, RtlpTimeout.QuadPart / (-10000000)); DbgPrint("RTL: Resource at %p\n",Resource); TimeoutCount += 1; if (TimeoutCount > 2) { // // If the image is a Win32 image, then raise an // exception and try to get to the uae popup. // RtlpPossibleDeadlock ((PVOID) Resource); } DbgPrint("RTL: Re-Waiting\n"); } else { RtlRaiseStatus (Status); } } while (TRUE); // // Enter the critical section // RtlEnterCriticalSection(&Resource->CriticalSection); // // If there are no shared users and it is not currently acquired // for exclusive use then we can acquire the resource for // exclusive access. We can also acquire it if the resource // indicates exclusive access but there isn't currently an owner // if ((Resource->NumberOfActive == 0) || ((Resource->NumberOfActive == -1) && (Resource->ExclusiveOwnerThread == NULL))) { // // The resource is ours, so indicate that we have it and // exit the critical section and return. // Resource->NumberOfActive = -1; Resource->ExclusiveOwnerThread = NtCurrentTeb()->ClientId.UniqueThread; RtlLeaveCriticalSection(&Resource->CriticalSection); return; } // // Otherwise check to see if we already have exclusive access to // the resource and can simply recursively acquire it again. // if (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread) { // // The resource is ours (recursively) so indicate that we have // it and exit the critical section and return. // Resource->NumberOfActive -= 1; RtlLeaveCriticalSection(&Resource->CriticalSection); return; } } } // // The resource is not currently acquired for shared so this is a // spurious call // #if DBG DbgPrint("NTDLL: Failed error - SHARED_RESOURCE_CONV_ERROR\n"); DbgBreakPoint(); #endif RtlLeaveCriticalSection(&Resource->CriticalSection); } VOID RtlConvertExclusiveToShared ( IN PRTL_RESOURCE Resource ) /*++ Routine Description: This routine converts a resource acquired for exclusive access into one acquired for shared access. Upon return from the procedure the resource is acquired for shared access Arguments: Resource - Supplies the resource to acquire for shared access, it must already be acquired for exclusive access Return Value: None --*/ { LONG PreviousCount; NTSTATUS Status; // // Enter the critical section // RtlEnterCriticalSection(&Resource->CriticalSection); // // Release our exclusive access. // if (Resource->NumberOfActive == -1) { Resource->ExclusiveOwnerThread = NULL; // // Check to see if there are waiting shared, who should now get the // resource along with us // if (Resource->NumberOfWaitingShared > 0) { // // Set the new state to indicate that all of the shared requesters // have access including us, and there are no more waiting shared // requesters, and then release all of the shared requsters // Resource->NumberOfActive = Resource->NumberOfWaitingShared + 1; Resource->NumberOfWaitingShared = 0; Status = NtReleaseSemaphore (Resource->SharedSemaphore, Resource->NumberOfActive - 1, &PreviousCount); if (!NT_SUCCESS(Status)) { RtlLeaveCriticalSection(&Resource->CriticalSection); RtlRaiseStatus(Status); } } else { // // There is no one waiting for shared access so it's only ours // Resource->NumberOfActive = 1; } } else { // // The resource is not currently acquired for exclusive, or we've // recursively acquired it, so this must be a spurious call // #if DBG DbgPrint("NTDLL: Failed error - SHARED_RESOURCE_CONV_ERROR\n"); DbgBreakPoint(); #endif } RtlLeaveCriticalSection(&Resource->CriticalSection); return; } VOID RtlDeleteResource ( IN PRTL_RESOURCE Resource ) /*++ Routine Description: This routine deletes (i.e., uninitializes) the input resource variable Arguments: Resource - Supplies the resource variable being deleted Return Value: None --*/ { RtlDeleteCriticalSection (&Resource->CriticalSection); NtClose (Resource->SharedSemaphore); NtClose (Resource->ExclusiveSemaphore); RtlpFreeDebugInfo (Resource->DebugInfo); RtlZeroMemory (Resource, sizeof(*Resource)); return; } VOID RtlDumpResource ( IN PRTL_RESOURCE Resource ) { DbgPrint("Resource @ %lx\n", Resource); DbgPrint(" NumberOfWaitingShared = %lx\n", Resource->NumberOfWaitingShared); DbgPrint(" NumberOfWaitingExclusive = %lx\n", Resource->NumberOfWaitingExclusive); DbgPrint(" NumberOfActive = %lx\n", Resource->NumberOfActive); return; } NTSTATUS RtlInitializeCriticalSection ( IN PRTL_CRITICAL_SECTION CriticalSection ) /*++ Routine Description: This routine initializes the input critial section variable Arguments: CriticalSection - Supplies the resource variable being initialized Return Value: NTSTATUS. --*/ { return RtlInitializeCriticalSectionAndSpinCount (CriticalSection, 0); } #define MAX_SPIN_COUNT 0x00ffffff #define PREALLOCATE_EVENT_MASK 0x80000000 VOID RtlEnableEarlyCriticalSectionEventCreation ( VOID ) /*++ Routine Description: This routine marks the PEB of the calling process so critical section events are created at critical section creation time rather than at contention time. This allows critical processes not to have to worry about error paths later on at the expense of extra pool consumed. Arguments: None Return Value: None --*/ { NtCurrentPeb ()->NtGlobalFlag |= FLG_CRITSEC_EVENT_CREATION; } NTSTATUS RtlInitializeCriticalSectionAndSpinCount ( IN PRTL_CRITICAL_SECTION CriticalSection, ULONG SpinCount ) /*++ Routine Description: This routine initializes the input critial section variable Arguments: CriticalSection - Supplies the resource variable being initialized SpinCount - Supplies the spincount to use for MP collisions. Return Value: NTSTATUS - Status of semaphore creation. --*/ { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // // Initialize the lock fields, the count indicates how many are waiting // to enter or are in the critical section, LockSemaphore is the object // to wait on when entering the critical section. SpinLock is used // for the add interlock instruction. Recursion count is the number of // times the critical section has been recursively entered. // CriticalSection->LockCount = -1; CriticalSection->RecursionCount = 0; CriticalSection->OwningThread = 0; CriticalSection->LockSemaphore = 0; if ( NtCurrentPeb()->NumberOfProcessors > 1 ) { CriticalSection->SpinCount = SpinCount & MAX_SPIN_COUNT; } else { CriticalSection->SpinCount = 0; } ASSERT (GlobalKeyedEventHandle != NULL); // // Initialize debugging information. // DebugInfo = (PRTL_CRITICAL_SECTION_DEBUG) RtlpAllocateDebugInfo (); if (DebugInfo == NULL) { return STATUS_NO_MEMORY; } DebugInfo->Type = RTL_CRITSECT_TYPE; DebugInfo->ContentionCount = 0; DebugInfo->EntryCount = 0; // // It is important to set critical section pointers and potential // stack trace before we insert the resource in the process' // resource list because the list can be randomly traversed from // other threads that check for orphaned resources. // DebugInfo->CriticalSection = CriticalSection; CriticalSection->DebugInfo = DebugInfo; // // Try to get a stack trace. If no trace database was created // then the log() function is a no op. // DebugInfo->CreatorBackTraceIndex = (USHORT) RtlLogStackBackTrace(); // // If the critical section lock itself is not being initialized, then // synchronize the insert of the critical section in the process locks // list. Otherwise, insert the critical section with no synchronization. // if (CriticalSection != &RtlCriticalSectionLock) { RtlEnterCriticalSection(&RtlCriticalSectionLock); InsertTailList(&RtlCriticalSectionList, &DebugInfo->ProcessLocksList); RtlLeaveCriticalSection(&RtlCriticalSectionLock ); } else { InsertTailList(&RtlCriticalSectionList, &DebugInfo->ProcessLocksList); } if (IsCritSecLogging(CriticalSection)) { PTHREAD_LOCAL_DATA pThreadLocalData = NULL; PPERFINFO_TRACE_HEADER pEventHeader = NULL; USHORT ReqSize = sizeof(CRIT_SEC_INITIALIZE_EVENT_DATA) + FIELD_OFFSET(PERFINFO_TRACE_HEADER, Data); AcquireBufferLocation (&pEventHeader, &pThreadLocalData, &ReqSize); if(pEventHeader && pThreadLocalData) { PCRIT_SEC_INITIALIZE_EVENT_DATA pCritSecInitializeEvent = (PCRIT_SEC_INITIALIZE_EVENT_DATA) ((SIZE_T)pEventHeader + (SIZE_T)FIELD_OFFSET(PERFINFO_TRACE_HEADER, Data )); pEventHeader->Packet.Size = ReqSize; pEventHeader->Packet.HookId= (USHORT) PERFINFO_LOG_TYPE_CRITSEC_INITIALIZE; pCritSecInitializeEvent->Address = (PVOID)CriticalSection; pCritSecInitializeEvent->SpinCount = (PVOID)CriticalSection->SpinCount; ReleaseBufferLocation(pThreadLocalData); } } return STATUS_SUCCESS; } ULONG RtlSetCriticalSectionSpinCount ( IN PRTL_CRITICAL_SECTION CriticalSection, ULONG SpinCount ) /*++ Routine Description: This routine initializes the input critial section variable Arguments: CriticalSection - Supplies the resource variable being initialized Return Value: Returns the previous critical section spin count --*/ { ULONG OldSpinCount; OldSpinCount = (ULONG)CriticalSection->SpinCount; if ( NtCurrentPeb()->NumberOfProcessors > 1 ) { CriticalSection->SpinCount = SpinCount & MAX_SPIN_COUNT; } else { CriticalSection->SpinCount = 0; } return OldSpinCount; } BOOLEAN RtlpCreateCriticalSectionSem ( IN PRTL_CRITICAL_SECTION CriticalSection ) { NTSTATUS Status, Status1; HANDLE SemHandle; #if defined (RTLP_USE_GLOBAL_KEYED_EVENT) Status = STATUS_INSUFFICIENT_RESOURCES; SemHandle = NULL; #else Status = NtCreateEvent (&SemHandle, DESIRED_EVENT_ACCESS, NULL, SynchronizationEvent, FALSE); #endif if (NT_SUCCESS (Status)) { if (InterlockedCompareExchangePointer (&CriticalSection->LockSemaphore, SemHandle, NULL) != NULL) { Status1 = NtClose (SemHandle); ASSERT (NT_SUCCESS (Status1)); } else { #if DBG ProtectHandle(SemHandle); #endif // DBG } } else { ASSERT (GlobalKeyedEventHandle != NULL); InterlockedCompareExchangePointer (&CriticalSection->LockSemaphore, GlobalKeyedEventHandle, NULL); } return TRUE; } VOID RtlpCheckDeferredCriticalSection( IN PRTL_CRITICAL_SECTION CriticalSection ) { if (CriticalSection->LockSemaphore == NULL) { RtlpCreateCriticalSectionSem(CriticalSection); } return; } NTSTATUS RtlDeleteCriticalSection ( IN PRTL_CRITICAL_SECTION CriticalSection ) /*++ Routine Description: This routine deletes (i.e., uninitializes) the input critical section variable Arguments: CriticalSection - Supplies the resource variable being deleted Return Value: NTSTATUS. --*/ { NTSTATUS Status; PRTL_CRITICAL_SECTION_DEBUG DebugInfo; HANDLE LockSemaphore; LockSemaphore = CriticalSection->LockSemaphore; if (LockSemaphore != NULL && !RtlpIsKeyedEvent (LockSemaphore)) { #if DBG UnProtectHandle (LockSemaphore); #endif // DBG Status = NtClose (LockSemaphore); } else { Status = STATUS_SUCCESS; } // // Remove critical section from the list // DebugInfo = NULL; RtlEnterCriticalSection (&RtlCriticalSectionLock); try { DebugInfo = CriticalSection->DebugInfo; if (DebugInfo != NULL) { RemoveEntryList (&DebugInfo->ProcessLocksList); RtlZeroMemory (DebugInfo, sizeof (*DebugInfo)); } } finally { RtlLeaveCriticalSection (&RtlCriticalSectionLock); } if (DebugInfo != NULL) { RtlpFreeDebugInfo( DebugInfo ); } RtlZeroMemory (CriticalSection, sizeof(RTL_CRITICAL_SECTION)); return Status; } // // The following support routines are called from the machine language // implementations of RtlEnterCriticalSection and RtlLeaveCriticalSection // to execute the slow path logic of either waiting for a critical section // or releasing a critical section to a waiting thread. // void RtlpWaitForCriticalSection ( IN PRTL_CRITICAL_SECTION CriticalSection ) { NTSTATUS st; ULONG TimeoutCount = 0; PLARGE_INTEGER TimeoutTime; LOGICAL CsIsLoaderLock; HANDLE LockSemaphore; PTEB Teb; // // Critical sections are disabled during exit process so that // apps that are not careful during shutdown don't hang // CsIsLoaderLock = (CriticalSection == &LdrpLoaderLock); Teb = NtCurrentTeb (); Teb->WaitingOnLoaderLock = (ULONG)CsIsLoaderLock; if ( LdrpShutdownInProgress && ((!CsIsLoaderLock) || (CsIsLoaderLock && LdrpShutdownThreadId == Teb->ClientId.UniqueThread) ) ) { // // Slimy reinitialization of the critical section with the count // biased by one. This is how the critical section would normally // look to the thread coming out of this function. Note that the // semaphore handle is leaked, but since the app is exiting, it's ok // CriticalSection->LockCount = 0; CriticalSection->RecursionCount = 0; CriticalSection->OwningThread = 0; CriticalSection->LockSemaphore = 0; Teb->WaitingOnLoaderLock = 0; return; } if (RtlpTimoutDisable) { TimeoutTime = NULL; } else { TimeoutTime = &RtlpTimeout; } LockSemaphore = CriticalSection->LockSemaphore; if (LockSemaphore == NULL) { RtlpCheckDeferredCriticalSection (CriticalSection); LockSemaphore = CriticalSection->LockSemaphore; } CriticalSection->DebugInfo->EntryCount++; do { CriticalSection->DebugInfo->ContentionCount++; #if 0 DbgPrint( "NTDLL: Waiting for CritSect: %p owned by ThreadId: %X Count: %u Level: %u\n", CriticalSection, CriticalSection->OwningThread, CriticalSection->LockCount, CriticalSection->RecursionCount ); #endif if( IsCritSecLogging(CriticalSection)){ PTHREAD_LOCAL_DATA pThreadLocalData = NULL; PPERFINFO_TRACE_HEADER pEventHeader = NULL; USHORT ReqSize = sizeof(CRIT_SEC_COLLISION_EVENT_DATA) + FIELD_OFFSET(PERFINFO_TRACE_HEADER, Data); AcquireBufferLocation(&pEventHeader, &pThreadLocalData, &ReqSize ); if(pEventHeader && pThreadLocalData) { PCRIT_SEC_COLLISION_EVENT_DATA pCritSecCollEvent = (PCRIT_SEC_COLLISION_EVENT_DATA)( (SIZE_T)pEventHeader +(SIZE_T)FIELD_OFFSET(PERFINFO_TRACE_HEADER, Data )); pEventHeader->Packet.Size = ReqSize; pEventHeader->Packet.HookId= (USHORT) PERFINFO_LOG_TYPE_CRITSEC_COLLISION; pCritSecCollEvent->Address = (PVOID)CriticalSection; pCritSecCollEvent->SpinCount = (PVOID)CriticalSection->SpinCount; pCritSecCollEvent->LockCount = CriticalSection->LockCount; pCritSecCollEvent->OwningThread = (PVOID)CriticalSection->OwningThread; ReleaseBufferLocation(pThreadLocalData); } } if (!RtlpIsKeyedEvent (LockSemaphore)) { st = NtWaitForSingleObject (LockSemaphore, FALSE, TimeoutTime); } else { st = NtWaitForKeyedEvent (LockSemaphore, CriticalSection, FALSE, TimeoutTime); } if ( st == STATUS_TIMEOUT ) { // // This code path will be taken only if the TimeoutTime parameter for // Wait() was not null. // DbgPrint( "RTL: Enter Critical Section Timeout (%I64u secs) %d\n", TimeoutTime->QuadPart / (-10000000), TimeoutCount ); DbgPrint( "RTL: Pid.Tid %x.%x, owner tid %x Critical Section %p - ContentionCount == %lu\n", Teb->ClientId.UniqueProcess, Teb->ClientId.UniqueThread, CriticalSection->OwningThread, CriticalSection, CriticalSection->DebugInfo->ContentionCount ); TimeoutCount += 1; if ((TimeoutCount > 2) && (CriticalSection != &LdrpLoaderLock)) { // // If the image is a Win32 image, then raise an exception // and try to get to the uae popup // RtlpPossibleDeadlock ((PVOID) CriticalSection); } DbgPrint("RTL: Re-Waiting\n"); } else { if (NT_SUCCESS(st)) { // // If some errant thread calls SetEvent on a bogus handle // which happens to match the handle we are using in the // critical section, everything gets really messed up since // two threads now own the lock at the same time. ASSERT // that no other thread owns the lock if we have been // granted ownership. // ASSERT(CriticalSection->OwningThread == 0); if (CsIsLoaderLock) { CriticalSection->OwningThread = Teb->ClientId.UniqueThread; Teb->WaitingOnLoaderLock = 0; } return; } RtlRaiseStatus(st); } } while (TRUE); } VOID RtlpUnWaitCriticalSection ( IN PRTL_CRITICAL_SECTION CriticalSection ) { NTSTATUS st; HANDLE LockSemaphore; LockSemaphore = CriticalSection->LockSemaphore; if (LockSemaphore == NULL) { RtlpCheckDeferredCriticalSection (CriticalSection); LockSemaphore = CriticalSection->LockSemaphore; } if (!RtlpIsKeyedEvent (LockSemaphore)) { st = NtSetEventBoostPriority (LockSemaphore); } else { st = NtReleaseKeyedEvent (LockSemaphore, CriticalSection, FALSE, 0); } if (!NT_SUCCESS (st)) { RtlRaiseStatus(st); } } VOID RtlpNotOwnerCriticalSection ( IN PRTL_CRITICAL_SECTION CriticalSection ) { LOGICAL CsIsLoaderLock; // // Critical sections are disabled during exit process so that // apps that are not careful during shutdown don't hang // CsIsLoaderLock = (CriticalSection == &LdrpLoaderLock); if ( LdrpShutdownInProgress && ((!CsIsLoaderLock) || (CsIsLoaderLock && LdrpShutdownThreadId == NtCurrentTeb()->ClientId.UniqueThread) ) ) { return; } if (NtCurrentPeb()->BeingDebugged) { DbgPrint( "NTDLL: Calling thread (%X) not owner of CritSect: %p Owner ThreadId: %X\n", NtCurrentTeb()->ClientId.UniqueThread, CriticalSection, CriticalSection->OwningThread ); DbgBreakPoint(); } RtlRaiseStatus (STATUS_RESOURCE_NOT_OWNED); } #if DBG void RtlpCriticalSectionIsOwned ( IN PRTL_CRITICAL_SECTION CriticalSection ) { // // The loader lock gets handled differently, so don't assert on it // if ((CriticalSection == &LdrpLoaderLock) && (CriticalSection->OwningThread == NtCurrentTeb()->ClientId.UniqueThread)) return; // // If we're being debugged, throw up a warning // if (NtCurrentPeb()->BeingDebugged) { DbgPrint( "NTDLL: Calling thread (%X) shouldn't enter CritSect: %p Owner ThreadId: %X\n", NtCurrentTeb()->ClientId.UniqueThread, CriticalSection, CriticalSection->OwningThread ); DbgBreakPoint(); } } #endif ///////////////////////////////////////////////////////////////////// /////////////////////////////////////////// Critical section verifier ///////////////////////////////////////////////////////////////////// // // This variable enables the critical section verifier (abandoned locks, // terminatethread() while holding locks, etc.). // BOOLEAN RtlpCriticalSectionVerifier = FALSE; // // Settable from debugger to avoid a flurry of similar failures. // BOOLEAN RtlpCsVerifyDoNotBreak = FALSE; VOID RtlCheckHeldCriticalSections ( IN HANDLE hThread, IN PRTL_CRITICAL_SECTION const *LocksHeld ) /*++ Routine Description: This routine is called to ensure that the given thread does not hold any locks other than the ones in a specified list of known-held locks. Arguments: hThread -- the thread to check LocksHeld -- Locks known to be held by the thread Return Value: None. --*/ { NTSTATUS Status; THREAD_BASIC_INFORMATION ThreadInfo; PLIST_ENTRY Entry; PRTL_CRITICAL_SECTION_DEBUG DebugInfo; RTL_CRITICAL_SECTION_DEBUG ExtraDebugInfoCopy = {0}; // initialized because of W4 warning PRTL_CRITICAL_SECTION CriticalSection; RTL_CRITICAL_SECTION CritSectCopy = {0}; // initialized because of W4 warning PRTL_CRITICAL_SECTION const *LockHeld; BOOLEAN Copied; // // We do not check anything if critical section verifier is not on. // if (RtlpCriticalSectionVerifier == FALSE || RtlpCsVerifyDoNotBreak == TRUE ) { return; } // // We do not do anything if we are shutting down the process. // if (LdrpShutdownInProgress) { return; } // // Query the thread ID and TEB address. // Status = NtQueryInformationThread (hThread, ThreadBasicInformation, &ThreadInfo, sizeof(ThreadInfo), NULL); if (! NT_SUCCESS(Status)) { return; } // // Don't do anything else if number of owned critical sections // stored in the TEB is zero. The app verifier keeps this TEB // counter updated on all platforms instead of just x86 chk // without the verifier, at least for critical sections that were not // entered from ntdll. // if (ThreadInfo.TebBaseAddress->CountOfOwnedCriticalSections < 1) { return; } // // Iterate the global list of critical sections // RtlEnterCriticalSection (&RtlCriticalSectionLock); try { for (Entry = RtlCriticalSectionList.Flink; Entry != &RtlCriticalSectionList; Entry = Entry->Flink) { DebugInfo = CONTAINING_RECORD (Entry, RTL_CRITICAL_SECTION_DEBUG, ProcessLocksList); CriticalSection = DebugInfo->CriticalSection; if (CriticalSection == &RtlCriticalSectionLock) { // // We know that we're holding this one, so there's no // problem -- continue on. // continue; } if (LocksHeld) { // // We have a list of okay-to-hold critical sections; // scan through it, looking for this critical section. // If we find it, we'll skip it and continue walking // ProcessLocksList. // for (LockHeld = LocksHeld; *LockHeld; LockHeld++) { if (CriticalSection == *LockHeld) { // // We've found this critical section in the // LocksHeld array. // break; } } if (*LockHeld) { // // Our caller expected the thread to be holding // this critical section; skip it, and continue // walking through ProcessLocksList. // continue; } } Copied = TRUE; try { CritSectCopy = *CriticalSection; } except (EXCEPTION_EXECUTE_HANDLER) { Copied = FALSE; } if (Copied == FALSE) { // // Exception while reading the contents of the critsect. // The critsect has probably been decommitted without a call to // RtlDeleteCriticalSection. // // You might think the entry could be deleted from the list, // but it can't... there may be another RTL_CRITICAL_SECTION out // there that is truly allocated, and whose DebugInfo pointer // points at this DebugInfo. In that case, when that critsect // is deleted, the RtlCriticalSectionList is corrupted. // VERIFIER_STOP (APPLICATION_VERIFIER_LOCK_IN_FREED_MEMORY, "undeleted critical section in freed memory", CriticalSection, "Critical section address", DebugInfo, "Critical section debug info address", RtlpGetStackTraceAddress (DebugInfo->CreatorBackTraceIndex), "Initialization stack trace. Use dds to dump it if non-NULL.", NULL, "" ); } else if(CritSectCopy.DebugInfo != DebugInfo) { // // Successfully read the critical section structure but // the current debug info field of this critical section // doesn't point to the current DebugInfo - it was probably // initialized more than one time or simply corrupted. // // Try to make a copy of the DebugInfo currently pointed // by our critical section. This might fail if the critical // section is corrupted. // Copied = TRUE; try { ExtraDebugInfoCopy = *(CritSectCopy.DebugInfo); } except (EXCEPTION_EXECUTE_HANDLER) { Copied = FALSE; } if (Copied == FALSE) { // // Exception while reading the contents of the debug info. // The current critical section structure is corrupted. // VERIFIER_STOP (APPLICATION_VERIFIER_LOCK_CORRUPTED, "corrupted critical section", CriticalSection, "Critical section address", CritSectCopy.DebugInfo, "Invalid debug info address of this critical section", DebugInfo, "Address of the debug info found in the active list.", RtlpGetStackTraceAddress (DebugInfo->CreatorBackTraceIndex), "Initialization stack trace. Use dds to dump it if non-NULL." ); } else { // // Successfully read this second debug info // of the same critical section. // VERIFIER_STOP (APPLICATION_VERIFIER_LOCK_DOUBLE_INITIALIZE, "double initialized or corrupted critical section", CriticalSection, "Critical section address.", DebugInfo, "Address of the debug info found in the active list.", RtlpGetStackTraceAddress (DebugInfo->CreatorBackTraceIndex), "First initialization stack trace. Use dds to dump it if non-NULL.", RtlpGetStackTraceAddress (ExtraDebugInfoCopy.CreatorBackTraceIndex), "Second initialization stack trace. Use dds to dump it if non-NULL."); } } else if (CritSectCopy.OwningThread == ThreadInfo.ClientId.UniqueThread && CritSectCopy.LockCount != -1) { // // The thread has a critical section locked. Since this API is called // whenever the thread is in a context that does not allow this // we will have to report the issue (e.g. thread exits or is // terminated, a thread pool work item finished, etc.). // VERIFIER_STOP (APPLICATION_VERIFIER_EXIT_THREAD_OWNS_LOCK, "Thread is in a state in which it cannot own a critical section", ThreadInfo.ClientId.UniqueThread, "Thread identifier", CriticalSection, "Critical section address", DebugInfo, "Critical section debug info address", RtlpGetStackTraceAddress (DebugInfo->CreatorBackTraceIndex), "Initialization stack trace. Use dds to dump it if non-NULL." ); } } } finally { // // Release the CS list lock. // RtlLeaveCriticalSection (&RtlCriticalSectionLock); } } VOID RtlCheckForOrphanedCriticalSections ( IN HANDLE hThread ) /*++ Routine Description: This routine is called from kernel32's ExitThread, TerminateThread and SuspendThread in an effort to track calls that kill threads while they own critical sections. Arguments: hThread -- thread to be killed Return Value: None. --*/ { // The work is performed by RtlCheckHeldCriticalSections, which we // call with the following okay-to-be-held critical section. PRTL_CRITICAL_SECTION const LocksHeld[] = { &LdrpLoaderLock, NULL }; RtlCheckHeldCriticalSections(hThread, LocksHeld); }