2433 lines
59 KiB
C
2433 lines
59 KiB
C
/*++
|
|
|
|
Copyright (c) 1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
vfpdlock.c
|
|
|
|
Abstract:
|
|
|
|
Detect deadlocks in arbitrary synchronization objects.
|
|
|
|
Author:
|
|
|
|
Jordan Tigani (jtigani) 2-May-2000
|
|
Silviu Calinoiu (silviuc) 9-May-2000
|
|
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
//
|
|
// TO DO LIST
|
|
//
|
|
// - detect a resource allocated on the stack of current thread and give
|
|
// warning about this.
|
|
// - clean deletion of resources, threads, nodes
|
|
// - get rid of FirstNode hack
|
|
// - create nodes based on (R, T, Stk).
|
|
// - keep deleted nodes around if other nodes in the path are still alive.
|
|
// See ISSUE in ViDeadlockDeleteRange.
|
|
// - Make sure NodeCount is updated all over the place.
|
|
//
|
|
|
|
#define _BUGGY_ 1
|
|
|
|
//
|
|
// ISSUE --
|
|
// This ifdef lets us move the code back and forth between the kernel
|
|
// and the buggy driver -- the latter is for testing purposes
|
|
//
|
|
|
|
#if _BUGGY_
|
|
|
|
#include <ntddk.h>
|
|
#include "deadlock.h"
|
|
|
|
#else
|
|
|
|
#include "vfdef.h"
|
|
#endif
|
|
|
|
//#include "vfpdlock.h"
|
|
|
|
//
|
|
// Deadlock detection structures.
|
|
//
|
|
// There are three important structures involved: THREAD, RESOURCE, NODE.
|
|
//
|
|
// For every active thread in the system that holds at least one resource
|
|
// the package maintains a THREAD structure. This gets created when a thread
|
|
// acquires first resource and gets destroyed when thread releases the last
|
|
// resource. If a thread does not hold any resource it will not have a
|
|
// corresponding THREAD structure.
|
|
//
|
|
// For every resource in the system there is a RESOURCE structure. A dead resource
|
|
// might still have a RESOURCE laying around because the algorithm to garbage
|
|
// collect old resources is of the lazy type.
|
|
//
|
|
// Every acquisition of a resource is modeled by a NODE structure. When a thread
|
|
// acquires resource B while holding A the package will create a NODE for B and link
|
|
// it to the node for A. Beware that this is a very general description of what
|
|
// happens.
|
|
//
|
|
// There are three important functions that make the interface with the outside
|
|
// world.
|
|
//
|
|
// ViDeadlockAddResource hook for resource initialization
|
|
// ViDeadlockQueryAcquireResource checks for deadlock before resource acquisition
|
|
// ViDeadlockAcquireResource hook for resource acquire
|
|
// ViDeadlockReleaseResource hook for resource release
|
|
//
|
|
// Unfortunately almost no kernel synchronization object has a delete routine
|
|
// therefore we need to lazily garbage collect any zombie resources from our
|
|
// structures.
|
|
//
|
|
|
|
//
|
|
// Did we initialize the verifier deadlock detection package?
|
|
// If this variable is false no detection will be done whatsoever.
|
|
//
|
|
|
|
BOOLEAN ViDeadlockDetectionInitialized = FALSE;
|
|
|
|
//
|
|
// Enable/disable the deadlock detection package. This can be used
|
|
// to disable temporarily the deadlock detection package.
|
|
//
|
|
|
|
BOOLEAN ViDeadlockDetectionEnabled =
|
|
#if _BUGGY_
|
|
TRUE;
|
|
#else
|
|
FALSE;
|
|
#endif
|
|
|
|
|
|
#define VI_DEADLOCK_FLAG_RECURSIVE_ACQUISITION_OK 0x1
|
|
#define VI_DEADLOCK_FLAG_NO_INITIALIZATION_FUNCTION 0x2
|
|
|
|
ULONG ViDeadlockResourceTypeInfo[ViDeadlockTypeMaximum] =
|
|
{
|
|
// ViDeadlockUnknown //
|
|
0,
|
|
|
|
// ViDeadlockMutex//
|
|
VI_DEADLOCK_FLAG_RECURSIVE_ACQUISITION_OK,
|
|
|
|
// ViDeadlockFastMutex //
|
|
VI_DEADLOCK_FLAG_NO_INITIALIZATION_FUNCTION,
|
|
|
|
};
|
|
|
|
|
|
//
|
|
// Max depth of stack traces captured.
|
|
//
|
|
|
|
#define VI_MAX_STACK_DEPTH 8
|
|
|
|
NTSYSAPI
|
|
USHORT
|
|
NTAPI
|
|
RtlCaptureStackBackTrace(
|
|
IN ULONG FramesToSkip,
|
|
IN ULONG FramesToCapture,
|
|
OUT PVOID *BackTrace,
|
|
OUT PULONG BackTraceHash
|
|
);
|
|
|
|
//
|
|
// Deadlock specific issues (bugs)
|
|
//
|
|
// SELF_DEADLOCK
|
|
//
|
|
// Acquire resource recursively.
|
|
//
|
|
// DEADLOCK_DETECTED
|
|
//
|
|
// Plain deadlock. Need the previous information
|
|
// messages to build a deadlock proof.
|
|
//
|
|
// UNINITIALIZED_RESOURCE
|
|
//
|
|
// Acquiring a resource that was never initialized.
|
|
//
|
|
// UNEXPECTED_RELEASE
|
|
//
|
|
// Releasing a resource which is not the last one
|
|
// acquired by the current thread.
|
|
//
|
|
// UNEXPECTED_THREAD
|
|
//
|
|
// Current thread does not have any resources
|
|
// acquired.
|
|
//
|
|
// MULTIPLE_INITIALIZATION
|
|
//
|
|
// Attempting to initialize a second time the same
|
|
// resource.
|
|
//
|
|
|
|
#define VI_DEADLOCK_ISSUE_DEADLOCK_SELF_DEADLOCK 0x1000
|
|
#define VI_DEADLOCK_ISSUE_DEADLOCK_DETECTED 0x1001
|
|
#define VI_DEADLOCK_ISSUE_UNINITIALIZED_RESOURCE 0x1002
|
|
#define VI_DEADLOCK_ISSUE_UNEXPECTED_RELEASE 0x1003
|
|
#define VI_DEADLOCK_ISSUE_UNEXPECTED_THREAD 0x1004
|
|
#define VI_DEADLOCK_ISSUE_MULTIPLE_INITIALIZATION 0x1005
|
|
#define VI_DEADLOCK_ISSUE_THREAD_HOLDS_RESOURCES 0x1006
|
|
|
|
//
|
|
// VI_DEADLOCK_NODE
|
|
//
|
|
|
|
typedef struct _VI_DEADLOCK_NODE {
|
|
|
|
//
|
|
// Node representing the acquisition of the previous resource.
|
|
//
|
|
|
|
struct _VI_DEADLOCK_NODE * Parent;
|
|
|
|
//
|
|
// Node representing the next resource acquisitions, that are
|
|
// done after acquisition of the current resource.
|
|
//
|
|
|
|
struct _LIST_ENTRY ChildrenList;
|
|
|
|
//
|
|
// Field used to chain siblings in the tree. A parent node has the
|
|
// ChildrenList field as the head of the children list that is chained
|
|
// with the Siblings field.
|
|
//
|
|
|
|
struct _LIST_ENTRY SiblingsList;
|
|
|
|
|
|
//
|
|
// List of nodes representing the same resource acquisition
|
|
// as the current node but in different contexts (lock combinations).
|
|
//
|
|
|
|
struct _LIST_ENTRY ResourceList;
|
|
|
|
//
|
|
// Back pointer to the descriptor for this resource.
|
|
// If the node has been marked for deletion then the
|
|
// ResourceAddress field should be used and it has the address
|
|
// of the kernel resource address involved. The Root pointer is
|
|
// no longer valid because we deallocate the RESOURCE structure
|
|
// when it gets deleted.
|
|
//
|
|
|
|
union {
|
|
struct _VI_DEADLOCK_RESOURCE * Root;
|
|
|
|
PVOID ResourceAddress;
|
|
};
|
|
|
|
//
|
|
// The number of nodes that are below this one at any depth.
|
|
// This counter is used in the node deletion algorithms. It is
|
|
// incremented by one on all ancestors of a node created (resource
|
|
// acquired) and decremented by one when resource gets deleted.
|
|
// If a root of a tree has NodeCount equal to zero the whole tree
|
|
// will be deleted.
|
|
//
|
|
|
|
ULONG NodeCount;
|
|
|
|
//
|
|
// When we find a deadlock, we keep this info around in order to
|
|
// be able to identify the parties involved who have caused
|
|
// the deadlock.
|
|
//
|
|
|
|
PKTHREAD Thread;
|
|
|
|
PVOID StackTrace[VI_MAX_STACK_DEPTH];
|
|
|
|
} VI_DEADLOCK_NODE, *PVI_DEADLOCK_NODE;
|
|
|
|
|
|
//
|
|
// VI_DEADLOCK_RESOURCE
|
|
//
|
|
|
|
typedef struct _VI_DEADLOCK_RESOURCE {
|
|
|
|
//
|
|
// Since we may need to clean up different kinds of resources
|
|
// in different ways, keep track of what kind of resource
|
|
// this is.
|
|
//
|
|
|
|
VI_DEADLOCK_RESOURCE_TYPE Type;
|
|
|
|
//
|
|
// The address of the synchronization object used by the kernel.
|
|
//
|
|
|
|
PVOID ResourceAddress;
|
|
|
|
//
|
|
// The thread that curently owns the resource
|
|
// (null if no owner)
|
|
//
|
|
PKTHREAD ThreadOwner;
|
|
|
|
//
|
|
// List of resource nodes representing acquisitions of this resource.
|
|
//
|
|
|
|
LIST_ENTRY ResourceList;
|
|
|
|
//
|
|
// Number of resource nodes created for this resource.
|
|
// ISSUE: Why do we need this counter ? (silviuc)
|
|
//
|
|
|
|
ULONG NodeCount;
|
|
|
|
//
|
|
// List used for chaining resources from a hash bucket.
|
|
//
|
|
|
|
LIST_ENTRY HashChainList;
|
|
|
|
//
|
|
// Stack trace of the resource creator.
|
|
//
|
|
|
|
PVOID InitializeStackTrace [VI_MAX_STACK_DEPTH];
|
|
|
|
|
|
} VI_DEADLOCK_RESOURCE, * PVI_DEADLOCK_RESOURCE;
|
|
|
|
|
|
//
|
|
// VI_DEADLOCK_THREAD
|
|
//
|
|
|
|
typedef struct _VI_DEADLOCK_THREAD {
|
|
|
|
//
|
|
// Kernel thread address
|
|
//
|
|
|
|
PKTHREAD Thread;
|
|
|
|
//
|
|
// The node representing the last resource acquisition made by
|
|
// this thread.
|
|
//
|
|
|
|
PVI_DEADLOCK_NODE CurrentNode;
|
|
|
|
//
|
|
// Thread list. It is used for chaining into a hash bucket.
|
|
//
|
|
|
|
LIST_ENTRY ListEntry;
|
|
|
|
} VI_DEADLOCK_THREAD, *PVI_DEADLOCK_THREAD;
|
|
|
|
typedef struct _VI_DEADLOCK_PARTICIPANT {
|
|
//
|
|
// Address of participant -- could be a resource
|
|
// address or a resource node, depending on whether
|
|
// NodeInformation is set
|
|
//
|
|
// NULL participant means that there are no more
|
|
// participants
|
|
//
|
|
|
|
PVOID Participant;
|
|
|
|
//
|
|
// True: Participant is type VI_DEADLOCK_NODE
|
|
// False: Participant is a PVOID and shouldn't be deref'd
|
|
//
|
|
BOOLEAN NodeInformation;
|
|
|
|
} VI_DEADLOCK_PARTICIPANT, *PVI_DEADLOCK_PARTICIPANT;
|
|
|
|
//
|
|
// Deadlock resource and thread databases.
|
|
//
|
|
//
|
|
|
|
#define VI_DEADLOCK_HASH_BINS 1
|
|
|
|
PLIST_ENTRY ViDeadlockResourceDatabase;
|
|
PLIST_ENTRY ViDeadlockThreadDatabase;
|
|
|
|
ULONG ViDeadlockNumberParticipants;
|
|
|
|
PVI_DEADLOCK_PARTICIPANT ViDeadlockParticipation;
|
|
|
|
//
|
|
// Performance counters
|
|
//
|
|
|
|
ULONG ViDeadlockNumberOfNodes;
|
|
ULONG ViDeadlockNumberOfResources;
|
|
ULONG ViDeadlockNumberOfThreads;
|
|
|
|
//
|
|
// Maximum recursion depth for deadlock detection algorithm.
|
|
//
|
|
|
|
#define VI_DEADLOCK_MAXIMUM_DEGREE 4
|
|
|
|
ULONG ViDeadlockMaximumDegree;
|
|
|
|
//
|
|
// Verifier deadlock detection pool tag.
|
|
//
|
|
|
|
#define VI_DEADLOCK_TAG 'kclD'
|
|
|
|
//
|
|
// Global `deadlock lock database' lock
|
|
//
|
|
|
|
KSPIN_LOCK ViDeadlockDatabaseLock;
|
|
PKTHREAD ViDeadlockDatabaseOwner;
|
|
|
|
#define LOCK_DEADLOCK_DATABASE(OldIrql) \
|
|
KeAcquireSpinLock(&ViDeadlockDatabaseLock, (OldIrql)); \
|
|
ViDeadlockDatabaseOwner = KeGetCurrentThread ();
|
|
|
|
#define UNLOCK_DEADLOCK_DATABASE(OldIrql) \
|
|
ViDeadlockDatabaseOwner = NULL; \
|
|
KeReleaseSpinLock(&ViDeadlockDatabaseLock, OldIrql);
|
|
|
|
//
|
|
// Internal deadlock detection functions
|
|
//
|
|
|
|
VOID
|
|
ViDeadlockDetectionInitialize(
|
|
);
|
|
|
|
PLIST_ENTRY
|
|
ViDeadlockDatabaseHash(
|
|
IN PLIST_ENTRY Database,
|
|
IN PVOID Address
|
|
);
|
|
|
|
PVI_DEADLOCK_RESOURCE
|
|
ViDeadlockSearchResource(
|
|
IN PVOID ResourceAddress
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockAddResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockQueryAcquireResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockSimilarNode (
|
|
IN PVOID Resource,
|
|
IN PKTHREAD Thread,
|
|
IN PVOID * Trace,
|
|
IN PVI_DEADLOCK_NODE Node
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockAcquireResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type
|
|
);
|
|
|
|
|
|
VOID
|
|
ViDeadlockReleaseResource(
|
|
IN PVOID Resource
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockAnalyze(
|
|
IN PVOID ResourceAddress,
|
|
IN PVI_DEADLOCK_NODE CurrentNode,
|
|
IN ULONG Degree
|
|
);
|
|
|
|
PVI_DEADLOCK_THREAD
|
|
ViDeadlockSearchThread (
|
|
PKTHREAD Thread
|
|
);
|
|
|
|
PVI_DEADLOCK_THREAD
|
|
ViDeadlockAddThread (
|
|
PKTHREAD Thread
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDeleteThread (
|
|
PVI_DEADLOCK_THREAD Thread
|
|
);
|
|
|
|
PVOID
|
|
ViDeadlockAllocate (
|
|
SIZE_T Size
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockFree (
|
|
PVOID Object
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockReportIssue (
|
|
ULONG_PTR Param1,
|
|
ULONG_PTR Param2,
|
|
ULONG_PTR Param3,
|
|
ULONG_PTR Param4
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockAddParticipant(
|
|
PVOID ResourceAddress,
|
|
PVI_DEADLOCK_NODE FirstParticipant, OPTIONAL
|
|
PVI_DEADLOCK_NODE SecondParticipant,
|
|
ULONG Degree
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDeleteResource (
|
|
PVI_DEADLOCK_RESOURCE Resource
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDeleteTree (
|
|
PVI_DEADLOCK_NODE Root
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockIsNodeMarkedForDeletion (
|
|
PVI_DEADLOCK_NODE Node
|
|
);
|
|
|
|
|
|
PVOID
|
|
ViDeadlockGetNodeResourceAddress (
|
|
PVI_DEADLOCK_NODE Node
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
|
|
#if ! _BUGGY_
|
|
#pragma alloc_text(INIT, ViDeadlockDetectionInitialize)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAnalyze)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDatabaseHash)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockSearchResource)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAddResource)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockSimilarNode)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAcquireResource)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockReleaseResource)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockSearchThread)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAddThread)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDeleteThread)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAllocate)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockFree)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockReportIssue)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAddParticipant)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDeleteMemoryRange);
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDeleteResource);
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockWholeTree);
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockIsNodeMarkedForDeletion);
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockGetNodeResourceAddress);
|
|
|
|
#endif
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
///////////////////// Initialization and deadlock database management
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
PLIST_ENTRY
|
|
ViDeadlockDatabaseHash(
|
|
IN PLIST_ENTRY Database,
|
|
IN PVOID Address
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines hashes the resource address into the deadlock database.
|
|
The hash bin is represented by a list entry
|
|
|
|
silviuc: very simple minded hash table.
|
|
|
|
Arguments:
|
|
|
|
ResourceAddress: Address of the resource that is being hashed
|
|
|
|
Return Value:
|
|
|
|
PLIST_ENTRY -- the list entry associated with the hash bin we land in.
|
|
--*/
|
|
{
|
|
return Database + ((ULONG_PTR)Address % VI_DEADLOCK_HASH_BINS);
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockDetectionInitialize(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the data structures necessary for detecting
|
|
deadlocks in kernel synchronization objects.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None. Sets ViDeadlockDetectionInitialized to TRUE if successful.
|
|
|
|
Environment:
|
|
|
|
System initialization only.
|
|
|
|
--*/
|
|
{
|
|
ULONG I;
|
|
SIZE_T TableSize;
|
|
SIZE_T ParticipationTableSize;
|
|
|
|
//
|
|
// Allocate hash tables for resources and threads.
|
|
//
|
|
|
|
TableSize = sizeof (LIST_ENTRY) * VI_DEADLOCK_HASH_BINS;
|
|
|
|
ViDeadlockResourceDatabase = ViDeadlockAllocate (TableSize);
|
|
|
|
if (!ViDeadlockResourceDatabase) {
|
|
return;
|
|
}
|
|
|
|
ViDeadlockThreadDatabase = ViDeadlockAllocate (TableSize);
|
|
|
|
if (!ViDeadlockThreadDatabase) {
|
|
ViDeadlockFree (ViDeadlockResourceDatabase);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Initialize all.
|
|
//
|
|
|
|
for (I = 0; I < VI_DEADLOCK_HASH_BINS; I += 1) {
|
|
|
|
InitializeListHead(&ViDeadlockResourceDatabase[I]);
|
|
InitializeListHead(&ViDeadlockThreadDatabase[I]);
|
|
}
|
|
|
|
KeInitializeSpinLock(&ViDeadlockDatabaseLock);
|
|
|
|
ViDeadlockMaximumDegree = VI_DEADLOCK_MAXIMUM_DEGREE;
|
|
|
|
ViDeadlockNumberParticipants = FALSE;
|
|
|
|
ViDeadlockDetectionInitialized = TRUE;
|
|
//ViDeadlockDetectionEnabled = TRUE;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////// Deadlock detection logic
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
BOOLEAN
|
|
ViDeadlockAnalyze(
|
|
IN PVOID ResourceAddress,
|
|
IN PVI_DEADLOCK_NODE AcquiredNode,
|
|
IN ULONG Degree
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines whether the acquisition of a certain resource
|
|
could result in a deadlock.
|
|
|
|
The routine assumes the deadlock database lock is held.
|
|
|
|
Arguments:
|
|
|
|
ResourceAddress - address of the resource that will be acquired
|
|
|
|
AcquiredNode - a graph describing which resources have been acquired by
|
|
the current thread.
|
|
|
|
Return Value:
|
|
|
|
True if deadlock detected, false otherwise.
|
|
|
|
--*/
|
|
{
|
|
|
|
PVI_DEADLOCK_RESOURCE CurrentResource;
|
|
PVI_DEADLOCK_NODE CurrentAcquiredNode;
|
|
PVI_DEADLOCK_NODE CurrentNode;
|
|
PVI_DEADLOCK_NODE CurrentParent;
|
|
BOOLEAN FoundDeadlock;
|
|
PLIST_ENTRY Current;
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread ());
|
|
ASSERT (AcquiredNode);
|
|
|
|
//
|
|
// Stop recursion if it gets to deep.
|
|
//
|
|
|
|
if (Degree > ViDeadlockMaximumDegree) {
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
FoundDeadlock = FALSE;
|
|
|
|
|
|
|
|
CurrentAcquiredNode = AcquiredNode;
|
|
|
|
//
|
|
// Loop over all nodes containing same resource as all of the Acquired nodes
|
|
// parameter. For each such node we will traverse the Parent chain
|
|
// to check if ResourceAddress appears at some point. If it does
|
|
// we found a two way deadlock (caused by two threads).
|
|
//
|
|
while(CurrentAcquiredNode != NULL) {
|
|
|
|
//
|
|
// Check for a self cycle.
|
|
//
|
|
if (ViDeadlockGetNodeResourceAddress(CurrentAcquiredNode) == ResourceAddress) {
|
|
|
|
ViDeadlockAddParticipant(ResourceAddress, NULL, CurrentAcquiredNode, Degree);
|
|
FoundDeadlock = TRUE;
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
CurrentResource = CurrentAcquiredNode->Root;
|
|
|
|
Current = CurrentResource->ResourceList.Flink;
|
|
|
|
while (Current != &(CurrentResource->ResourceList)) {
|
|
|
|
CurrentNode = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
CurrentParent = CurrentNode->Parent;
|
|
|
|
//
|
|
// Traverse the parent chain looking for two-way deadlocks.
|
|
//
|
|
|
|
while (CurrentParent != NULL) {
|
|
|
|
if (ViDeadlockGetNodeResourceAddress(CurrentParent) == ResourceAddress) {
|
|
|
|
FoundDeadlock = TRUE;
|
|
|
|
|
|
if (! Degree) {
|
|
|
|
ViDeadlockAddParticipant(ResourceAddress,
|
|
NULL,
|
|
CurrentAcquiredNode,
|
|
Degree);
|
|
|
|
ViDeadlockAddParticipant(ResourceAddress,
|
|
CurrentNode,
|
|
CurrentParent,
|
|
Degree);
|
|
|
|
|
|
} else {
|
|
|
|
ViDeadlockAddParticipant(ResourceAddress,
|
|
AcquiredNode,
|
|
CurrentParent,
|
|
Degree);
|
|
}
|
|
|
|
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
CurrentParent = CurrentParent->Parent;
|
|
}
|
|
|
|
//
|
|
// Move on to the next node (AcquiredNode->Root type of nodes).
|
|
//
|
|
|
|
Current = Current->Flink;
|
|
}
|
|
CurrentAcquiredNode = CurrentAcquiredNode->Parent;
|
|
}
|
|
|
|
CurrentAcquiredNode = AcquiredNode;
|
|
|
|
while(CurrentAcquiredNode != NULL) {
|
|
|
|
//
|
|
// In order to find multiway deadlocks we traverse the Parent chain
|
|
// a second time and enter into recursion. This way we can detect
|
|
// cycles in the graph that are caused by multiple threads (up to Degree).
|
|
//
|
|
|
|
CurrentResource = CurrentAcquiredNode->Root;
|
|
|
|
Current = CurrentResource->ResourceList.Flink;
|
|
|
|
while (Current != &(CurrentResource->ResourceList)) {
|
|
|
|
CurrentNode = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
//
|
|
// Loop again over parents but this time get into recursion.
|
|
// We could have done this in the loop above but we want to first search
|
|
// completely the existing graph (tree actually) and only after that
|
|
// traverse it recursively.
|
|
//
|
|
|
|
CurrentParent = CurrentNode->Parent;
|
|
|
|
while (CurrentParent != NULL) {
|
|
|
|
FoundDeadlock = ViDeadlockAnalyze (ResourceAddress,
|
|
CurrentParent,
|
|
Degree + 1);
|
|
|
|
if (FoundDeadlock) {
|
|
|
|
|
|
if (! Degree) {
|
|
|
|
ViDeadlockAddParticipant(ResourceAddress,
|
|
CurrentNode,
|
|
CurrentParent,
|
|
Degree);
|
|
|
|
ViDeadlockAddParticipant(ResourceAddress,
|
|
NULL,
|
|
CurrentAcquiredNode,
|
|
Degree);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
ViDeadlockAddParticipant(ResourceAddress,
|
|
AcquiredNode,
|
|
CurrentParent,
|
|
Degree);
|
|
|
|
}
|
|
|
|
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
CurrentParent = CurrentParent->Parent;
|
|
}
|
|
|
|
//
|
|
// Move on to the next node (AcquiredNode->Root type of nodes).
|
|
//
|
|
|
|
Current = Current->Flink;
|
|
}
|
|
|
|
CurrentAcquiredNode = CurrentAcquiredNode->Parent;
|
|
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (FoundDeadlock && Degree == 0) {
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_DEADLOCK_DETECTED,
|
|
(ULONG_PTR)ResourceAddress,
|
|
(ULONG_PTR)CurrentAcquiredNode,
|
|
0);
|
|
}
|
|
|
|
return FoundDeadlock;
|
|
#
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////// Resource management
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
PVI_DEADLOCK_RESOURCE
|
|
ViDeadlockSearchResource(
|
|
IN PVOID ResourceAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine finds the resource descriptor structure for a
|
|
resource if one exists.
|
|
|
|
Arguments:
|
|
|
|
ResourceAddress: Address of the resource in question (as used by
|
|
the kernel).
|
|
|
|
Return Value:
|
|
|
|
PVI_DEADLOCK_RESOURCE structure describing the resource, if available,
|
|
or else NULL
|
|
|
|
Note. The caller of the function should hold the database lock.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY ListHead;
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_RESOURCE Resource;
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread ());
|
|
|
|
ListHead = ViDeadlockDatabaseHash (ViDeadlockResourceDatabase, ResourceAddress);
|
|
|
|
if (IsListEmpty(ListHead)) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Current = ListHead->Flink;
|
|
|
|
while (Current != ListHead) {
|
|
|
|
Resource = CONTAINING_RECORD(Current,
|
|
VI_DEADLOCK_RESOURCE,
|
|
HashChainList);
|
|
|
|
if (Resource->ResourceAddress == ResourceAddress) {
|
|
|
|
return Resource;
|
|
}
|
|
|
|
Current = Current->Flink;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockAddResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine adds an entry for a new resource to our deadlock detection
|
|
database.
|
|
|
|
Arguments:
|
|
|
|
Resource: Address of the resource in question as used by the kernel.
|
|
|
|
Type: Type of the resource.
|
|
|
|
Return Value:
|
|
|
|
True if we created and initialized a new RESOURCE structure.
|
|
|
|
Note. The caller of the function should not hold the database
|
|
lock.
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY hashBin;
|
|
PVI_DEADLOCK_RESOURCE resourceRoot;
|
|
PVI_DEADLOCK_NODE resourceNode;
|
|
KIRQL OldIrql;
|
|
ULONG HashValue;
|
|
|
|
//
|
|
// If we aren't initialized or package is not enabled
|
|
// we return immediately.
|
|
//
|
|
|
|
if (! (ViDeadlockDetectionInitialized && ViDeadlockDetectionEnabled)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if this resource was initialized before.
|
|
// This would be a bug.
|
|
//
|
|
|
|
LOCK_DEADLOCK_DATABASE( &OldIrql );
|
|
|
|
resourceRoot = ViDeadlockSearchResource (Resource);
|
|
|
|
if (resourceRoot) {
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_MULTIPLE_INITIALIZATION,
|
|
(ULONG_PTR)Resource,
|
|
(ULONG_PTR)resourceRoot,
|
|
0);
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql);
|
|
return TRUE;
|
|
}
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql);
|
|
|
|
//
|
|
// Allocate the memory for the root of the new resource's tree.
|
|
//
|
|
|
|
resourceRoot = ViDeadlockAllocate (sizeof(VI_DEADLOCK_RESOURCE));
|
|
|
|
if (NULL == resourceRoot) {
|
|
return FALSE;
|
|
}
|
|
|
|
RtlZeroMemory(resourceRoot, sizeof(VI_DEADLOCK_RESOURCE));
|
|
|
|
//
|
|
// Fill information about resource.
|
|
//
|
|
|
|
resourceRoot->Type = Type;
|
|
resourceRoot->ResourceAddress = Resource;
|
|
|
|
InitializeListHead (&resourceRoot->ResourceList);
|
|
|
|
resourceRoot->NodeCount = 0;
|
|
|
|
//
|
|
// Capture the stack trace of the guy that creates the resource first.
|
|
// This should happen when resource gets initialized.
|
|
//
|
|
|
|
RtlCaptureStackBackTrace (0, // silviuc: how many frames to skip?
|
|
VI_MAX_STACK_DEPTH,
|
|
resourceRoot->InitializeStackTrace,
|
|
&HashValue);
|
|
|
|
//
|
|
// Figure out which hash bin this resource corresponds to.
|
|
//
|
|
|
|
hashBin = ViDeadlockDatabaseHash(ViDeadlockResourceDatabase, Resource);
|
|
|
|
//
|
|
// Now add to the list corresponding to the current hash bin
|
|
//
|
|
|
|
LOCK_DEADLOCK_DATABASE( &OldIrql );
|
|
|
|
InsertHeadList(hashBin,
|
|
&(resourceRoot->HashChainList));
|
|
|
|
ViDeadlockNumberOfResources += 1;
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockQueryAcquireResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine makes sure that it is ok to acquire the resource without
|
|
causing a deadlock. .
|
|
|
|
Arguments:
|
|
|
|
Resource: Address of the resource in question as used by kernel.
|
|
|
|
Type: Type of the resource.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PVI_DEADLOCK_THREAD ThreadEntry;
|
|
KIRQL OldIrql;
|
|
PVI_DEADLOCK_NODE CurrentNode;
|
|
PVI_DEADLOCK_RESOURCE ResourceRoot;
|
|
PLIST_ENTRY Current;
|
|
BOOLEAN FoundDeadlock;
|
|
ULONG DeadlockFlags;
|
|
|
|
//
|
|
// If we are not initialized or package is not enabled
|
|
// we return immediately.
|
|
//
|
|
|
|
|
|
if (! (ViDeadlockDetectionInitialized && ViDeadlockDetectionEnabled)) {
|
|
return FALSE;
|
|
}
|
|
|
|
FoundDeadlock = FALSE;
|
|
|
|
CurrentThread = KeGetCurrentThread();
|
|
|
|
DeadlockFlags = ViDeadlockResourceTypeInfo[Type];
|
|
|
|
LOCK_DEADLOCK_DATABASE( &OldIrql );
|
|
|
|
//
|
|
// Look for this thread in our thread list.
|
|
// Add a new thread if it is not in the list.
|
|
//
|
|
|
|
ThreadEntry = ViDeadlockSearchThread (CurrentThread);
|
|
|
|
if (ThreadEntry == NULL) {
|
|
//
|
|
// Threads without allocations can't cause deadlocks
|
|
//
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Check if this resource is already in our database
|
|
//
|
|
|
|
ResourceRoot = ViDeadlockSearchResource (Resource);
|
|
|
|
//
|
|
// Resources that we haven't seen before can't cause deadlocks
|
|
//
|
|
if (ResourceRoot == NULL) {
|
|
goto Exit;
|
|
}
|
|
|
|
|
|
ASSERT (ResourceRoot);
|
|
ASSERT (ThreadEntry);
|
|
|
|
//
|
|
// Check if thread holds any resources.
|
|
// Threads that don't have any resources
|
|
// yet can't cause deadlocks
|
|
//
|
|
|
|
if (ThreadEntry->CurrentNode == NULL) {
|
|
goto Exit;
|
|
}
|
|
|
|
|
|
//
|
|
// If we get here, the current thread had already acquired resources.
|
|
//
|
|
|
|
//
|
|
// Find whether the link already exists. We are looking for a direct
|
|
// link between ThreadEntry->CurrentNode and Resource (parameter).
|
|
//
|
|
|
|
Current = ThreadEntry->CurrentNode->ChildrenList.Flink;
|
|
|
|
while (Current != &(ThreadEntry->CurrentNode->ChildrenList)) {
|
|
|
|
CurrentNode = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
SiblingsList);
|
|
|
|
if (ViDeadlockGetNodeResourceAddress(CurrentNode) == Resource) {
|
|
|
|
//
|
|
// We have found a link.
|
|
// A link that already exists doesn't have to be
|
|
// checked for a deadlock because it would have
|
|
// been caught when the link was created...
|
|
// so we can just update the pointers and exit.
|
|
//
|
|
|
|
ThreadEntry->CurrentNode = CurrentNode;
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
Current = Current->Flink;
|
|
}
|
|
|
|
//
|
|
// Now we know that we're in it for the long haul ..
|
|
// doesn't cause a deadlock
|
|
//
|
|
|
|
CurrentNode = NULL;
|
|
|
|
//
|
|
// We will analyze deadlock if the resource just about to be acquired
|
|
// was acquired before and there are nodes in the graph for the
|
|
// resource.
|
|
//
|
|
|
|
|
|
|
|
if (ViDeadlockAnalyze(Resource, ThreadEntry->CurrentNode, 0)) {
|
|
|
|
//
|
|
// Go back to ground 0 with this thread
|
|
//
|
|
ThreadEntry->CurrentNode = NULL;
|
|
FoundDeadlock = TRUE;
|
|
|
|
}
|
|
|
|
//
|
|
// Exit point.
|
|
//
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Release deadlock database and return.
|
|
//
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
return FoundDeadlock;
|
|
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockSimilarNode (
|
|
IN PVOID Resource,
|
|
IN PKTHREAD Thread,
|
|
IN PVOID * Trace,
|
|
IN PVI_DEADLOCK_NODE Node
|
|
)
|
|
{
|
|
SIZE_T Index;
|
|
|
|
if (Resource == ViDeadlockGetNodeResourceAddress(Node) &&
|
|
Thread == Node->Thread) {
|
|
|
|
Index = RtlCompareMemory (Trace, Node->StackTrace, sizeof (Node->StackTrace));
|
|
|
|
if (Index == sizeof (Node->StackTrace)) {
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockAcquireResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine makes sure that it is ok to acquire the resource without
|
|
causing a deadlock. It will also update the resource graph with the new
|
|
resource acquisition.
|
|
|
|
Arguments:
|
|
|
|
Resource: Address of the resource in question as used by kernel.
|
|
|
|
Type: Type of the resource.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PVI_DEADLOCK_THREAD ThreadEntry;
|
|
KIRQL OldIrql;
|
|
PVI_DEADLOCK_NODE CurrentNode;
|
|
PVI_DEADLOCK_RESOURCE ResourceRoot;
|
|
PLIST_ENTRY Current;
|
|
ULONG HashValue;
|
|
ULONG DeadlockFlags;
|
|
BOOLEAN CreatingRootNode = FALSE;
|
|
PVOID Trace [VI_MAX_STACK_DEPTH];
|
|
|
|
//
|
|
// If we are not initialized or package is not enabled
|
|
// we return immediately.
|
|
//
|
|
|
|
if (! (ViDeadlockDetectionInitialized && ViDeadlockDetectionEnabled)) {
|
|
return;
|
|
}
|
|
|
|
CurrentThread = KeGetCurrentThread();
|
|
|
|
DeadlockFlags = ViDeadlockResourceTypeInfo[Type];
|
|
|
|
//
|
|
// Capture stack trace. We will need it to figure out
|
|
// if we've been in this state before. We will skip two frames
|
|
// for ViDeadlockAcquireResource and the verifier thunk that
|
|
// calls it.
|
|
//
|
|
|
|
|
|
RtlZeroMemory (Trace, sizeof Trace);
|
|
|
|
RtlCaptureStackBackTrace (
|
|
2,
|
|
VI_MAX_STACK_DEPTH,
|
|
Trace,
|
|
&HashValue);
|
|
|
|
//
|
|
// Lock the deadlock database.
|
|
//
|
|
|
|
LOCK_DEADLOCK_DATABASE( &OldIrql );
|
|
|
|
//
|
|
// Look for this thread in our thread list.
|
|
// Add a new thread if it is not in the list.
|
|
//
|
|
|
|
ThreadEntry = ViDeadlockSearchThread (CurrentThread);
|
|
|
|
if (ThreadEntry == NULL) {
|
|
|
|
//
|
|
// Note that ViDeadlockAddThread will drop the lock
|
|
// while allocating memory and then reacquire it.
|
|
//
|
|
|
|
ThreadEntry = ViDeadlockAddThread (CurrentThread);
|
|
|
|
if (ThreadEntry == NULL) {
|
|
|
|
//
|
|
// If we cannot allocate a new thread entry then
|
|
// no deadlock detection will happen.
|
|
//
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if this resource is already in our database
|
|
//
|
|
|
|
ResourceRoot = ViDeadlockSearchResource (Resource);
|
|
|
|
if (ResourceRoot == NULL) {
|
|
|
|
//
|
|
// Could not find the resource descriptor.
|
|
//
|
|
|
|
|
|
if ((DeadlockFlags & VI_DEADLOCK_FLAG_NO_INITIALIZATION_FUNCTION)) {
|
|
|
|
//
|
|
// Certain resource types have no initialization function ..
|
|
// in which case we'll get an 'acquire' without an 'add'
|
|
// first -- this is entirely OK
|
|
//
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
|
|
if (FALSE == ViDeadlockAddResource(Resource, Type) ) {
|
|
return;
|
|
}
|
|
|
|
LOCK_DEADLOCK_DATABASE( &OldIrql );
|
|
|
|
//
|
|
// Note that even though we dropped the lock, we don't have
|
|
// to reobtain the thread entry pointer -- since thread
|
|
// entry for the current thread can't have gone away.
|
|
//
|
|
|
|
ResourceRoot = ViDeadlockSearchResource (Resource);
|
|
|
|
|
|
} else {
|
|
|
|
//
|
|
// This resource type does have an initialization function --
|
|
// and it wasn't called. This is bad.
|
|
//
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_UNINITIALIZED_RESOURCE,
|
|
(ULONG_PTR)Resource,
|
|
0,
|
|
0);
|
|
|
|
//
|
|
// ISSUE (silviuc) Difficult to recover from this failure.
|
|
// We will complain during release that resource was not acquired.
|
|
//
|
|
|
|
ThreadEntry->CurrentNode = NULL;
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point we have a THREAD and a RESOURCE to play with.
|
|
// In addition we are just about to acquire the resource which means
|
|
// there should not be another thread owning it.
|
|
//
|
|
|
|
ASSERT (ResourceRoot);
|
|
ASSERT (ThreadEntry);
|
|
ASSERT (NULL == ResourceRoot->ThreadOwner);
|
|
|
|
//
|
|
// Check if thread holds any resources. If it does we will have to determine
|
|
// at that local point in the dependency graph if we need to create a
|
|
// new node. If this is the first resource acquired by the thread we need
|
|
// to create a new root node or reuse one created in the past.
|
|
//
|
|
// A node created in the past will match the current situation if the same
|
|
// thread acquires, the same resource is acquired and the stack traces match.
|
|
// A node represents a triad (Thread, Resource, StackTrace).
|
|
//
|
|
|
|
if (ThreadEntry->CurrentNode != NULL) {
|
|
|
|
//
|
|
// If we get here, the current thread had already acquired resources.
|
|
// Must now do three things ...
|
|
//
|
|
// 1. if link already exists, update pointers and exit
|
|
// 2. otherwise create a new node
|
|
// 3. check for deadlocks
|
|
//
|
|
|
|
//
|
|
// Find whether the link already exists. We are looking for a direct
|
|
// link between ThreadEntry->CurrentNode and Resource (parameter).
|
|
//
|
|
|
|
Current = ThreadEntry->CurrentNode->ChildrenList.Flink;
|
|
|
|
while (Current != &(ThreadEntry->CurrentNode->ChildrenList)) {
|
|
|
|
CurrentNode = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
SiblingsList);
|
|
|
|
Current = Current->Flink;
|
|
|
|
if (ViDeadlockSimilarNode (Resource, CurrentThread, Trace, CurrentNode)) {
|
|
|
|
//
|
|
// We have found a link.
|
|
// A link that already exists doesn't have to be
|
|
// checked for a deadlock because it would have
|
|
// been caught when the link was created...
|
|
// so we can just update the pointers to reflect the new
|
|
// resource acquired and exit.
|
|
//
|
|
|
|
ThreadEntry->CurrentNode = CurrentNode;
|
|
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now we know that we're in it for the long haul .. we must create a new
|
|
// link and make sure that it doesn't cause a deadlock
|
|
//
|
|
|
|
CurrentNode = NULL;
|
|
|
|
//
|
|
// We will analyze deadlock if the resource just about to be acquired
|
|
// was acquired before and there are nodes in the graph for the
|
|
// resource.
|
|
//
|
|
|
|
if (ResourceRoot->NodeCount > 0) {
|
|
|
|
if (ViDeadlockAnalyze(Resource, ThreadEntry->CurrentNode, 0)) {
|
|
|
|
//
|
|
// Go back to ground 0 with this thread if a deadlock has been
|
|
// detected.
|
|
//
|
|
|
|
ThreadEntry->CurrentNode = NULL;
|
|
ResourceRoot->ThreadOwner = NULL;
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Thread does not have any resources acquired. We have to figure out
|
|
// if this is a scenario we have encountered in the past by looking
|
|
// at all nodes (that are roots) for the resource to be acquired.
|
|
//
|
|
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node;
|
|
BOOLEAN FoundNode = FALSE;
|
|
|
|
Current = ResourceRoot->ResourceList.Flink;
|
|
|
|
while (Current != &(ResourceRoot->ResourceList)) {
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
Current = Node->ResourceList.Flink;
|
|
|
|
if (Node->Parent == NULL) {
|
|
|
|
if (ViDeadlockSimilarNode (Resource, CurrentThread, Trace, Node)) {
|
|
|
|
FoundNode = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FoundNode) {
|
|
|
|
ThreadEntry->CurrentNode = Node;
|
|
goto Exit;
|
|
}
|
|
else {
|
|
|
|
CreatingRootNode = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// At this moment we know for sure the new link will not cause
|
|
// a deadlock. We will create the new resource node.
|
|
//
|
|
|
|
CurrentNode = ViDeadlockAllocate (sizeof (VI_DEADLOCK_NODE));
|
|
|
|
if (CurrentNode != NULL) {
|
|
|
|
//
|
|
// Initialize the new resource node
|
|
//
|
|
|
|
RtlZeroMemory (CurrentNode, sizeof *CurrentNode);
|
|
|
|
CurrentNode->Parent = ThreadEntry->CurrentNode;
|
|
|
|
CurrentNode->Root = ResourceRoot;
|
|
|
|
InitializeListHead (&(CurrentNode->ChildrenList));
|
|
|
|
//
|
|
// Add to the children list of the parent.
|
|
//
|
|
|
|
if (! CreatingRootNode) {
|
|
|
|
InsertHeadList(&(ThreadEntry->CurrentNode->ChildrenList),
|
|
&(CurrentNode->SiblingsList));
|
|
}
|
|
|
|
//
|
|
// Register the new resource node in the list of nodes maintained
|
|
// for this resource.
|
|
//
|
|
|
|
InsertHeadList(&(ResourceRoot->ResourceList),
|
|
&(CurrentNode->ResourceList));
|
|
|
|
ResourceRoot->NodeCount += 1;
|
|
|
|
//
|
|
// Update NodeCount for all NODEs all the way up to the
|
|
// root of the tree.
|
|
//
|
|
|
|
{
|
|
PVI_DEADLOCK_NODE Parent;
|
|
|
|
Parent = CurrentNode->Parent;
|
|
|
|
while (Parent != NULL) {
|
|
|
|
Parent->NodeCount += 1;
|
|
|
|
Parent = Parent->Parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update current resource held by thread.
|
|
//
|
|
// NOTE -- Do this even if the allocation FAILS --
|
|
// Since when the resource is released, we won't
|
|
// recognize it (since the node could not be allocated)
|
|
// and we'll bugcheck.
|
|
//
|
|
|
|
ThreadEntry->CurrentNode = CurrentNode;
|
|
|
|
//
|
|
// Exit point.
|
|
//
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Add information we use to identify the culprit should
|
|
// a deadlock occur
|
|
//
|
|
|
|
if (CurrentNode) {
|
|
|
|
CurrentNode->Thread = CurrentThread;
|
|
ResourceRoot->ThreadOwner = CurrentThread;
|
|
|
|
RtlCopyMemory (CurrentNode->StackTrace, Trace, sizeof Trace);
|
|
}
|
|
|
|
//
|
|
// Release deadlock database and return.
|
|
//
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockReleaseResource(
|
|
IN PVOID Resource
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine does the maintenance necessary to release resources from our
|
|
deadlock detection database.
|
|
|
|
Arguments:
|
|
|
|
Resource: Address of the resource in question.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
--*/
|
|
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PVI_DEADLOCK_THREAD ThreadEntry;
|
|
KIRQL OldIrql;
|
|
PVI_DEADLOCK_NODE CurrentNode;
|
|
PVI_DEADLOCK_RESOURCE ResourceRoot;
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner != KeGetCurrentThread());
|
|
|
|
//
|
|
// If we aren't initialized or package is not enabled
|
|
// we return immediately.
|
|
//
|
|
|
|
if (! (ViDeadlockDetectionInitialized && ViDeadlockDetectionEnabled)) {
|
|
return;
|
|
}
|
|
|
|
CurrentThread = KeGetCurrentThread();
|
|
|
|
LOCK_DEADLOCK_DATABASE( &OldIrql );
|
|
|
|
ResourceRoot = ViDeadlockSearchResource (Resource);
|
|
|
|
if (ResourceRoot == NULL) {
|
|
//
|
|
// This is probably bad but we con't complain since
|
|
// we may have faild an allocation -- we don't want
|
|
// to accuse somebody of foul play just because
|
|
// our allocation function failed
|
|
//
|
|
// ISSUE (silviuc): should complain if no allocation ever failed.
|
|
//
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
return;
|
|
}
|
|
|
|
if (ResourceRoot->ThreadOwner == NULL) {
|
|
//
|
|
// Most likely someone is releasing a resource that
|
|
// was never acquired. However the other possibility
|
|
// is that we have failed an allocation. So we can't
|
|
// complain -- but we can't really do anything either
|
|
//
|
|
// ISSUE (silviuc): should complain if no allocation ever failed.
|
|
//
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Look for this thread in our thread list,
|
|
// Note we are looking actually for the thread
|
|
// that acquired the resource -- not the current one
|
|
// It should, in fact be the current one, but if
|
|
// the resource is being released in a different thread
|
|
// from the one it was acquired in, we need the original.
|
|
//
|
|
|
|
ThreadEntry = ViDeadlockSearchThread (ResourceRoot->ThreadOwner);
|
|
|
|
if (NULL == ThreadEntry) {
|
|
//
|
|
// This can happen when we recover from an unexpected release --
|
|
// there is still a therad owner for the resource but we nuked the
|
|
// thread entry.
|
|
// Indicate that we no longer hold this resource.
|
|
//
|
|
// ISSUE (silviuc): So, we do not need to complain here ?
|
|
//
|
|
ResourceRoot->ThreadOwner = NULL;
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
return;
|
|
}
|
|
|
|
|
|
if (ResourceRoot->ThreadOwner != CurrentThread) {
|
|
|
|
//
|
|
// Someone acquired a resource but it was
|
|
// released in another thread. This is bad
|
|
// design.
|
|
//
|
|
|
|
DbgPrint("Thread %p acquired resource %p but thread %p released it\n",
|
|
ThreadEntry->Thread, Resource, CurrentThread );
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_UNEXPECTED_THREAD,
|
|
(ULONG_PTR)ResourceRoot,
|
|
(ULONG_PTR)ThreadEntry,
|
|
(ULONG_PTR)ViDeadlockSearchThread(CurrentThread)
|
|
);
|
|
//
|
|
// If we don't want this to be fatal, in order to
|
|
// continue we must pretend that the current
|
|
// thread is the resource's owner.
|
|
//
|
|
CurrentThread = ResourceRoot->ThreadOwner;
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// Wipe out the resource owner since resource will be released.
|
|
//
|
|
|
|
ResourceRoot->ThreadOwner = NULL;
|
|
|
|
//
|
|
// Check the case where the thread does not appear to hold a resource.
|
|
// ISSUE (silviuc): We kind of cluttered the code to make it restartable
|
|
// after an error. It might be better to clean it up even if not restartable.
|
|
//
|
|
|
|
if(NULL == ThreadEntry->CurrentNode) {
|
|
|
|
//
|
|
// This will happen when we recover from a deadlock
|
|
//
|
|
|
|
UNLOCK_DEADLOCK_DATABASE( OldIrql );
|
|
return;
|
|
|
|
}
|
|
|
|
//
|
|
// All nodes must have a root -- just make sure because we're deref'ing
|
|
// it in a minute
|
|
//
|
|
|
|
ASSERT (ThreadEntry->CurrentNode->Root);
|
|
|
|
//
|
|
// Found the thread list entry
|
|
//
|
|
|
|
if (ViDeadlockGetNodeResourceAddress(ThreadEntry->CurrentNode) != Resource) {
|
|
|
|
//
|
|
// Getting here means that soembody acquires a then b then tries
|
|
// to release b before a. This is bad.
|
|
//
|
|
//
|
|
// ISSUE (jtigani): -- flesh out reporting -- so we can prove that this
|
|
// actually happenned.
|
|
//
|
|
DbgPrint("ERROR: Must release resources in reverse-order\n");
|
|
DbgPrint("Resource %p acquired before resource %p -- \n"
|
|
"Current thread is trying to release it first\n",
|
|
Resource,
|
|
ViDeadlockGetNodeResourceAddress(ThreadEntry->CurrentNode));
|
|
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_UNEXPECTED_RELEASE,
|
|
(ULONG_PTR)Resource,
|
|
(ULONG_PTR)ThreadEntry->CurrentNode,
|
|
(ULONG_PTR)CurrentThread);
|
|
|
|
//
|
|
// The thread state is hosed.
|
|
// Try to keep going.
|
|
//
|
|
|
|
ThreadEntry->CurrentNode = NULL;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Indicate that we have released the curent node
|
|
//
|
|
ThreadEntry->CurrentNode = ThreadEntry->CurrentNode->Parent;
|
|
|
|
}
|
|
|
|
//
|
|
// If thread does not hold resources anymore we will destroy the
|
|
// thread information.
|
|
//
|
|
|
|
if (ThreadEntry->CurrentNode == NULL) {
|
|
|
|
ViDeadlockDeleteThread (ThreadEntry);
|
|
}
|
|
|
|
|
|
UNLOCK_DEADLOCK_DATABASE(OldIrql);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////// Thread management
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
PVI_DEADLOCK_THREAD
|
|
ViDeadlockSearchThread (
|
|
PKTHREAD Thread
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches for a thread in the thread database.
|
|
|
|
The function assumes the deadlock database lock is held.
|
|
|
|
Arguments:
|
|
|
|
Thread - thread address
|
|
|
|
Return Value:
|
|
|
|
Address of VI_DEADLOCK_THREAD structure if thread was found.
|
|
Null otherwise.
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_THREAD ThreadInfo;
|
|
PLIST_ENTRY HashBin;
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread ());
|
|
|
|
ThreadInfo = NULL;
|
|
|
|
HashBin = ViDeadlockDatabaseHash(ViDeadlockThreadDatabase, Thread);
|
|
|
|
if (IsListEmpty(HashBin)) {
|
|
return NULL;
|
|
}
|
|
|
|
Current = HashBin->Flink;
|
|
|
|
while (Current != HashBin) {
|
|
|
|
ThreadInfo = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_THREAD,
|
|
ListEntry);
|
|
|
|
if (ThreadInfo->Thread == Thread) {
|
|
return ThreadInfo;
|
|
}
|
|
|
|
Current = Current->Flink;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
PVI_DEADLOCK_THREAD
|
|
ViDeadlockAddThread (
|
|
PKTHREAD Thread
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine adds a new thread to the thread database.
|
|
|
|
The function assumes the deadlock database lock is held. It will
|
|
drop the lock while allocating memory for the thread structure and
|
|
then reacquire the lock. Special attention in the caller of this
|
|
function (ViDeadlockAcquireResource) because any internal pointer
|
|
should be reobtained since the lock was dropped.
|
|
|
|
Arguments:
|
|
|
|
Thread - thread address
|
|
|
|
Return Value:
|
|
|
|
Address of the VI_DEADLOCK_THREAD resource just added.
|
|
Null if allocation failed.
|
|
--*/
|
|
{
|
|
KIRQL OldIrql;
|
|
PVI_DEADLOCK_THREAD ThreadInfo;
|
|
PLIST_ENTRY HashBin;
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread ());
|
|
|
|
//
|
|
// It is safe to play with OldIrql like below because this function
|
|
// is called from ViDeadlockAcquireResource with irql raised at DPC
|
|
// level.
|
|
//
|
|
|
|
OldIrql = DISPATCH_LEVEL;
|
|
|
|
UNLOCK_DEADLOCK_DATABASE (OldIrql);
|
|
|
|
ThreadInfo = ViDeadlockAllocate (sizeof(VI_DEADLOCK_THREAD));
|
|
|
|
LOCK_DEADLOCK_DATABASE (&OldIrql);
|
|
|
|
if (ThreadInfo == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
RtlZeroMemory (ThreadInfo, sizeof *ThreadInfo);
|
|
|
|
ThreadInfo->Thread = Thread;
|
|
|
|
HashBin = ViDeadlockDatabaseHash(ViDeadlockThreadDatabase, Thread);
|
|
|
|
InsertHeadList (HashBin,
|
|
&ThreadInfo->ListEntry);
|
|
|
|
ViDeadlockNumberOfThreads += 1;
|
|
|
|
return ThreadInfo;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockDeleteThread (
|
|
PVI_DEADLOCK_THREAD Thread
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes a thread.
|
|
|
|
Arguments:
|
|
|
|
Thread - thread address
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
--*/
|
|
{
|
|
KIRQL OldIrql;
|
|
VI_DEADLOCK_THREAD ThreadInfo;
|
|
PLIST_ENTRY Current;
|
|
BOOLEAN Result;
|
|
PKTHREAD CurrentThread;
|
|
PLIST_ENTRY HashBin;
|
|
|
|
CurrentThread = KeGetCurrentThread ();
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == CurrentThread);
|
|
ASSERT (Thread && Thread->Thread == CurrentThread);
|
|
ASSERT (Thread->CurrentNode == NULL);
|
|
|
|
RemoveEntryList (&(Thread->ListEntry));
|
|
|
|
ViDeadlockNumberOfThreads -= 1;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////// Allocate/Free
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
PVOID
|
|
ViDeadlockAllocate (
|
|
SIZE_T Size
|
|
)
|
|
{
|
|
return ExAllocatePoolWithTag(NonPagedPool, Size, VI_DEADLOCK_TAG);
|
|
}
|
|
|
|
VOID
|
|
ViDeadlockFree (
|
|
PVOID Object
|
|
)
|
|
{
|
|
ExFreePool (Object);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////// Error reporting and debugging
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
ULONG ViDeadlockDebug = 0x01;
|
|
|
|
VOID
|
|
ViDeadlockReportIssue (
|
|
ULONG_PTR Param1,
|
|
ULONG_PTR Param2,
|
|
ULONG_PTR Param3,
|
|
ULONG_PTR Param4
|
|
)
|
|
{
|
|
|
|
|
|
if ((ViDeadlockDebug & 0x01)) {
|
|
|
|
DbgPrint ("Verifier: deadlock: stop: %u %p %p %p %p \n",
|
|
DRIVER_VERIFIER_DETECTED_VIOLATION,
|
|
Param1,
|
|
Param2,
|
|
Param3,
|
|
Param4);
|
|
|
|
DbgBreakPoint ();
|
|
}
|
|
else {
|
|
|
|
KeBugCheckEx (DRIVER_VERIFIER_DETECTED_VIOLATION,
|
|
Param1,
|
|
Param2,
|
|
Param3,
|
|
Param4);
|
|
}
|
|
|
|
ViDeadlockNumberParticipants = FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockAddParticipant(
|
|
PVOID ResourceAddress,
|
|
PVI_DEADLOCK_NODE FirstParticipant, OPTIONAL
|
|
PVI_DEADLOCK_NODE SecondParticipant,
|
|
ULONG Degree
|
|
)
|
|
{
|
|
ULONG Participants;
|
|
|
|
if (0 == ViDeadlockNumberParticipants) {
|
|
|
|
Participants = Degree + 2;
|
|
|
|
ViDeadlockParticipation = ViDeadlockAllocate(
|
|
sizeof(VI_DEADLOCK_PARTICIPANT) * (2 * Participants + 1)
|
|
);
|
|
RtlZeroMemory(
|
|
ViDeadlockParticipation,
|
|
sizeof(VI_DEADLOCK_PARTICIPANT) * (2 * Participants + 1)
|
|
);
|
|
|
|
|
|
|
|
DbgPrint("|**********************************************************************\n");
|
|
DbgPrint("|** \n");
|
|
DbgPrint("|** Deadlock detected trying to acquire synchronization object at \n");
|
|
DbgPrint("|** address %p (%d-way deadlock)\n",
|
|
ResourceAddress,
|
|
Participants
|
|
);
|
|
|
|
if (ViDeadlockParticipation) {
|
|
DbgPrint("|** For more information, type \n");
|
|
DbgPrint("|** !deadlock\n");
|
|
DbgPrint("|** \n");
|
|
DbgPrint("|**********************************************************************\n");
|
|
|
|
|
|
} else {
|
|
DbgPrint("|** More information is not available because memory could\n");
|
|
DbgPrint("|** not be allocated to save the state information");
|
|
DbgPrint("|** \n");
|
|
DbgPrint("|**********************************************************************\n");
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
#if 0
|
|
DbgPrint ("Verifier: deadlock: message: participant1 @ %p, participant2 @ %p, \n",
|
|
(FirstParticipant) ?
|
|
ViDeadlockGetNodeResourceAddress(FirstParticipant) :
|
|
ResourceAddress,
|
|
|
|
ViDeadlockGetNodeResourceAddress(SecondParticipant)
|
|
);
|
|
#endif
|
|
if (FirstParticipant) {
|
|
|
|
ViDeadlockParticipation[ViDeadlockNumberParticipants].NodeInformation =
|
|
TRUE;
|
|
ViDeadlockParticipation[ViDeadlockNumberParticipants].Participant =
|
|
FirstParticipant;
|
|
|
|
} else {
|
|
|
|
ViDeadlockParticipation[ViDeadlockNumberParticipants].NodeInformation =
|
|
FALSE;
|
|
ViDeadlockParticipation[ViDeadlockNumberParticipants].Participant =
|
|
ViDeadlockSearchResource(ResourceAddress);
|
|
|
|
}
|
|
|
|
ViDeadlockParticipation[ViDeadlockNumberParticipants+1].NodeInformation =
|
|
TRUE;
|
|
ViDeadlockParticipation[ViDeadlockNumberParticipants+1].Participant =
|
|
SecondParticipant;
|
|
|
|
ViDeadlockNumberParticipants +=2;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////// Resource cleanup
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
VOID
|
|
ViDeadlockDeleteMemoryRange(
|
|
IN PVOID Address,
|
|
IN SIZE_T Size
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called whenever some region of kernel virtual space
|
|
is no longer valid. We need this hook because most kernel resources
|
|
do not have a "delete resource" function and we need to figure out
|
|
what resources are not valid. Otherwise our dependency graph will
|
|
become populated by many zombie resources.
|
|
|
|
The important moments when the function gets called are ExFreePool
|
|
(and friends) and driver unloading. Dynamic and static memory are the
|
|
main regions where a resource gets allocated. There can be the possibility
|
|
of a resource allocated on the stack but this is a very weird scenario.
|
|
We might need to detect this and flag it as a potential issue.
|
|
|
|
If a resource or thread lives within the range specified then all graph
|
|
paths with nodes reachable from the resource or thread will be wiped out.
|
|
|
|
ISSUE (silviuc). This policy might lose some cases.
|
|
For instance if T1 acquires ABC,
|
|
then C is deleted and then T2 acquires BA this is a potential deadlock but
|
|
we will not catch it because when C gets deleted the whole ABC path will
|
|
disappear. Right now we have no solution for that. If we do not delete there
|
|
is no way we can decide when to wipe regions of the graph withouth creating
|
|
accumulations of zombies. One solution would be to keep nodes around and delete
|
|
them only if all nodes in the tree are supposed to be deleted.
|
|
|
|
Arguments:
|
|
|
|
Address - start address of the range to be deleted.
|
|
|
|
Size - size in bytes of the range to be deleted.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG Index;
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_RESOURCE Resource;
|
|
PVI_DEADLOCK_THREAD Thread;
|
|
KIRQL OldIrql;
|
|
|
|
LOCK_DEADLOCK_DATABASE(&OldIrql)
|
|
|
|
//
|
|
// Iterate all resources and delete the ones contained in the
|
|
// memory range.
|
|
//
|
|
|
|
for (Index = 0; Index < VI_DEADLOCK_HASH_BINS; Index += 1) {
|
|
|
|
Current = ViDeadlockResourceDatabase[Index].Flink;
|
|
|
|
while (Current != &(ViDeadlockResourceDatabase[Index])) {
|
|
|
|
|
|
Resource = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_RESOURCE,
|
|
HashChainList);
|
|
|
|
Current = Current->Flink;
|
|
|
|
if ((PVOID)(Resource->ResourceAddress) >= Address &&
|
|
((ULONG_PTR)(Resource->ResourceAddress) <= ((ULONG_PTR)Address + Size))) {
|
|
|
|
ViDeadlockDeleteResource (Resource);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Iterate all threads and delete the ones contained in the
|
|
// memory range. Note that it is actually a bug if we find a
|
|
// thread to be deleted because this means thread is dying while
|
|
// holding some resources.
|
|
//
|
|
|
|
for (Index = 0; Index < VI_DEADLOCK_HASH_BINS; Index += 1) {
|
|
|
|
Current = ViDeadlockThreadDatabase[Index].Flink;
|
|
|
|
while (Current != &(ViDeadlockThreadDatabase[Index])) {
|
|
|
|
|
|
Thread = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_THREAD,
|
|
ListEntry);
|
|
|
|
Current = Current->Flink;
|
|
|
|
if ((PVOID)(Thread->Thread) >= Address &&
|
|
((ULONG_PTR)(Thread->Thread) <= ((ULONG_PTR)Address + Size))) {
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_THREAD_HOLDS_RESOURCES,
|
|
(ULONG_PTR)Thread,
|
|
(ULONG_PTR)(Thread->CurrentNode),
|
|
0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
UNLOCK_DEADLOCK_DATABASE(OldIrql)
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockIsNodeMarkedForDeletion (
|
|
PVI_DEADLOCK_NODE Node
|
|
)
|
|
{
|
|
ASSERT (Node);
|
|
|
|
if (Node->ResourceList.Flink == NULL) {
|
|
return TRUE;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
PVOID
|
|
ViDeadlockGetNodeResourceAddress (
|
|
PVI_DEADLOCK_NODE Node
|
|
)
|
|
{
|
|
if (ViDeadlockIsNodeMarkedForDeletion(Node)) {
|
|
return Node->ResourceAddress;
|
|
}
|
|
else {
|
|
return Node->Root->ResourceAddress;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
ViDeadlockDeleteResource (
|
|
PVI_DEADLOCK_RESOURCE Resource
|
|
)
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node;
|
|
PVI_DEADLOCK_NODE Parent;
|
|
PVI_DEADLOCK_NODE Root;
|
|
|
|
ASSERT (Resource != NULL);
|
|
|
|
//
|
|
// Traverse the list of nodes representing acquisition of this resource
|
|
// and mark them all as deleted. If the NodeCount of the root becomes zero
|
|
// then we can delete the whole tree under the root.
|
|
//
|
|
|
|
Current = Resource->ResourceList.Flink;
|
|
|
|
while (Current != &(Resource->ResourceList)) {
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
|
|
Current = Current->Flink;
|
|
|
|
//
|
|
// Mark node as deleted
|
|
//
|
|
|
|
Node->ResourceList.Flink = NULL;
|
|
Node->ResourceList.Blink = NULL;
|
|
Node->ResourceAddress = Node->Root->ResourceAddress;
|
|
|
|
//
|
|
// Update NodeCount all the way to the root.
|
|
//
|
|
|
|
Parent = Node->Parent;
|
|
Root = Node;
|
|
|
|
while (Parent != NULL) {
|
|
|
|
Parent->NodeCount -= 1;
|
|
|
|
Root = Parent;
|
|
Parent = Parent->Parent;
|
|
}
|
|
|
|
//
|
|
// If all nodes in the tree have been marked as deleted
|
|
// it is time to delete and deallocate the whole tree.
|
|
//
|
|
|
|
if (Root->NodeCount == 0) {
|
|
|
|
ViDeadlockDeleteTree (Root);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Delete the resource structure.
|
|
//
|
|
|
|
ViDeadlockFree (Node->Root);
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockDeleteTree (
|
|
PVI_DEADLOCK_NODE Root
|
|
)
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node;
|
|
|
|
Current = Root->ChildrenList.Flink;
|
|
|
|
while (Current != &(Root->ChildrenList)) {
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
SiblingsList);
|
|
|
|
Current = Current->Flink;
|
|
|
|
ViDeadlockDeleteTree (Node);
|
|
}
|
|
|
|
ViDeadlockFree (Root);
|
|
}
|
|
|
|
|
|
|