422 lines
9.3 KiB
C
422 lines
9.3 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 2000 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:
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#ifndef _VF_DEADLOCK_
|
||
|
#define _VF_DEADLOCK_
|
||
|
|
||
|
//
|
||
|
// Deadlock detection package initialization.
|
||
|
//
|
||
|
|
||
|
VOID
|
||
|
VfDeadlockDetectionInitialize(
|
||
|
);
|
||
|
|
||
|
//
|
||
|
// Functions called from IovCallDriver (driver verifier replacement for
|
||
|
// IoCallDriver) just before and after the real call to the driver is made.
|
||
|
//
|
||
|
|
||
|
BOOLEAN
|
||
|
VfDeadlockBeforeCallDriver (
|
||
|
IN PDEVICE_OBJECT DeviceObject,
|
||
|
IN OUT PIRP Irp
|
||
|
);
|
||
|
|
||
|
VOID
|
||
|
VfDeadlockAfterCallDriver (
|
||
|
IN PDEVICE_OBJECT DeviceObject,
|
||
|
IN OUT PIRP Irp,
|
||
|
IN BOOLEAN PagingIrp
|
||
|
);
|
||
|
|
||
|
//
|
||
|
// Maximum depth of stack traces captured.
|
||
|
//
|
||
|
|
||
|
#define VI_MAX_STACK_DEPTH 8
|
||
|
|
||
|
#define NO_OF_DEADLOCK_PARTICIPANTS 32
|
||
|
|
||
|
//
|
||
|
// 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;
|
||
|
|
||
|
union {
|
||
|
|
||
|
//
|
||
|
// List of nodes representing the same resource acquisition
|
||
|
// as the current node but in different contexts (lock combinations).
|
||
|
//
|
||
|
|
||
|
struct _LIST_ENTRY ResourceList;
|
||
|
|
||
|
//
|
||
|
// Used to chain free nodes. This is used only after the node has
|
||
|
// been deleted (resource was freed). Nodes are kept in a cache
|
||
|
// to reduce contention for the kernel pool.
|
||
|
//
|
||
|
|
||
|
struct _LIST_ENTRY FreeListEntry;
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Back pointer to the descriptor for this resource.
|
||
|
//
|
||
|
|
||
|
struct _VI_DEADLOCK_RESOURCE * Root;
|
||
|
|
||
|
//
|
||
|
// 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.
|
||
|
//
|
||
|
|
||
|
struct _VI_DEADLOCK_THREAD * ThreadEntry;
|
||
|
|
||
|
//
|
||
|
// Fields used for decision making within the deadlock analysis
|
||
|
// algorithm.
|
||
|
//
|
||
|
// Active: 1 if the node represents a resource currently acquired,
|
||
|
// 0 if resource was acquired in the past.
|
||
|
//
|
||
|
// OnlyTryAcquiredUsed: 1 if resource was always acquired with TryAcquire.
|
||
|
// 0 if at least once normal acquire was used. A node that uses
|
||
|
// only TryAcquire cannot be involved in a deadlock.
|
||
|
//
|
||
|
// SequenceNumber: field that gets a unique stamp during each deadlock
|
||
|
// analysis run. It helps figure out if the node was touched
|
||
|
// already in the current graph traversal.
|
||
|
//
|
||
|
|
||
|
struct {
|
||
|
|
||
|
ULONG Active : 1;
|
||
|
ULONG OnlyTryAcquireUsed : 1;
|
||
|
ULONG SequenceNumber : 30;
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Stack traces for the resource acquisition moment.
|
||
|
// Used when displaying deadlock proofs. On free builds
|
||
|
// anything other than the first entry (return address)
|
||
|
// may be bogus in case stack trace capturing failed.
|
||
|
//
|
||
|
|
||
|
PVOID StackTrace[VI_MAX_STACK_DEPTH];
|
||
|
PVOID ParentStackTrace[VI_MAX_STACK_DEPTH];
|
||
|
|
||
|
} VI_DEADLOCK_NODE, *PVI_DEADLOCK_NODE;
|
||
|
|
||
|
//
|
||
|
// VI_DEADLOCK_RESOURCE
|
||
|
//
|
||
|
|
||
|
typedef struct _VI_DEADLOCK_RESOURCE {
|
||
|
|
||
|
//
|
||
|
// Resource type (mutex, spinlock, etc.).
|
||
|
//
|
||
|
|
||
|
VI_DEADLOCK_RESOURCE_TYPE Type;
|
||
|
|
||
|
//
|
||
|
// Resource flags
|
||
|
//
|
||
|
// NodeCount : number of resource nodes created for this resource.
|
||
|
//
|
||
|
// RecursionCount : number of times this resource has been recursively acquired
|
||
|
// It makes sense to put this counter in the resource because as long as
|
||
|
// resource is acquired only one thread can operate on it.
|
||
|
//
|
||
|
|
||
|
struct {
|
||
|
ULONG NodeCount : 16;
|
||
|
ULONG RecursionCount : 16;
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// The address of the synchronization object used by the kernel.
|
||
|
//
|
||
|
|
||
|
PVOID ResourceAddress;
|
||
|
|
||
|
//
|
||
|
// The thread that currently owns the resource. The field is
|
||
|
// null if nobody owns the resource.
|
||
|
//
|
||
|
|
||
|
struct _VI_DEADLOCK_THREAD * ThreadOwner;
|
||
|
|
||
|
//
|
||
|
// List of resource nodes representing acquisitions of this resource.
|
||
|
//
|
||
|
|
||
|
LIST_ENTRY ResourceList;
|
||
|
|
||
|
union {
|
||
|
|
||
|
//
|
||
|
// List used for chaining resources from a hash bucket.
|
||
|
//
|
||
|
|
||
|
LIST_ENTRY HashChainList;
|
||
|
|
||
|
//
|
||
|
// Used to chain free resources. This list is used only after
|
||
|
// the resource has been freed and we put the structure
|
||
|
// into a cache to reduce kernel pool contention.
|
||
|
//
|
||
|
|
||
|
LIST_ENTRY FreeListEntry;
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Stack trace of the resource creator. On free builds we
|
||
|
// may have here only a return address that is bubbled up
|
||
|
// from verifier thunks.
|
||
|
//
|
||
|
|
||
|
PVOID StackTrace [VI_MAX_STACK_DEPTH];
|
||
|
|
||
|
//
|
||
|
// Stack trace for last acquire
|
||
|
//
|
||
|
|
||
|
PVOID LastAcquireTrace [VI_MAX_STACK_DEPTH];
|
||
|
|
||
|
//
|
||
|
// Stack trace for last release
|
||
|
//
|
||
|
|
||
|
PVOID LastReleaseTrace [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.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// We have separate graph branches for spinlocks and other types
|
||
|
// of locks (fast mutex, mutex). The thread keeps a list of both types
|
||
|
// so that we can properly release locks
|
||
|
//
|
||
|
|
||
|
PVI_DEADLOCK_NODE CurrentSpinNode;
|
||
|
PVI_DEADLOCK_NODE CurrentOtherNode;
|
||
|
|
||
|
union {
|
||
|
|
||
|
//
|
||
|
// Thread list. It is used for chaining into a hash bucket.
|
||
|
//
|
||
|
|
||
|
LIST_ENTRY ListEntry;
|
||
|
|
||
|
//
|
||
|
// Used to chain free nodes. The list is used only after we decide
|
||
|
// to delete the thread structure (possibly because it does not
|
||
|
// hold resources anymore). Keeping the structures in a cache
|
||
|
// reduces pool contention.
|
||
|
//
|
||
|
|
||
|
LIST_ENTRY FreeListEntry;
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Count of resources currently acquired by a thread. When this becomes
|
||
|
// zero the thread will be destroyed. The count goes up during acquire
|
||
|
// and down during release.
|
||
|
//
|
||
|
|
||
|
ULONG NodeCount;
|
||
|
|
||
|
//
|
||
|
// This counter is used to count how many IoCallDriver() calls with
|
||
|
// paging IRPs are active for this thread. This information is necessary
|
||
|
// to decide if we should temporarily disable deadlock verification
|
||
|
// to avoid known lack of lock hierarchy issues in file system drivers.
|
||
|
//
|
||
|
|
||
|
ULONG PagingCount;
|
||
|
|
||
|
} VI_DEADLOCK_THREAD, *PVI_DEADLOCK_THREAD;
|
||
|
|
||
|
//
|
||
|
// Deadlock verifier globals
|
||
|
//
|
||
|
|
||
|
typedef struct _VI_DEADLOCK_GLOBALS {
|
||
|
|
||
|
//
|
||
|
// Structure counters: [0] - current, [1] - maximum
|
||
|
//
|
||
|
|
||
|
ULONG Nodes[2];
|
||
|
ULONG Resources[2];
|
||
|
ULONG Threads[2];
|
||
|
|
||
|
//
|
||
|
// Maximum times for Acquire() and Release() in ticks.
|
||
|
//
|
||
|
|
||
|
LONGLONG TimeAcquire;
|
||
|
LONGLONG TimeRelease;
|
||
|
|
||
|
//
|
||
|
// Total number of kernel pool bytes used by the deadlock verifier
|
||
|
//
|
||
|
|
||
|
SIZE_T BytesAllocated;
|
||
|
|
||
|
//
|
||
|
// Resource and thread collection.
|
||
|
//
|
||
|
|
||
|
PLIST_ENTRY ResourceDatabase;
|
||
|
PLIST_ENTRY ThreadDatabase;
|
||
|
|
||
|
//
|
||
|
// How many times ExAllocatePool failed on us?
|
||
|
// If this is >0 we stop deadlock verification.
|
||
|
//
|
||
|
|
||
|
ULONG AllocationFailures;
|
||
|
|
||
|
//
|
||
|
// How many nodes have been trimmed when we decided to forget
|
||
|
// partially the history of some resources.
|
||
|
//
|
||
|
|
||
|
ULONG NodesTrimmedBasedOnAge;
|
||
|
ULONG NodesTrimmedBasedOnCount;
|
||
|
|
||
|
//
|
||
|
// Deadlock analysis statistics
|
||
|
//
|
||
|
|
||
|
ULONG NodesSearched;
|
||
|
ULONG MaxNodesSearched;
|
||
|
ULONG SequenceNumber;
|
||
|
|
||
|
ULONG RecursionDepthLimit;
|
||
|
ULONG SearchedNodesLimit;
|
||
|
|
||
|
ULONG DepthLimitHits;
|
||
|
ULONG SearchLimitHits;
|
||
|
|
||
|
//
|
||
|
// Number of times we have to exonerate a deadlock because
|
||
|
// it was protected by a common resource (e.g. thread 1 takes ABC,
|
||
|
// thread 2 takes ACB -- this will get flagged initially by our algorithm
|
||
|
// since B&C are taken out of order but is not actually a deadlock.
|
||
|
//
|
||
|
|
||
|
ULONG ABC_ACB_Skipped;
|
||
|
|
||
|
//
|
||
|
// How many locks are held simultaneously while the system is running?
|
||
|
//
|
||
|
|
||
|
#if DBG
|
||
|
ULONG NodeLevelCounter[8];
|
||
|
ULONG GraphNodes[8];
|
||
|
|
||
|
ULONG TotalReleases;
|
||
|
ULONG OutOfOrderReleases;
|
||
|
ULONG RootNodesDeleted;
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// Caches of freed structures (thread, resource, node) used to
|
||
|
// decrease kernel pool contention.
|
||
|
//
|
||
|
|
||
|
LIST_ENTRY FreeResourceList;
|
||
|
LIST_ENTRY FreeThreadList;
|
||
|
LIST_ENTRY FreeNodeList;
|
||
|
|
||
|
ULONG FreeResourceCount;
|
||
|
ULONG FreeThreadCount;
|
||
|
ULONG FreeNodeCount;
|
||
|
|
||
|
//
|
||
|
// Resource address that caused the deadlock
|
||
|
//
|
||
|
|
||
|
PVOID Instigator;
|
||
|
|
||
|
//
|
||
|
// Number of participants in the deadlock
|
||
|
//
|
||
|
|
||
|
ULONG NumberOfParticipants;
|
||
|
|
||
|
//
|
||
|
// List of the nodes that participate in the deadlock
|
||
|
//
|
||
|
|
||
|
PVI_DEADLOCK_NODE Participant [NO_OF_DEADLOCK_PARTICIPANTS];
|
||
|
|
||
|
LOGICAL CacheReductionInProgress;
|
||
|
} VI_DEADLOCK_GLOBALS, *PVI_DEADLOCK_GLOBALS;
|
||
|
|
||
|
#endif
|