mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5100 lines
129 KiB
5100 lines
129 KiB
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
vfdeadlock.c
|
|
|
|
Abstract:
|
|
|
|
Detect deadlocks in arbitrary kernel synchronization objects.
|
|
|
|
Author:
|
|
|
|
Jordan Tigani (jtigani) 2-May-2000
|
|
Silviu Calinoiu (silviuc) 9-May-2000
|
|
|
|
Revision History:
|
|
|
|
Silviu Calinoiu (silviuc) 30-Sep-2000
|
|
|
|
Rewrote garbage collection of resources since now we have support
|
|
from ExFreePool.
|
|
|
|
Got rid of the ref/deref scheme for threads.
|
|
|
|
Major optimization work.
|
|
|
|
--*/
|
|
|
|
/*++
|
|
|
|
The Deadlock Verifier
|
|
|
|
The deadlock verifier is used to detect potential deadlocks. It does this
|
|
by acquiring the history of how resources are acquired and trying to figure
|
|
out on the fly if there are any potential lock hierarchy issues. The algorithms
|
|
for finding cycles in the lock dependency graph is totally "blind". This means
|
|
that if a driver acquires lock A then B in one place and lock B then A in
|
|
another this will be triggered as a deadlock issue. This will happen even if you
|
|
can build a proof based on other contextual factors that the deadlock can never
|
|
happen.
|
|
|
|
The deadlock verifier assumes there are four operations during the lifetime
|
|
of a resource: initialize(), acquire(), release() and free(). The only one that
|
|
is caught 100% of the time is free() due to special support from the kernel
|
|
pool manager. The other ones can be missed if the operations are performed
|
|
by an unverified driver or by kernel with kernel verifier disabled. The most
|
|
typical of these misses is the initialize(). For example the kernel initializes
|
|
a resource and then passes it to a driver to be used in acquire()/releae() cycles.
|
|
This situation is covered 100% by the deadlock verifier. It will never complain
|
|
about "resource uninitialized" issues.
|
|
|
|
Missing acquire() or release() operations is trickier to deal with.
|
|
This can happen if the a verified driver acquires a resource and then another
|
|
driver that is not verified releases it or viceversa. This is in and on itself
|
|
a very bad programming practive and therefore the deadlock verifier will flag
|
|
these issues. As a side note we cannot do too much about working around them
|
|
given that we would like to. Also, because missing acquire() or release()
|
|
operations puts deadlock verifier internal structures into inconsistent
|
|
states these failures are difficult to debug.
|
|
|
|
The deadlock verifier stores the lock dependency graph using three types
|
|
of structures: 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. This is created
|
|
when Initialize() is called in a verified driver or we first encounter an
|
|
Acquire() in a verified driver. Note that a resource can be initialized in
|
|
an unverified driver and then passed to a verified driver for use. Therefore
|
|
we can encounter Acquire() operations for resources that are not in the
|
|
deadlock verifier database. The resource gets deleted from the database when
|
|
the memory containing it is freed either because ExFreePool gets called or
|
|
|
|
Every acquisition of a resource is modeled by a NODE structure. When a thread
|
|
acquires resource B while holding A the deadlock verifier will create a NODE
|
|
for B and link it to the node for A.
|
|
|
|
There are three important functions that make the interface with the outside
|
|
world.
|
|
|
|
VfDeadlockInitializeResource hook for resource initialization
|
|
VfDeadlockAcquireResource hook for resource acquire
|
|
VfDeadlockReleaseResource hook for resource release
|
|
VerifierDeadlockFreePool hook called from ExFreePool for every free()
|
|
|
|
|
|
--*/
|
|
|
|
#include "vfdef.h"
|
|
|
|
//
|
|
// *TO DO* LIST
|
|
//
|
|
// [-] Hook KeTryAcquireSpinLock
|
|
// [-] Implement dynamic reset scheme for weird situations
|
|
// [-] Implement Strict and VeryStrict scheme.
|
|
//
|
|
|
|
//
|
|
// Enable/disable the deadlock detection package. This can be used
|
|
// to disable temporarily the deadlock detection package.
|
|
//
|
|
|
|
BOOLEAN ViDeadlockDetectionEnabled;
|
|
|
|
//
|
|
// If true we will complain about release() without acquire() or acquire()
|
|
// while we think the resource is still owned. This can happen legitimately
|
|
// if a lock is shared between drivers and for example acquire() happens in
|
|
// an unverified driver and release() in a verified one or viceversa. The
|
|
// safest thing would be to enable this checks only if kernel verifier and
|
|
// dirver verifier for all drivers are enabled.
|
|
//
|
|
|
|
BOOLEAN ViDeadlockStrict;
|
|
|
|
//
|
|
// If true we will complain about uninitialized and double initialized
|
|
// resources. If false we resolve quitely these issues on the fly by
|
|
// simulating an initialize ourselves during the acquire() operation.
|
|
// This can happen legitimately if the resource is initialized in an
|
|
// unverified driver and passed to a verified one to be used. Therefore
|
|
// the safest thing would be to enable this only if kernel verifier and
|
|
// all driver verifier for all dirvers are enabled.
|
|
//
|
|
|
|
BOOLEAN ViDeadlockVeryStrict;
|
|
|
|
//
|
|
// The way to deal with release() without acquire() issues is to reset
|
|
// the deadlock verifier completely. Here we keep a counter of how often
|
|
// does this happen.
|
|
//
|
|
|
|
ULONG ViDeadlockResets;
|
|
|
|
//
|
|
// If this is true only spinlocks are verified. All other resources
|
|
// are just ignored.
|
|
//
|
|
|
|
BOOLEAN ViDeadlockVerifyOnlySpinlocks;
|
|
|
|
ULONG ViVerifyOnlySpinlocksFromRegistry;
|
|
|
|
//
|
|
// AgeWindow is used while trimming the graph nodes that have not
|
|
// been accessed in a while. If the global age minus the age of the node
|
|
// is bigger than the age window then the node is a candidate for trimming.
|
|
//
|
|
// The TrimThreshold variable controls if the trimming will start for a
|
|
// resource. As long as a resource has less than TrimThreshold nodes we will
|
|
// not apply the ageing algorithm to trim nodes for that resource.
|
|
//
|
|
|
|
ULONG ViDeadlockAgeWindow = 2000;
|
|
|
|
ULONG ViDeadlockTrimThreshold = 128;
|
|
|
|
//
|
|
// Various deadlock verification flags flags
|
|
//
|
|
// Recursive aquisition ok: mutexes can be recursively acquired
|
|
//
|
|
// No initialization function: if resource type does not have such a function
|
|
// we cannot expect that in acquire() the resource is already initialized
|
|
// by a previous call to initialize(). Fast mutexes are like this.
|
|
//
|
|
// Reverse release ok: release is not done in the same order as acquire
|
|
//
|
|
// Reinitialize ok: sometimes they reinitialize the resource.
|
|
//
|
|
// Note that a resource might appear as uninitialized if it is initialized
|
|
// in an unverified driver and then passed to a verified driver that calls
|
|
// acquire(). This is for instance the case with device extensions that are
|
|
// allocated by the kernel but used by a particular driver.
|
|
//
|
|
// silviuc: based on this maybe we should drop the whole not initialized thing?
|
|
//
|
|
|
|
#define VI_DEADLOCK_FLAG_RECURSIVE_ACQUISITION_OK 0x0001
|
|
#define VI_DEADLOCK_FLAG_NO_INITIALIZATION_FUNCTION 0x0002
|
|
#define VI_DEADLOCK_FLAG_REVERSE_RELEASE_OK 0x0004
|
|
#define VI_DEADLOCK_FLAG_REINITIALIZE_OK 0x0008
|
|
|
|
//
|
|
// Specific verification flags for each resource type. The
|
|
// indeces in the vector match the values for the enumeration
|
|
// type VI_DEADLOCK_RESOURCE_TYPE from ntos\inc\verifier.h.
|
|
//
|
|
|
|
ULONG ViDeadlockResourceTypeInfo[VfDeadlockTypeMaximum] =
|
|
{
|
|
// ViDeadlockUnknown //
|
|
0,
|
|
|
|
// ViDeadlockMutex//
|
|
VI_DEADLOCK_FLAG_RECURSIVE_ACQUISITION_OK |
|
|
0,
|
|
|
|
// ViDeadlockFastMutex //
|
|
VI_DEADLOCK_FLAG_NO_INITIALIZATION_FUNCTION |
|
|
0,
|
|
|
|
// ViDeadlockFastMutexUnsafe //
|
|
VI_DEADLOCK_FLAG_NO_INITIALIZATION_FUNCTION |
|
|
VI_DEADLOCK_FLAG_REVERSE_RELEASE_OK |
|
|
0,
|
|
|
|
// ViDeadlockSpinLock //
|
|
VI_DEADLOCK_FLAG_REVERSE_RELEASE_OK |
|
|
VI_DEADLOCK_FLAG_REINITIALIZE_OK |
|
|
0,
|
|
|
|
// ViDeadlockQueuedSpinLock //
|
|
VI_DEADLOCK_FLAG_NO_INITIALIZATION_FUNCTION |
|
|
0,
|
|
};
|
|
|
|
|
|
NTSYSAPI
|
|
USHORT
|
|
NTAPI
|
|
RtlCaptureStackBackTrace(
|
|
IN ULONG FramesToSkip,
|
|
IN ULONG FramesToCapture,
|
|
OUT PVOID *BackTrace,
|
|
OUT PULONG BackTraceHash
|
|
);
|
|
|
|
//
|
|
// Control debugging behavior. A zero value means bugcheck for every failure.
|
|
//
|
|
|
|
ULONG ViDeadlockDebug;
|
|
|
|
//
|
|
// Various health indicators
|
|
//
|
|
|
|
struct {
|
|
|
|
ULONG AllocationFailures : 1;
|
|
ULONG KernelVerifierEnabled : 1;
|
|
ULONG DriverVerifierForAllEnabled : 1;
|
|
ULONG SequenceNumberOverflow : 1;
|
|
ULONG DeadlockParticipantsOverflow : 1;
|
|
ULONG ResourceNodeCountOverflow : 1;
|
|
ULONG Reserved : 15;
|
|
|
|
} ViDeadlockState;
|
|
|
|
//
|
|
// Maximum number of locks acceptable to be hold simultaneously
|
|
//
|
|
|
|
ULONG ViDeadlockSimultaneousLocksLimit = 10;
|
|
|
|
//
|
|
// Deadlock verifier specific issues (bugs)
|
|
//
|
|
// SELF_DEADLOCK
|
|
//
|
|
// Acquiring the same 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. Spinlocks are handled like this
|
|
// in a few drivers. It is not a bug per se.
|
|
//
|
|
// UNEXPECTED_THREAD
|
|
//
|
|
// Current thread does not have any resources acquired. This may be legit if
|
|
// we acquire in one thread and release in another. This would be bad programming
|
|
// practice but not a crash waiting to happen per se.
|
|
//
|
|
// MULTIPLE_INITIALIZATION
|
|
//
|
|
// Attempting to initialize a second time the same resource.
|
|
//
|
|
// THREAD_HOLDS_RESOURCES
|
|
//
|
|
// Thread was killed while holding resources or a resource is being
|
|
// deleted while holding resources.
|
|
//
|
|
|
|
#define VI_DEADLOCK_ISSUE_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
|
|
#define VI_DEADLOCK_ISSUE_UNACQUIRED_RESOURCE 0x1007
|
|
|
|
//
|
|
// Performance counters read from registry.
|
|
//
|
|
|
|
ULONG ViSearchedNodesLimitFromRegistry;
|
|
ULONG ViRecursionDepthLimitFromRegistry;
|
|
|
|
//
|
|
// Water marks for the cache of freed structures.
|
|
//
|
|
|
|
#define VI_DEADLOCK_MAX_FREE_THREAD 0x10
|
|
#define VI_DEADLOCK_MAX_FREE_NODE 0x40
|
|
#define VI_DEADLOCK_MAX_FREE_RESOURCE 0x20
|
|
|
|
WORK_QUEUE_ITEM ViTrimDeadlockPoolWorkItem;
|
|
|
|
//
|
|
// Amount of memory preallocated if kernel verifier
|
|
// is enabled. If kernel verifier is enabled no memory
|
|
// is ever allocated from kernel pool except in the
|
|
// DeadlockDetectionInitialize() routine.
|
|
//
|
|
|
|
ULONG ViDeadlockReservedThreads = 0x200;
|
|
ULONG ViDeadlockReservedNodes = 0x4000;
|
|
ULONG ViDeadlockReservedResources = 0x2000;
|
|
|
|
//
|
|
// Block types that can be allocated.
|
|
//
|
|
|
|
typedef enum {
|
|
|
|
ViDeadlockUnknown = 0,
|
|
ViDeadlockResource,
|
|
ViDeadlockNode,
|
|
ViDeadlockThread
|
|
|
|
} VI_DEADLOCK_ALLOC_TYPE;
|
|
|
|
//
|
|
// VI_DEADLOCK_GLOBALS
|
|
//
|
|
|
|
#define VI_DEADLOCK_HASH_BINS 0x1F
|
|
|
|
PVI_DEADLOCK_GLOBALS ViDeadlockGlobals;
|
|
|
|
//
|
|
// Default maximum recursion depth for the deadlock
|
|
// detection algorithm. This can be overridden by registry.
|
|
//
|
|
|
|
#define VI_DEADLOCK_MAXIMUM_DEGREE 4
|
|
|
|
//
|
|
// Default maximum number of searched nodes for the deadlock
|
|
// detection algorithm. This can be overridden by registry.
|
|
//
|
|
|
|
#define VI_DEADLOCK_MAXIMUM_SEARCH 1000
|
|
|
|
//
|
|
// Verifier deadlock detection pool tag.
|
|
//
|
|
|
|
#define VI_DEADLOCK_TAG 'kclD'
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////// Internal deadlock detection functions
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
VOID
|
|
VfDeadlockDetectionInitialize (
|
|
);
|
|
|
|
VOID
|
|
VfDeadlockDetectionCleanup (
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDetectionReset (
|
|
);
|
|
|
|
PLIST_ENTRY
|
|
ViDeadlockDatabaseHash(
|
|
IN PLIST_ENTRY Database,
|
|
IN PVOID Address
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockIsDriverInList (
|
|
PUNICODE_STRING BigString,
|
|
PUNICODE_STRING Match
|
|
);
|
|
|
|
PVI_DEADLOCK_RESOURCE
|
|
ViDeadlockSearchResource(
|
|
IN PVOID ResourceAddress
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockSimilarNode (
|
|
IN PVOID Resource,
|
|
IN BOOLEAN TryNode,
|
|
IN PVI_DEADLOCK_NODE Node
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockCanProceed (
|
|
IN PVOID Resource, OPTIONAL
|
|
IN PVOID CallAddress, OPTIONAL
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type OPTIONAL
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockAnalyze(
|
|
IN PVOID ResourceAddress,
|
|
IN PVI_DEADLOCK_NODE CurrentNode,
|
|
IN BOOLEAN FirstCall,
|
|
IN ULONG Degree
|
|
);
|
|
|
|
PVI_DEADLOCK_THREAD
|
|
ViDeadlockSearchThread (
|
|
PKTHREAD Thread
|
|
);
|
|
|
|
PVI_DEADLOCK_THREAD
|
|
ViDeadlockAddThread (
|
|
PKTHREAD Thread,
|
|
PVOID ReservedThread
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDeleteThread (
|
|
PVI_DEADLOCK_THREAD Thread,
|
|
BOOLEAN Cleanup
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockAddResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type,
|
|
IN PVOID Caller,
|
|
IN PVOID ReservedResource
|
|
);
|
|
|
|
PVOID
|
|
ViDeadlockAllocate (
|
|
VI_DEADLOCK_ALLOC_TYPE Type
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockFree (
|
|
PVOID Object,
|
|
VI_DEADLOCK_ALLOC_TYPE Type
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockTrimPoolCache (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockTrimPoolCacheWorker (
|
|
PVOID
|
|
);
|
|
|
|
PVOID
|
|
ViDeadlockAllocateFromPoolCache (
|
|
PULONG Count,
|
|
ULONG MaximumCount,
|
|
PLIST_ENTRY List,
|
|
SIZE_T Offset
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockFreeIntoPoolCache (
|
|
PVOID Object,
|
|
PULONG Count,
|
|
PLIST_ENTRY List,
|
|
SIZE_T Offset
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockReportIssue (
|
|
ULONG_PTR Param1,
|
|
ULONG_PTR Param2,
|
|
ULONG_PTR Param3,
|
|
ULONG_PTR Param4
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockAddParticipant(
|
|
PVI_DEADLOCK_NODE Node
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDeleteResource (
|
|
PVI_DEADLOCK_RESOURCE Resource,
|
|
BOOLEAN Cleanup
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDeleteNode (
|
|
PVI_DEADLOCK_NODE Node,
|
|
BOOLEAN Cleanup
|
|
);
|
|
|
|
ULONG
|
|
ViDeadlockNodeLevel (
|
|
PVI_DEADLOCK_NODE Node
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockCertify(
|
|
VOID
|
|
);
|
|
|
|
BOOLEAN
|
|
ViDeadlockDetectionIsLockedAlready (
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDetectionLock (
|
|
PKIRQL OldIrql
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockDetectionUnlock (
|
|
KIRQL OldIrql
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockCheckThreadConsistency (
|
|
PVI_DEADLOCK_THREAD Thread,
|
|
BOOLEAN Recursion
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockCheckNodeConsistency (
|
|
PVI_DEADLOCK_NODE Node,
|
|
BOOLEAN Recursion
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockCheckResourceConsistency (
|
|
PVI_DEADLOCK_RESOURCE Resource,
|
|
BOOLEAN Recursion
|
|
);
|
|
|
|
PVI_DEADLOCK_THREAD
|
|
ViDeadlockCheckThreadReferences (
|
|
PVI_DEADLOCK_NODE Node
|
|
);
|
|
|
|
BOOLEAN
|
|
ViIsThreadInsidePagingCodePaths (
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockCheckDuplicatesAmongChildren (
|
|
PVI_DEADLOCK_NODE Parent,
|
|
PVI_DEADLOCK_NODE Child
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockCheckDuplicatesAmongRoots (
|
|
PVI_DEADLOCK_NODE Root
|
|
);
|
|
|
|
LOGICAL
|
|
ViDeadlockSimilarNodes (
|
|
PVI_DEADLOCK_NODE NodeA,
|
|
PVI_DEADLOCK_NODE NodeB
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockMergeNodes (
|
|
PVI_DEADLOCK_NODE NodeTo,
|
|
PVI_DEADLOCK_NODE NodeFrom
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockTrimResources (
|
|
PLIST_ENTRY HashList
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockForgetResourceHistory (
|
|
PVI_DEADLOCK_RESOURCE Resource,
|
|
ULONG TrimThreshold,
|
|
ULONG AgeThreshold
|
|
);
|
|
|
|
VOID
|
|
ViDeadlockCheckStackLimits (
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
|
|
#pragma alloc_text(PAGEVRFY, VfDeadlockDetectionInitialize)
|
|
#pragma alloc_text(PAGEVRFY, VfDeadlockInitializeResource)
|
|
#pragma alloc_text(PAGEVRFY, VfDeadlockAcquireResource)
|
|
#pragma alloc_text(PAGEVRFY, VfDeadlockReleaseResource)
|
|
#pragma alloc_text(PAGEVRFY, VfDeadlockDeleteMemoryRange)
|
|
#pragma alloc_text(PAGEVRFY, VfDeadlockDetectionCleanup)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDetectionReset)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDetectionLock)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDetectionUnlock)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDetectionIsLockedAlready)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCanProceed)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAnalyze)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDatabaseHash)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockSearchResource)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockSimilarNode)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockSearchThread)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAddThread)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDeleteThread)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAddResource)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAllocate)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockFree)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockTrimPoolCache)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockTrimPoolCacheWorker)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAllocateFromPoolCache)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockFreeIntoPoolCache)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockReportIssue)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockAddParticipant)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDeleteResource)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockDeleteNode)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockNodeLevel)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCertify)
|
|
|
|
#pragma alloc_text(PAGEVRFY, VerifierDeadlockFreePool)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCheckResourceConsistency)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCheckThreadConsistency)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCheckNodeConsistency)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCheckThreadReferences)
|
|
|
|
#pragma alloc_text(PAGEVRFY, VfDeadlockBeforeCallDriver)
|
|
#pragma alloc_text(PAGEVRFY, VfDeadlockAfterCallDriver)
|
|
#pragma alloc_text(PAGEVRFY, ViIsThreadInsidePagingCodePaths)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCheckDuplicatesAmongChildren)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCheckDuplicatesAmongRoots)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockSimilarNodes)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockMergeNodes)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockTrimResources)
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockForgetResourceHistory)
|
|
|
|
#pragma alloc_text(PAGEVRFY, ViDeadlockCheckStackLimits)
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////// Lock/unlock deadlock verifier
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Global `deadlock lock database' lock
|
|
//
|
|
|
|
KSPIN_LOCK ViDeadlockDatabaseLock;
|
|
PKTHREAD ViDeadlockDatabaseOwner;
|
|
|
|
|
|
VOID
|
|
ViDeadlockDetectionLock (
|
|
PKIRQL OldIrql
|
|
)
|
|
{
|
|
KeAcquireSpinLock(&ViDeadlockDatabaseLock, (OldIrql));
|
|
ViDeadlockDatabaseOwner = KeGetCurrentThread ();
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockDetectionUnlock (
|
|
KIRQL OldIrql
|
|
)
|
|
{
|
|
ViDeadlockDatabaseOwner = NULL;
|
|
KeReleaseSpinLock(&ViDeadlockDatabaseLock, OldIrql);
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockDetectionIsLockedAlready (
|
|
)
|
|
{
|
|
PVOID CurrentThread;
|
|
PVOID CurrentOwner;
|
|
|
|
ASSERT (ViDeadlockGlobals);
|
|
ASSERT (ViDeadlockDetectionEnabled);
|
|
|
|
//
|
|
// Figure out if are in a recursive call into the deadlock verifier.
|
|
// This can happen if we try to allocate/free pool while we execute
|
|
// code in the deadlock verifier.
|
|
//
|
|
// silviuc: can this be done instead with a simple read ?
|
|
//
|
|
|
|
CurrentThread = (PVOID) KeGetCurrentThread();
|
|
|
|
CurrentOwner = InterlockedCompareExchangePointer (&ViDeadlockDatabaseOwner,
|
|
CurrentThread,
|
|
CurrentThread);
|
|
|
|
if (CurrentOwner == CurrentThread) {
|
|
|
|
return TRUE;
|
|
}
|
|
else {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
///////////////////// Initialization and deadlock database management
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
PLIST_ENTRY
|
|
ViDeadlockDatabaseHash(
|
|
IN PLIST_ENTRY Database,
|
|
IN PVOID Address
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine hashes the resource address into the deadlock database.
|
|
The hash bin is represented by a list entry.
|
|
|
|
NOTE -- be careful modifying the method used for hashing --
|
|
we currently hash based on the PFN or page number of
|
|
the address given. This knowledge is used to optimize
|
|
the number of hash bins needed to look through
|
|
in order to delete addresses. For example, suppose
|
|
the address was 0x1020. This is PFN 1 and if we were
|
|
deleting addresses 0x1020-0x1040, we'd only have to
|
|
look in a single hash bin to find and remove the
|
|
address. Read VfDeadlockDeleteMemoryRange() for more details.
|
|
|
|
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)>> PAGE_SHIFT) % VI_DEADLOCK_HASH_BINS);
|
|
}
|
|
|
|
|
|
VOID
|
|
VfDeadlockDetectionInitialize(
|
|
IN LOGICAL VerifyAllDrivers,
|
|
IN LOGICAL VerifyKernel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the data structures necessary for detecting
|
|
deadlocks in kernel synchronization objects.
|
|
|
|
Arguments:
|
|
|
|
VerifyAllDrivers - Supplies TRUE if we are verifying all drivers.
|
|
|
|
VerifyKernel - Supplies TRUE if we are verifying the kernel.
|
|
|
|
Return Value:
|
|
|
|
None. If successful ViDeadlockGlobals will point to a fully initialized
|
|
structure.
|
|
|
|
Environment:
|
|
|
|
System initialization only.
|
|
|
|
--*/
|
|
{
|
|
ULONG I;
|
|
SIZE_T TableSize;
|
|
PLIST_ENTRY Current;
|
|
PVOID Block;
|
|
|
|
//
|
|
// Allocate the globals structure. ViDeadlockGlobals value is
|
|
// used to figure out if the whole initialization was successful
|
|
// or not.
|
|
//
|
|
|
|
ViDeadlockGlobals = ExAllocatePoolWithTag (NonPagedPool,
|
|
sizeof (VI_DEADLOCK_GLOBALS),
|
|
VI_DEADLOCK_TAG);
|
|
|
|
if (ViDeadlockGlobals == NULL) {
|
|
goto Failed;
|
|
}
|
|
|
|
RtlZeroMemory (ViDeadlockGlobals, sizeof (VI_DEADLOCK_GLOBALS));
|
|
|
|
ExInitializeWorkItem (&ViTrimDeadlockPoolWorkItem,
|
|
ViDeadlockTrimPoolCacheWorker,
|
|
NULL);
|
|
|
|
//
|
|
// Allocate hash tables for resources and threads.
|
|
//
|
|
|
|
TableSize = sizeof (LIST_ENTRY) * VI_DEADLOCK_HASH_BINS;
|
|
|
|
ViDeadlockGlobals->ResourceDatabase = ExAllocatePoolWithTag (NonPagedPool,
|
|
TableSize,
|
|
VI_DEADLOCK_TAG);
|
|
|
|
if (!ViDeadlockGlobals->ResourceDatabase) {
|
|
return;
|
|
}
|
|
|
|
ViDeadlockGlobals->ThreadDatabase = ExAllocatePoolWithTag (NonPagedPool,
|
|
TableSize,
|
|
VI_DEADLOCK_TAG);
|
|
|
|
if (!ViDeadlockGlobals->ThreadDatabase) {
|
|
goto Failed;
|
|
}
|
|
|
|
//
|
|
// Initialize the free lists.
|
|
//
|
|
|
|
InitializeListHead(&ViDeadlockGlobals->FreeResourceList);
|
|
InitializeListHead(&ViDeadlockGlobals->FreeThreadList);
|
|
InitializeListHead(&ViDeadlockGlobals->FreeNodeList);
|
|
|
|
//
|
|
// Initialize hash bins and database lock.
|
|
//
|
|
|
|
for (I = 0; I < VI_DEADLOCK_HASH_BINS; I += 1) {
|
|
|
|
InitializeListHead(&(ViDeadlockGlobals->ResourceDatabase[I]));
|
|
InitializeListHead(&ViDeadlockGlobals->ThreadDatabase[I]);
|
|
}
|
|
|
|
KeInitializeSpinLock (&ViDeadlockDatabaseLock);
|
|
|
|
//
|
|
// Did user request only spin locks? This relieves the
|
|
// memory consumption.
|
|
//
|
|
|
|
if (ViVerifyOnlySpinlocksFromRegistry) {
|
|
ViDeadlockVerifyOnlySpinlocks = TRUE;
|
|
}
|
|
|
|
//
|
|
// Initialize deadlock analysis parameters
|
|
//
|
|
|
|
ViDeadlockGlobals->RecursionDepthLimit = (ViRecursionDepthLimitFromRegistry) ?
|
|
ViRecursionDepthLimitFromRegistry :
|
|
VI_DEADLOCK_MAXIMUM_DEGREE;
|
|
|
|
ViDeadlockGlobals->SearchedNodesLimit = (ViSearchedNodesLimitFromRegistry) ?
|
|
ViSearchedNodesLimitFromRegistry :
|
|
VI_DEADLOCK_MAXIMUM_SEARCH;
|
|
|
|
//
|
|
// Preallocate all resources if kernel verifier is enabled.
|
|
//
|
|
|
|
if (VerifyKernel) {
|
|
|
|
PVOID Block;
|
|
|
|
for (I = 0; I < ViDeadlockReservedThreads; I += 1) {
|
|
|
|
Block = ExAllocatePoolWithTag( NonPagedPool,
|
|
sizeof (VI_DEADLOCK_THREAD),
|
|
VI_DEADLOCK_TAG);
|
|
|
|
if (Block == NULL) {
|
|
goto Failed;
|
|
}
|
|
|
|
ViDeadlockGlobals->BytesAllocated += sizeof (VI_DEADLOCK_THREAD);
|
|
ViDeadlockGlobals->Threads[0] += 1;
|
|
ViDeadlockFree (Block, ViDeadlockThread);
|
|
}
|
|
|
|
for (I = 0; I < ViDeadlockReservedNodes; I += 1) {
|
|
|
|
Block = ExAllocatePoolWithTag( NonPagedPool,
|
|
sizeof (VI_DEADLOCK_NODE),
|
|
VI_DEADLOCK_TAG);
|
|
|
|
if (Block == NULL) {
|
|
goto Failed;
|
|
}
|
|
|
|
ViDeadlockGlobals->BytesAllocated += sizeof (VI_DEADLOCK_NODE);
|
|
ViDeadlockGlobals->Nodes[0] += 1;
|
|
ViDeadlockFree (Block, ViDeadlockNode);
|
|
}
|
|
|
|
for (I = 0; I < ViDeadlockReservedResources; I += 1) {
|
|
|
|
Block = ExAllocatePoolWithTag( NonPagedPool,
|
|
sizeof (VI_DEADLOCK_RESOURCE),
|
|
VI_DEADLOCK_TAG);
|
|
|
|
if (Block == NULL) {
|
|
goto Failed;
|
|
}
|
|
|
|
ViDeadlockGlobals->BytesAllocated += sizeof (VI_DEADLOCK_RESOURCE);
|
|
ViDeadlockGlobals->Resources[0] += 1;
|
|
ViDeadlockFree (Block, ViDeadlockResource);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Mark that everything went fine and return
|
|
//
|
|
|
|
if (VerifyKernel) {
|
|
ViDeadlockState.KernelVerifierEnabled = 1;
|
|
}
|
|
|
|
if (VerifyAllDrivers) {
|
|
|
|
ViDeadlockState.DriverVerifierForAllEnabled = 1;
|
|
|
|
ViDeadlockStrict = TRUE;
|
|
|
|
if (ViDeadlockState.KernelVerifierEnabled == 1) {
|
|
|
|
//
|
|
// silviuc: The VeryStrict option is unfunctional right now because
|
|
// KeInitializeSpinLock is a kernel routine and therefore
|
|
// cannot be hooked for kernel locks.
|
|
//
|
|
|
|
// ViDeadlockVeryStrict = TRUE;
|
|
}
|
|
}
|
|
|
|
ViDeadlockDetectionEnabled = TRUE;
|
|
return;
|
|
|
|
Failed:
|
|
|
|
//
|
|
// Cleanup if any of our allocations failed
|
|
//
|
|
|
|
Current = ViDeadlockGlobals->FreeNodeList.Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->FreeNodeList)) {
|
|
|
|
Block = (PVOID) CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
FreeListEntry);
|
|
|
|
Current = Current->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
Current = ViDeadlockGlobals->FreeNodeList.Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->FreeResourceList)) {
|
|
|
|
Block = (PVOID) CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_RESOURCE,
|
|
FreeListEntry);
|
|
|
|
Current = Current->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
Current = ViDeadlockGlobals->FreeNodeList.Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->FreeThreadList)) {
|
|
|
|
Block = (PVOID) CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_THREAD,
|
|
FreeListEntry);
|
|
|
|
Current = Current->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
if (NULL != ViDeadlockGlobals->ResourceDatabase) {
|
|
ExFreePool(ViDeadlockGlobals->ResourceDatabase);
|
|
}
|
|
|
|
if (NULL != ViDeadlockGlobals->ThreadDatabase) {
|
|
ExFreePool(ViDeadlockGlobals->ThreadDatabase);
|
|
}
|
|
|
|
if (NULL != ViDeadlockGlobals) {
|
|
ExFreePool(ViDeadlockGlobals);
|
|
|
|
//
|
|
// Important to set this to null for failure because it is
|
|
// used to figure out if the package got initialized or not.
|
|
//
|
|
|
|
ViDeadlockGlobals = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
VfDeadlockDetectionCleanup (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine tears down all deadlock verifier internal structures.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG Index;
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_RESOURCE Resource;
|
|
PVI_DEADLOCK_THREAD Thread;
|
|
PVOID Block;
|
|
|
|
//
|
|
// If we are not initialized then nothing to do.
|
|
//
|
|
|
|
if (ViDeadlockGlobals == NULL) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Iterate all resources and delete them. This will also delete
|
|
// all nodes associated with resources.
|
|
//
|
|
|
|
for (Index = 0; Index < VI_DEADLOCK_HASH_BINS; Index += 1) {
|
|
|
|
Current = ViDeadlockGlobals->ResourceDatabase[Index].Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->ResourceDatabase[Index])) {
|
|
|
|
|
|
Resource = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_RESOURCE,
|
|
HashChainList);
|
|
|
|
Current = Current->Flink;
|
|
|
|
ViDeadlockDeleteResource (Resource, TRUE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Iterate all threads and delete them.
|
|
//
|
|
|
|
for (Index = 0; Index < VI_DEADLOCK_HASH_BINS; Index += 1) {
|
|
Current = ViDeadlockGlobals->ThreadDatabase[Index].Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->ThreadDatabase[Index])) {
|
|
|
|
Thread = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_THREAD,
|
|
ListEntry);
|
|
|
|
Current = Current->Flink;
|
|
|
|
ViDeadlockDeleteThread (Thread, TRUE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Everything should be in the pool caches by now.
|
|
//
|
|
|
|
ASSERT (ViDeadlockGlobals->BytesAllocated == 0);
|
|
|
|
//
|
|
// Free pool caches.
|
|
//
|
|
|
|
Current = ViDeadlockGlobals->FreeNodeList.Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->FreeNodeList)) {
|
|
|
|
Block = (PVOID) CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
FreeListEntry);
|
|
|
|
Current = Current->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
Current = ViDeadlockGlobals->FreeNodeList.Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->FreeResourceList)) {
|
|
|
|
Block = (PVOID) CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_RESOURCE,
|
|
FreeListEntry);
|
|
|
|
Current = Current->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
Current = ViDeadlockGlobals->FreeNodeList.Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->FreeThreadList)) {
|
|
|
|
Block = (PVOID) CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_THREAD,
|
|
FreeListEntry);
|
|
|
|
Current = Current->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
//
|
|
// Free databases and global structure
|
|
//
|
|
|
|
ExFreePool (ViDeadlockGlobals->ResourceDatabase);
|
|
ExFreePool (ViDeadlockGlobals->ThreadDatabase);
|
|
|
|
ExFreePool(ViDeadlockGlobals);
|
|
|
|
ViDeadlockGlobals = NULL;
|
|
ViDeadlockDetectionEnabled = FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockDetectionReset (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine resets all internal deadlock verifier structures. All nodes,
|
|
resources, threads are forgotten. They will all go into free pool caches
|
|
ready to be used in a new life cycle.
|
|
|
|
The function is usually called with the deadlock verifier lock held.
|
|
It will not touch the lock at all therefore the caller will still
|
|
hold the lock after return.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG Index;
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_RESOURCE Resource;
|
|
PVI_DEADLOCK_THREAD Thread;
|
|
|
|
//
|
|
// If we are not initialized or not enabled then nothing to do.
|
|
//
|
|
|
|
if (ViDeadlockGlobals == NULL || ViDeadlockDetectionEnabled == FALSE) {
|
|
return;
|
|
}
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread());
|
|
|
|
//
|
|
// Iterate all resources and delete them. This will also delete
|
|
// all nodes associated with resources.
|
|
//
|
|
|
|
for (Index = 0; Index < VI_DEADLOCK_HASH_BINS; Index += 1) {
|
|
|
|
Current = ViDeadlockGlobals->ResourceDatabase[Index].Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->ResourceDatabase[Index])) {
|
|
|
|
|
|
Resource = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_RESOURCE,
|
|
HashChainList);
|
|
|
|
Current = Current->Flink;
|
|
|
|
ViDeadlockDeleteResource (Resource, TRUE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Iterate all threads and delete them.
|
|
//
|
|
|
|
for (Index = 0; Index < VI_DEADLOCK_HASH_BINS; Index += 1) {
|
|
Current = ViDeadlockGlobals->ThreadDatabase[Index].Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->ThreadDatabase[Index])) {
|
|
|
|
Thread = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_THREAD,
|
|
ListEntry);
|
|
|
|
Current = Current->Flink;
|
|
|
|
ViDeadlockDeleteThread (Thread, TRUE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Everything should be in the pool caches by now.
|
|
//
|
|
|
|
ASSERT (ViDeadlockGlobals->BytesAllocated == 0);
|
|
|
|
//
|
|
// Update counters and forget past failures.
|
|
//
|
|
|
|
ViDeadlockGlobals->AllocationFailures = 0;
|
|
ViDeadlockResets += 1;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockCanProceed (
|
|
IN PVOID Resource, OPTIONAL
|
|
IN PVOID CallAddress, OPTIONAL
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by deadlock verifier exports (initialize,
|
|
acquire, release) to figure out if deadlock verification should
|
|
proceed for the current operation. There are several reasons
|
|
why the return should be false. We failed to initialize the
|
|
deadlock verifier package or the caller is an amnestied driver
|
|
or the deadlock verification is temporarily disabled, etc.
|
|
|
|
Arguments:
|
|
|
|
Resource - address of the kernel resource operated upon
|
|
|
|
CallAddress - address of the caller for the operation
|
|
|
|
Return Value:
|
|
|
|
True if deadlock verification should proceed for the current
|
|
operation.
|
|
|
|
Environment:
|
|
|
|
Internal. Called by deadlock verifier exports.
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// From ntos\mm\mi.h - this lock is acquired with
|
|
// KeTryAcquireSpinLock which cannot be hooked for
|
|
// kernel code.
|
|
//
|
|
|
|
extern KSPIN_LOCK MmExpansionLock;
|
|
|
|
UNREFERENCED_PARAMETER (CallAddress);
|
|
|
|
//
|
|
// Skip if package not initialized
|
|
//
|
|
|
|
if (ViDeadlockGlobals == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Skip is package is disabled
|
|
//
|
|
|
|
if (! ViDeadlockDetectionEnabled) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Skip if operation happens above DPC level. This avoids a case
|
|
// where KeAcquireSpinlockRaiseToSynch is used to acquire a spinlock.
|
|
// During lock release when we need to acquire the deadlock verifier lock
|
|
// driver verifier will complain about lowering the IRQL. Since this is a
|
|
// very uncommon interface it is not worth for now to add the code to
|
|
// actually verify operations on this lock (MmProtectedPteLock). That would
|
|
// require first to add thunking code in driver verifier for raisetosynch
|
|
// interface.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() > DISPATCH_LEVEL) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if anybody switched the stack.
|
|
//
|
|
|
|
ViDeadlockCheckStackLimits ();
|
|
|
|
//
|
|
// If it is only as spinlock affair then skip.
|
|
//
|
|
|
|
if (Type != VfDeadlockUnknown) {
|
|
|
|
if (ViDeadlockVerifyOnlySpinlocks && Type != VfDeadlockSpinLock) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We do not check the deadlock verifier lock
|
|
//
|
|
|
|
if (Resource == &ViDeadlockDatabaseLock) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Skip kernel locks acquired with KeTryAcquireSpinLock
|
|
//
|
|
|
|
if (Resource == &MmExpansionLock) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Figure out if are in a recursive call into the deadlock verifier.
|
|
// This can happen if we try to allocate/free pool while we execute
|
|
// code in the deadlock verifier.
|
|
//
|
|
|
|
if (ViDeadlockDetectionIsLockedAlready ()) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Skip if we ever encountered an allocation failure
|
|
//
|
|
|
|
if (ViDeadlockGlobals->AllocationFailures > 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////// Deadlock detection logic
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockAnalyze(
|
|
IN PVOID ResourceAddress,
|
|
IN PVI_DEADLOCK_NODE AcquiredNode,
|
|
IN BOOLEAN FirstCall,
|
|
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 node representing the most recent resource acquisition
|
|
made by the thread trying to acquire `ResourceAddress'.
|
|
|
|
FirstCall - true if this is not a recursive call made from within the
|
|
function. It is used for doing one time per analysis only operations.
|
|
|
|
Degree - depth of recursion.
|
|
|
|
Return Value:
|
|
|
|
True if deadlock detected, false otherwise.
|
|
|
|
--*/
|
|
{
|
|
PVI_DEADLOCK_NODE CurrentNode;
|
|
PVI_DEADLOCK_RESOURCE CurrentResource;
|
|
PVI_DEADLOCK_NODE CurrentParent;
|
|
BOOLEAN FoundDeadlock;
|
|
PLIST_ENTRY Current;
|
|
|
|
ASSERT (AcquiredNode);
|
|
|
|
//
|
|
// Setup global counters.
|
|
//
|
|
|
|
if (FirstCall) {
|
|
|
|
ViDeadlockGlobals->NodesSearched = 0;
|
|
ViDeadlockGlobals->SequenceNumber += 1;
|
|
ViDeadlockGlobals->NumberOfParticipants = 0;
|
|
ViDeadlockGlobals->Instigator = NULL;
|
|
|
|
if (ViDeadlockGlobals->SequenceNumber == ((1 << 30) - 2)) {
|
|
ViDeadlockState.SequenceNumberOverflow = 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If our node is already stamped with the current sequence number
|
|
// then we have been here before in the current search. There is a very
|
|
// remote possibility that the node was not touched in the last
|
|
// 2^N calls to this function and the sequence number counter
|
|
// overwrapped but we can live with this.
|
|
//
|
|
|
|
if (AcquiredNode->SequenceNumber == ViDeadlockGlobals->SequenceNumber) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Update the counter of nodes touched in this search
|
|
//
|
|
|
|
ViDeadlockGlobals->NodesSearched += 1;
|
|
|
|
//
|
|
// Stamp node with current sequence number.
|
|
//
|
|
|
|
AcquiredNode->SequenceNumber = ViDeadlockGlobals->SequenceNumber;
|
|
|
|
//
|
|
// Stop recursion if it gets too deep.
|
|
//
|
|
|
|
if (Degree > ViDeadlockGlobals->RecursionDepthLimit) {
|
|
|
|
ViDeadlockGlobals->DepthLimitHits += 1;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Stop recursion if it gets too lengthy
|
|
//
|
|
|
|
if (ViDeadlockGlobals->NodesSearched >= ViDeadlockGlobals->SearchedNodesLimit) {
|
|
|
|
ViDeadlockGlobals->SearchLimitHits += 1;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if AcquiredNode's resource equals ResourceAddress.
|
|
// This is the final point for a deadlock detection because
|
|
// we managed to find a path in the graph that leads us to the
|
|
// same resource as the one to be acquired. From now on we
|
|
// will start returning from recursive calls and build the
|
|
// deadlock proof along the way.
|
|
//
|
|
|
|
ASSERT (AcquiredNode->Root);
|
|
|
|
if (ResourceAddress == AcquiredNode->Root->ResourceAddress) {
|
|
|
|
ASSERT (FALSE == FirstCall);
|
|
|
|
FoundDeadlock = TRUE;
|
|
|
|
ViDeadlockAddParticipant (AcquiredNode);
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Iterate all nodes in the graph using the same resource from AcquiredNode.
|
|
//
|
|
|
|
FoundDeadlock = FALSE;
|
|
|
|
CurrentResource = AcquiredNode->Root;
|
|
|
|
Current = CurrentResource->ResourceList.Flink;
|
|
|
|
while (Current != &(CurrentResource->ResourceList)) {
|
|
|
|
CurrentNode = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
ASSERT (CurrentNode->Root);
|
|
ASSERT (CurrentNode->Root == CurrentResource);
|
|
|
|
//
|
|
// Mark node as visited
|
|
//
|
|
|
|
CurrentNode->SequenceNumber = ViDeadlockGlobals->SequenceNumber;
|
|
|
|
//
|
|
// Check recursively the parent of the CurrentNode. This will check the
|
|
// whole parent chain eventually through recursive calls.
|
|
//
|
|
|
|
CurrentParent = CurrentNode->Parent;
|
|
|
|
if (CurrentParent != NULL) {
|
|
|
|
//
|
|
// If we are traversing the Parent chain of AcquiredNode we do not
|
|
// increment the recursion Degree because we know the chain will
|
|
// end. For calls to other similar nodes we have to protect against
|
|
// too much recursion (time consuming).
|
|
//
|
|
|
|
if (CurrentNode != AcquiredNode) {
|
|
|
|
//
|
|
// Recurse across the graph
|
|
//
|
|
|
|
FoundDeadlock = ViDeadlockAnalyze (ResourceAddress,
|
|
CurrentParent,
|
|
FALSE,
|
|
Degree + 1);
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Recurse down the graph
|
|
//
|
|
|
|
FoundDeadlock = ViDeadlockAnalyze (ResourceAddress,
|
|
CurrentParent,
|
|
FALSE,
|
|
Degree);
|
|
|
|
}
|
|
|
|
if (FoundDeadlock) {
|
|
|
|
ViDeadlockAddParticipant(CurrentNode);
|
|
|
|
if (CurrentNode != AcquiredNode) {
|
|
|
|
ViDeadlockAddParticipant(AcquiredNode);
|
|
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Current = Current->Flink;
|
|
}
|
|
|
|
|
|
Exit:
|
|
|
|
if (FoundDeadlock && FirstCall) {
|
|
|
|
//
|
|
// Make sure that the deadlock does not look like ABC - ACB.
|
|
// These sequences are protected by a common resource and therefore
|
|
// this is not a real deadlock.
|
|
//
|
|
|
|
if (ViDeadlockCertify ()) {
|
|
|
|
//
|
|
// Print deadlock information and save the address so the
|
|
// debugger knows who caused the deadlock.
|
|
//
|
|
|
|
ViDeadlockGlobals->Instigator = ResourceAddress;
|
|
|
|
DbgPrint("****************************************************************************\n");
|
|
DbgPrint("** **\n");
|
|
DbgPrint("** Deadlock detected! Type !deadlock in the debugger for more information **\n");
|
|
DbgPrint("** **\n");
|
|
DbgPrint("****************************************************************************\n");
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_DEADLOCK_DETECTED,
|
|
(ULONG_PTR)ResourceAddress,
|
|
(ULONG_PTR)AcquiredNode,
|
|
0);
|
|
|
|
//
|
|
// It is impossible to continue at this point.
|
|
//
|
|
|
|
return FALSE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we decided that this was not a deadlock after all, set the return value
|
|
// to not return a deadlock
|
|
//
|
|
|
|
FoundDeadlock = FALSE;
|
|
}
|
|
}
|
|
|
|
if (FirstCall) {
|
|
|
|
if (ViDeadlockGlobals->NodesSearched > ViDeadlockGlobals->MaxNodesSearched) {
|
|
|
|
ViDeadlockGlobals->MaxNodesSearched = ViDeadlockGlobals->NodesSearched;
|
|
}
|
|
}
|
|
|
|
return FoundDeadlock;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockCertify(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A potential deadlock has been detected. However our algorithm will generate
|
|
false positives in a certain case -- if two deadlocking nodes are ever taken
|
|
after the same node -- i.e. A->B->C A->C->B. While this can be considered
|
|
bad programming practice it is not really a deadlock and we should not
|
|
bugcheck.
|
|
|
|
Also we must check to make sure that there are no nodes at the top of the
|
|
deadlock chains that have only been acquired with try-acquire... this does
|
|
not cause a real deadlock.
|
|
|
|
The deadlock database lock should be held.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
True if this is really a deadlock, false to exonerate.
|
|
|
|
--*/
|
|
{
|
|
PVI_DEADLOCK_NODE innerNode,outerNode;
|
|
ULONG innerParticipant,outerParticipant;
|
|
ULONG numberOfParticipants;
|
|
|
|
ULONG currentParticipant;
|
|
|
|
numberOfParticipants = ViDeadlockGlobals->NumberOfParticipants;
|
|
|
|
//
|
|
// Note -- this isn't a particularly efficient way to do this. However,
|
|
// it is a particularly easy way to do it. This function should be called
|
|
// extremely rarely -- so IMO there isn't really a problem here.
|
|
//
|
|
|
|
//
|
|
// Outer loop
|
|
//
|
|
outerParticipant = numberOfParticipants;
|
|
while(outerParticipant > 1) {
|
|
outerParticipant--;
|
|
|
|
for (outerNode = ViDeadlockGlobals->Participant[outerParticipant]->Parent;
|
|
outerNode != NULL;
|
|
outerNode = outerNode->Parent ) {
|
|
|
|
//
|
|
// Inner loop
|
|
//
|
|
innerParticipant = outerParticipant-1;
|
|
while (innerParticipant) {
|
|
innerParticipant--;
|
|
|
|
for(innerNode = ViDeadlockGlobals->Participant[innerParticipant]->Parent;
|
|
innerNode != NULL;
|
|
innerNode = innerNode->Parent) {
|
|
|
|
if (innerNode->Root->ResourceAddress == outerNode->Root->ResourceAddress) {
|
|
//
|
|
// The twain shall meet -- this is not a deadlock
|
|
//
|
|
ViDeadlockGlobals->ABC_ACB_Skipped++;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
for (currentParticipant = 1; currentParticipant < numberOfParticipants; currentParticipant += 1) {
|
|
if (ViDeadlockGlobals->Participant[currentParticipant]->Root->ResourceAddress ==
|
|
ViDeadlockGlobals->Participant[currentParticipant-1]->Root->ResourceAddress) {
|
|
//
|
|
// This is the head of a chain...
|
|
//
|
|
if (ViDeadlockGlobals->Participant[currentParticipant-1]->OnlyTryAcquireUsed == TRUE) {
|
|
//
|
|
// Head of a chain used only try acquire. This can never cause a deadlock.
|
|
//
|
|
return FALSE;
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////// 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;
|
|
|
|
ListHead = ViDeadlockDatabaseHash (ViDeadlockGlobals->ResourceDatabase,
|
|
ResourceAddress);
|
|
|
|
if (IsListEmpty (ListHead)) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Trim resources from this hash list. It has nothing to do with searching
|
|
// but it is a good place to do this operation.
|
|
//
|
|
|
|
ViDeadlockTrimResources (ListHead);
|
|
|
|
//
|
|
// Now search the bucket for our resource.
|
|
//
|
|
|
|
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
|
|
VfDeadlockInitializeResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type,
|
|
IN PVOID Caller,
|
|
IN BOOLEAN DoNotAcquireLock
|
|
)
|
|
/*++
|
|
|
|
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.
|
|
|
|
Caller: address of the caller
|
|
|
|
DoNotAcquireLock: if true it means the call is done internally and the
|
|
deadlock verifier lock is already held.
|
|
|
|
Return Value:
|
|
|
|
True if we created and initialized a new RESOURCE structure.
|
|
|
|
--*/
|
|
{
|
|
PVOID ReservedResource;
|
|
BOOLEAN Result;
|
|
KIRQL OldIrql;
|
|
|
|
UNREFERENCED_PARAMETER (DoNotAcquireLock);
|
|
|
|
//
|
|
// If we are not initialized or package is not enabled
|
|
// we return immediately.
|
|
//
|
|
|
|
if (! ViDeadlockCanProceed(Resource, Caller, Type)) {
|
|
return FALSE;
|
|
}
|
|
|
|
ReservedResource = ViDeadlockAllocate (ViDeadlockResource);
|
|
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
|
|
Result = ViDeadlockAddResource (Resource,
|
|
Type,
|
|
Caller,
|
|
ReservedResource);
|
|
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
return Result;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockAddResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type,
|
|
IN PVOID Caller,
|
|
IN PVOID ReservedResource
|
|
)
|
|
/*++
|
|
|
|
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.
|
|
|
|
Caller: address of the caller
|
|
|
|
ReservedResource: block of memory to be used by the new resource.
|
|
|
|
Return Value:
|
|
|
|
True if we created and initialized a new RESOURCE structure.
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY HashBin;
|
|
PVI_DEADLOCK_RESOURCE ResourceRoot;
|
|
PKTHREAD Thread;
|
|
ULONG HashValue;
|
|
ULONG DeadlockFlags;
|
|
BOOLEAN ReturnValue = FALSE;
|
|
|
|
//
|
|
// Check if this resource was initialized before.
|
|
// This would be a bug in most of the cases.
|
|
//
|
|
|
|
ResourceRoot = ViDeadlockSearchResource (Resource);
|
|
|
|
if (ResourceRoot) {
|
|
|
|
DeadlockFlags = ViDeadlockResourceTypeInfo[Type];
|
|
|
|
//
|
|
// Check if we are reinitializing a good resource. This is a valid
|
|
// operation (although weird) only for spinlocks. Some drivers do that.
|
|
//
|
|
// silviuc: should we enforce here for the resource to be released first?
|
|
//
|
|
|
|
if(! (DeadlockFlags & VI_DEADLOCK_FLAG_REINITIALIZE_OK)) {
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_MULTIPLE_INITIALIZATION,
|
|
(ULONG_PTR)Resource,
|
|
(ULONG_PTR)ResourceRoot,
|
|
0);
|
|
}
|
|
|
|
//
|
|
// Well, the resource has just been reinitialized. We will live with
|
|
// that. We will break though if we reinitialize a resource that is
|
|
// acquired. In principle this state might be bogus if we missed
|
|
// a release() operation.
|
|
//
|
|
|
|
if (ResourceRoot->ThreadOwner != NULL) {
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_MULTIPLE_INITIALIZATION,
|
|
(ULONG_PTR)Resource,
|
|
(ULONG_PTR)ResourceRoot,
|
|
1);
|
|
}
|
|
|
|
ReturnValue = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// At this point we know for sure the resource is not represented in the
|
|
// deadlock verifier database.
|
|
//
|
|
|
|
ASSERT (ViDeadlockSearchResource (Resource) == NULL);
|
|
|
|
Thread = KeGetCurrentThread();
|
|
|
|
//
|
|
// Check to see if the resource is on the stack.
|
|
// If it is we will not verify it.
|
|
//
|
|
// SilviuC: what about the DPC stack ? We will ignore this issue for now.
|
|
//
|
|
|
|
if ((ULONG_PTR) Resource < (ULONG_PTR) Thread->InitialStack &&
|
|
(ULONG_PTR) Resource > (ULONG_PTR) Thread->StackLimit ) {
|
|
|
|
ReturnValue = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Use reserved memory for the new resource.
|
|
// Set ReservedResource to null to signal that memory has
|
|
// been used. This will prevent freeing it at the end.
|
|
//
|
|
|
|
ResourceRoot = ReservedResource;
|
|
ReservedResource = NULL;
|
|
|
|
if (ResourceRoot == NULL) {
|
|
|
|
ReturnValue = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Fill information about resource.
|
|
//
|
|
|
|
RtlZeroMemory (ResourceRoot, sizeof(VI_DEADLOCK_RESOURCE));
|
|
|
|
ResourceRoot->Type = Type;
|
|
ResourceRoot->ResourceAddress = Resource;
|
|
|
|
InitializeListHead (&ResourceRoot->ResourceList);
|
|
|
|
//
|
|
// Capture the stack trace of the guy that creates the resource first.
|
|
// This should happen when resource gets initialized or during the first
|
|
// acquire.
|
|
//
|
|
|
|
RtlCaptureStackBackTrace (2,
|
|
VI_MAX_STACK_DEPTH,
|
|
ResourceRoot->StackTrace,
|
|
&HashValue);
|
|
|
|
ResourceRoot->StackTrace[0] = Caller;
|
|
|
|
//
|
|
// Figure out which hash bin this resource corresponds to.
|
|
//
|
|
|
|
HashBin = ViDeadlockDatabaseHash (ViDeadlockGlobals->ResourceDatabase, Resource);
|
|
|
|
//
|
|
// Now add to the corrsponding hash bin
|
|
//
|
|
|
|
InsertHeadList(HashBin, &ResourceRoot->HashChainList);
|
|
|
|
ReturnValue = TRUE;
|
|
|
|
Exit:
|
|
|
|
if (ReservedResource) {
|
|
ViDeadlockFree (ReservedResource, ViDeadlockResource);
|
|
}
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViDeadlockSimilarNode (
|
|
IN PVOID Resource,
|
|
IN BOOLEAN TryNode,
|
|
IN PVI_DEADLOCK_NODE Node
|
|
)
|
|
/*++
|
|
|
|
Routine description:
|
|
|
|
This routine determines if an acquisition with the (resource, try)
|
|
characteristics is already represented in the Node parameter.
|
|
|
|
We used to match nodes based on (resource, thread, stack trace, try)
|
|
4-tuplet but this really causes an explosion in the number of nodes.
|
|
Such a method would yield more accurate proofs but does not affect
|
|
the correctness of the deadlock detection algorithms.
|
|
|
|
Return value:
|
|
|
|
True if similar node.
|
|
|
|
--*/
|
|
{
|
|
ASSERT (Node);
|
|
ASSERT (Node->Root);
|
|
|
|
if (Resource == Node->Root->ResourceAddress
|
|
&& TryNode == Node->OnlyTryAcquireUsed) {
|
|
|
|
//
|
|
// Second condition is important to keep nodes for TryAcquire operations
|
|
// separated from normal acquires. A TryAcquire cannot cause a deadlock
|
|
// and therefore we have to be careful not to report bogus deadlocks.
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
else {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
VfDeadlockAcquireResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type,
|
|
IN PKTHREAD Thread,
|
|
IN BOOLEAN TryAcquire,
|
|
IN PVOID Caller
|
|
)
|
|
/*++
|
|
|
|
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.
|
|
|
|
Thread: thread attempting to acquire the resource
|
|
|
|
TryAcquire: true if this is a tryacquire() operation
|
|
|
|
Caller: address of the caller
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PVI_DEADLOCK_THREAD ThreadEntry;
|
|
KIRQL OldIrql = 0;
|
|
PVI_DEADLOCK_NODE CurrentNode;
|
|
PVI_DEADLOCK_NODE NewNode;
|
|
PVI_DEADLOCK_RESOURCE ResourceRoot;
|
|
PLIST_ENTRY Current;
|
|
ULONG HashValue;
|
|
ULONG DeadlockFlags;
|
|
BOOLEAN CreatingRootNode = FALSE;
|
|
BOOLEAN ThreadCreated = FALSE;
|
|
LARGE_INTEGER StartTime;
|
|
LARGE_INTEGER EndTime;
|
|
BOOLEAN AddResult;
|
|
PVOID ReservedThread;
|
|
PVOID ReservedNode;
|
|
PVOID ReservedResource;
|
|
PVI_DEADLOCK_NODE ThreadCurrentNode;
|
|
|
|
CurrentNode = NULL;
|
|
ThreadEntry = NULL;
|
|
ThreadCurrentNode = NULL;
|
|
|
|
//
|
|
// If we are not initialized or package is not enabled
|
|
// we return immediately.
|
|
//
|
|
|
|
if (! ViDeadlockCanProceed(Resource, Caller, Type)) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Skip if the current thread is inside paging code paths.
|
|
//
|
|
|
|
if (ViIsThreadInsidePagingCodePaths ()) {
|
|
return;
|
|
}
|
|
|
|
CurrentThread = Thread;
|
|
|
|
DeadlockFlags = ViDeadlockResourceTypeInfo[Type];
|
|
|
|
//
|
|
// Before getting into the real stuff trim the pool cache.
|
|
// This needs to happen out of any locks.
|
|
//
|
|
|
|
ViDeadlockTrimPoolCache ();
|
|
|
|
//
|
|
// Reserve resources that might be needed. If upon exit these
|
|
// variables are null it means the allocation either failed or was used.
|
|
// In both cases we do not need to free anything.
|
|
//
|
|
|
|
ReservedThread = ViDeadlockAllocate (ViDeadlockThread);
|
|
ReservedNode = ViDeadlockAllocate (ViDeadlockNode);
|
|
ReservedResource = ViDeadlockAllocate (ViDeadlockResource);
|
|
|
|
//
|
|
// Lock the deadlock database.
|
|
//
|
|
|
|
ViDeadlockDetectionLock( &OldIrql );
|
|
|
|
KeQueryTickCount (&StartTime);
|
|
|
|
//
|
|
// Allocate a node that might be needed. If we will not use it
|
|
// we will deallocate it at the end. If we fail to allocate
|
|
// we will return immediately.
|
|
//
|
|
|
|
NewNode = ReservedNode;
|
|
ReservedNode = NULL;
|
|
|
|
if (NewNode == NULL) {
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Find the thread descriptor. If there is none we will create one.
|
|
//
|
|
|
|
ThreadEntry = ViDeadlockSearchThread (CurrentThread);
|
|
|
|
if (ThreadEntry == NULL) {
|
|
|
|
ThreadEntry = ViDeadlockAddThread (CurrentThread, ReservedThread);
|
|
ReservedThread = NULL;
|
|
|
|
if (ThreadEntry == NULL) {
|
|
|
|
//
|
|
// If we cannot allocate a new thread entry then
|
|
// no deadlock detection will happen.
|
|
//
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
ThreadCreated = TRUE;
|
|
}
|
|
|
|
#if DBG
|
|
if (Type == VfDeadlockSpinLock) {
|
|
|
|
if (ThreadEntry->CurrentSpinNode != NULL) {
|
|
|
|
ASSERT(ThreadEntry->CurrentSpinNode->Root->ThreadOwner == ThreadEntry);
|
|
ASSERT(ThreadEntry->CurrentSpinNode->ThreadEntry == ThreadEntry);
|
|
ASSERT(ThreadEntry->NodeCount != 0);
|
|
ASSERT(ThreadEntry->CurrentSpinNode->Active != 0);
|
|
ASSERT(ThreadEntry->CurrentSpinNode->Root->NodeCount != 0);
|
|
|
|
}
|
|
}
|
|
else {
|
|
|
|
if (ThreadEntry->CurrentOtherNode != NULL) {
|
|
|
|
ASSERT(ThreadEntry->CurrentOtherNode->Root->ThreadOwner == ThreadEntry);
|
|
ASSERT(ThreadEntry->CurrentOtherNode->ThreadEntry == ThreadEntry);
|
|
ASSERT(ThreadEntry->NodeCount != 0);
|
|
ASSERT(ThreadEntry->CurrentOtherNode->Active != 0);
|
|
ASSERT(ThreadEntry->CurrentOtherNode->Root->NodeCount != 0);
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Find the resource descriptor. If we do not find a descriptor
|
|
// we will create one on the fly.
|
|
//
|
|
|
|
ResourceRoot = ViDeadlockSearchResource (Resource);
|
|
|
|
if (ResourceRoot == NULL) {
|
|
|
|
//
|
|
// Could not find the resource descriptor therefore we need to create one.
|
|
// Note that we will not complain about the resource not being initialized
|
|
// before because there are legitimate reasons for this to happen. For
|
|
// example the resource was initialized in an unverified driver and then
|
|
// passed to a verified driver that caled acquire().
|
|
//
|
|
|
|
if (ViDeadlockVeryStrict) {
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_UNINITIALIZED_RESOURCE,
|
|
(ULONG_PTR) Resource,
|
|
(ULONG_PTR) NULL,
|
|
(ULONG_PTR) NULL);
|
|
}
|
|
|
|
AddResult = ViDeadlockAddResource (Resource,
|
|
Type,
|
|
Caller,
|
|
ReservedResource);
|
|
|
|
ReservedResource = NULL;
|
|
|
|
if (AddResult == FALSE) {
|
|
|
|
//
|
|
// If we failed to add the resource then no deadlock detection.
|
|
//
|
|
|
|
if (ThreadCreated) {
|
|
ViDeadlockDeleteThread (ThreadEntry, FALSE);
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Search again the resource. This time we should find it.
|
|
//
|
|
|
|
ResourceRoot = ViDeadlockSearchResource (Resource);
|
|
}
|
|
|
|
//
|
|
// 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 unless it is a recursive
|
|
// acquisition.
|
|
//
|
|
|
|
ASSERT (ResourceRoot);
|
|
ASSERT (ThreadEntry);
|
|
|
|
if (Type == VfDeadlockSpinLock) {
|
|
ThreadCurrentNode = ThreadEntry->CurrentSpinNode;
|
|
}
|
|
else {
|
|
ThreadCurrentNode = ThreadEntry->CurrentOtherNode;
|
|
}
|
|
|
|
//
|
|
// Since we just acquired the resource the valid value for ThreadOwner is
|
|
// null or ThreadEntry (for a recursive acquisition). This might not be
|
|
// true if we missed a release() from an unverified driver. So we will
|
|
// not complain about it. We will just put the resource in a consistent
|
|
// state and continue;
|
|
//
|
|
|
|
if (ResourceRoot->ThreadOwner) {
|
|
if (ResourceRoot->ThreadOwner != ThreadEntry) {
|
|
ResourceRoot->RecursionCount = 0;
|
|
}
|
|
else {
|
|
ASSERT (ResourceRoot->RecursionCount > 0);
|
|
}
|
|
}
|
|
else {
|
|
ASSERT (ResourceRoot->RecursionCount == 0);
|
|
}
|
|
|
|
ResourceRoot->ThreadOwner = ThreadEntry;
|
|
ResourceRoot->RecursionCount += 1;
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
if (ThreadCurrentNode != NULL) {
|
|
|
|
//
|
|
// If we get here, the current thread had already acquired resources.
|
|
// Check to see if this resource has already been acquired.
|
|
//
|
|
|
|
if (ResourceRoot->RecursionCount > 1) {
|
|
|
|
//
|
|
// Recursive acquisition is OK for some resources...
|
|
//
|
|
|
|
if ((DeadlockFlags & VI_DEADLOCK_FLAG_RECURSIVE_ACQUISITION_OK) != 0) {
|
|
|
|
//
|
|
// Recursion can't cause a deadlock. Don't set CurrentNode
|
|
// since we don't want to move any pointers.
|
|
//
|
|
|
|
goto Exit;
|
|
|
|
} else {
|
|
|
|
//
|
|
// This is a recursive acquire for a resource type that is not allowed
|
|
// to acquire recursively. Note on continuing from here: we have a recursion
|
|
// count of two which will come in handy when the resources are released.
|
|
//
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_SELF_DEADLOCK,
|
|
(ULONG_PTR)Resource,
|
|
(ULONG_PTR)ResourceRoot,
|
|
(ULONG_PTR)ThreadEntry);
|
|
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If link already exists, update pointers and exit.
|
|
// otherwise check for deadlocks and create a new node
|
|
//
|
|
|
|
Current = ThreadCurrentNode->ChildrenList.Flink;
|
|
|
|
while (Current != &(ThreadCurrentNode->ChildrenList)) {
|
|
|
|
CurrentNode = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
SiblingsList);
|
|
|
|
Current = Current->Flink;
|
|
|
|
if (ViDeadlockSimilarNode (Resource, TryAcquire, 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 in the first place. We can just update
|
|
// the pointers to reflect the new resource acquired and exit.
|
|
//
|
|
// We apply our graph compression function to minimize duplicates.
|
|
//
|
|
|
|
ViDeadlockCheckDuplicatesAmongChildren (ThreadCurrentNode,
|
|
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. Later in the
|
|
// function CurrentNode being null will signify that we need to create
|
|
// a new node.
|
|
//
|
|
|
|
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. Try acquire can not be the cause of a deadlock.
|
|
// Don't analyze on try acquires.
|
|
//
|
|
|
|
if (ResourceRoot->NodeCount > 0 && TryAcquire == FALSE) {
|
|
|
|
if (ViDeadlockAnalyze (Resource, ThreadCurrentNode, TRUE, 0)) {
|
|
|
|
//
|
|
// If we are here we detected deadlock. The analyze() function
|
|
// does all the reporting. Being here means we hit `g' in the
|
|
// debugger. We will just exit and do not add this resource
|
|
// to the graph.
|
|
//
|
|
|
|
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.
|
|
// Note that all this is bookkeeping but we cannot encounter a deadlock
|
|
// from now on.
|
|
//
|
|
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node = NULL;
|
|
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, TryAcquire, Node)) {
|
|
|
|
//
|
|
// We apply our graph compression function to minimize duplicates.
|
|
//
|
|
|
|
ViDeadlockCheckDuplicatesAmongRoots (Node);
|
|
|
|
FoundNode = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FoundNode) {
|
|
|
|
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.
|
|
//
|
|
|
|
if (NewNode != NULL) {
|
|
|
|
CurrentNode = NewNode;
|
|
|
|
//
|
|
// Set newnode to NULL to signify it has been used -- otherwise it
|
|
// will get freed at the end of this function.
|
|
//
|
|
|
|
NewNode = NULL;
|
|
|
|
//
|
|
// Initialize the new resource node
|
|
//
|
|
|
|
RtlZeroMemory (CurrentNode, sizeof *CurrentNode);
|
|
|
|
CurrentNode->Active = 0;
|
|
CurrentNode->Parent = ThreadCurrentNode;
|
|
CurrentNode->Root = ResourceRoot;
|
|
|
|
InitializeListHead (&(CurrentNode->ChildrenList));
|
|
|
|
//
|
|
// Mark the TryAcquire type of the node.
|
|
//
|
|
|
|
CurrentNode->OnlyTryAcquireUsed = TryAcquire;
|
|
|
|
//
|
|
// Add to the children list of the parent.
|
|
//
|
|
|
|
if (! CreatingRootNode) {
|
|
|
|
InsertHeadList(&(ThreadCurrentNode->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;
|
|
|
|
if (ResourceRoot->NodeCount > 0xFFF0) {
|
|
ViDeadlockState.ResourceNodeCountOverflow = 1;
|
|
}
|
|
|
|
//
|
|
// Add to the graph statistics.
|
|
//
|
|
#if DBG
|
|
{
|
|
ULONG Level;
|
|
|
|
Level = ViDeadlockNodeLevel (CurrentNode);
|
|
|
|
if (Level < 8) {
|
|
ViDeadlockGlobals->GraphNodes[Level] += 1;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Exit point.
|
|
//
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Add information we use to identify the culprit should
|
|
// a deadlock occur
|
|
//
|
|
|
|
if (CurrentNode) {
|
|
|
|
ASSERT (ThreadEntry);
|
|
ASSERT (ThreadCurrentNode == CurrentNode->Parent);
|
|
|
|
CurrentNode->Active = 1;
|
|
|
|
//
|
|
// The node should have thread entry field null either because
|
|
// it was newly created or because the node was released in the
|
|
// past and therefore the field was zeroed.
|
|
//
|
|
// silviuc: true? What about if we miss release() operations.
|
|
//
|
|
|
|
ASSERT (CurrentNode->ThreadEntry == NULL);
|
|
|
|
CurrentNode->ThreadEntry = ThreadEntry;
|
|
|
|
if (Type == VfDeadlockSpinLock) {
|
|
ThreadEntry->CurrentSpinNode = CurrentNode;
|
|
}
|
|
else {
|
|
ThreadEntry->CurrentOtherNode = CurrentNode;
|
|
}
|
|
|
|
ThreadEntry->NodeCount += 1;
|
|
|
|
#if DBG
|
|
if (ThreadEntry->NodeCount <= 8) {
|
|
ViDeadlockGlobals->NodeLevelCounter[ThreadEntry->NodeCount - 1] += 1;
|
|
}
|
|
else {
|
|
ViDeadlockGlobals->NodeLevelCounter[7] += 1;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// If we have a parent, save the parent's stack trace
|
|
//
|
|
|
|
if (CurrentNode->Parent) {
|
|
|
|
RtlCopyMemory(CurrentNode->ParentStackTrace,
|
|
CurrentNode->Parent->StackTrace,
|
|
sizeof (CurrentNode->ParentStackTrace));
|
|
}
|
|
|
|
//
|
|
// Capture stack trace for the current acquire.
|
|
//
|
|
|
|
RtlCaptureStackBackTrace (2,
|
|
VI_MAX_STACK_DEPTH,
|
|
CurrentNode->StackTrace,
|
|
&HashValue);
|
|
|
|
if (CurrentNode->Parent) {
|
|
CurrentNode->ParentStackTrace[0] = CurrentNode->Parent->StackTrace[0];
|
|
}
|
|
|
|
CurrentNode->StackTrace[0] = Caller;
|
|
|
|
//
|
|
// Copy the trace for the last acquire in the resource object.
|
|
//
|
|
|
|
RtlCopyMemory (CurrentNode->Root->LastAcquireTrace,
|
|
CurrentNode->StackTrace,
|
|
sizeof (CurrentNode->Root->LastAcquireTrace));
|
|
}
|
|
|
|
//
|
|
// We allocated space for a new node but it didn't get used -- put it back
|
|
// in the list (don't worry this doesn't do a real 'free' it just puts it
|
|
// in a free list).
|
|
//
|
|
|
|
if (NewNode != NULL) {
|
|
|
|
ViDeadlockFree (NewNode, ViDeadlockNode);
|
|
}
|
|
|
|
//
|
|
// Release deadlock database and return.
|
|
//
|
|
|
|
KeQueryTickCount (&EndTime);
|
|
|
|
if (EndTime.QuadPart - StartTime.QuadPart > ViDeadlockGlobals->TimeAcquire) {
|
|
ViDeadlockGlobals->TimeAcquire = EndTime.QuadPart - StartTime.QuadPart;
|
|
}
|
|
|
|
//
|
|
// Free up unused reserved resources
|
|
//
|
|
|
|
if (ReservedResource) {
|
|
ViDeadlockFree (ReservedResource, ViDeadlockResource);
|
|
}
|
|
|
|
if (ReservedNode) {
|
|
ViDeadlockFree (ReservedNode, ViDeadlockNode);
|
|
}
|
|
|
|
if (ReservedThread) {
|
|
ViDeadlockFree (ReservedThread, ViDeadlockThread);
|
|
}
|
|
|
|
ViDeadlockDetectionUnlock( OldIrql );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
VfDeadlockReleaseResource(
|
|
IN PVOID Resource,
|
|
IN VI_DEADLOCK_RESOURCE_TYPE Type,
|
|
IN PKTHREAD Thread,
|
|
IN PVOID Caller
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine does the maintenance necessary to release resources from our
|
|
deadlock detection database.
|
|
|
|
Arguments:
|
|
|
|
Resource: Address of the resource in question.
|
|
|
|
Thread: thread releasing the resource. In most of the cases this is the
|
|
current thread but it might be different for resources that can be
|
|
acquired in one thread and released in another one.
|
|
|
|
Caller: address of the caller of release()
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
--*/
|
|
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PVI_DEADLOCK_THREAD ThreadEntry;
|
|
KIRQL OldIrql = 0;
|
|
PVI_DEADLOCK_RESOURCE ResourceRoot;
|
|
PVI_DEADLOCK_NODE ReleasedNode;
|
|
LARGE_INTEGER StartTime;
|
|
LARGE_INTEGER EndTime;
|
|
ULONG HashValue;
|
|
PVI_DEADLOCK_NODE ThreadCurrentNode;
|
|
|
|
UNREFERENCED_PARAMETER (Caller);
|
|
|
|
//
|
|
// If we aren't initialized or package is not enabled
|
|
// we return immediately.
|
|
//
|
|
|
|
if (! ViDeadlockCanProceed(Resource, Caller, Type)) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Skip if the current thread is inside paging code paths.
|
|
//
|
|
|
|
if (ViIsThreadInsidePagingCodePaths ()) {
|
|
return;
|
|
}
|
|
|
|
ReleasedNode = NULL;
|
|
CurrentThread = Thread;
|
|
ThreadEntry = NULL;
|
|
|
|
ViDeadlockDetectionLock( &OldIrql );
|
|
|
|
KeQueryTickCount (&StartTime);
|
|
|
|
ResourceRoot = ViDeadlockSearchResource (Resource);
|
|
|
|
if (ResourceRoot == NULL) {
|
|
|
|
//
|
|
// Release called with a resource address that was never
|
|
// stored in our resource database. This can happen in
|
|
// the following circumstances:
|
|
//
|
|
// (a) resource is released but we never seen it before
|
|
// because it was acquired in an unverified driver.
|
|
//
|
|
// (b) we have encountered allocation failures that prevented
|
|
// us from completing an acquire() or initialize().
|
|
//
|
|
// All are legitimate cases and therefore we just ignore the
|
|
// release operation.
|
|
//
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Check if we are trying to release a resource that was never
|
|
// acquired.
|
|
//
|
|
|
|
if (ResourceRoot->RecursionCount == 0) {
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_UNACQUIRED_RESOURCE,
|
|
(ULONG_PTR)Resource,
|
|
(ULONG_PTR)ResourceRoot,
|
|
(ULONG_PTR)ViDeadlockSearchThread(CurrentThread));
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
ASSERT (ResourceRoot->RecursionCount > 0);
|
|
ASSERT (ResourceRoot->ThreadOwner);
|
|
|
|
ThreadEntry = ResourceRoot->ThreadOwner;
|
|
|
|
if (ThreadEntry->Thread != CurrentThread) {
|
|
|
|
//
|
|
// Someone acquired a resource that is released in another thread.
|
|
// This is bad design but we have to live with it.
|
|
//
|
|
// NB. If this occurrs, we may call a non-deadlock a deadlock.
|
|
// For example, we see a simple deadlock -- AB BA
|
|
// If another thread releases B, there won't actually
|
|
// be a deadlock. Kind of annoying and ugly.
|
|
//
|
|
|
|
#if DBG
|
|
DbgPrint("Thread %p acquired resource %p but thread %p released it\n",
|
|
ThreadEntry->Thread, Resource, CurrentThread );
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_UNEXPECTED_THREAD,
|
|
(ULONG_PTR)Resource,
|
|
(ULONG_PTR)ThreadEntry->Thread,
|
|
(ULONG_PTR)CurrentThread
|
|
);
|
|
#endif
|
|
|
|
//
|
|
// 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 = ThreadEntry->Thread;
|
|
}
|
|
|
|
//
|
|
// In this moment we have a resource (ResourceRoot) and a
|
|
// thread (ThreadEntry) to play with.
|
|
//
|
|
|
|
ASSERT (ResourceRoot && ThreadEntry);
|
|
|
|
if (ResourceRoot->Type == VfDeadlockSpinLock) {
|
|
ThreadCurrentNode = ThreadEntry->CurrentSpinNode;
|
|
}
|
|
else {
|
|
ThreadCurrentNode = ThreadEntry->CurrentOtherNode;
|
|
}
|
|
|
|
ASSERT (ThreadCurrentNode);
|
|
ASSERT (ThreadCurrentNode->Root);
|
|
ASSERT (ThreadEntry->NodeCount > 0);
|
|
|
|
ResourceRoot->RecursionCount -= 1;
|
|
|
|
if (ResourceRoot->RecursionCount > 0) {
|
|
|
|
//
|
|
// Just decrement the recursion count and do not change any state
|
|
//
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Wipe out the resource owner.
|
|
//
|
|
|
|
ResourceRoot->ThreadOwner = NULL;
|
|
|
|
#if DBG
|
|
ViDeadlockGlobals->TotalReleases += 1;
|
|
#endif
|
|
|
|
//
|
|
// Check for out of order releases
|
|
//
|
|
|
|
if (ThreadCurrentNode->Root != ResourceRoot) {
|
|
|
|
#if DBG
|
|
ViDeadlockGlobals->OutOfOrderReleases += 1;
|
|
#endif
|
|
|
|
//
|
|
// Getting here means that somebody acquires a then b then tries
|
|
// to release a before b. This is bad for certain kinds of resources,
|
|
// and for others we have to look the other way.
|
|
//
|
|
|
|
if ((ViDeadlockResourceTypeInfo[ThreadCurrentNode->Root->Type] &
|
|
VI_DEADLOCK_FLAG_REVERSE_RELEASE_OK) == 0) {
|
|
|
|
DbgPrint("Deadlock detection: Must release resources in reverse-order\n");
|
|
DbgPrint("Resource %p acquired before resource %p -- \n"
|
|
"Current thread (%p) is trying to release it first\n",
|
|
Resource,
|
|
ThreadCurrentNode->Root->ResourceAddress,
|
|
ThreadEntry);
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_UNEXPECTED_RELEASE,
|
|
(ULONG_PTR)Resource,
|
|
(ULONG_PTR)ThreadCurrentNode->Root->ResourceAddress,
|
|
(ULONG_PTR)ThreadEntry);
|
|
}
|
|
|
|
//
|
|
// We need to mark the node for the out of order released resource as
|
|
// not active so that other threads will be able to acquire it.
|
|
//
|
|
|
|
{
|
|
PVI_DEADLOCK_NODE Current;
|
|
|
|
ASSERT (ThreadCurrentNode->Active == 1);
|
|
ASSERT (ThreadCurrentNode->ThreadEntry == ThreadEntry);
|
|
|
|
Current = ThreadCurrentNode;
|
|
|
|
while (Current != NULL) {
|
|
|
|
if (Current->Root == ResourceRoot) {
|
|
|
|
ASSERT (Current->Active == 1);
|
|
ASSERT (Current->Root->RecursionCount == 0);
|
|
ASSERT (Current->ThreadEntry == ThreadEntry);
|
|
|
|
Current->Active = 0;
|
|
ReleasedNode = Current;
|
|
|
|
break;
|
|
}
|
|
|
|
Current = Current->Parent;
|
|
}
|
|
|
|
if (Current == NULL) {
|
|
|
|
//
|
|
// If we do not manage to find an active node we must be in an
|
|
// weird state. The resource must be here or else we would have
|
|
// gotten an `unxpected release' bugcheck.
|
|
//
|
|
|
|
ASSERT (0);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// We need to release the top node held by the thread.
|
|
//
|
|
|
|
ASSERT (ThreadCurrentNode->Active);
|
|
|
|
ReleasedNode = ThreadCurrentNode;
|
|
ReleasedNode->Active = 0;
|
|
}
|
|
|
|
//
|
|
// Put the `CurrentNode' field of the thread in a consistent state.
|
|
// It should point to the most recent active node that it owns.
|
|
//
|
|
|
|
if (ResourceRoot->Type == VfDeadlockSpinLock) {
|
|
|
|
while (ThreadEntry->CurrentSpinNode) {
|
|
|
|
if (ThreadEntry->CurrentSpinNode->Active == 1) {
|
|
if (ThreadEntry->CurrentSpinNode->ThreadEntry == ThreadEntry) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ThreadEntry->CurrentSpinNode = ThreadEntry->CurrentSpinNode->Parent;
|
|
}
|
|
}
|
|
else {
|
|
|
|
while (ThreadEntry->CurrentOtherNode) {
|
|
|
|
if (ThreadEntry->CurrentOtherNode->Active == 1) {
|
|
if (ThreadEntry->CurrentOtherNode->ThreadEntry == ThreadEntry) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ThreadEntry->CurrentOtherNode = ThreadEntry->CurrentOtherNode->Parent;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
//
|
|
// Properly release the node if there is one to be released.
|
|
//
|
|
|
|
if (ReleasedNode) {
|
|
|
|
ASSERT (ReleasedNode->Active == 0);
|
|
ASSERT (ReleasedNode->Root->ThreadOwner == 0);
|
|
ASSERT (ReleasedNode->Root->RecursionCount == 0);
|
|
ASSERT (ReleasedNode->ThreadEntry == ThreadEntry);
|
|
ASSERT (ThreadEntry->NodeCount > 0);
|
|
|
|
if (ResourceRoot->Type == VfDeadlockSpinLock) {
|
|
ASSERT (ThreadEntry->CurrentSpinNode != ReleasedNode);
|
|
}
|
|
else {
|
|
ASSERT (ThreadEntry->CurrentOtherNode != ReleasedNode);
|
|
}
|
|
|
|
ReleasedNode->ThreadEntry = NULL;
|
|
ThreadEntry->NodeCount -= 1;
|
|
|
|
#if DBG
|
|
|
|
ViDeadlockCheckNodeConsistency (ReleasedNode, FALSE);
|
|
ViDeadlockCheckResourceConsistency (ReleasedNode->Root, FALSE);
|
|
ViDeadlockCheckThreadConsistency (ThreadEntry, FALSE);
|
|
#endif
|
|
|
|
if (ThreadEntry && ThreadEntry->NodeCount == 0) {
|
|
ViDeadlockDeleteThread (ThreadEntry, FALSE);
|
|
}
|
|
|
|
//
|
|
// If this is a root node with no children, delete the node
|
|
// too. This is important to keep memory low. A single node
|
|
// can never be the cause of a deadlock.
|
|
//
|
|
|
|
if (ReleasedNode->Parent == NULL && IsListEmpty(&(ReleasedNode->ChildrenList))) {
|
|
ViDeadlockDeleteNode (ReleasedNode, FALSE);
|
|
#if DBG
|
|
ViDeadlockGlobals->RootNodesDeleted += 1;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//
|
|
// Capture the trace for the last release in the resource object.
|
|
//
|
|
|
|
if (ResourceRoot) {
|
|
|
|
RtlCaptureStackBackTrace (2,
|
|
VI_MAX_STACK_DEPTH,
|
|
ResourceRoot->LastReleaseTrace,
|
|
&HashValue);
|
|
}
|
|
|
|
KeQueryTickCount (&EndTime);
|
|
|
|
if (EndTime.QuadPart - StartTime.QuadPart > ViDeadlockGlobals->TimeRelease) {
|
|
ViDeadlockGlobals->TimeRelease = EndTime.QuadPart - StartTime.QuadPart;
|
|
}
|
|
|
|
ViDeadlockDetectionUnlock (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;
|
|
PLIST_ENTRY ListHead;
|
|
PVI_DEADLOCK_THREAD ThreadInfo;
|
|
|
|
ThreadInfo = NULL;
|
|
|
|
ListHead = ViDeadlockDatabaseHash (ViDeadlockGlobals->ThreadDatabase, Thread);
|
|
|
|
if (IsListEmpty(ListHead)) {
|
|
return NULL;
|
|
}
|
|
|
|
Current = ListHead->Flink;
|
|
|
|
while (Current != ListHead) {
|
|
|
|
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,
|
|
PVOID ReservedThread
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine adds a new thread to the thread database.
|
|
|
|
The function assumes the deadlock database lock is held.
|
|
|
|
Arguments:
|
|
|
|
Thread - thread address
|
|
|
|
Return Value:
|
|
|
|
Address of the VI_DEADLOCK_THREAD structure just added.
|
|
Null if allocation failed.
|
|
--*/
|
|
{
|
|
PVI_DEADLOCK_THREAD ThreadInfo;
|
|
PLIST_ENTRY HashBin;
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread());
|
|
|
|
//
|
|
// Use reserved block for the new thread. Set ReservedThread
|
|
// to null to signal that block was used.
|
|
//
|
|
|
|
ThreadInfo = ReservedThread;
|
|
ReservedThread = NULL;
|
|
|
|
if (ThreadInfo == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
RtlZeroMemory (ThreadInfo, sizeof *ThreadInfo);
|
|
|
|
ThreadInfo->Thread = Thread;
|
|
|
|
HashBin = ViDeadlockDatabaseHash (ViDeadlockGlobals->ThreadDatabase, Thread);
|
|
|
|
InsertHeadList(HashBin, &ThreadInfo->ListEntry);
|
|
|
|
return ThreadInfo;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockDeleteThread (
|
|
PVI_DEADLOCK_THREAD Thread,
|
|
BOOLEAN Cleanup
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes a thread.
|
|
|
|
Arguments:
|
|
|
|
Thread - thread address
|
|
|
|
Cleanup - true if this is a call generated from DeadlockDetectionCleanup().
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
--*/
|
|
{
|
|
if (Cleanup == FALSE) {
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread());
|
|
|
|
if (Thread->NodeCount != 0
|
|
|| Thread->CurrentSpinNode != NULL
|
|
|| Thread->CurrentOtherNode != NULL) {
|
|
|
|
//
|
|
// A thread should not be deleted while it has resources acquired.
|
|
//
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_THREAD_HOLDS_RESOURCES,
|
|
(ULONG_PTR)(Thread->Thread),
|
|
(ULONG_PTR)(Thread),
|
|
(ULONG_PTR)0);
|
|
} else {
|
|
|
|
ASSERT (Thread->NodeCount == 0);
|
|
}
|
|
|
|
}
|
|
|
|
RemoveEntryList (&(Thread->ListEntry));
|
|
|
|
ViDeadlockFree (Thread, ViDeadlockThread);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////// Allocate/Free
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
PVOID
|
|
ViDeadlockAllocateFromPoolCache (
|
|
PULONG Count,
|
|
ULONG MaximumCount,
|
|
PLIST_ENTRY List,
|
|
SIZE_T Offset
|
|
)
|
|
{
|
|
PVOID Address = NULL;
|
|
PLIST_ENTRY Entry;
|
|
|
|
UNREFERENCED_PARAMETER (MaximumCount);
|
|
|
|
if (*Count > 0) {
|
|
|
|
*Count -= 1;
|
|
Entry = RemoveHeadList (List);
|
|
Address = (PVOID)((SIZE_T)Entry - Offset);
|
|
}
|
|
|
|
return Address;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockFreeIntoPoolCache (
|
|
PVOID Object,
|
|
PULONG Count,
|
|
PLIST_ENTRY List,
|
|
SIZE_T Offset
|
|
)
|
|
{
|
|
PLIST_ENTRY Entry;
|
|
|
|
Entry = (PLIST_ENTRY)((SIZE_T)Object + Offset);
|
|
|
|
*Count += 1;
|
|
InsertHeadList(List, Entry);
|
|
}
|
|
|
|
|
|
PVOID
|
|
ViDeadlockAllocate (
|
|
VI_DEADLOCK_ALLOC_TYPE Type
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to allocate deadlock verifier structures,
|
|
that is nodes, resources and threads.
|
|
|
|
Arguments:
|
|
|
|
Type - what structure do we need to allocate (node, resource or thread).
|
|
|
|
Return Value:
|
|
|
|
Address of the newly allocate structure or null if allocation failed.
|
|
|
|
Side effects:
|
|
|
|
If allocation fails the routine will bump the AllocationFailures field
|
|
from ViDeadlockGlobals.
|
|
|
|
--*/
|
|
{
|
|
PVOID Address = NULL;
|
|
KIRQL OldIrql;
|
|
SIZE_T Offset;
|
|
SIZE_T Size = 0;
|
|
|
|
//
|
|
// If it is a resource, thread, or node alocation, see
|
|
// if we have a pre-allocated one on the free list.
|
|
//
|
|
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
|
|
switch (Type) {
|
|
|
|
case ViDeadlockThread:
|
|
|
|
Offset = (SIZE_T)(&(((PVI_DEADLOCK_THREAD)0)->FreeListEntry));
|
|
Size = sizeof (VI_DEADLOCK_THREAD);
|
|
|
|
Address = ViDeadlockAllocateFromPoolCache (&(ViDeadlockGlobals->FreeThreadCount),
|
|
VI_DEADLOCK_MAX_FREE_THREAD,
|
|
&(ViDeadlockGlobals->FreeThreadList),
|
|
Offset);
|
|
|
|
break;
|
|
|
|
case ViDeadlockResource:
|
|
|
|
Offset = (SIZE_T)(&(((PVI_DEADLOCK_RESOURCE)0)->FreeListEntry));
|
|
Size = sizeof (VI_DEADLOCK_RESOURCE);
|
|
|
|
Address = ViDeadlockAllocateFromPoolCache (&(ViDeadlockGlobals->FreeResourceCount),
|
|
VI_DEADLOCK_MAX_FREE_RESOURCE,
|
|
&(ViDeadlockGlobals->FreeResourceList),
|
|
Offset);
|
|
|
|
break;
|
|
|
|
case ViDeadlockNode:
|
|
|
|
Offset = (SIZE_T)(&(((PVI_DEADLOCK_NODE)0)->FreeListEntry));
|
|
Size = sizeof (VI_DEADLOCK_NODE);
|
|
|
|
Address = ViDeadlockAllocateFromPoolCache (&(ViDeadlockGlobals->FreeNodeCount),
|
|
VI_DEADLOCK_MAX_FREE_NODE,
|
|
&(ViDeadlockGlobals->FreeNodeList),
|
|
Offset);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ASSERT (0);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we did not find anything and kernel verifier is not active
|
|
// then go to the kernel pool for a direct allocation. If kernel
|
|
// verifier is enabled everything is preallocated and we never
|
|
// call into the kernel pool.
|
|
//
|
|
|
|
if (Address == NULL && ViDeadlockState.KernelVerifierEnabled == 0) {
|
|
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
Address = ExAllocatePoolWithTag(NonPagedPool, Size, VI_DEADLOCK_TAG);
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
}
|
|
|
|
if (Address) {
|
|
|
|
switch (Type) {
|
|
|
|
case ViDeadlockThread:
|
|
ViDeadlockGlobals->Threads[0] += 1;
|
|
|
|
if (ViDeadlockGlobals->Threads[0] > ViDeadlockGlobals->Threads[1]) {
|
|
ViDeadlockGlobals->Threads[1] = ViDeadlockGlobals->Threads[0];
|
|
}
|
|
break;
|
|
|
|
case ViDeadlockResource:
|
|
ViDeadlockGlobals->Resources[0] += 1;
|
|
|
|
if (ViDeadlockGlobals->Resources[0] > ViDeadlockGlobals->Resources[1]) {
|
|
ViDeadlockGlobals->Resources[1] = ViDeadlockGlobals->Resources[0];
|
|
}
|
|
break;
|
|
|
|
case ViDeadlockNode:
|
|
ViDeadlockGlobals->Nodes[0] += 1;
|
|
|
|
if (ViDeadlockGlobals->Nodes[0] > ViDeadlockGlobals->Nodes[1]) {
|
|
ViDeadlockGlobals->Nodes[1] = ViDeadlockGlobals->Nodes[0];
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT (0);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
|
|
ViDeadlockState.AllocationFailures = 1;
|
|
ViDeadlockGlobals->AllocationFailures += 1;
|
|
|
|
//
|
|
// Note that making the AllocationFailures counter bigger than zero
|
|
// essentially disables deadlock verification because the CanProceed()
|
|
// routine will start returning false.
|
|
//
|
|
}
|
|
|
|
//
|
|
// Update statistics. No need to zero the block since every
|
|
// call site takes care of this.
|
|
//
|
|
|
|
if (Address) {
|
|
|
|
#if DBG
|
|
RtlFillMemory (Address, Size, 0xFF);
|
|
#endif
|
|
ViDeadlockGlobals->BytesAllocated += Size;
|
|
}
|
|
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
|
|
return Address;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockFree (
|
|
PVOID Object,
|
|
VI_DEADLOCK_ALLOC_TYPE Type
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deallocates a deadlock verifier structure (node, resource
|
|
or thread). The function will place the block in the corrsponding cache
|
|
based on the type of the structure. The routine never calls ExFreePool.
|
|
|
|
The reason for not calling ExFreePool is that we get notifications from
|
|
ExFreePool every time it gets called. Sometimes the notification comes
|
|
with pool locks held and therefore we cannot call again.
|
|
|
|
Arguments:
|
|
|
|
Object - block to deallocate
|
|
|
|
Type - type of object (node, resource, thread).
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
//
|
|
// Note ... if a thread, node, or resource is being freed, we must not
|
|
// call ExFreePool. Since the pool lock may be already held, calling ExFreePool
|
|
// would cause a recursive spinlock acquisition (which is bad).
|
|
// Instead, we move everything to a 'free' list and try to reuse.
|
|
// Non-thread-node-resource frees get ExFreePooled
|
|
//
|
|
{
|
|
SIZE_T Offset;
|
|
SIZE_T Size = 0;
|
|
|
|
switch (Type) {
|
|
|
|
case ViDeadlockThread:
|
|
|
|
ViDeadlockGlobals->Threads[0] -= 1;
|
|
Size = sizeof (VI_DEADLOCK_THREAD);
|
|
|
|
Offset = (SIZE_T)(&(((PVI_DEADLOCK_THREAD)0)->FreeListEntry));
|
|
|
|
ViDeadlockFreeIntoPoolCache (Object,
|
|
&(ViDeadlockGlobals->FreeThreadCount),
|
|
&(ViDeadlockGlobals->FreeThreadList),
|
|
Offset);
|
|
break;
|
|
|
|
case ViDeadlockResource:
|
|
|
|
ViDeadlockGlobals->Resources[0] -= 1;
|
|
Size = sizeof (VI_DEADLOCK_RESOURCE);
|
|
|
|
Offset = (SIZE_T)(&(((PVI_DEADLOCK_RESOURCE)0)->FreeListEntry));
|
|
|
|
ViDeadlockFreeIntoPoolCache (Object,
|
|
&(ViDeadlockGlobals->FreeResourceCount),
|
|
&(ViDeadlockGlobals->FreeResourceList),
|
|
Offset);
|
|
break;
|
|
|
|
case ViDeadlockNode:
|
|
|
|
ViDeadlockGlobals->Nodes[0] -= 1;
|
|
Size = sizeof (VI_DEADLOCK_NODE);
|
|
|
|
Offset = (SIZE_T)(&(((PVI_DEADLOCK_NODE)0)->FreeListEntry));
|
|
|
|
ViDeadlockFreeIntoPoolCache (Object,
|
|
&(ViDeadlockGlobals->FreeNodeCount),
|
|
&(ViDeadlockGlobals->FreeNodeList),
|
|
Offset);
|
|
break;
|
|
|
|
default:
|
|
|
|
ASSERT (0);
|
|
break;
|
|
}
|
|
|
|
ViDeadlockGlobals->BytesAllocated -= Size;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockTrimPoolCache (
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function trims the pool caches to decent levels. It is carefully
|
|
written to queue a work item to do the actual processing (freeing of pool)
|
|
because the caller may hold various pool mutexes above us.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
if (ViDeadlockState.KernelVerifierEnabled == 1) {
|
|
return;
|
|
}
|
|
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
|
|
if (ViDeadlockGlobals->CacheReductionInProgress == TRUE) {
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
return;
|
|
}
|
|
|
|
if ((ViDeadlockGlobals->FreeThreadCount > VI_DEADLOCK_MAX_FREE_THREAD) ||
|
|
(ViDeadlockGlobals->FreeNodeCount > VI_DEADLOCK_MAX_FREE_NODE) ||
|
|
(ViDeadlockGlobals->FreeResourceCount > VI_DEADLOCK_MAX_FREE_RESOURCE)){
|
|
|
|
ExQueueWorkItem (&ViTrimDeadlockPoolWorkItem, DelayedWorkQueue);
|
|
ViDeadlockGlobals->CacheReductionInProgress = TRUE;
|
|
}
|
|
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
ViDeadlockTrimPoolCacheWorker (
|
|
PVOID Parameter
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function trims the pool caches to decent levels. It is carefully
|
|
written so that ExFreePool is called without holding any deadlock
|
|
verifier locks.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Worker thread, PASSIVE_LEVEL, no locks held.
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY ListOfThreads;
|
|
LIST_ENTRY ListOfNodes;
|
|
LIST_ENTRY ListOfResources;
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY Entry;
|
|
LOGICAL CacheReductionNeeded;
|
|
|
|
UNREFERENCED_PARAMETER (Parameter);
|
|
|
|
ASSERT (KeGetCurrentIrql () == PASSIVE_LEVEL);
|
|
|
|
CacheReductionNeeded = FALSE;
|
|
|
|
InitializeListHead (&ListOfThreads);
|
|
InitializeListHead (&ListOfNodes);
|
|
InitializeListHead (&ListOfResources);
|
|
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
|
|
while (ViDeadlockGlobals->FreeThreadCount > VI_DEADLOCK_MAX_FREE_THREAD) {
|
|
|
|
Entry = RemoveHeadList (&(ViDeadlockGlobals->FreeThreadList));
|
|
InsertTailList (&ListOfThreads, Entry);
|
|
ViDeadlockGlobals->FreeThreadCount -= 1;
|
|
CacheReductionNeeded = TRUE;
|
|
}
|
|
|
|
while (ViDeadlockGlobals->FreeNodeCount > VI_DEADLOCK_MAX_FREE_NODE) {
|
|
|
|
Entry = RemoveHeadList (&(ViDeadlockGlobals->FreeNodeList));
|
|
InsertTailList (&ListOfNodes, Entry);
|
|
ViDeadlockGlobals->FreeNodeCount -= 1;
|
|
CacheReductionNeeded = TRUE;
|
|
}
|
|
|
|
while (ViDeadlockGlobals->FreeResourceCount > VI_DEADLOCK_MAX_FREE_RESOURCE) {
|
|
|
|
Entry = RemoveHeadList (&(ViDeadlockGlobals->FreeResourceList));
|
|
InsertTailList (&ListOfResources, Entry);
|
|
ViDeadlockGlobals->FreeResourceCount -= 1;
|
|
CacheReductionNeeded = TRUE;
|
|
}
|
|
|
|
//
|
|
// Don't clear CacheReductionInProgress until the pool allocations are
|
|
// freed to prevent needless recursion.
|
|
//
|
|
|
|
if (CacheReductionNeeded == FALSE) {
|
|
ViDeadlockGlobals->CacheReductionInProgress = FALSE;
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
return;
|
|
}
|
|
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
|
|
//
|
|
// Now, out of the deadlock verifier lock we can deallocate the
|
|
// blocks trimmed.
|
|
//
|
|
|
|
Entry = ListOfThreads.Flink;
|
|
|
|
while (Entry != &ListOfThreads) {
|
|
|
|
PVI_DEADLOCK_THREAD Block;
|
|
|
|
Block = CONTAINING_RECORD (Entry,
|
|
VI_DEADLOCK_THREAD,
|
|
FreeListEntry);
|
|
|
|
Entry = Entry->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
Entry = ListOfNodes.Flink;
|
|
|
|
while (Entry != &ListOfNodes) {
|
|
|
|
PVI_DEADLOCK_NODE Block;
|
|
|
|
Block = CONTAINING_RECORD (Entry,
|
|
VI_DEADLOCK_NODE,
|
|
FreeListEntry);
|
|
|
|
Entry = Entry->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
Entry = ListOfResources.Flink;
|
|
|
|
while (Entry != &ListOfResources) {
|
|
|
|
PVI_DEADLOCK_RESOURCE Block;
|
|
|
|
Block = CONTAINING_RECORD (Entry,
|
|
VI_DEADLOCK_RESOURCE,
|
|
FreeListEntry);
|
|
|
|
Entry = Entry->Flink;
|
|
ExFreePool (Block);
|
|
}
|
|
|
|
//
|
|
// It's safe to clear CacheReductionInProgress now that the pool
|
|
// allocations are freed.
|
|
//
|
|
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
ViDeadlockGlobals->CacheReductionInProgress = FALSE;
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////// Error reporting and debugging
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Variable accessed by the !deadlock debug extension to investigate
|
|
// failures.
|
|
//
|
|
|
|
ULONG_PTR ViDeadlockIssue[4];
|
|
|
|
|
|
VOID
|
|
ViDeadlockReportIssue (
|
|
ULONG_PTR Param1,
|
|
ULONG_PTR Param2,
|
|
ULONG_PTR Param3,
|
|
ULONG_PTR Param4
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to report a deadlock verifier issue.
|
|
If we are in debug mode we will just break in debugger.
|
|
Otherwise we will bugcheck,
|
|
|
|
Arguments:
|
|
|
|
Param1..Param4 - relevant information for the point of failure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ViDeadlockIssue[0] = Param1;
|
|
ViDeadlockIssue[1] = Param2;
|
|
ViDeadlockIssue[2] = Param3;
|
|
ViDeadlockIssue[3] = Param4;
|
|
|
|
|
|
if (ViDeadlockDebug) {
|
|
|
|
DbgPrint ("Verifier: deadlock: stop: %p %p %p %p %p \n",
|
|
DRIVER_VERIFIER_DETECTED_VIOLATION,
|
|
Param1,
|
|
Param2,
|
|
Param3,
|
|
Param4);
|
|
|
|
DbgBreakPoint ();
|
|
}
|
|
else {
|
|
|
|
KeBugCheckEx (DRIVER_VERIFIER_DETECTED_VIOLATION,
|
|
Param1,
|
|
Param2,
|
|
Param3,
|
|
Param4);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockAddParticipant(
|
|
PVI_DEADLOCK_NODE Node
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Adds a new node to the set of nodes involved in a deadlock.
|
|
The function is called only from ViDeadlockAnalyze().
|
|
|
|
Arguments:
|
|
|
|
Node - node to be added to the deadlock participants collection.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG Index;
|
|
|
|
Index = ViDeadlockGlobals->NumberOfParticipants;
|
|
|
|
if (Index >= NO_OF_DEADLOCK_PARTICIPANTS) {
|
|
|
|
ViDeadlockState.DeadlockParticipantsOverflow = 1;
|
|
return;
|
|
}
|
|
|
|
ViDeadlockGlobals->Participant[Index] = Node;
|
|
ViDeadlockGlobals->NumberOfParticipants += 1;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////// Resource cleanup
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
VOID
|
|
VfDeadlockDeleteMemoryRange(
|
|
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.
|
|
|
|
NOTE ON OPTIMIZATION -- rather than having to search through all of the
|
|
resources we've collected, we can make a simple optimization -- if we
|
|
put the resources into hash bins based on PFN or the page address (i.e.
|
|
page number for address 1A020 is 1A), we only have to look into a single
|
|
hash bin for each page that the range spans. Worst case scenario is when
|
|
we have an extremely long allocation, but even in this case we only look
|
|
through each hash bin once.
|
|
|
|
Arguments:
|
|
|
|
Address - start address of the range to be deleted.
|
|
|
|
Size - size in bytes of the range to be deleted.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG SpanningPages;
|
|
ULONG Index;
|
|
ULONG_PTR Start;
|
|
ULONG_PTR End;
|
|
PLIST_ENTRY ListHead;
|
|
PLIST_ENTRY CurrentEntry;
|
|
PVI_DEADLOCK_RESOURCE Resource;
|
|
PVI_DEADLOCK_THREAD Thread;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// If we are not initialized or package is not enabled
|
|
// we return immediately.
|
|
//
|
|
|
|
if (! ViDeadlockCanProceed(NULL, NULL, VfDeadlockUnknown)) {
|
|
return;
|
|
}
|
|
|
|
SpanningPages = (ULONG) ADDRESS_AND_SIZE_TO_SPAN_PAGES (Address, Size);
|
|
|
|
|
|
if (SpanningPages > VI_DEADLOCK_HASH_BINS ) {
|
|
SpanningPages = VI_DEADLOCK_HASH_BINS;
|
|
|
|
}
|
|
|
|
Start = (ULONG_PTR) Address;
|
|
End = Start + (ULONG_PTR) Size;
|
|
|
|
ViDeadlockDetectionLock(&OldIrql);
|
|
|
|
//
|
|
// Iterate all resources and delete the ones contained in the
|
|
// memory range.
|
|
//
|
|
|
|
for (Index = 0; Index < SpanningPages; Index += 1) {
|
|
|
|
//
|
|
// See optimization note above for description of why we only look
|
|
// in a single hash bin.
|
|
//
|
|
|
|
ListHead = ViDeadlockDatabaseHash (ViDeadlockGlobals->ResourceDatabase,
|
|
(PVOID) (Start + Index * PAGE_SIZE));
|
|
|
|
CurrentEntry = ListHead->Flink;
|
|
|
|
while (CurrentEntry != ListHead) {
|
|
|
|
Resource = CONTAINING_RECORD (CurrentEntry,
|
|
VI_DEADLOCK_RESOURCE,
|
|
HashChainList);
|
|
|
|
CurrentEntry = CurrentEntry->Flink;
|
|
|
|
if ((ULONG_PTR)(Resource->ResourceAddress) >= Start &&
|
|
(ULONG_PTR)(Resource->ResourceAddress) < End) {
|
|
|
|
ViDeadlockDeleteResource (Resource, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Iterate all threads and delete the ones contained in the
|
|
// memory range.
|
|
//
|
|
|
|
for (Index = 0; Index < SpanningPages; Index += 1) {
|
|
|
|
ListHead = ViDeadlockDatabaseHash (ViDeadlockGlobals->ThreadDatabase,
|
|
(PVOID) (Start + Index * PAGE_SIZE));
|
|
|
|
CurrentEntry = ListHead->Flink;
|
|
|
|
while (CurrentEntry != ListHead) {
|
|
|
|
Thread = CONTAINING_RECORD (CurrentEntry,
|
|
VI_DEADLOCK_THREAD,
|
|
ListEntry);
|
|
|
|
CurrentEntry = CurrentEntry->Flink;
|
|
|
|
if ((ULONG_PTR)(Thread->Thread) >= Start &&
|
|
(ULONG_PTR)(Thread->Thread) < End) {
|
|
|
|
#if DBG
|
|
if (Thread->NodeCount > 0) {
|
|
DbgPrint ("Deadlock verifier: deleting thread %p while holding resources %p \n");
|
|
DbgBreakPoint ();
|
|
}
|
|
#endif
|
|
|
|
ViDeadlockDeleteThread (Thread, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
ViDeadlockDetectionUnlock(OldIrql);
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockDeleteResource (
|
|
PVI_DEADLOCK_RESOURCE Resource,
|
|
BOOLEAN Cleanup
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes a routine and all nodes representing
|
|
acquisitions of that resource.
|
|
|
|
Arguments:
|
|
|
|
Resource - resource to be deleted
|
|
|
|
Cleanup - true if are called from ViDeadlockDetectionCleanup
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node;
|
|
|
|
ASSERT (Resource != NULL);
|
|
ASSERT (Cleanup || ViDeadlockDatabaseOwner == KeGetCurrentThread());
|
|
|
|
|
|
//
|
|
// Check if the resource being deleted is still acquired.
|
|
// Note that this might fire if we loose release() operations
|
|
// performed by an unverified driver.
|
|
//
|
|
|
|
if (Cleanup == FALSE && Resource->ThreadOwner != NULL) {
|
|
|
|
ViDeadlockReportIssue (VI_DEADLOCK_ISSUE_THREAD_HOLDS_RESOURCES,
|
|
(ULONG_PTR) (Resource->ResourceAddress),
|
|
(ULONG_PTR) (Resource->ThreadOwner->Thread),
|
|
(ULONG_PTR) (Resource));
|
|
}
|
|
|
|
//
|
|
// If this is a normal delete (not a cleanup) we will collapse all trees
|
|
// containing nodes for this resource. If it is a cleanup we will just
|
|
// wipe out the node.
|
|
//
|
|
|
|
Current = Resource->ResourceList.Flink;
|
|
|
|
while (Current != &(Resource->ResourceList)) {
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
|
|
Current = Current->Flink;
|
|
|
|
ASSERT (Node->Root == Resource);
|
|
|
|
ViDeadlockDeleteNode (Node, Cleanup);
|
|
}
|
|
|
|
//
|
|
// There should not be any NODEs for the resource at this moment.
|
|
//
|
|
|
|
ASSERT (&(Resource->ResourceList) == Resource->ResourceList.Flink);
|
|
ASSERT (&(Resource->ResourceList) == Resource->ResourceList.Blink);
|
|
|
|
//
|
|
// Remote the resource from the hash table and
|
|
// delete the resource structure.
|
|
//
|
|
|
|
RemoveEntryList (&(Resource->HashChainList));
|
|
ViDeadlockFree (Resource, ViDeadlockResource);
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockTrimResources (
|
|
PLIST_ENTRY HashList
|
|
)
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_RESOURCE Resource;
|
|
|
|
Current = HashList->Flink;
|
|
|
|
while (Current != HashList) {
|
|
|
|
Resource = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_RESOURCE,
|
|
HashChainList);
|
|
Current = Current->Flink;
|
|
|
|
ViDeadlockForgetResourceHistory (Resource,
|
|
ViDeadlockTrimThreshold,
|
|
ViDeadlockAgeWindow);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
ViDeadlockForgetResourceHistory (
|
|
PVI_DEADLOCK_RESOURCE Resource,
|
|
ULONG TrimThreshold,
|
|
ULONG AgeThreshold
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes sone of the nodes representing
|
|
acquisitions of that resource. In essence we forget
|
|
part of the history of that resource.
|
|
|
|
Arguments:
|
|
|
|
Resource - resource for which we wipe out nodes.
|
|
|
|
TrimThreshold - how many nodes should remain
|
|
|
|
AgeThreshold - nodes older than this will go away
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node;
|
|
ULONG NodesTrimmed = 0;
|
|
ULONG SequenceNumber;
|
|
|
|
ASSERT (Resource != NULL);
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread());
|
|
|
|
//
|
|
// If resource is owned we cannot do anything,
|
|
//
|
|
|
|
if (Resource->ThreadOwner) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If resource has less than TrimThreshold nodes it is still fine.
|
|
//
|
|
|
|
if (Resource->NodeCount < TrimThreshold) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Delete some nodes of the resource based on ageing.
|
|
//
|
|
|
|
SequenceNumber = ViDeadlockGlobals->SequenceNumber;
|
|
|
|
Current = Resource->ResourceList.Flink;
|
|
|
|
while (Current != &(Resource->ResourceList)) {
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
|
|
Current = Current->Flink;
|
|
|
|
ASSERT (Node->Root == Resource);
|
|
|
|
//
|
|
// Special care here because the sequence numbers are 32bits
|
|
// and they can overflow. In an ideal world the global sequence
|
|
// is always greater or equal to the node sequence but if it
|
|
// overwrapped it can be the other way around.
|
|
//
|
|
|
|
if (SequenceNumber > Node->SequenceNumber) {
|
|
|
|
if (SequenceNumber - Node->SequenceNumber > AgeThreshold) {
|
|
|
|
ViDeadlockDeleteNode (Node, FALSE);
|
|
NodesTrimmed += 1;
|
|
}
|
|
}
|
|
else {
|
|
|
|
if (SequenceNumber + Node->SequenceNumber > AgeThreshold) {
|
|
|
|
ViDeadlockDeleteNode (Node, FALSE);
|
|
NodesTrimmed += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
ViDeadlockGlobals->NodesTrimmedBasedOnAge += NodesTrimmed;
|
|
|
|
//
|
|
// If resource has less than TrimThreshold nodes it is fine.
|
|
//
|
|
|
|
if (Resource->NodeCount < TrimThreshold) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If we did not manage to trim the nodes by the age algorithm then
|
|
// we trim everything that we encounter.
|
|
//
|
|
|
|
NodesTrimmed = 0;
|
|
|
|
Current = Resource->ResourceList.Flink;
|
|
|
|
while (Current != &(Resource->ResourceList)) {
|
|
|
|
if (Resource->NodeCount < TrimThreshold) {
|
|
break;
|
|
}
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
|
|
Current = Current->Flink;
|
|
|
|
ASSERT (Node->Root == Resource);
|
|
|
|
ViDeadlockDeleteNode (Node, FALSE);
|
|
NodesTrimmed += 1;
|
|
}
|
|
|
|
ViDeadlockGlobals->NodesTrimmedBasedOnCount += NodesTrimmed;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockDeleteNode (
|
|
PVI_DEADLOCK_NODE Node,
|
|
BOOLEAN Cleanup
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes a node from a graph and collapses the tree,
|
|
that is connects its childrend with its parent.
|
|
|
|
If we are during a cleanup we will just delete the node without
|
|
collapsing the tree.
|
|
|
|
Arguments:
|
|
|
|
Node - node to be deleted.
|
|
|
|
Cleanup - true if we are during a total cleanup
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Child;
|
|
ULONG Children;
|
|
|
|
ASSERT (Node);
|
|
|
|
//
|
|
// If are during a cleanup just delete the node and return.
|
|
//
|
|
|
|
if (Cleanup) {
|
|
|
|
RemoveEntryList (&(Node->ResourceList));
|
|
ViDeadlockFree (Node, ViDeadlockNode);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If we are here we need to collapse the tree
|
|
//
|
|
|
|
ASSERT (ViDeadlockDatabaseOwner == KeGetCurrentThread());
|
|
|
|
if (Node->Parent) {
|
|
|
|
//
|
|
// All Node's children must become Parent's children
|
|
//
|
|
|
|
Current = Node->ChildrenList.Flink;
|
|
|
|
while (Current != &(Node->ChildrenList)) {
|
|
|
|
Child = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
SiblingsList);
|
|
|
|
Current = Current->Flink;
|
|
|
|
RemoveEntryList (&(Child->SiblingsList));
|
|
|
|
Child->Parent = Node->Parent;
|
|
|
|
InsertTailList (&(Node->Parent->ChildrenList),
|
|
&(Child->SiblingsList));
|
|
}
|
|
|
|
RemoveEntryList (&(Node->SiblingsList));
|
|
}
|
|
else {
|
|
|
|
//
|
|
// All Node's children must become roots of the graph
|
|
//
|
|
|
|
Current = Node->ChildrenList.Flink;
|
|
Children = 0;
|
|
Child = NULL;
|
|
|
|
while (Current != &(Node->ChildrenList)) {
|
|
|
|
Children += 1;
|
|
|
|
Child = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
SiblingsList);
|
|
|
|
Current = Current->Flink;
|
|
|
|
RemoveEntryList (&(Child->SiblingsList));
|
|
|
|
Child->Parent = NULL;
|
|
Child->SiblingsList.Flink = NULL;
|
|
Child->SiblingsList.Blink = NULL;
|
|
}
|
|
}
|
|
|
|
ASSERT (Node->Root);
|
|
ASSERT (Node->Root->NodeCount > 0);
|
|
|
|
Node->Root->NodeCount -= 1;
|
|
|
|
RemoveEntryList (&(Node->ResourceList));
|
|
ViDeadlockFree (Node, ViDeadlockNode);
|
|
}
|
|
|
|
|
|
ULONG
|
|
ViDeadlockNodeLevel (
|
|
PVI_DEADLOCK_NODE Node
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine computes the level of a graph node.
|
|
|
|
Arguments:
|
|
|
|
Node - graph node
|
|
|
|
Return Value:
|
|
|
|
Level of the node. A root node has level zero.
|
|
--*/
|
|
{
|
|
PVI_DEADLOCK_NODE Current;
|
|
ULONG Level = 0;
|
|
|
|
Current = Node->Parent;
|
|
|
|
while (Current) {
|
|
|
|
Level += 1;
|
|
Current = Current->Parent;
|
|
}
|
|
|
|
return Level;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////// Incremental graph compression
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// SilviuC: should write a comment about graph compression
|
|
// This is a very smart and tricky algorithm :-)
|
|
//
|
|
|
|
VOID
|
|
ViDeadlockCheckDuplicatesAmongChildren (
|
|
PVI_DEADLOCK_NODE Parent,
|
|
PVI_DEADLOCK_NODE Child
|
|
)
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node;
|
|
LOGICAL FoundOne;
|
|
|
|
FoundOne = FALSE;
|
|
Current = Parent->ChildrenList.Flink;
|
|
|
|
while (Current != &(Parent->ChildrenList)) {
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
SiblingsList);
|
|
|
|
ASSERT (Current->Flink);
|
|
Current = Current->Flink;
|
|
|
|
if (ViDeadlockSimilarNodes (Node, Child)) {
|
|
|
|
if (FoundOne == FALSE) {
|
|
ASSERT (Node == Child);
|
|
FoundOne = TRUE;
|
|
}
|
|
else {
|
|
|
|
ViDeadlockMergeNodes (Child, Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockCheckDuplicatesAmongRoots (
|
|
PVI_DEADLOCK_NODE Root
|
|
)
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node;
|
|
PVI_DEADLOCK_RESOURCE Resource;
|
|
LOGICAL FoundOne;
|
|
|
|
FoundOne = FALSE;
|
|
Resource = Root->Root;
|
|
Current = Resource->ResourceList.Flink;
|
|
|
|
while (Current != &(Resource->ResourceList)) {
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
ResourceList);
|
|
|
|
ASSERT (Current->Flink);
|
|
Current = Current->Flink;
|
|
|
|
if (Node->Parent == NULL && ViDeadlockSimilarNodes (Node, Root)) {
|
|
|
|
if (FoundOne == FALSE) {
|
|
ASSERT (Node == Root);
|
|
FoundOne = TRUE;
|
|
}
|
|
else {
|
|
|
|
ViDeadlockMergeNodes (Root, Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
LOGICAL
|
|
ViDeadlockSimilarNodes (
|
|
PVI_DEADLOCK_NODE NodeA,
|
|
PVI_DEADLOCK_NODE NodeB
|
|
)
|
|
{
|
|
if (NodeA->Root == NodeB->Root
|
|
&& NodeA->OnlyTryAcquireUsed == NodeB->OnlyTryAcquireUsed) {
|
|
|
|
return TRUE;
|
|
}
|
|
else {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockMergeNodes (
|
|
PVI_DEADLOCK_NODE NodeTo,
|
|
PVI_DEADLOCK_NODE NodeFrom
|
|
)
|
|
{
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_NODE Node;
|
|
|
|
//
|
|
// If NodeFrom is currently acquired then copy the same
|
|
// characteristics to NodeTo. Since the locks are exclusive
|
|
// it is impossible to have NodeTo also acquired.
|
|
//
|
|
|
|
if (NodeFrom->ThreadEntry) {
|
|
ASSERT (NodeTo->ThreadEntry == NULL);
|
|
NodeTo->ThreadEntry = NodeFrom->ThreadEntry;
|
|
|
|
RtlCopyMemory (NodeTo->StackTrace,
|
|
NodeFrom->StackTrace,
|
|
sizeof (NodeTo->StackTrace));
|
|
|
|
RtlCopyMemory (NodeTo->ParentStackTrace,
|
|
NodeFrom->ParentStackTrace,
|
|
sizeof (NodeTo->ParentStackTrace));
|
|
}
|
|
|
|
if (NodeFrom->Active) {
|
|
ASSERT (NodeTo->Active == 0);
|
|
NodeTo->Active = NodeFrom->Active;
|
|
}
|
|
|
|
//
|
|
// Move each child of NodeFrom as a child of NodeTo.
|
|
//
|
|
|
|
Current = NodeFrom->ChildrenList.Flink;
|
|
|
|
while (Current != &(NodeFrom->ChildrenList)) {
|
|
|
|
Node = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_NODE,
|
|
SiblingsList);
|
|
|
|
ASSERT (Current->Flink);
|
|
Current = Current->Flink;
|
|
|
|
RemoveEntryList (&(Node->SiblingsList));
|
|
|
|
ASSERT (Node->Parent == NodeFrom);
|
|
Node->Parent = NodeTo;
|
|
|
|
InsertTailList (&(NodeTo->ChildrenList),
|
|
&(Node->SiblingsList));
|
|
}
|
|
|
|
//
|
|
// NodeFrom is empty. Delete it.
|
|
//
|
|
|
|
ASSERT (IsListEmpty(&(NodeFrom->ChildrenList)));
|
|
|
|
if (NodeFrom->Parent) {
|
|
RemoveEntryList (&(NodeFrom->SiblingsList));
|
|
}
|
|
|
|
NodeFrom->Root->NodeCount -= 1;
|
|
RemoveEntryList (&(NodeFrom->ResourceList));
|
|
ViDeadlockFree (NodeFrom, ViDeadlockNode);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////// ExFreePool() hook
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
VOID
|
|
VerifierDeadlockFreePool(
|
|
IN PVOID Address,
|
|
IN SIZE_T NumberOfBytes
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine receives notification of all pool manager memory frees.
|
|
|
|
Arguments:
|
|
|
|
Address - Supplies the virtual address being freed.
|
|
|
|
NumberOfBytes - Supplies the number of bytes spanned by the allocation.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
This is called at various points either just before or just after the
|
|
allocation has been freed, depending on which is convenient for the pool
|
|
manager (this varies based on type of allocation).
|
|
|
|
For special pool or small pool, no pool resources are held on entry and
|
|
the memory still exists and is referencable.
|
|
|
|
For non-special pool allocations of PAGE_SIZE or greater, this routine is
|
|
called while the memory still exists and is referencable, BUT the nonpaged
|
|
pool spinlock (or paged pool mutex) is held on entry and so EXTREME care
|
|
must be taken in this routine.
|
|
|
|
--*/
|
|
|
|
{
|
|
VfDeadlockDeleteMemoryRange (Address, NumberOfBytes);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////// Consistency checks
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Node Resource Thread
|
|
//
|
|
// Root ThreadOwner CurrentNode
|
|
// ThreadEntry RecursionCount NodeCount
|
|
// Active ResourceAddress Thread
|
|
//
|
|
//
|
|
//
|
|
//
|
|
|
|
VOID
|
|
ViDeadlockCheckThreadConsistency (
|
|
PVI_DEADLOCK_THREAD Thread,
|
|
BOOLEAN Recursion
|
|
)
|
|
{
|
|
if (Thread->CurrentSpinNode == NULL && Thread->CurrentOtherNode == NULL) {
|
|
ASSERT (Thread->NodeCount == 0);
|
|
return;
|
|
}
|
|
|
|
if (Thread->CurrentSpinNode) {
|
|
|
|
ASSERT (Thread->NodeCount > 0);
|
|
ASSERT (Thread->CurrentSpinNode->Active);
|
|
|
|
if (Recursion == FALSE) {
|
|
ViDeadlockCheckNodeConsistency (Thread->CurrentSpinNode, TRUE);
|
|
ViDeadlockCheckResourceConsistency (Thread->CurrentSpinNode->Root, TRUE);
|
|
}
|
|
}
|
|
|
|
if (Thread->CurrentOtherNode) {
|
|
|
|
ASSERT (Thread->NodeCount > 0);
|
|
ASSERT (Thread->CurrentOtherNode->Active);
|
|
|
|
if (Recursion == FALSE) {
|
|
ViDeadlockCheckNodeConsistency (Thread->CurrentOtherNode, TRUE);
|
|
ViDeadlockCheckResourceConsistency (Thread->CurrentOtherNode->Root, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
ViDeadlockCheckNodeConsistency (
|
|
PVI_DEADLOCK_NODE Node,
|
|
BOOLEAN Recursion
|
|
)
|
|
{
|
|
if (Node->ThreadEntry) {
|
|
|
|
ASSERT (Node->Active == 1);
|
|
|
|
if (Recursion == FALSE) {
|
|
ViDeadlockCheckThreadConsistency (Node->ThreadEntry, TRUE);
|
|
ViDeadlockCheckResourceConsistency (Node->Root, TRUE);
|
|
}
|
|
}
|
|
else {
|
|
|
|
ASSERT (Node->Active == 0);
|
|
|
|
if (Recursion == FALSE) {
|
|
ViDeadlockCheckResourceConsistency (Node->Root, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
ViDeadlockCheckResourceConsistency (
|
|
PVI_DEADLOCK_RESOURCE Resource,
|
|
BOOLEAN Recursion
|
|
)
|
|
{
|
|
if (Resource->ThreadOwner) {
|
|
|
|
ASSERT (Resource->RecursionCount > 0);
|
|
|
|
if (Recursion == FALSE) {
|
|
ViDeadlockCheckThreadConsistency (Resource->ThreadOwner, TRUE);
|
|
|
|
if (Resource->Type == VfDeadlockSpinLock) {
|
|
ViDeadlockCheckNodeConsistency (Resource->ThreadOwner->CurrentSpinNode, TRUE);
|
|
}
|
|
else {
|
|
ViDeadlockCheckNodeConsistency (Resource->ThreadOwner->CurrentOtherNode, TRUE);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
ASSERT (Resource->RecursionCount == 0);
|
|
}
|
|
}
|
|
|
|
PVI_DEADLOCK_THREAD
|
|
ViDeadlockCheckThreadReferences (
|
|
PVI_DEADLOCK_NODE Node
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine iterates all threads in order to check if `Node' is
|
|
referred in the `CurrentNode' field in any of them.
|
|
|
|
Arguments:
|
|
|
|
Node - node to search
|
|
|
|
Return Value:
|
|
|
|
If everything goes ok we should not find the node and the return
|
|
value is null. Otherwise we return the thread referring to the node.
|
|
|
|
--*/
|
|
{
|
|
ULONG Index;
|
|
PLIST_ENTRY Current;
|
|
PVI_DEADLOCK_THREAD Thread;
|
|
|
|
for (Index = 0; Index < VI_DEADLOCK_HASH_BINS; Index += 1) {
|
|
Current = ViDeadlockGlobals->ThreadDatabase[Index].Flink;
|
|
|
|
while (Current != &(ViDeadlockGlobals->ThreadDatabase[Index])) {
|
|
|
|
Thread = CONTAINING_RECORD (Current,
|
|
VI_DEADLOCK_THREAD,
|
|
ListEntry);
|
|
|
|
if (Thread->CurrentSpinNode == Node) {
|
|
return Thread;
|
|
}
|
|
|
|
if (Thread->CurrentOtherNode == Node) {
|
|
return Thread;
|
|
}
|
|
|
|
Current = Current->Flink;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////// Detect paging code paths
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
BOOLEAN
|
|
VfDeadlockBeforeCallDriver (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN OUT PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks if the IRP is a paging I/O IRP. If it is it will
|
|
disable deadlock verification in this thread until the after() function
|
|
is called.
|
|
|
|
The function also ignores mounting IRPs. There are drivers that have
|
|
locks inversed in mounting code paths but mounting can never happen
|
|
in parallel with normal access.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - same parameter used in IoCallDriver call.
|
|
|
|
Irp - IRP passed to the driver as used in IoCallDriver call.
|
|
|
|
Return Value:
|
|
|
|
True if the Irp parameter is a paging IRP.
|
|
|
|
--*/
|
|
{
|
|
KIRQL OldIrql;
|
|
PKTHREAD SystemThread;
|
|
PVI_DEADLOCK_THREAD VerifierThread;
|
|
BOOLEAN PagingIrp = FALSE;
|
|
PVOID ReservedThread = NULL;
|
|
|
|
UNREFERENCED_PARAMETER (DeviceObject);
|
|
|
|
//
|
|
// Skip if package not initialized
|
|
//
|
|
|
|
if (ViDeadlockGlobals == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Skip if package is disabled
|
|
//
|
|
|
|
if (! ViDeadlockDetectionEnabled) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If it is not a paging I/O IRP or a mounting IRP we do not care.
|
|
//
|
|
|
|
if ((Irp->Flags & (IRP_PAGING_IO | IRP_MOUNT_COMPLETION)) == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Find the deadlock verifier structure maintained for the current
|
|
// thread. If we do not find one then we will create one. On top of
|
|
// this mount/page IRP there might be locks acquired and we want to
|
|
// skip them too. The only situations where we observed that lock
|
|
// hierarchies are not respected is when at least one lock was acquired
|
|
// before the IoCallDriver() with a paging IRP or no lock acquired before
|
|
// a mount IRP (udfs.sys). For this last case we need to create a thread
|
|
// in which to increase the PageCount counter.
|
|
//
|
|
|
|
SystemThread = KeGetCurrentThread ();
|
|
|
|
ReservedThread = ViDeadlockAllocate (ViDeadlockThread);
|
|
|
|
if (ReservedThread == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
|
|
VerifierThread = ViDeadlockSearchThread (SystemThread);
|
|
|
|
if (VerifierThread == NULL) {
|
|
|
|
VerifierThread = ViDeadlockAddThread (SystemThread,
|
|
ReservedThread);
|
|
|
|
ReservedThread = NULL;
|
|
|
|
ASSERT (VerifierThread);
|
|
}
|
|
|
|
//
|
|
// At this point VerifierThread points to a deadlock verifier
|
|
// thread structure. We need to bump the paging recursion count
|
|
// to mark that one more level of paging I/O is active.
|
|
//
|
|
|
|
VerifierThread->PagingCount += 1;
|
|
|
|
PagingIrp = TRUE;
|
|
|
|
//
|
|
// Unlock the deadlock verifier lock and exit.
|
|
//
|
|
|
|
if (ReservedThread) {
|
|
ViDeadlockFree (ReservedThread, ViDeadlockThread);
|
|
}
|
|
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
|
|
return PagingIrp;
|
|
}
|
|
|
|
|
|
VOID
|
|
VfDeadlockAfterCallDriver (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN OUT PIRP Irp,
|
|
IN BOOLEAN PagingIrp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called after an IoCallDriver() call returns. It is used
|
|
to undo any state created by the before() function.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - same parameter used in IoCallDriver call.
|
|
|
|
Irp - IRP passed to the driver as used in IoCallDriver call.
|
|
|
|
PagingIrp - true if a previous call to the before() routine returned
|
|
true signalling a paging IRP.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
KIRQL OldIrql;
|
|
PKTHREAD SystemThread;
|
|
PVI_DEADLOCK_THREAD VerifierThread;
|
|
|
|
UNREFERENCED_PARAMETER (DeviceObject);
|
|
UNREFERENCED_PARAMETER (Irp);
|
|
|
|
//
|
|
// Skip if package not initialized
|
|
//
|
|
|
|
if (ViDeadlockGlobals == NULL) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Skip if package is disabled
|
|
//
|
|
|
|
if (! ViDeadlockDetectionEnabled) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If it is not a paging I/O IRP we do not care.
|
|
//
|
|
|
|
if (! PagingIrp) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Find the deadlock verifier structure maintained for the current
|
|
// thread. If we do not find one then we will let deadlock verifier
|
|
// do its job. The only situations where we observed that lock
|
|
// hierarchies are not respected is when at least one lock was acquired
|
|
// before the IoCallDriver() with a paging IRP.
|
|
//
|
|
|
|
SystemThread = KeGetCurrentThread ();
|
|
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
|
|
VerifierThread = ViDeadlockSearchThread (SystemThread);
|
|
|
|
if (VerifierThread == NULL) {
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// At this point VerifierThread points to a deadlock verifier
|
|
// thread structure. We need to bump the paging recursion count
|
|
// to mark that one more level of paging I/O is active.
|
|
//
|
|
|
|
ASSERT (VerifierThread->PagingCount > 0);
|
|
|
|
VerifierThread->PagingCount -= 1;
|
|
|
|
//
|
|
// Unlock the deadlock verifier lock and exit.
|
|
//
|
|
|
|
Exit:
|
|
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ViIsThreadInsidePagingCodePaths (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks if current thread is inside paging code paths.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
KIRQL OldIrql;
|
|
PKTHREAD SystemThread;
|
|
PVI_DEADLOCK_THREAD VerifierThread;
|
|
BOOLEAN Paging = FALSE;
|
|
|
|
SystemThread = KeGetCurrentThread ();
|
|
|
|
ViDeadlockDetectionLock (&OldIrql);
|
|
|
|
VerifierThread = ViDeadlockSearchThread (SystemThread);
|
|
|
|
if (VerifierThread && VerifierThread->PagingCount > 0) {
|
|
Paging = TRUE;
|
|
}
|
|
|
|
ViDeadlockDetectionUnlock (OldIrql);
|
|
|
|
return Paging;
|
|
}
|
|
|
|
|
|
VOID
|
|
ViDeadlockCheckStackLimits (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function checks that the current stack is a thread stack
|
|
or a DPC stack. This will catch drivers that switch their stacks.
|
|
|
|
--*/
|
|
{
|
|
#if defined(_X86_)
|
|
|
|
ULONG_PTR StartStack;
|
|
ULONG_PTR EndStack;
|
|
ULONG_PTR HintAddress;
|
|
|
|
_asm mov HintAddress, EBP;
|
|
|
|
if (KeGetCurrentIrql() > DISPATCH_LEVEL) {
|
|
return;
|
|
}
|
|
|
|
StartStack = (ULONG_PTR)(KeGetCurrentThread()->StackLimit);
|
|
EndStack = (ULONG_PTR)(KeGetCurrentThread()->StackBase);
|
|
|
|
if (StartStack <= HintAddress && HintAddress <= EndStack) {
|
|
return;
|
|
}
|
|
|
|
EndStack = (ULONG_PTR)(KeGetPcr()->Prcb->DpcStack);
|
|
StartStack = EndStack - KERNEL_STACK_SIZE;
|
|
|
|
if (EndStack && StartStack <= HintAddress && HintAddress <= EndStack) {
|
|
return;
|
|
}
|
|
|
|
KeBugCheckEx (DRIVER_VERIFIER_DETECTED_VIOLATION,
|
|
0x90,
|
|
(ULONG_PTR)(KeGetPcr()->Prcb),
|
|
0,
|
|
0);
|
|
|
|
#else
|
|
return;
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|