675 lines
21 KiB
C
675 lines
21 KiB
C
/*++
|
|
|
|
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
|
|
|
|
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;
|
|
ULONG i;
|
|
ULONG j;
|
|
ULONG64 Thread;
|
|
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;
|
|
|
|
INIT_API();
|
|
GetCurrentProcessor(Client, &dwProcessor, NULL);
|
|
ResourceToDump = 0;
|
|
|
|
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 ContentionCount, 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 mutexes.
|
|
//
|
|
|
|
DumpStaticFastMutex("CmpKcbLock");
|
|
DumpStaticFastMutex("FsRtlCreateLockInfo");
|
|
DumpStaticFastMutex("MmPageFileCreationLock");
|
|
DumpStaticFastMutex("MmSectionCommitMutex");
|
|
DumpStaticFastMutex("MmSectionBasedMutex");
|
|
DumpStaticFastMutex("ObpRootDirectoryMutex");
|
|
DumpStaticFastMutex("PspActiveProcessMutex");
|
|
DumpStaticFastMutex("PspProcessLockMutex");
|
|
DumpStaticFastMutex("PspProcessSecurityLock");
|
|
DumpStaticFastMutex("SepLsaQueueLock");
|
|
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);
|
|
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;
|
|
}
|
|
|
|
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 {
|
|
TotalUsedLocks++;
|
|
dprintf(" Shared %u owning threads\n", ActiveCount);
|
|
}
|
|
|
|
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;
|
|
|
|
dprintf(" Threads: ");
|
|
|
|
if (DdkResource == 0) {
|
|
|
|
GetFieldValue( Resource + OwnerThreadsOffset, "nt!_OWNER_ENTRY","OwnerThread",Thread);
|
|
GetFieldValue( Resource + OwnerThreadsOffset, "nt!_OWNER_ENTRY","OwnerCount",ThreadCount);
|
|
if (Thread != 0) {
|
|
j++;
|
|
dprintf("%08p-%02x ", Thread, ThreadCount);
|
|
|
|
if (Thread & 3) {
|
|
dprintf("*** Unknown owner, possibly FileSystem");
|
|
j=4;
|
|
} else if (GetFieldValue(Thread, "nt!_ETHREAD", "Tcb.Header.Type", ThreadType) ||
|
|
(ThreadType != ThreadObject)) {
|
|
dprintf("*** Invalid thread");
|
|
j=4;
|
|
|
|
}
|
|
|
|
if (Verbose) {
|
|
dprintf("\n\n");
|
|
DumpThread(dwProcessor, " ", Thread, 0xf );
|
|
}
|
|
}
|
|
|
|
GetFieldValue( Resource + OwnerThreadsOffset +SizeofOwnerEntry,
|
|
"nt!_OWNER_ENTRY","OwnerThread",Thread);
|
|
GetFieldValue( Resource + OwnerThreadsOffset +SizeofOwnerEntry,
|
|
"nt!_OWNER_ENTRY","OwnerCount",ThreadCount);
|
|
if (Thread != 0) {
|
|
j++;
|
|
|
|
dprintf("%08p-%02x ", Thread, ThreadCount);
|
|
|
|
if (Thread & 3) {
|
|
dprintf("*** Unknown owner, possibly FileSystem");
|
|
j=4;
|
|
} else if (GetFieldValue(Thread, "nt!_ETHREAD", "Tcb.Header.Type", ThreadType) ||
|
|
(ThreadType != ThreadObject)) {
|
|
dprintf("*** Invalid thread");
|
|
j=4;
|
|
|
|
}
|
|
|
|
if (Verbose) {
|
|
|
|
dprintf("\n\n");
|
|
DumpThread( dwProcessor, " ", Thread, 0xf );
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ");
|
|
}
|
|
|
|
dprintf("%08p-%02x ", Thread, ThreadCount);
|
|
j++;
|
|
|
|
if (Thread & 3) {
|
|
dprintf("*** Unknown owner, possibly FileSystem");
|
|
j=4;
|
|
} else if (GetFieldValue(Thread, "nt!_ETHREAD", "Tcb.Header.Type", ThreadType) ||
|
|
(ThreadType != ThreadObject)) {
|
|
dprintf("*** Invalid thread");
|
|
j=4;
|
|
|
|
}
|
|
|
|
//
|
|
// In verbose mode also dump the thread stacks
|
|
//
|
|
|
|
if (Verbose) {
|
|
dprintf("\n\n");
|
|
DumpThread( dwProcessor, " ", Thread, 0xf );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( CheckControlC() ) {
|
|
hr = E_INVALIDARG;
|
|
goto exitBangLocks;
|
|
}
|
|
}
|
|
|
|
if (j) {
|
|
dprintf("\n");
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
if ((SkippedLocks++ % 32) == 0) {
|
|
if (SkippedLocks == 1) {
|
|
dprintf("KD: Scanning for held locks." );
|
|
|
|
} else {
|
|
dprintf("." );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ResourceToDump != 0) {
|
|
break;
|
|
}
|
|
|
|
GetFieldValue( Resource,"nt!_ERESOURCE","SystemResourcesList.Flink", Next);
|
|
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;
|
|
}
|