Windows2000/private/ntos/dll/resource.c
2020-09-30 17:12:32 +02:00

1217 lines
44 KiB
C

/*++
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 in only from user mode.
They make use of Nt system services.
*/
#include <ntos.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <heap.h>
#include "ldrp.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;
VOID RtlpInitDeferedCriticalSection(VOID);
RTL_CRITICAL_SECTION DeferedCriticalSection;
#if DBG
BOOLEAN ProtectHandle(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(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;
}
#endif // DBG
RTL_CRITICAL_SECTION_DEBUG RtlpStaticDebugInfo[64];
PRTL_CRITICAL_SECTION_DEBUG RtlpDebugInfoFreeList;
BOOLEAN RtlpCritSectInitialized;
PRTL_CRITICAL_SECTION_DEBUG RtlpChainDebugInfo(IN PVOID BaseAddress, IN ULONG Size)
{
PRTL_CRITICAL_SECTION_DEBUG p, p1;
if (Size = Size / sizeof(RTL_CRITICAL_SECTION_DEBUG)) {
p = (PRTL_CRITICAL_SECTION_DEBUG)BaseAddress + Size - 1;
*(PRTL_CRITICAL_SECTION_DEBUG*)p = NULL;
while (--Size) {
p1 = p - 1;
*(PRTL_CRITICAL_SECTION_DEBUG*)p1 = p;
p = p1;
}
}
return p;
}
PVOID RtlpAllocateDebugInfo(VOID);
VOID RtlpFreeDebugInfo(IN PVOID DebugInfo);
PVOID RtlpAllocateDebugInfo(VOID)
{
PRTL_CRITICAL_SECTION_DEBUG p;
PPEB Peb;
if (RtlpCritSectInitialized) {
RtlEnterCriticalSection(&DeferedCriticalSection);
}
try {
p = RtlpDebugInfoFreeList;
if (p == NULL) {
Peb = NtCurrentPeb();
p = RtlAllocateHeap(Peb->ProcessHeap, 0, PAGE_SIZE - HEAP_GRANULARITY);
if (!p) {
KdPrint(("NTDLL: Unable to allocate debug information from heap\n"));
}
else {
p = RtlpChainDebugInfo(p, PAGE_SIZE - HEAP_GRANULARITY);
}
}
if (p != NULL) {
RtlpDebugInfoFreeList = *(PRTL_CRITICAL_SECTION_DEBUG*)p;
}
}
finally{
if (RtlpCritSectInitialized) {
RtlLeaveCriticalSection(&DeferedCriticalSection);
}
}
return p;
}
VOID RtlpFreeDebugInfo(IN PVOID DebugInfo)
{
RtlEnterCriticalSection(&DeferedCriticalSection);
try {
RtlZeroMemory(DebugInfo, sizeof(RTL_CRITICAL_SECTION_DEBUG));
*(PRTL_CRITICAL_SECTION_DEBUG*)DebugInfo = RtlpDebugInfoFreeList;
RtlpDebugInfoFreeList = (PRTL_CRITICAL_SECTION_DEBUG)DebugInfo;
}
finally{
RtlLeaveCriticalSection(&DeferedCriticalSection);
}
}
VOID RtlpCreateCriticalSectionSem(IN PRTL_CRITICAL_SECTION CriticalSection);
VOID RtlpInitDeferedCriticalSection(VOID)
{
InitializeListHead(&RtlCriticalSectionList);
if (sizeof(RTL_CRITICAL_SECTION_DEBUG) != sizeof(RTL_RESOURCE_DEBUG)) {
DbgPrint("NTDLL: Critical Section & Resource Debug Info length mismatch.\n");
return;
}
RtlpDebugInfoFreeList = RtlpChainDebugInfo(RtlpStaticDebugInfo, sizeof(RtlpStaticDebugInfo));
RtlInitializeCriticalSectionAndSpinCount(&RtlCriticalSectionLock, 1000);
RtlInitializeCriticalSectionAndSpinCount(&DeferedCriticalSection, 1000);
RtlpCreateCriticalSectionSem(&DeferedCriticalSection);
RtlpCritSectInitialized = TRUE;
}
BOOLEAN NtdllOkayToLockRoutine(IN PVOID 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;
// 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.
Status = RtlInitializeCriticalSectionAndSpinCount(&Resource->CriticalSection, 1000);
if (!NT_SUCCESS(Status)) {
RtlRaiseStatus(Status);
}
Resource->CriticalSection.DebugInfo->Type = RTL_RESOURCE_TYPE;
ResourceDebugInfo = (PRTL_RESOURCE_DEBUG)RtlpAllocateDebugInfo();
if (ResourceDebugInfo == NULL) {
RtlRaiseStatus(STATUS_NO_MEMORY);
}
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)) {
RtlRaiseStatus(Status);
}
Resource->NumberOfWaitingShared = 0;
Status = NtCreateSemaphore(&Resource->ExclusiveSemaphore, DESIRED_SEMAPHORE_ACCESS, NULL, 0, MAXLONG);
if (!NT_SUCCESS(Status)) {
RtlRaiseStatus(Status);
}
Resource->NumberOfWaitingExclusive = 0;
// Initialize the current state of the resource
Resource->NumberOfActive = 0;
Resource->ExclusiveOwnerThread = NULL;
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 for must return immediately
Return Value:
BOOLEAN - TRUE if the resource is acquired and FALSE otherwise
*/
{
NTSTATUS Status;
ULONG TimeoutCount = 0;
PLARGE_INTEGER TimeoutTime = &RtlpTimeout;
// 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 recusively shared but is blocked by an exclusive waiter.
// The test to reanable 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 recusive request and grant access to the resource.
}
else if (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread) {
// The resource is ours (recusively) so indicate that we have it and exit the critial 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 immedately, 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++;
RtlLeaveCriticalSection(&Resource->CriticalSection);
rewait:
if (Resource->Flags & RTL_RESOURCE_FLAG_LONG_TERM) {
TimeoutTime = NULL;
}
Status = NtWaitForSingleObject(Resource->SharedSemaphore, FALSE, TimeoutTime);
if (Status == STATUS_TIMEOUT) {
DbgPrint("RTL: Acquire Shared Sem Timeout %d(2 minutes)\n", TimeoutCount);
DbgPrint("RTL: Resource at %p\n", Resource);
TimeoutCount++;
if (TimeoutCount > 2) {
PIMAGE_NT_HEADERS NtHeaders;
// If the image is a Win32 image, then raise an exception and try to get to the uae popup
NtHeaders = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
if (NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI ||
NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) {
EXCEPTION_RECORD ExceptionRecord;
ExceptionRecord.ExceptionCode = STATUS_POSSIBLE_DEADLOCK;
ExceptionRecord.ExceptionFlags = 0;
ExceptionRecord.ExceptionRecord = NULL;
ExceptionRecord.ExceptionAddress = (PVOID)RtlRaiseException;
ExceptionRecord.NumberParameters = 1;
ExceptionRecord.ExceptionInformation[0] = (ULONG_PTR)Resource;
RtlRaiseException(&ExceptionRecord);
}
else {
DbgBreakPoint();
}
}
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 for must return immediately
Return Value:
BOOLEAN - TRUE if the resource is acquired and FALSE otherwise
*/
{
NTSTATUS Status;
ULONG TimeoutCount = 0;
PLARGE_INTEGER TimeoutTime = &RtlpTimeout;
// Loop until the resource is ours or exit if we cannot wait.
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 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 = NtCurrentTeb()->ClientId.UniqueThread;
RtlLeaveCriticalSection(&Resource->CriticalSection);
return TRUE;
}
// Otherwise check to see if we already have exclusive access to the resource and can simply recusively acquire it again.
if (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread) {
// The resource is ours (recusively) so indicate that we have it and exit the critial section
Resource->NumberOfActive -= 1;
RtlLeaveCriticalSection(&Resource->CriticalSection);
return TRUE;
}
// Check if we are allowed to wait or must return immedately, 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++;
RtlLeaveCriticalSection(&Resource->CriticalSection);
rewait:
if (Resource->Flags & RTL_RESOURCE_FLAG_LONG_TERM) {
TimeoutTime = NULL;
}
Status = NtWaitForSingleObject(Resource->ExclusiveSemaphore, FALSE, TimeoutTime);
if (Status == STATUS_TIMEOUT) {
DbgPrint("RTL: Acquire Exclusive Sem Timeout %d (2 minutes)\n", TimeoutCount);
DbgPrint("RTL: Resource at %p\n", Resource);
TimeoutCount++;
if (TimeoutCount > 2) {
PIMAGE_NT_HEADERS NtHeaders;
// If the image is a Win32 image, then raise an exception and try to get to the uae popup
NtHeaders = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
if (NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI ||
NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) {
EXCEPTION_RECORD ExceptionRecord;
ExceptionRecord.ExceptionCode = STATUS_POSSIBLE_DEADLOCK;
ExceptionRecord.ExceptionFlags = 0;
ExceptionRecord.ExceptionRecord = NULL;
ExceptionRecord.ExceptionAddress = (PVOID)RtlRaiseException;
ExceptionRecord.NumberParameters = 1;
ExceptionRecord.ExceptionInformation[0] = (ULONG_PTR)Resource;
RtlRaiseException(&ExceptionRecord);
}
else {
DbgBreakPoint();
}
}
DbgPrint("RTL: Re-Waiting\n");
goto rewait;
}
if (!NT_SUCCESS(Status)) {
RtlRaiseStatus(Status);
}
}
}
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)) {
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)) {
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)) {
RtlRaiseStatus(Status);
}
}
}
#if DBG
}
else {
// The resource isn't current 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);
}
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 = 0;
// 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 = NtCurrentTeb()->ClientId.UniqueThread;
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 == NtCurrentTeb()->ClientId.UniqueThread)) {
// 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) {
// 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++;
RtlLeaveCriticalSection(&Resource->CriticalSection);
rewait:
Status = NtWaitForSingleObject(Resource->ExclusiveSemaphore, FALSE, &RtlpTimeout);
if (Status == STATUS_TIMEOUT) {
DbgPrint("RTL: Convert Exclusive Sem Timeout %d (2 minutes)\n", TimeoutCount);
DbgPrint("RTL: Resource at %p\n", Resource);
TimeoutCount++;
if (TimeoutCount > 2) {
PIMAGE_NT_HEADERS NtHeaders;
// If the image is a Win32 image, then raise an exception and try to get to the uae popup
NtHeaders = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
if (NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI ||
NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) {
EXCEPTION_RECORD ExceptionRecord;
ExceptionRecord.ExceptionCode = STATUS_POSSIBLE_DEADLOCK;
ExceptionRecord.ExceptionFlags = 0;
ExceptionRecord.ExceptionRecord = NULL;
ExceptionRecord.ExceptionAddress = (PVOID)RtlRaiseException;
ExceptionRecord.NumberParameters = 1;
ExceptionRecord.ExceptionInformation[0] = (ULONG_PTR)Resource;
RtlRaiseException(&ExceptionRecord);
}
else {
DbgBreakPoint();
}
}
DbgPrint("RTL: Re-Waiting\n");
goto rewait;
}
if (!NT_SUCCESS(Status)) {
RtlRaiseStatus(Status);
}
// 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 recusively acquire it again.
if (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread) {
// The resource is ours (recusively) 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
}
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;
RtlEnterCriticalSection(&Resource->CriticalSection);// Enter the critical section
// If there is only one shared user (it's us) and we can acquire the resource for 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)) {
RtlRaiseStatus(Status);
}
}
else {
// There is no one waiting for shared access so it's only ours
Resource->NumberOfActive = 1;
}
RtlLeaveCriticalSection(&Resource->CriticalSection);
return;
}
// 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
}
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));
}
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);
}
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:
TBD - Status of semaphore creation.
*/
{
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 contetion 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
Return Value:
TBD - Status of semaphore creation.
*/
{
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
NTSTATUS Status;
// 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;
}
if ((SpinCount & PREALLOCATE_EVENT_MASK) || (NtCurrentPeb()->NtGlobalFlag & FLG_CRITSEC_EVENT_CREATION)) {
Status = NtCreateEvent(&CriticalSection->LockSemaphore, DESIRED_EVENT_ACCESS, NULL, SynchronizationEvent, FALSE);
if (!NT_SUCCESS(Status)) {
return Status;
}
#if DBG
ProtectHandle(CriticalSection->LockSemaphore);
#endif // DBG
}
// Initialize debugging information.
DebugInfo = (PRTL_CRITICAL_SECTION_DEBUG)RtlpAllocateDebugInfo();
if (DebugInfo == NULL) {
if (CriticalSection->LockSemaphore) {
#if DBG
UnProtectHandle(CriticalSection->LockSemaphore);
#endif // DBG
NtClose(CriticalSection->LockSemaphore);
CriticalSection->LockSemaphore = 0;
}
return STATUS_NO_MEMORY;
}
DebugInfo->Type = RTL_CRITSECT_TYPE;
DebugInfo->ContentionCount = 0;
DebugInfo->EntryCount = 0;
// 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) && (RtlpCritSectInitialized != FALSE)) {
RtlEnterCriticalSection(&RtlCriticalSectionLock);
InsertTailList(&RtlCriticalSectionList, &DebugInfo->ProcessLocksList);
RtlLeaveCriticalSection(&RtlCriticalSectionLock);
}
else {
InsertTailList(&RtlCriticalSectionList, &DebugInfo->ProcessLocksList);
}
DebugInfo->CriticalSection = CriticalSection;
CriticalSection->DebugInfo = DebugInfo;
#if i386
DebugInfo->CreatorBackTraceIndex = (USHORT)RtlLogStackBackTrace();
#endif // i386
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;
}
else {
CriticalSection->SpinCount = 0;
}
return OldSpinCount;
}
VOID RtlpCreateCriticalSectionSem(IN PRTL_CRITICAL_SECTION CriticalSection)
{
NTSTATUS Status;
LARGE_INTEGER tmo = {(ULONG)-100000, -1}; // Start at 1/100th of a second
LONGLONG max_tmo = (LONGLONG)-10000000 * 60 * 3; // Wait for a max of 3 minutes
// Loop trying to create the event on failure.
while (1) {
Status = NtCreateEvent(&CriticalSection->LockSemaphore, DESIRED_EVENT_ACCESS, NULL, SynchronizationEvent, FALSE);
if (NT_SUCCESS(Status)) {
break;
}
else {
if (tmo.QuadPart < max_tmo) {
KdPrint(("NTDLL: Warning. Unable to allocate lock semaphore for Cs %p. Status %x raising\n", CriticalSection, Status));
InterlockedDecrement(&CriticalSection->LockCount);
RtlRaiseStatus(Status);
}
KdPrint(("NTDLL: Warning. Unable to allocate lock semaphore for Cs %p. Status %x retrying\n", CriticalSection, Status));
Status = NtDelayExecution(FALSE, &tmo);
if (!NT_SUCCESS(Status)) {
InterlockedDecrement(&CriticalSection->LockCount);
RtlRaiseStatus(Status);
}
tmo.QuadPart *= 2;
}
}
#if DBG
ProtectHandle(CriticalSection->LockSemaphore);
#endif // DBG
}
VOID RtlpCheckDeferedCriticalSection(IN PRTL_CRITICAL_SECTION CriticalSection)
{
RtlEnterCriticalSection(&DeferedCriticalSection);
try {
if (!CriticalSection->LockSemaphore) {
RtlpCreateCriticalSectionSem(CriticalSection);
}
}
finally{
RtlLeaveCriticalSection(&DeferedCriticalSection);
}
}
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:
TBD - Status of semaphore close.
*/
{
NTSTATUS Status;
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
if (CriticalSection->LockSemaphore) {
#if DBG
UnProtectHandle(CriticalSection->LockSemaphore);
#endif // DBG
Status = NtClose(CriticalSection->LockSemaphore);
}
else {
Status = STATUS_SUCCESS;
}
// Remove critical section from the list
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, FIELD_OFFSET(RTL_CRITICAL_SECTION, SpinCount) + sizeof(ULONG));
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;
BOOLEAN CsIsLoaderLock;
// critical sections are disabled during exit process so that apps that are not carefull during shutdown don't hang
CsIsLoaderLock = (CriticalSection == NtCurrentPeb()->LoaderLock);
NtCurrentTeb()->WaitingOnLoaderLock = (ULONG)CsIsLoaderLock;
if (LdrpShutdownInProgress && ((!CsIsLoaderLock) || (CsIsLoaderLock && LdrpShutdownThreadId == NtCurrentTeb()->ClientId.UniqueThread))) {
// slimey 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;
NtCurrentTeb()->WaitingOnLoaderLock = 0;
return;
}
if (RtlpTimoutDisable) {
TimeoutTime = NULL;
}
else {
TimeoutTime = &RtlpTimeout;
}
if (!CriticalSection->LockSemaphore) {
RtlpCheckDeferedCriticalSection(CriticalSection);
}
CriticalSection->DebugInfo->EntryCount++;
while (TRUE) {
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
st = NtWaitForSingleObject(CriticalSection->LockSemaphore, FALSE, TimeoutTime);
if (st == STATUS_TIMEOUT) {
DbgPrint("RTL: Enter Critical Section Timeout (2 minutes) %d\n", TimeoutCount);
DbgPrint("RTL: Pid.Tid %x.%x, owner tid %x Critical Section %p - ContentionCount == %lu\n",
NtCurrentTeb()->ClientId.UniqueProcess,
NtCurrentTeb()->ClientId.UniqueThread,
CriticalSection->OwningThread,
CriticalSection, CriticalSection->DebugInfo->ContentionCount);
TimeoutCount++;
if (TimeoutCount > 2 && CriticalSection != NtCurrentPeb()->LoaderLock) {
PIMAGE_NT_HEADERS NtHeaders;
// If the image is a Win32 image, then raise an exception and try to get to the uae popup
NtHeaders = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
if (NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI ||
NtHeaders->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) {
EXCEPTION_RECORD ExceptionRecord;
ExceptionRecord.ExceptionCode = STATUS_POSSIBLE_DEADLOCK;
ExceptionRecord.ExceptionFlags = 0;
ExceptionRecord.ExceptionRecord = NULL;
ExceptionRecord.ExceptionAddress = (PVOID)RtlRaiseException;
ExceptionRecord.NumberParameters = 1;
ExceptionRecord.ExceptionInformation[0] = (ULONG_PTR)CriticalSection;
RtlRaiseException(&ExceptionRecord);
}
else {
DbgBreakPoint();
}
}
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 = NtCurrentTeb()->ClientId.UniqueThread;
NtCurrentTeb()->WaitingOnLoaderLock = 0;
}
return;
}
else {
RtlRaiseStatus(st);
}
}
}
}
void RtlpUnWaitCriticalSection(IN PRTL_CRITICAL_SECTION CriticalSection)
{
NTSTATUS st;
#if 0
DbgPrint("NTDLL: Releasing CritSect: %p ThreadId: %X\n", CriticalSection, CriticalSection->OwningThread);
#endif
if (!CriticalSection->LockSemaphore) {
RtlpCheckDeferedCriticalSection(CriticalSection);
}
#if DBG
// Sneaky trick here to catch sleazy apps (csrss) that erroneously call
// NtSetEvent on an event that happens to be somebody else's
// critical section. Only allow setting a protected handle if the low bit of PreviousState is set.
st = NtSetEvent(CriticalSection->LockSemaphore, (PLONG)1);
#else
st = NtSetEvent(CriticalSection->LockSemaphore, NULL);
#endif
if (NT_SUCCESS(st)) {
return;
}
else {
RtlRaiseStatus(st);
}
}
void RtlpNotOwnerCriticalSection(IN PRTL_CRITICAL_SECTION CriticalSection)
{
BOOLEAN CsIsLoaderLock;
// critical sections are disabled during exit process so that apps that are not carefull during shutdown don't hang
CsIsLoaderLock = (CriticalSection == NtCurrentPeb()->LoaderLock);
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);
#if i386
_asm { int 3 }
#else
DbgBreakPoint();
#endif
}
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 == NtCurrentPeb()->LoaderLock && 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);
#if i386
_asm { int 3 }
#else
DbgBreakPoint();
#endif
}
}
#endif
void RtlCheckForOrphanedCriticalSections(IN HANDLE hThread)
/*++
Routine Description:
This routine is called from kernel32's ExitThread and TerminateThread
in an effort to track calls that kill threads while they own critical sections.
Arguments:
hThread -- thread to be killed
Return Value:
None.
*/
{
// This whole routine should be called only on checked builds.
// It is a stub in the free build, so that a checked kernel32.dll
// can bind against a free ntdll.dll
#if DBG
NTSTATUS Status;
THREAD_BASIC_INFORMATION ThreadInfo;
PLIST_ENTRY Entry;
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
PRTL_CRITICAL_SECTION CriticalSection;
BOOLEAN OrphanAboutToHappen;
RTL_CRITICAL_SECTION CritSectCopy;
if (LdrpShutdownInProgress) {
// In the middle of shutting down the process
return;
}
Status = NtQueryInformationThread(hThread, ThreadBasicInformation, &ThreadInfo, sizeof(ThreadInfo), NULL);
if (!NT_SUCCESS(Status)) {
return;
}
OrphanAboutToHappen = FALSE;
RtlEnterCriticalSection(&RtlCriticalSectionLock);
for (Entry = RtlCriticalSectionList.Flink; Entry != &RtlCriticalSectionList; Entry = Entry->Flink) {
DebugInfo = CONTAINING_RECORD(Entry, RTL_CRITICAL_SECTION_DEBUG, ProcessLocksList);
CriticalSection = DebugInfo->CriticalSection;
if (CriticalSection == &RtlCriticalSectionLock || CriticalSection == NtCurrentPeb()->LoaderLock) {
// Skip these critsects.
continue;
}
// Call NtReadVirtualMemory() and make a copy of the critsect.
// This won't AV and break to the debugger if the critsect's memory has been freed without an RtlDeleteCriticalSection call.
Status = NtReadVirtualMemory(NtCurrentProcess(), CriticalSection, &CritSectCopy, sizeof(CritSectCopy), NULL);
if (!NT_SUCCESS(Status) || CritSectCopy.DebugInfo != DebugInfo) {
// Error reading the contents of the critsect. The critsect
// has probably been decommitted without a call to RtlDeleteCriticalSection. Ignore it.
// 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 who DebugInfo pointer
// points at this DebugInfo. In that case, when that critsect is deleted, the RtlCriticalSectionList is corrupted.
}
else if (CritSectCopy.OwningThread == ThreadInfo.ClientId.UniqueThread && CritSectCopy.LockCount != -1) {
// The thread is about to die with a critical section locked.
DbgPrint("NTDLL: Pid.Tid %x.%x Critsec %p about to be orphaned when owning thread is terminated.\n",
NtCurrentTeb()->ClientId.UniqueProcess, NtCurrentTeb()->ClientId.UniqueThread, CriticalSection);
OrphanAboutToHappen = TRUE;
}
}
if (OrphanAboutToHappen) {
#if i386
_asm { int 3 }
#else
DbgBreakPoint();
#endif
}
RtlLeaveCriticalSection(&RtlCriticalSectionLock);
#endif
}