/*++ Copyright (c) 1992 Microsoft Corporation Module Name: locks.c Abstract: WinDbg Extension Api Author: Ramon J San Andres (ramonsa) 5-Nov-1993 Environment: User Mode. Revision History: --*/ #include "precomp.h" #pragma hdrstop BOOLEAN FindQWord( IN ULONG64 Value, IN ULONG64 Array[], IN ULONG EntriesToSearch ) { // clear low few bits which are sometimes used as flag bits :( Value &= ~(ULONG64)3; while (EntriesToSearch) { if (Array[--EntriesToSearch] == Value) { return TRUE; } } return FALSE; } BOOLEAN GetWaitListFromDispatcherHeader( IN ULONG64 ShWaitListArray[], IN ULONG ArrayByteSize, IN ULONG64 DispatcherHeaderAddress, OUT PULONG EntriesFilledIn ) { ULONG KThreadWaitListOffset; ULONG64 TerminationAddress = 0; ULONG64 CurrListEntry; ULONG MaxEntries = ArrayByteSize / sizeof( ULONG64); ULONG64 NextListEntry; ULONG64 ThreadAddr; ULONG64 Mask = IsPtr64() ? ~(ULONG64)0 : 0xffffffff; *EntriesFilledIn = 0; GetFieldOffset("nt!_KTHREAD", "WaitBlock", &KThreadWaitListOffset); GetFieldOffset("nt!_DISPATCHER_HEADER", "WaitListHead", &(ULONG)TerminationAddress); TerminationAddress += DispatcherHeaderAddress; CurrListEntry = TerminationAddress; // note: does not process initial (pased in) list entry as an object. do { GetFieldValue( CurrListEntry, "nt!_LIST_ENTRY", "Flink", NextListEntry); CurrListEntry = NextListEntry; if (CurrListEntry != TerminationAddress) { ThreadAddr = CurrListEntry - KThreadWaitListOffset; ShWaitListArray[ *EntriesFilledIn] = ThreadAddr & Mask; *EntriesFilledIn += 1; } // better check CTRL+C here in case we're walking trash memory if ((0 == (*EntriesFilledIn % 10)) && CheckControlC() ) { *EntriesFilledIn = -1; return FALSE; } } while ((CurrListEntry != TerminationAddress) && (*EntriesFilledIn < MaxEntries)); // edge case here (entries == maxentries) but.. if (*EntriesFilledIn == MaxEntries) { *EntriesFilledIn = -1; } return TRUE; } void ShowThread( ULONG dwProcessor, ULONG64 Thread, ULONG ThreadCount, ULONG Verbose, PULONG Count, BOOLEAN IsOwner) { ULONG ThreadType; ULONG64 ActualThread; if (Thread != 0) { (*Count)++; dprintf("%08p-%02x%s ", Thread, ThreadCount, IsOwner ? "<*>" : " "); ActualThread = (Thread | 3) - 3; if (GetFieldValue(ActualThread, "nt!_ETHREAD", "Tcb.Header.Type", ThreadType) || (ThreadType != ThreadObject)) { dprintf("*** Unknown owner, possibly FileSystem"); *Count=4; } else if (Thread & 3) { dprintf("*** Actual Thread %p", ActualThread); *Count=4; } if (Verbose) { dprintf("\n\n"); DumpThread(dwProcessor, " ", ActualThread, 0xf ); } } } DECLARE_API( locks ) /*++ Routine Description: Dump kernel mode resource locks Arguments: arg - [-V] [-P] [Address] Return Value: None --*/ { UCHAR Buffer[256]; LONG ActiveCount; ULONG ContentionCount; ULONG64 Displacement; BOOLEAN DisplayZero; ULONG64 End; USHORT Flag; ULONG Index; USHORT NumberOfExclusiveWaiters; USHORT NumberOfSharedWaiters; BOOLEAN Performance; ULONG64 PerformanceData; ULONG TableSize; ULONG64 ResourceHead; ULONG64 Next; ULONG Result; ULONG64 ResourceToDump; ULONG64 Resource; ULONG64 DdkResource; ULONG64 ShWaitListArray[1024]; BOOLEAN Owner; ULONG i; ULONG j; ULONG64 Thread; ULONG64 SharedWaitersSmpAdr; ULONG64 ExclusiveWaitersEvAdr; BOOLEAN DetermineSharedOwners; BOOLEAN AllSharedOwners; ULONG SharedWaiterCount; LONG ThreadCount; UCHAR DdkThreadCount; BOOLEAN Verbose; PUCHAR s; ULONG TotalLocks; ULONG TotalUsedLocks; ULONG SkippedLocks; ULONG SizeOfListEntry, SizeofOwnerEntry; ULONG InitialOwnerThreadsOffset, OwnerThreadsOffset; ULONG dwProcessor=0; HRESULT hr = S_OK; ULONG64 Link; ULONG64 FlinkBlink; ULONG64 BlinkFlink; ULONG ResourceListOffset; INIT_API(); GetCurrentProcessor(Client, &dwProcessor, NULL); ResourceToDump = 0; GetFieldOffset("nt!_ERESOURCE", "SystemResourcesList", &ResourceListOffset); DisplayZero = FALSE; Performance = FALSE; Verbose = FALSE; s = (PSTR)args; while ( s != NULL && *s ) { if (*s == '-' || *s == '/') { while (*++s) { switch (*s) { case 'D': case 'd': DisplayZero = TRUE; break; case 'P': case 'p': Performance = TRUE; break; case 'V': case 'v': Verbose = TRUE; break; case ' ': goto gotBlank; default: dprintf( "KD: !locks invalid option flag '-%c'\n", *s ); break; } } } else if (*s != ' ') { ResourceToDump = GetExpression(s); s = strpbrk( s, " " ); } else { gotBlank: s++; } } // // Dump performance data if requested. // if (Performance != FALSE) { UCHAR ResPerf[]="nt!_RESOURCE_PERFORMANCE_DATA"; ULONG TotalResourceCount, ActiveResourceCount, ExclusiveAcquire; ULONG SharedFirstLevel, SharedSecondLevel, StarveFirstLevel, StarveSecondLevel; ULONG WaitForExclusive, OwnerTableExpands, MaximumTableExpand; ULONG HashTableOffset; dprintf("**** Dump Resource Performance Data ****\n\n"); PerformanceData = GetExpression("nt!ExpResourcePerformanceData"); if ((PerformanceData == 0) || GetFieldValue(PerformanceData, ResPerf,"TotalResourceCount",TotalResourceCount)) { // // The target build does not support resource performance data. // dprintf("%08p: No resource performance data available\n", PerformanceData); } else { GetFieldOffset(ResPerf, "HashTable", &HashTableOffset); GetFieldValue(PerformanceData, ResPerf, "ActiveResourceCount", ActiveResourceCount); GetFieldValue(PerformanceData, ResPerf,"ExclusiveAcquire", ExclusiveAcquire); GetFieldValue(PerformanceData, ResPerf, "SharedFirstLevel", SharedFirstLevel); GetFieldValue(PerformanceData, ResPerf,"SharedSecondLevel", SharedSecondLevel); GetFieldValue(PerformanceData, ResPerf, "StarveFirstLevel", StarveFirstLevel); GetFieldValue(PerformanceData, ResPerf, "StarveSecondLevel", StarveSecondLevel); GetFieldValue(PerformanceData, ResPerf, "WaitForExclusive", WaitForExclusive); GetFieldValue(PerformanceData, ResPerf, "OwnerTableExpands", OwnerTableExpands); GetFieldValue(PerformanceData, ResPerf, "MaximumTableExpand", MaximumTableExpand); // // Output the summary statistics. // dprintf("Total resources initialized : %u\n", TotalResourceCount); dprintf("Currently active resources : %u\n", ActiveResourceCount); dprintf("Exclusive resource acquires : %u\n", ExclusiveAcquire); dprintf("Shared resource acquires (fl) : %u\n", SharedFirstLevel); dprintf("Shared resource acquires (sl) : %u\n", SharedSecondLevel); dprintf("Starve resource acquires (fl) : %u\n", StarveFirstLevel); dprintf("Starve resource acquires (sl) : %u\n", StarveSecondLevel); dprintf("Shared wait resource acquires : %u\n", WaitForExclusive); dprintf("Owner table expansions : %u\n", OwnerTableExpands); dprintf("Maximum table expansion : %u\n\n", MaximumTableExpand); // // Dump the inactive resource statistics. // dprintf(" Inactive Resource Statistics\n"); dprintf("Contention Number Initialization Address\n\n"); SizeOfListEntry = GetTypeSize("nt!_LIST_ENTRY"); for (Index = 0; Index < RESOURCE_HASH_TABLE_SIZE; Index += 1) { End = HashTableOffset + PerformanceData + SizeOfListEntry * Index; GetFieldValue(End,"nt!_LIST_ENTRY","Flink",Next); while (Next != End) { ULONG64 Address; ULONG Number; if (CheckControlC()) { break; } if (!GetFieldValue(Next, "nt!_RESOURCE_HASH_ENTRY", "Address", Address)) { GetSymbol(Address, Buffer, &Displacement); GetFieldValue(Next,"nt!_RESOURCE_HASH_ENTRY","Number",Number); GetFieldValue(Next,"nt!_RESOURCE_HASH_ENTRY","ContentionCount",ContentionCount); dprintf("%10d %6d %s", ContentionCount, Number, Buffer); if (Displacement != 0) { dprintf("+0x%x", Displacement); } dprintf("\n"); } GetFieldValue(Next,"nt!_RESOURCE_HASH_ENTRY","ListEntry.Flink", Next); } } // // Dump the active resource statistics. // dprintf("\n Active Resource Statistics\n"); dprintf("Resource Contention Initialization Address\n\n"); // // Read the resource listhead and check if it is empty. // ResourceHead = GetNtDebuggerData( ExpSystemResourcesList ); if ((ResourceHead == 0) || (!GetFieldValue(ResourceHead, "nt!_LIST_ENTRY", "Flink", Next) == FALSE)) { dprintf("%08p: Unable to get value of ExpSystemResourcesList\n", ResourceHead ); hr = E_INVALIDARG; goto exitBangLocks; } if (Next == 0) { dprintf("ExpSystemResourcesList is NULL!\n"); hr = E_INVALIDARG; goto exitBangLocks; } // // Scan the resource list and dump the resource information. // while(Next != ResourceHead) { ULONG64 Address; if (CheckControlC()) { break; } Resource = Next; // SystemResourcesList is the first element in struct // CONTAINING_RECORD(Next, ERESOURCE, SystemResourcesList); if (!GetFieldValue(Resource, "nt!_ERESOURCE", "ContentionCount", ContentionCount) == FALSE) { dprintf("%08p: Unable to read _ERESOURCE\n", Resource); continue; } else { GetFieldValue(Resource,"nt!_ERESOURCE","Address",Address); GetFieldValue(Resource,"nt!_ERESOURCE","ContentionCount",ContentionCount); if ((ContentionCount != 0) || (DisplayZero != FALSE)) { GetSymbol(Address, Buffer, &Displacement); dprintf("%08p %10d %s", Resource, ContentionCount, Buffer); if (Displacement != 0) { dprintf("+0x%x", Displacement); } dprintf("\n"); } } GetFieldValue(Resource,"nt!_ERESOURCE","SystemResourcesList.Flink",Next); } dprintf("\n"); // // Dump the active fast mutex statistics. // dprintf("\n Active Fast Mutex Statistics\n"); dprintf("Address Contention Fast Mutex Name\n\n"); // // Dump statistics for static fast/guarded mutexes. // DumpStaticFastMutex("FsRtlCreateLockInfo"); DumpStaticFastMutex("PspActiveProcessMutex"); dprintf("\n"); } hr = E_INVALIDARG; goto exitBangLocks; } // // Dump remaining lock data. // if (ResourceToDump == 0) { dprintf("**** DUMP OF ALL RESOURCE OBJECTS ****\n"); ResourceHead = GetNtDebuggerData( ExpSystemResourcesList ); if ( !ResourceHead || (GetFieldValue(ResourceHead, "nt!_LIST_ENTRY", "Flink", Next) != FALSE)) { dprintf("%08p: Unable to get value of ExpSystemResourcesList\n", ResourceHead ); hr = E_INVALIDARG; goto exitBangLocks; } if (Next == 0) { dprintf("ExpSystemResourcesList is NULL!\n"); hr = E_INVALIDARG; goto exitBangLocks; } } else { Next = 0; ResourceHead = 1; } TotalLocks = 0; TotalUsedLocks = 0; SkippedLocks = 0; // Get the offset of OwnerThreads in ERESOURCE if (GetFieldOffset("nt!_ERESOURCE", "OwnerThreads", &OwnerThreadsOffset)) { dprintf("Cannot get _ERESOURCE type\n"); hr = E_INVALIDARG; goto exitBangLocks; } if (!(SizeofOwnerEntry = GetTypeSize("nt!_OWNER_ENTRY"))) { dprintf("Cannot get nt!_OWNER_ENTRY type\n"); hr = E_INVALIDARG; goto exitBangLocks; } while(Next != ResourceHead) { ULONG64 OwnerThreads, OwnerCounts, OwnerTable; if (Next != 0) { Resource = Next;// SystemResourcesList is the first element of struct ERESOURCE // CONTAINING_RECORD(Next,ERESOURCE,SystemResourcesList); } else { Resource = ResourceToDump; } /* if ( GetFieldValue( Resource, "NTDDK_ERESOURCE", "OwnerThreads", OwnerThreads) ) { dprintf("%08lx: Unable to read NTDDK_ERESOURCE\n", Resource ); break; }*/ // // Detect here if this is an NtDdk resource, and behave // appropriatelty. If the OwnerThreads is a pointer to the initial // owner threads array (this must take into account that the LOCAL // data structure is a copy of what's in the remote machine in a // different address) // // DdkResource = (PNTDDK_ERESOURCE)&ResourceContents; { DdkResource = 0; GetFieldValue( Resource,"nt!_ERESOURCE","ActiveCount", ActiveCount); GetFieldValue( Resource,"nt!_ERESOURCE","ContentionCount",ContentionCount); GetFieldValue( Resource,"nt!_ERESOURCE","NumberOfExclusiveWaiters",NumberOfExclusiveWaiters); GetFieldValue( Resource,"nt!_ERESOURCE","NumberOfSharedWaiters",NumberOfSharedWaiters); GetFieldValue( Resource,"nt!_ERESOURCE","Flag",Flag); GetFieldValue( Resource,"nt!_ERESOURCE","OwnerTable",OwnerTable); GetFieldValue( Resource,"nt!_ERESOURCE","SharedWaiters",SharedWaitersSmpAdr); GetFieldValue( Resource,"nt!_ERESOURCE","ExclusiveWaiters",ExclusiveWaitersEvAdr); // // Grab the Flink->Blink and Blink->Flink contents to verify list integrity. Since // ExDeleteResource doesn't null any fields, it's possible that what looks like // a resource is no more. // FlinkBlink = BlinkFlink = 0; GetFieldValue( Resource,"nt!_ERESOURCE","SystemResourcesList.Flink",Link); GetFieldValue( Link,"nt!_LIST_ENTRY","Blink",FlinkBlink); GetFieldValue( Resource,"nt!_ERESOURCE","SystemResourcesList.Blink",Link); GetFieldValue( Link,"nt!_LIST_ENTRY","Flink",BlinkFlink); TableSize = 0; if (OwnerTable != 0) { if (GetFieldValue(OwnerTable, "nt!_OWNER_ENTRY", "TableSize", TableSize)) { dprintf("\n%08p: Unable to read TableSize for resource\n", OwnerTable); break; } } } TotalLocks++; if ((ResourceToDump != 0) || Verbose || (ActiveCount != 0)) { EXPRLastDump = Resource; if (SkippedLocks) { dprintf("\n"); SkippedLocks = 0; } DetermineSharedOwners = AllSharedOwners = FALSE; dprintf("\n"); dumpSymbolicAddress(Resource, Buffer, TRUE); dprintf("Resource @ %s", Buffer ); if (ActiveCount == 0) { dprintf(" Available\n"); } else if (Flag & ResourceOwnedExclusive) { TotalUsedLocks++; dprintf(" Exclusively owned\n"); } else { // owned shared TotalUsedLocks++; dprintf(" Shared %u owning threads\n", ActiveCount); if (NumberOfSharedWaiters) { DetermineSharedOwners = TRUE; } else { AllSharedOwners = TRUE; } } if (FlinkBlink != Resource+ResourceListOffset) { dprintf("\nWARNING: SystemResourcesList->Flink chain invalid. Resource may be corrupted, or already deleted.\n\n"); } if (BlinkFlink != Resource+ResourceListOffset) { dprintf("\nWARNING: SystemResourcesList->Blink chain invalid. Resource may be corrupted, or already deleted.\n\n"); } if (ContentionCount != 0) { dprintf(" Contention Count = %u\n", ContentionCount); } if (NumberOfSharedWaiters != 0) { dprintf(" NumberOfSharedWaiters = %u\n", NumberOfSharedWaiters); } if (NumberOfExclusiveWaiters != 0) { dprintf(" NumberOfExclusiveWaiters = %u\n", NumberOfExclusiveWaiters); } if (ActiveCount != 0) { ULONG ThreadType; j = 0; if (DetermineSharedOwners) { // Extract the list of shared waiters from the semaphore. if (!GetWaitListFromDispatcherHeader( ShWaitListArray, sizeof( ShWaitListArray), SharedWaitersSmpAdr, &SharedWaiterCount)) { hr = E_INVALIDARG; goto exitBangLocks; } // The count could be -1 meaning there were too many for our array. if (-1 == SharedWaiterCount) { dprintf("<< Too many shared waiters to determine owners >>\n"); DetermineSharedOwners = FALSE; SharedWaiterCount = NumberOfSharedWaiters; } if (SharedWaiterCount != NumberOfSharedWaiters) { dprintf("WARNING: Shared waiters in semaphore waitlist (%d) != count in resource (%d)\n", SharedWaiterCount, NumberOfSharedWaiters); } } dprintf(" Threads: "); // Print the embedded 2 owner entries if (DdkResource == 0) { GetFieldValue( Resource + OwnerThreadsOffset, "nt!_OWNER_ENTRY","OwnerThread",Thread); GetFieldValue( Resource + OwnerThreadsOffset, "nt!_OWNER_ENTRY","OwnerCount",ThreadCount); Owner = ResourceOwnedExclusive; ShowThread(dwProcessor, Thread, ThreadCount, Verbose, &j, Owner); GetFieldValue( Resource + OwnerThreadsOffset +SizeofOwnerEntry, "nt!_OWNER_ENTRY","OwnerThread",Thread); GetFieldValue( Resource + OwnerThreadsOffset +SizeofOwnerEntry, "nt!_OWNER_ENTRY","OwnerCount",ThreadCount); Owner = DetermineSharedOwners ? !FindQWord( Thread, ShWaitListArray, SharedWaiterCount) : AllSharedOwners; ShowThread(dwProcessor, Thread, ThreadCount, Verbose, &j, Owner); } if (TableSize > 2000) { // sanity check dprintf("Owner TableSize too large (%ld) - probably a bad resource.\n"); hr = E_INVALIDARG; goto exitBangLocks; } // Now list the entries from the overflow owner table for (i = DdkResource ? 0 : 1; i < TableSize; i++) { { GetFieldValue( OwnerTable + SizeofOwnerEntry*i, "nt!_OWNER_ENTRY","OwnerThread",Thread); GetFieldValue( OwnerTable + SizeofOwnerEntry*i, "nt!_OWNER_ENTRY","OwnerCount",ThreadCount); } if ((Thread == 0) && (ThreadCount == 0)) { continue; } if (j == 4) { j = 0; dprintf("\n "); } Owner = DetermineSharedOwners ? !FindQWord( Thread, ShWaitListArray, SharedWaiterCount) : AllSharedOwners; ShowThread(dwProcessor, Thread, ThreadCount, Verbose, &j, Owner); if ( CheckControlC() ) { hr = E_INVALIDARG; goto exitBangLocks; } } // List any exclusive waiters if (NumberOfExclusiveWaiters) { // Extract the list of waiters from the event. if (!GetWaitListFromDispatcherHeader( ShWaitListArray, sizeof( ShWaitListArray), ExclusiveWaitersEvAdr, &SharedWaiterCount)) { hr = E_INVALIDARG; goto exitBangLocks; } // The count could be -1 meaning there were too many for our array. if (-1 == SharedWaiterCount) { dprintf("<< Too many exclusive waiters to list>>\n"); } else { ULONG Count; if (SharedWaiterCount != NumberOfExclusiveWaiters) { dprintf("WARNING: Exclusive waiters in event waitlist (%d) != count in resource (%d)\n", SharedWaiterCount, NumberOfExclusiveWaiters); } dprintf("\n Threads Waiting On Exclusive Access:"); for (Count = 0; Count < SharedWaiterCount; Count++) { if (0 == (Count % 4)) { dprintf("\n "); } dprintf("%08p ",ShWaitListArray[Count]); if ((0 == (Count % 10)) && CheckControlC()) { hr = E_INVALIDARG; goto exitBangLocks; } } dprintf("\n"); } } if (j) { dprintf("\n"); } } } else { if ((SkippedLocks++ % 32) == 0) { if (SkippedLocks == 1) { dprintf("KD: Scanning for held locks." ); } else { dprintf("." ); } } } if (ResourceToDump != 0) { break; } if (hr = GetFieldValue( Resource,"nt!_ERESOURCE","SystemResourcesList.Flink", Next)) { dprintf("Error %lx in reading nt!_ERESOURCE.SystemResourcesList.Flink @ %p\n", Resource); goto exitBangLocks; } if ( CheckControlC() ) { hr = E_INVALIDARG; goto exitBangLocks; } } if (SkippedLocks) { dprintf("\n"); } dprintf( "%u total locks", TotalLocks ); if (TotalUsedLocks) { dprintf( ", %u locks currently held", TotalUsedLocks ); } dprintf("\n"); exitBangLocks: EXIT_API(); return hr; } VOID DumpStaticFastMutex ( IN PCHAR Name ) /*++ Routine Description: This function dumps the contention statistics for a fast mutex. Arguments: Name - Supplies a pointer to the symbol name for the fast mutex. Return Value: None. --*/ { ULONG64 FastMutex; ULONG Contention; ULONG Result; // // Get the address of the fast mutex, read the fast mutex contents, // and dump the contention data. // FastMutex = GetExpression(Name); if ((FastMutex != 0) && (!GetFieldValue(FastMutex, "nt!_FAST_MUTEX", "Contention", Contention))) { dprintf("%08p %10u %s\n", FastMutex, Contention, &Name[0]); } return; }