Nonmemory Resource Annotations
Annotations can help PREfast for Drivers (PFD) to more accurately detect leaks and other problems with allocations of nonmemory resources, such as spin locks.
A number of nonmemory resource types, such as critical regions and spin locks, can leak. PREfast for Drivers (PFD) can detect leaks of nonmemory resources, just as it can detect memory leaks. However, attempting to apply memory semantics to nonmemory objects leads to various kinds of incorrect analysis because of semantic differences between the two types. For example, there is no concept of aliasing for nonmemory resources. In general, if a resource must be aliased, it probably is better modeled as memory.
Acquiring and Releasing Nonmemory Resources
Use the __drv_acquiresResource and __drv_releasesResource annotations to specify the acquisition and release of nonmemory resources in a function parameter. Follow the same guidelines you use with the __drv_allocatesMem and __drv_freesMem annotations and apply the annotation to the specific parameter (or return vaule) that is being acquired or released. The nonmemory resource annotations have the following syntax:
__drv_acquiresResource(kind)
__drv_releasesResource(kind)
- (kind)
The kind parameter indicates the kind of resource. The value of kind must match in acquisitions and releases of the resource. The kind parameter can be any name; a few names that are already in use include SpinLock, QueuedSpinLock, InterruptSpinLock, CancelSpinLock, Resource, ResourceLite, FloatState, EngFloatState, FastMutex, UnsafeFastMutex, and Critical-Region. Unrelated usages of the same kind value do not conflict.
Examples
In the following examples, the functions acquire and release a resource that is named SpinLock. The value is held in the variable SpinLock. (The symbols are in different namespaces, so there is no conflict.)
VOID
KeAcquireSpinLock(
__inout
__drv_deref(__drv_acquiresResource(SpinLock))
PKSPIN_LOCK SpinLock,
__out PKIRQL OldIrql
);
VOID
KeReleaseSpinLock(
__inout
__drv_deref(__drv_releasesResource(SpinLock))
PKSPIN_LOCK SpinLock,
__in KIRQL NewIrql
);
Global Resources
Operations on nonmemory resources must consider that the resource is often not held "in" some variable, but consists of global state information that is accessed by context or selected by some kind of identifier.
Use the __drv_acquiresResourceGlobal and __drv_releasesResourceGlobal annotations to indicate acquisition and release of this kind of resource for a function as a whole. These annotations have the following syntax:
__drv_acquiresResourceGlobal(kind, param)
__drv_releasesResourceGlobal(kind, param)
These annotations apply to the function as a whole, rather than to an individual function parameter. The kind and param parameters specify the kind of resource and the instance of the resource, respectively.
Creating a name for the resource. The __drv_acquiresResourceGlobal annotation creates a name that can be used to annotate a nonmemory resource that is not held in a variable. The annotation takes two parameters: the kind of object (resource) to be tracked, which is a known string constant (the resource class name), and the specific instance to be tracked. The function call typically provides the name of the specific instance (param).
Identifying the instance of the resource. If a resource is not held "in" a variable, another variable is often used to identify the instance of the resource that is wanted. Many of the resource annotation macros take a parameter that identifies the instance that is being allocated. This parameter is similar to the parameter to the _ecount annotation modifier, in that the annotation does not apply to the parameter itself; instead, the value of the parameter modifies the annotation of some other parameter (or the function as a whole).
For example, consider a resource that has no associated data, such as "the right to use I/O register n." In principle, if some function acquires that resource but does not release it, the resource can leak. For PFD to detect a leak of the resource, it must be able to identify the instance of the resource that is being requested or is owned by a function. That is, for the I/O register, it is necessary to annotate "the right to use I/O register n" as a resource.
In terms of the function that acquires the right to use that resource, n is a parameter to the acquiring function, and it is necessary to create an object that represents "the right to use I/O register n" to PFD. However, there is no object in the function that is being analyzed that holds "the right to use I/O register n," so there is nothing PFD can overload during simulation to hold that concept.
Annotations that are conditional on holding (or not holding) a resource can be written by using the holdsResource$(kind) and holdsResourceGlobal$(kind, param) annotations. For more information, see Conditional Annotations,
Example annotation for acquiring a global resource. The following example shows how to map the identification of a specific instance to a class name by using __drv_acquiresResourceGlobal. In this example, IORegister is the class name, and the regnum parameter identifies the instance of the resource.
__checkReturn
__drv_acquiresResourceGlobal(IORegister, regnum)
NTSTATUS acquireIORegister(int regnum);
In this example, a hypothetical function called acquireIORegister puts a name into some private namespace (not accessible to PFD) where the right to use IORegister regnum is held. When the right to use the register is relinquished, the symbol then indicates that the right is no longer held. This serves the same kind of purpose as the pointer to memory returned by malloc, without introducing anything into the actual source code of the function that is being analyzed. Note that __drv_acquiresResourceGlobal does not annotate the regnum parameter in any way. Instead, regnum (or, rather, its value as simulated by PFD) is a parameter to the annotation, just as the length parameter of _bcount(length) is a parameter to the _bcount annotation.
The Critical Region and Cancel Spin Lock
Certain resources, such as the critical region or the cancel spin lock, have no programmatic name; they simply exist. Only one instance of these resources can exist at a time. The same concept that was used in __drv_acquiresResourceGlobal, described earlier, can be used to annotate these resources, but the specific instance part of the name (the second parameter) can be omitted. In practice, special-purpose macros are used for this kind of resource.
As a special case for most drivers, two global resources are accessed by context: the critical region and the cancel spin lock. Neither has any name or parameter value. (KeEnterCriticalReqion has no parameters and no result value; it simply blocks until it succeeds.)
This can be expressed with the more generic annotations, but it is generally more convenient (and recommended) to use the following annotations:
__drv_acquiresCriticalRegion
__drv_releasesCriticalRegion
__drv_acquiresCancelSpinLock
__drv_releasesCancelSpinLock
These annotations also specify that the resource cannot already be held when the acquire operation occurs, and the resource must already be held when the release operation occurs. For more information, see Holding Nonmemory Resources later in this topic.
The following code example indicates that the function is acquiring the critical region.
__drv_acquiresCriticalRegion
VOID
KeEnterCriticalRegion(
);
The __drv_acquiresCriticalRegion annotation is the preferred way to express that the function is acquiring the critical region and it should be used instead of the following:
__drv_acquiresResourceGlobal(CriticalRegion, "").
You can use conditional annotations to indicate that acquisition of a resource might fail. For nonmemory resources, the indication of success or failure is usually separated from the resource. For example, it is only when KeTryToAcquireSpinLockAtDpcLevel returns TRUE that the spin lock is acquired.
NTKERNELAPI
BOOLEAN
__drv_valueIs(==0;==1)
FASTCALL
KeTryToAcquireSpinLockAtDpcLevel (
__inout
__drv_when(return==1,
__drv_deref(__drv_acquiresResource(SpinLock)))
PKSPIN_LOCK SpinLock
);
Holding Nonmemory Resources
A number of functions require that a resource be held (or not be held) before the function is called. The simplest examples are the special functions for critical regions and the cancel spin lock, in which the resource is about to be acquired or released. However, a number of other functions do not operate correctly unless the proper resources are held (or not held). For example, the function ExAcquireResourceExclusiveLite must be called with the critical region held.
The following annotations indicate that a resource must be held or not held:
__drv_mustHold(kind)
__drv_neverHold(kind)
__drv_mustHoldGlobal(kind, param)
__drv_neverHoldGlobal(kind, param)
__drv_mustHoldCriticalRegion
__drv_neverHoldCriticalRegion
__drv_mustHoldCancelSpinLock
__drv_neverHoldCancelSpinLock
The annotations __drv_mustHold(kind) and __drv_neverHold(kind) can be applied to a function as a whole (rather than to a particular parameter). In that case, it indicates that at least one resource of kind must be held, or that no resource of the kind can be held, respectively. For example, IoCompleteRequest cannot be called with any spin lock held, so __drv_neverHold(SpinLock) is used to indicate that no spin lock can be held when that function is called. The annotations __drv_mustHold(Memory) and __drv_neverHold(Memory) are special cases that indicate that the object that is held is a memory object (from a function such as malloc or new.)
Use the annotations __drv_mustHoldGlobal(kind, param**)** and __drv_neverHoldGlobal(kind, param**)** to specifiy that a global resource must be held or not held. The parameters kind and param have the same meaning as described earlier for acquiring and releasing global resources.
The annotations __drv_mustHoldCriticalRegion, __drv_neverHoldCriticalRegion,__drv_mustHoldCancelSpinLock, and __drv_neverHoldCancelSpinLock apply to the critical region and cancel spin lock, respectively.
__drv_mustHoldCriticalRegion
BOOLEAN
ExAcquireResourceExclusiveLite(
__in PERESOURCE Resource,
__in BOOLEAN Wait
);
__drv_neverHold(SpinLock)
VOID
IoCompleteRequest(
__in PIRP Irp,
__in CCHAR PriorityBoost
);
The previous examples for the following functions do not include the annotations that are necessary to prevent double-take or double-release.
VOID
KeAcquireSpinLock(
__inout
__deref(__drv_acquiresResource(SpinLock)
__drv_neverHold(SpinLock))
PKSPIN_LOCK SpinLock,
__out PKIRQL OldIrql
);
VOID
KeReleaseSpinLock(
__inout
__deref(__drv_releasesResource(SpinLock)
__drv_mustHold(SpinLock))
PKSPIN_LOCK SpinLock,
__in KIRQL NewIrql
);
Composite Annotations for Resources
The following composite annotations are used to annotate resource annotation functions similar to spin lock wrapper functions, in which the resource can have only one owner:
__drv_acquiresExclusiveResource(kind)
__drv_releasesExclusiveResource(kind)
__drv_acquiresExclusiveResourceGlobal(kind, param)
__drv_releasesExclusiveResourceGlobal(kind, param)
__drv_acquiresExclusiveResource(kind)
This annotation specifies that the resource kind is being acquired and cannot already be held.__drv_releasesExclusiveResource(kind)
This annotation specifies that the resource kind is being released and must already be held.__drv_acquiresExclusiveResourceGlobal(kind, param**)**
This annotation specifies that the instance param of global resource kind is being acquired and cannot already be held.__drv_releasesExclusiveResourceGlobal(kind, param**)**
This annotation specifies that the instance param of global resource kind is being released and must already be held.
For example, the following annotation indicates that the function is acquiring MySpinLock, which must not already be held.
VOID
GetMySpinLock(
__inout
__deref(__drv_acquiresExclusiveResource(MySpinLock))
PKSPIN_LOCK SpinLock,
__out PKIRQL OldIrql
);
Send comments about this topic to Microsoft
Build date: 5/3/2011