Driver Annotations


Inside Out 

Driver annotations enhance the ability of PREfast to find some very specific kinds of errors in driver source code. Driver annotations are intended to complement the general-purpose annotations described earlier in this chapter. The driver annotations are defined in %wdk%\inc\ddk\Driverspecs.h and begin with the prefix __drv.

Although the driver annotations were initially designed for WDM kernel-mode drivers, with few exceptions they apply at a low enough level that it does not matter which driver model you're using. Many of the driver annotations can also be used to annotate general kernel-mode code or UMDF drivers. Most of the examples in this section are drawn from WDM drivers, but the usages they illustrate should also work for WDF drivers. A few annotations make sense only for kernel-mode code and thus do not make sense for UMDF drivers. These annotations are noted in this chapter.

Driver annotations use a slightly different syntax than the general-purpose annotations such as __in and __out. Some driver annotations identify the exact object that is being annotated. For example, __drv_arg applies a list of annotations to a named formal parameter to a function. Other driver annotations describe the semantics of a function. For example, __drv_MaxIRQL indicates the maximum interrupt level at which a function can be called.

All driver annotations either implicitly or explicitly apply to a specific element of the function that is being annotated. The specific element can be one of the following:

  • The function itself (that is, the global state of the function).

  • A function parameter.

  • The function's return value.

  • The this pointer in C++ code.

A driver annotation can apply at any level of dereference, equivalent to the _deref general-purpose partial annotation. The annotation can apply to the pre-state or post-state condition of the function or parameter.

Important 

You can combine lower-level general-purpose annotations such as __notnull with driver annotations. However, you should not combine composite general-purpose annotations such as those built up from __in, __out, and __inout with driver annotations. For example, a composite general-purpose annotation such as __inout_ecount_part should appear separately from driver annotations-either adjacent to them or on a separate line-and should not be concatenated with a driver annotation or included in an annotation list.

Table 23-3 summarizes the driver annotations, in the order in which they are discussed in this chapter.

Table 23-3: Driver Annotations
Open table as spreadsheet

Annotation

Usage

__drv_arg(arg, anno_list)

__drv_arg(__param(n), anno_list)

__drv_deref(anno_list)

__drv_fun(anno_list)

__drv_in(anno_list)

__drv_in_deref(anno_list)

__drv_out(anno_list)

__drv_out_deref(anno_list)

__drv_ret(anno_list)

Basic driver annotations

__drv_when(cond, anno_list)

Conditional annotations

__drv_valueIs(list)

Function result annotations

__drv_strictTypeMatch(mode)

__drv_strictType(typename, mode)

Type annotations

__drv_notPointer

__drv_isObjectPointer

Pointer annotations

__drv_constant

__drv_nonConstant

Constant and nonconstant parameter annotations

__drv_formatString(kind)

Format string annotations

__drv_preferredFunction(name, reason)

__drv_reportError(string)

Diagnostic annotations

__drv_inTry

__drv_notInTry

Annotations for functions in __try statements

__drv_allocatesMem(type)

__drv_freesMem(type)

__drv_aliasesMem

Memory annotations

__drv_acquiresResource(kind)

__drv_releasesResource(kind)

__drv_acquiresResourceGlobal(kind, param)

__drv_releasesResourceGlobal(kind, param)

__drv_acquiresCriticalRegion

__drv_releasesCriticalRegion

__drv_acquiresCancelSpinLock

__drv_releasesCancelSpinLock

__drv_mustHold(kind)

__drv_neverHold(kind)

__drv_mustHoldGlobal(kind, param)

__drv_neverHoldGlobal(kind, param)

__drv_mustHoldCriticalRegion

__drv_neverHoldCriticalRegion

__drv_mustHoldCancelSpinLock

__drv_neverHoldCancelSpinLock

__drv_acquiresExclusiveResource(kind)

__drv_releasesExclusiveResource(kind)

__drv_acquiresExclusiveResourceGlobal(kind, param)

__drv_releasesExclusiveResourceGlobal(kind, param)

Nonmemory resource annotations

__drv_functionClass

Function class annotations

__drv_floatSaved

__drv_floatRestored

Floating point annotations

__drv_maxIRQL(value)

__drv_minIRQL(value)

__drv_setsIRQL(value)

__drv_requiresIRQL(value)

__drv_raisesIRQL(value)

__drv_savesIRQL

__drv_restoresIRQL

__drv_savesIRQLGlobal(kind, param)

__drv_restoresIRQLGlobal(kind, param)

__drv_minFunctionIRQL(value)

__drv_maxFunctionIRQL(value)

__drv_sameIRQL

__drv_isCancelIRQL

IRQL annotations

__drv_clearDoInit

DO_DEVICE_INITIALIZING annotation

__drv_interlocked

Annotations for interlocked operands

Basic Driver Annotations and Conventions

You can use the basic driver annotations in Table 23-4 throughout a driver. The anno_list argument consists of one or more annotations in a space-separated list.

Table 23-4: Basic Driver Annotations
Open table as spreadsheet

Annotation

Description

__drv_arg(arg, anno_list)

__drv_arg(__param(n), anno_list)

Indicates that the annotations in anno_list apply to arg, which is a named formal parameter to the function. The arg can be a parameter name or the C++ this pointer. You can use the indirection operator such as "*" to specify the level of dereference that is being annotated. You can use the annotation __param(n) in place of arg to specify a 1-based parameter position. For example, __param(3) indicates that the annotation applies to the third parameter to the function.

__drv_deref(anno_list)

Indicates that an additional level of dereference should be applied to the annotation. This annotation is equivalent to __drv_arg(*__param(n), anno_list); that is, it applies to the dereferenced value of n.

__drv_fun(anno_list)

Indicates that the annotations in anno_list apply to the function as a whole. That is, the annotation applies to some property of the global state of the calling function.

__drv_in(anno_list)

Indicates that anno_list applies on input-a precondition.

__drv_in_deref(anno_list)

Is equivalent to __drv_in(__drv_deref(anno_list)). This annotation is provided as a convenience.

__drv_out(anno_list)

Indicates that anno_list applies on output-a post-condition.

__drv_out_deref(anno_list)

Is equivalent to __drv_out(__drv_deref(anno_list)). This annotation is provided as a convenience.

__drv_ret(anno_list)

Indicates that the annotations in anno_list apply to the function return value.

The following conventions are used for annotation lists and for nesting annotations.

Annotation Lists for Drivers

An annotation list can contain one or more of the following:

  • Positioning annotations, such as __deref, __drv_deref, and __drv_arg.

  • Conditional annotations, specified with __drv_when.

  • General-purpose annotations, such as __notnull.

  • Other annotations that make sense for the function or parameter.

Annotation lists are frequently used with the __drv_arg annotation, which you can use to place the annotations that apply to a function parameter at the beginning of a function, instead of inline with the parameter. For example, the following __drv_arg annotation applies to a function parameter named NumberOfBytes. The annotation list consists of a single annotation, __in, as shown in the following example:

 __drv_arg(NumberOfBytes, __in) 

The following example shows another __drv_arg annotation that applies to a function parameter named *pBuffer:

 __drv_arg(*pBuffer,     __drv_neverHold(EngFloatState)     __drv_notPointer) 

The annotation list consists of two annotations: __drv_neverHold(EngFloatState), which specifies that the floating-point state should not be held when the function is called, and __drv_notPointer, which specifies that *pBuffer should point directly to memory. These annotations are explained in more detail in "Examples of Annotated System Functions," later in this chapter (see also Listing 23-32).

Other driver annotations that take annotation lists include __drv_in and __drv_out. For example, the following annotation list indicates that both the annotated output parameter and its first level of dereference must not be NULL. This annotation list would be placed inline with a function parameter, as shown in the following example:

 __drv_out(__notnull __deref(__notnull)) 

Note the use of __deref to apply the second __notnull annotation to the dereferenced value of the parameter.

Annotation lists can become unwieldy, especially when you are applying annotations at various levels of dereference. For example, suppose you want to annotate a ***p parameter as an input parameter that must not be NULL.

If you place the annotations inline, the annotation would look like the following, which is extremely difficult to read:

 __drv_in(__drv_deref(__drv_deref(__drv_deref(__notnull)))) char ***p 

Notice the three nested __drv_deref annotations, which are necessary to apply the __notnull annotation to ***p.

If you use a __drv_arg annotation instead, the annotation becomes much simpler to read, as shown in the following example:

 __drv_arg(***p, __drv_in(__notnull)) char ***p 

This __drv_arg annotation associates __drv_in(__notnull)) with ***p, instead of using __drv_deref three times to dereference ***p.

Nesting Annotations

You can combine the basic driver annotations by nesting them. Because there is often more than one way to express the same annotation, the choice is usually based on readability.

Each of the following five examples does exactly the same thing: applies the annotations in anno_list to the p1 parameter of xyz type at one level of dereference. These examples also show how to compose the annotation and how to separate a complex annotation from the parameter to which it applies.

The following two examples apply the annotations in anno_list to parameter p1of type xyz at one level of dereference. Both of these annotations must be placed inline with the parameter declaration:

 __drv_in_deref(anno_list) xyz p1; __drv_in(__drv_deref(anno_list)) xyz p1; 

The following three examples are equivalent to the two preceding examples, but they can be placed anywhere that an annotation for that function is valid. They are not required to be near the declaration of p1:

 __drv_arg(p1, __drv_in(__drv_deref(anno_list))) __drv_arg(*p1, __drv_in(anno_list)) __drv_arg(p1, __drv_in_deref(anno_list)) 

See "Examples of Annotated System Functions" later in this chapter for additional examples of nesting annotations.

Conditional Annotations

Some functions have complex interfaces in which some aspect of the interface is applicable only in certain circumstances. For such functions, applying annotations unconditionally can create a situation in which, no matter how the annotations are written, PREfast issues some false positives for perfectly good code.

You can use the __drv_when(cond, anno_list) annotation to conditionally apply annotations. This annotation specifies that the annotations in anno_list should be checked only if the cond conditional expression is true. In a __drv_when annotation:

  • Cond is a condition that is evaluated according to C syntax.

    If the condition cannot be evaluated to a constant, then PREfast simulates the function with both the true and false possibilities.

  • Anno_list is a list of appropriate annotations, as described in "Annotation Lists for Drivers" earlier in this chapter.

You can freely mix __drv_when with the annotations that are described in "Basic Driver Annotations and Conventions" earlier in this chapter.

For example, a primary use of a condition is to indicate that, if a called function is successful, it acquires some resource-either memory or another resource type. If the called function is unsuccessful, it does not acquire the resource and thus the resource cannot leak. This information is particularly important for functions that return NTSTATUS to indicate success because the resource that is acquired might not change in any detectable way. Only the function's status value indicates success or failure. See "Memory Annotations" later in this chapter for more about annotations for resources.

One of the more common examples of a function that benefits from conditional annotation is ExAcquireResourceExclusiveLite, as shown in the following example:

 BOOLEAN   ExAcquireResourceExclusiveLite(      __in PERESOURCE Resource,      __in BOOLEAN Wait); 

If Wait is true, then the function's BOOLEAN return value is not required to be checked. If Wait is false, however, the return value must always be checked. One approach would be to write code that checks when Wait is true, but this is both irritating and confusing. However, failing to check when Wait is false could result in a serious bug.

For the case in which Wait is false, PREfast requires the __checkReturn annotation for good analysis. To avoid checking the return value when Wait is true, the example in Listing 23-10 uses __drv_when to make the __checkReturn conditional.

Listing 23-10: Example of ExAcquireResourceExclusiveLite with conditional annotation

image from book
 __drv_when(!Wait, __checkReturn) BOOLEAN   ExAcquireResourceExclusiveLite(     __in PERESOURCE Resource,     __in BOOLEAN Wait); 
image from book

Examples of Nested Conditional Annotations

The following example shows nesting of driver annotations in a single condition. The annotations indicate that when Wait is true, p must not be NULL:

 __drv_when(Wait, __drv_arg(p, __drv_in(__drv_deref(__notnull))) 

Starting from the end of the annotation in this example:

  • __drv_in(__drv_deref(__notnull)) is the list of annotations to apply to p. These annotations specify that p should be checked on input and that the dereferenced value of p must not be NULL.

  • The enclosing __drv_arg annotation identifies p as the argument associated with the list of annotations.

  • The enclosing __drv_when specifies that, when Wait is true, the annotations in __drv_arg should be applied to p.

__drv_when annotations can be nested. The inner annotation is applied when both the inner and outer conditions are true. The following example shows nesting of two conditional annotations. These annotations indicate that that when Wait is true, p must not be NULL and, if both Wait and p2 are true, p3 must not be NULL:

     __drv_when(Wait, __drv_arg(p, __drv_in(__notnull))         __drv_when(p2, __drv_arg(p3, __drv_in(__notnull)) 

Starting from the end of the second line of this example-the inner condition:

  • __drv_arg(p3, __drv_in(__notnull)) specifies the annotations to apply to p3.

  • __drv_in(__notnull) is the annotation list, which describes an input argument that must not be NULL.

    The enclosing __drv_arg(p3 ) annotation associates p3 with this annotation list.

  • The enclosing __drv_when(p2, ) annotation specifies that the annotations should be applied to p3 only when p2 is true.

Starting from the end of the first line of this example-the outer condition:

  • __drv_arg(p, __drv_in(__notnull)) specifies the annotations to apply to p.

  • __drv_in(__notnull) is the annotation list. The annotations in this list describe an input argument that must not be NULL.

    The enclosing __drv_arg(p ) annotation associates p with this annotation list.

  • The enclosing __drv_when(Wait ) annotation specifies that the annotations should be applied to p only when both Wait and p2 are true (that is, Wait && p2).

Grammar for Conditional Expressions

The grammar for conditional expressions in annotations is a subset of the grammar that the C programming language supports.

Conditional Grammar Supported

The grammar for conditional expressions supports the following:

  • The +, -, *, /, unary -, <<, >>, <, <=, ==, !=, >, >=, !, &&, ||, ~, &, |, ?:, (, and ) operators.

    PREfast supports these operators with the usual operator precedence, along with the $ functions that are discussed in "Special Functions in Conditions" later in this chapter.

  • All forms of integer constants.

  • Parameter names and the unary * operator on parameter names.

Evaluation of Expressions

When PREfast evaluates a condition expression, it:

  • Performs constant folding.

    That is, when PREfast can determine the value of a parameter, PREfast uses that value. As in C, PREfast converts expressions in a Boolean context to zero or nonzero. Expressions can use #defined constant names.

  • Carries out all calculations as wide signed integers.

    PREfast does not rely on implied truncation to a narrower word size.

  • Evaluates the expressions in each __drv_when annotation independently.

    For a given function, all, some, or none of the conditional expressions might be true, depending on context. If two conditions are meant to be mutually exclusive, they should be coded so that they actually are.

Unsupported Expressions

PREfast does not support the following:

  • Enumerated constants

  • Consts (in C++)

  • sizeof operator in conditions

Depending on the circumstances, it is often possible to work around these limitations by combining existing annotations.

Special Functions in Conditions

The following functions can be called from within a condition. PREfast executes these functions when it analyzes code with annotations that contain them:

  • strstr$(p1, p2)

  • macroDefined$(p1)

  • isFunctionClass$(name)

strstr$(p1, p2)

This function computes the same value as the strstr C function, except that the result is the offset in the p1 string where the first instance of p2 is found. If p2 is not found in p1, strstr$ returns -1. The p1 and p1 arguments can be either parameter expressions or literals.

The usual usage is to determine-to the extent that PREfast is capable-whether p1 contains the p2 constant. For example, the following function call determines whether path contains any backslash characters:

 strstr$(path, "\\")>=0 

The following function call determines whether path begins with "C:":

 strstr$(path, "C:")==0 

macroDefined$(p1)

This function determines whether the p1 string is a symbol that is defined with #define. p1 should be a quoted string. If p1 is not a defined symbol, the function returns 0. If p1 is a defined symbol, the function returns 1. You can use defined symbols directly in __drv_when conditions, but macroDefined$ is the only way for __drv_when to determine whether the symbol is defined.

macroDefined$ is functionally equivalent to the __drv_defined macro, which has the same syntax as macroDefined$ but handles quoted strings more reliably. Like macroDefined$, __drv_defined(XYZ) returns a Boolean value that indicates whether XYZ is a defined symbol in the program.

You can often express an annotation without using either __drv_defined or macroDefined$, simply by assuming that the macro is expanded by the compiler. If that does not work, use __drv_defined.

isFunctionClass$(name)

This function determines whether the annotated function belongs to the class that is identified by name. See "Special Functions in Conditions" later in this chapter for more information about isFunctionClass$.

Function Result Annotations

Many functions have a limited set of possible results for an output parameter or the function's return value. Informing PREfast of this often makes its analysis much more accurate because PREfast can avoid following impossible paths. For example, a function that returns a BOOLEAN is returning an integer, which might be any value. The BOOLEAN type is simply a convention that indicates that the value should be only TRUE or FALSE.

You can use the __drv_valueIs(list) annotation to indicate a set of possible result values for a function. The result for the output parameter or function return value must be one of the values in list, which consists of a series of partial expressions in the form <relational operator><constant>, separated by semicolons.

For example, consider the following code:

 BOOLEAN b = boolfunc(...);     if (b == TRUE) {         // do something     } ...     if (b == FALSE) {         // do something else     } 

PREfast interprets b as an integer, but it cannot determine that b can have only two possible values. Therefore, when analyzing this function, PREfast might simulate situations in which both statements are skipped-for example, if PREfast assumed that b was 3. This kind of situation can lead to both false positives and false negatives. For this example, the b parameter can be annotated with __drv_valueIs(==0; ==1), which limits b to FALSE or TRUE.

Functions that return NTSTATUS are typically annotated with __drv_valueIs(<0; ==0), which indicates the value range for failure and success.

A few functions might be better annotated with __drv_valueIs(<0;>=0). These functions can return a strictly positive value for NTSTATUS, which means that the set of possible results is all integers-those less than zero and those greater than or equal to zero. This annotation, which is effectively no annotation at all, is included here for completeness.

You can combine conditions with the __drv_valueIs annotation to limit the result values to those that are made possible by the input parameters. For example, the annotations that are applied to the return value for ExAcquireResourceExclusiveLite indicate that if Wait is 0, the function can return either 0 or 1, but if Wait is nonzero, the function can return only 1, as shown in the following example:

 __drv_when(!Wait, __drv_valueIs(==0; ==1)) __drv_when(Wait, __drv_valueIs(==1)) 

An alternative annotation would be as follows:

 __drv_when(!Wait, __drv_valueIs(==0;==1) __checkReturn) 

This annotation indicates that if Wait is false, the function can return 0 or 1 and the result must be checked. If Wait is nonzero, it does not matter what the function returns because the return value is not required to be checked.

Type-Matching Annotations

The C and C++ languages permit some mixing of integer types. In particular, it is acceptable to pass an enumerated type where a generic integer type is expected. However, for a number of functions, it is easy to pass the wrong enumerated type.

You can use the __drv_strictTypeMatch(mode) and __drv_strictType(typename, mode) annotations to ensure that PREfast checks whether a function is called with exactly the right type:

  • For __drv_stringTypeMatch, the actual parameter must be the same type as the formal parameter, within the limits set by mode.

  • For __drv_strictType, the parameter must be the type that is specified by typename, within the limits set by mode.

The mode argument can be one of the following:

__drv_typeConst

The parameter takes a single simple operand that must match exactly. This annotation is typically used where the value is a single constant.

__drv_typeCond

The annotated expression can use the ?: operator to select between single operands. Parentheses are also permitted. This annotation is typically used to switch dynamically among a few constant arguments.

__drv_typeBitset

The parameter can take expressions that involve only operands of that type. This annotation is typically used for bitsets, but is not limited to bit operations.

__drv_typeExpr

The parameter can take the same operands as a __drv_typeBitset annotation, plus literal constants. For example, the POOL_TYPE parameter to ExAllocatePool uses this annotation.

The __drv_strictType annotation is useful for functions that take integer parameters whose value should be limited to members of a particular enumerated type. If either a variable or a constant might be passed to the function, it is helpful to specify both the typedef name for variables and an enumerated type for constants. This can be done by giving the typedef name and the enumerated type name, separated by a slash, as shown in the following example:

 __drv_strictType(KPROCESSOR_MODE/enum _MODE, __drv_typeCond) 

The KeWaitForMultipleObjects function provides several examples of type annotations. This function has three parameters that are different enumerated types. However, the parameters are easily confused, and the C compiler does not check for correctness. With appropriate annotations, PREfast can check that the parameters are of the correct type. See the notes after Listing 23-11 for an explanation.

Listing 23-11: Example of type annotations applied to KeWaitForMultipleObjects

image from book
 NTSTATUS KeWaitForMultipleObjects(       __in ULONG Count,       __in PVOID Object[],       __in         __drv_strictTypeMatch(__drv_typeConst)  // 1      WAIT_TYPE WaitType,      __in         __drv_strictTypeMatch(__drv_typeConst)  // 2      KWAIT_REASON WaitReason,      __in         __drv_strictType(KPROCESSOR_MODE/enum _MODE,             __drv_typeCond) //                    3      KPROCESSOR_MODE WaitMode,       __in BOOLEAN Alertable,       __in_opt PLARGE_INTEGER Timeout,       __in_opt PKWAIT_BLOCK WaitBlockArray); 
image from book

In the example in Listing 23-11:

  1. The __drv_strictTypeMatch(__drv_typeConst) annotation indicates that WaitType must be a member of the WAIT_TYPE enumerated type.

  2. The __drv_strictTypeMatch(__drv_typeConst) annotation indicates that WaitReason must be a member of the KWAIT_REASON enumerated type.

  3. The __drv_strictType(KPROCESSOR_MODE/enum _MODE, __drv_typeCond) annotation indicates that WaitMode must be either a variable of a KPROCESSOR_MODE type or a member of the _MODE enumerated type and can be passed as an expression that uses the ?: operator.

Some additional considerations apply to WaitMode:

  • Constants of enum _MODE type are semantically reasonable, and it is semantically reasonable to want to select between them with the ?: operator. However, it is not reasonable to allow arithmetic on those constants. __drv_typeCond allows use of the ?: operator but does not allow the use of any other operators.

  • KPROCESSOR_MODE is defined to be a char, and enumeration values are defined by C to be the same as int. Thus, there cannot be a symbolic name for a constant of type KPROCESSOR_MODE. Instead, enum _MODE is the closest match.

Pointer Annotations

Certain driver functions take PVOID as a parameter type, so they can accept any one of a number of different types. It is common to mistakenly pass the wrong type, for example, to pass &pStruct instead of pStruct as intended. For any type but PVOID, the compiler would diagnose this as an error. For the PVOID type, the compiler cannot detect the error because it has no type information to check.

The __drv_notPointer and __drv_isObjectPointer annotations enable PREfast to diagnose errors when a parameter must not be a pointer:

  • __drv_notPointer indicates that the parameter must be a struct or scalar value.

    For example, a __drv_notPointer annotation specifies that (PVOID) 1 is acceptable but (PVOID) &var is not acceptable.

    The __drv_notPointer annotation typically appears as __drv_deref(__drv_notPointer), which indicates that the parameter should be a pointer to a nonpointer object, usually a structure, and not a pointer to a pointer. A common error that this annotation helps PREfast to detect is passing &pStruct when pStruct was intended because you have forgotten you were passed a pointer to a structure rather than the structure.

  • __drv_isObjectPointer indicates that the parameter must be a pointer to a nonpointer object.

    This annotation is a simpler equivalent to __drv_deref(__drv_notPointer).

For example, in Listing 23-12, the annotation on the Object parameter of KeWaitForSingleObject causes PREfast to issue a warning if the function is called with Object as a pointer to a pointer.

Listing 23-12: Example of annotations for a function that must be called with a pointer to a nonpointer object

image from book
 NTSTATUS   KeWaitForSingleObject(      __in __drv_isObjectPointer PVOID Object,      __in         __drv_strictTypeMatch(__drv_typeConst)     KWAIT_REASON WaitReason,      __in         __drv_strictType(KPROCESSOR_MODE/enum _MODE,         __drv_typeCond)     KPROCESSOR_MODE WaitMode,     __in BOOLEAN Alertable,     __in_opt PLARGE_INTEGER Timeout     ); 
image from book

Constant and Non-Constant Parameter Annotations

You can use the __drv_constant and __drv_nonConstant annotations for functions that either require or prohibit the use of literal constants as parameters.

For example, a device driver should not assume that any port address is a constant. Therefore, the various READ_PORT_Xxx functions should all be annotated with __drv_nonConstant for the address of the port being read. For the occasional exception to this, either ignore or suppress the PREfast warning.

Another example is the Wait parameter to KeSetEvent. Although theoretically that parameter might be a variable, the requirements for what must be done before and after the call make it difficult and possibly confusing to call KeSetEvent with a variable. Therefore, Wait is annotated with __drv_constant. Again, the occasional exception can be handled by ignoring or suppressing the PREfast warning.

Format String Annotations

The __drv_formatString(kind) annotation indicates that the annotated parameter is a format string. Kind can be printf or scanf, which refers to the type of format string that is allowed, not the specific function being called. That is, the format string follows the rules for either printf or scanf. __drv_formatString can be used to annotate any function that is similar to printf or scanf.

This annotation causes PREfast to check that the argument list matches the format string and that potentially dangerous combinations are avoided.

The following annotation indicates that format is a format string for printf:

 int _snprintf(     __out_ecount(count) __possibly_notnullterminated LPSTR buffer,     __in size_t count,     __in __drv_in(__drv_formatString(printf)) LPCSTR *format    [, argument] ... ); 

Diagnostic Annotations

Occasionally, a particular combination of parameters is either dangerous or can be done better in some other way. PREfast can check for many such usage errors.

You can use the __drv_preferredFunction(name, reason) and __drv_reportError(string) annotations to generate error messages. These annotations are typically used in combination with the __drv_when conditional annotation. You should use these annotations for recommendations and for annotating specific usages to avoid.

If a function must never be used under any circumstances, you should mark it with #pragma __deprecated or __declspec(deprecated) so that the compiler can generate a compile-time error.

Annotations for Preferred Functions

You can use the __drv_preferredFunction(name, reason) annotation to generate error messages. Name can be anything, but is usually the name of a preferred function, whereas reason is an additional explanation of why that function is preferred.

For example, consider two hypothetical functions, GetResource and TryToGetResource. GetResource takes a Wait parameter. When Wait is TRUE (that is, nonzero), a call should wait until the resource is acquired. When Wait is FALSE, GetResource can still be used to acquire the resource, but TryToGetResource is more efficient. The following annotations would cause PREfast to flag this circumstance:

 __drv_when(!Wait, __drv_preferredFunction(TryToGetResource, "When calling GetResource with Wait==FALSE," "TryToGetResource performs better.")) 

Annotations for Error Messages

The __drv_reportError(string) annotation causes PREfast to generate a warning that it has encountered the error that the annotation describes. The __drv_reportError annotation is similar to __drv_preferredFunction except that it generates a warning to fix the problem that the string parameter describes.

For example, you can use __drv_reportError for unacceptable usage such as an attempt to use a must-succeed allocation from the ExAllocatePool family of functions, as in the following example:

 __drv_when(PoolType&0x1f==2 || PoolType&0x1f==6, _drv_reportError("Must succeed pool allocations are" "forbidden. Allocation failures cause a system crash")) 

Annotations for Functions in __try Statements

Certain functions must always be called from inside a structured exception handling (SEH) __try statement, whereas other functions must never be called from inside a __try statement. The __drv_inTry and __drv_notInTry annotations cause PREfast to check for proper usage within a function:

  • __drv_inTry indicates that the function must be called from inside a __try statement.

  • __drv_notInTry indicates that the function must not be called from inside a __try statement.

For example, use __drv_inTry with ProbeForRead and ProbeForWrite so that failed attempts to access memory are caught by a __try statement.

Memory Annotations

Drivers often use special-purpose functions to allocate and free memory. Drivers also often pass memory out of a function in a way that causes the memory to be aliased, where it will be dealt with later. Annotations can help PREfast more accurately detect leaks and other problems with allocations of both memory and nonmemory resources such as spin locks.

You can use the memory annotations described in the following sections to help PREfast more accurately check functions that allocate memory.

Annotations for Allocating and Freeing Memory

The __drv_allocatesMem(type) annotation indicates that the output value is allocated, either through a parameter or through the function result. The type parameter indicates the type of memory allocator used. This parameter is advisory-PREfast does not check it. However, this parameter might be checked by a future version of PREfast. The following are recommended values for the type parameter:

  • For malloc and free, type should be mem.

  • For the new operator, type should be object.

If a function that allocates memory indicates failure by returning NULL, you should also apply the __checkReturn annotation to the function.

__drv_freesMem(type) indicates that the memory that is passed as an input parameter is freed. In post-state, PREfast assumes that the annotated parameter is in an uninitialized state and, until the parameter is changed, PREfast treats further access through the actual parameter as an access to an uninitialized variable. Type should match the type used in __drv_allocatesMem.

Annotations for Aliasing Memory

You can apply the __drv_aliasesMem annotation to input parameters-including __in and __out parameters-to indicate that the called function saves the value of the parameter in some location where it will be found later and presumably freed.

In general, PREfast cannot confirm whether memory that is aliased is actually freed. The memory might continue to be accessed after a call to a function with this annotation. PREfast tries to identify when memory is aliased in several different ways, but it cannot determine whether memory is aliased for called functions without this annotation.

__drv_aliasesMem helps to suppress false "possibly leaking memory" warnings from PREfast. It does not take a type parameter because it operates in the same way on all kinds of memory.

The __drv_freesMem and __drv_aliasesMem annotations are mutually exclusive. __drv_freesMem indicates that the memory is discarded (that is, the memory is no longer accessible), and PREfast enforces this by invalidating the variable that contains the pointer to the freed memory. __drv_aliasesMem indicates simply that there is no longer a risk of that memory leaking, but the memory continues to exist and can be accessed subsequently.

Memory Annotation Examples

The examples in this section show the use of memory annotations.

The example in Listing 23-13 shows some of the annotations that are applied to the ExAllocatePool and ExFreePool functions, which are the classic examples of functions that allocate and free memory. These functions have additional parameter checks that are not shown in this listing.

Listing 23-13: Example of annotations for functions that allocate and free memory

image from book
 __checkReturn __drv_allocatesMem(Pool) __drv_when(PoolType&0x1f==2 || PoolType&0x1f==6, __drv_reportError("Must succeed pool allocations are" "forbidden. Allocation failures cause a system crash")) __bcount(NumberOfBytes) PVOID   ExAllocatePoolWithTag(     __in __drv_strictTypeMatch(__drv_typeExpr)     POOL_TYPE PoolType,     __in SIZE_T NumberOfBytes,     __in ULONG Tag     ); NTKERNELAPI VOID   ExFreePoolWithTag(     __in __drv_in(__drv_freesMem(Pool))     PVOID P,     __in ULONG Tag     ); 
image from book

In the example in Listing 23-14, the InsertHeadList function takes and holds Entry. That is, it aliases the memory that is occupied by Entry. The __drv_aliasesMem annotation in this example suppresses a potential memory leak warning from PREfast, but leaves Entry accessible.

Listing 23-14: Example of annotations for a function that aliases memory

image from book
 VOID   InsertHeadList(     __in PLIST_ENTRY ListHead,     __in __drv_in(__drv_aliasesMem) PLIST_ENTRY Entry     ); 
image from book

Nonmemory Resource Annotations

A number of nonmemory resource types, such as critical regions and spin locks, can leak. PREfast can detect leaks of nonmemory resources just as it can detect memory leaks. However, PREfast cannot apply memory semantics to nonmemory objects because semantic differences between the two types of objects would lead to various kinds of incorrect analysis. For example, there is no concept of aliasing for nonmemory resources. In general, if a resource must be aliased, it is better modeled as memory.

The following sections describe annotations related to nonmemory resource types, as summarized in Table 23-5.

Table 23-5: Nonmemory Resources Annotations
Open table as spreadsheet

Annotation

Usage

__drv_acquiresResource(kind)

__drv_releasesResource(kind)

Annotations for acquisition and release of nonmemory resources

__drv_acquiresResourceGlobal(kind, param)

__drv_releasesResourceGlobal(kind, param)

Annotations for global nonmemory resources

__drv_acquiresCriticalRegion

__drv_releasesCriticalRegion

__drv_acquiresCancelSpinLock

__drv_releasesCancelSpinLock

Annotations for the critical region and cancel spin lock

__drv_mustHold(kind)

__drv_neverHold(kind)

__drv_mustHoldGlobal(kind, param)

__drv_neverHoldGlobal(kind, param)

__drv_mustHoldCriticalRegion

__drv_neverHoldCriticalRegion

__drv_mustHoldCancelSpinLock

__drv_neverHoldCancelSpinLock

Annotations for holding and not holding nonmemory resources

__drv_acquiresExclusiveResource(kind)

__drv_releasesExclusiveResource(kind)

__drv_acquiresExclusiveResourceGlobal(kind, param)

__drv_releasesExclusiveResourceGlobal(kind, param)

Composite annotations for resources

Annotations for Acquisition and Release of Nonmemory Resources

You can use the __drv_acquiresResource(kind) and __drv_releasesResource(kind) annotations to indicate the acquisition and release of nonmemory resources in a function parameter.

For these annotations, the kind parameter indicates the kind of resource. The value of kind in acquisitions and releases of the resource must match. The kind parameter can be any name. A few names 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.

For example, in Listing 23-15, the functions acquire and release a resource that is named SpinLock. The value is held in a variable named SpinLock. The symbols are in different name spaces, so they do not conflict.

Listing 23-15: Example of annotations for functions that acquire and release a resource

image from book
 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     ); 
image from book

Annotations for Global Nonmemory 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.

You can use the __drv_acquiresResourceGlobal(kind, param) and __drv_releasesResourceGlobal(kind, param) annotations to indicate acquisition and release of this kind of resource for a function.

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.

Annotations for Naming a Resource

You can use the __drv_acquiresResourceGlobal annotation to create a name for annotating a nonmemory resource that is not held in a variable. The annotation takes the following two parameters:

  • kind A known string constant (that is, the resource class name) for the kind of object (that is, the resource) to be tracked.

  • param The specific instance to be tracked. The function call typically provides the name of the specific instance (that is, param).

Annotations for Identifying an Instance of a 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 size parameter to the _ecount(size) 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 PREfast 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 PREfast. However, no object in the function that is being analyzed holds "the right to use I/O register n," so there is nothing PREfast can overload during simulation to hold that concept.

The example in Listing 23-16 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.

Listing 23-16: Example of annotations for a function that acquires a global resource

image from book
 __checkReturn __drv_acquiresResourceGlobal(IORegister, regnum) NTSTATUS acquireIORegister(int regnum); 
image from book

The acquireIORegister function puts a name into some private name space that is not accessible to PREfast 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 PREfast-is a parameter to the annotation, just as the size parameter of _bcount(size) is a parameter to the _bcount annotation.

Annotations for the Critical Region and Cancel Spin Lock

The following annotations can be used to annotate functions that acquire or release the critical region or cancel spin lock:

  • __drv_acquiresCriticalRegion

  • __drv_releasesCriticalRegion

  • __drv_acquiresCancelSpinLock

  • __drv_releasesCancelSpinLock

Certain resources, such as the critical region and the cancel spin lock, have no programmatic name-they simply exist. Only one instance of these resources can exist at one time. The same concept that was used in __drv_acquiresResourceGlobal, as described in the previous section, can be used to annotate these resources, but the specific instance part of the name (that is, the second parameter) is implied by the macro name. In practice, special-purpose macros are used for this kind of resource.

As a special case for most drivers, the critical region and the cancel spin lock are two global resources that are accessed by context. Neither resource has any name or parameter value. KeEnterCriticalReqion has no parameters and no result value-it simply blocks until it succeeds.

You could express this with the more generic annotations, but it is recommended that you use the critical region and cancel spin lock annotations. 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 "Annotations for Holding and Not Holding Nonmemory Resources" later in this chapter.

The example in Listing 23-17 indicates that the function is acquiring the critical region. This annotation is the preferred way of expressing __drv_acquiresResourceGlobal(CriticalRegion, "").

Listing 23-17: Example of annotations for a function that acquires the critical region

image from book
 __drv_acquiresCriticalRegion VOID   KeEnterCriticalRegion(      ); 
image from book

You can use conditional annotations to indicate that acquisition of a resource might fail, as described in "Conditional Annotations" earlier in this chapter. For nonmemory resources, the indication of success or failure is usually separated from the resource. For example, as shown in Listing 23-18, it is only when KeTryToAcquireSpinLockAtDpcLevel returns TRUE that the spin lock is acquired.

Listing 23-18: Example of annotations for a function that might fail to acquire a resource

image from book
 NTKERNELAPI BOOLEAN __drv_valueIs(==0;==1) FASTCALL   KeTryToAcquireSpinLockAtDpcLevel (     __inout     __drv_when(return==1,              __drv_deref(__drv_acquiresResource(SpinLock)))     PKSPIN_LOCK SpinLock     ); 
image from book

Annotations for Holding and Not Holding Nonmemory Resources

The following annotations can be used to specify resources that either should or should not be held by a function:

  • __drv_mustHold(kind)

  • __drv_neverHold(kind)

  • __drv_mustHoldGlobal(kind, param)

  • __drv_neverHoldGlobal(kind, param)

  • __drv_mustHoldCriticalRegion

  • __drv_neverHoldCriticalRegion

  • __drv_mustHoldCancelSpinLock

  • __drv_neverHoldCancelSpinLock

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, ExAcquireResourceExclusiveLite must be called with the critical region held.

Annotations for Holding Nonmemory Resources in a Function

The __drv_mustHold(kind) and __drv_neverHold(kind) annotations can be applied to a function as a whole, rather than to a particular parameter. These annotations indicate that the function must hold at least one resource of kind or no resource of kind, 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 __drv_mustHold(Memory) and __drv_neverHold(Memory) annotations are special cases that indicate that the object being held is a memory object, from a function such as malloc or new.

Annotations for Holding a Global Nonmemory Resource

The __drv_mustHoldGlobal(kind, param) and __drv_neverHoldGlobal(kind, param) annotations are used to indicate that a global resource must be held or not held. Kind and param have the same meaning as described earlier for acquiring and releasing global resources.

Annotations for Holding the Critical Region or Cancel Spin Lock

The following annotations apply to the critical region and cancel spin lock, respectively:

  • __drv_mustHoldCriticalRegion

  • __drv_neverHoldCriticalRegion

  • __drv_mustHoldCancelSpinLock

  • __drv_neverHoldCancelSpinLock

The examples in Listing 23-19, 23-20, and 23-21 show the use of these annotations.

Listing 23-19: Example of annotations for a function that must hold the critical region

image from book
 __drv_mustHoldCriticalRegion BOOLEAN   ExAcquireResourceExclusiveLite(     __in PERESOURCE Resource,     __in BOOLEAN Wait     ); 
image from book

Listing 23-20: Example of annotations for a function that must never hold a spin lock

image from book
 __drv_neverHold(SpinLock) VOID   IoCompleteRequest(     __in PIRP Irp,     __in CCHAR PriorityBoost     ); 
image from book

The example in Listing 23-21 includes the annotations that are necessary to prevent taking or releasing a resource more than once, which was not shown in earlier examples.

Listing 23-21: Example of annotations for functions that must not take or release a resource more than once

image from book
 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     ); 
image from book

Composite Annotations for Resources

The following annotations can be used to annotate functions that allocate resources:

  • __drv_acquiresExclusiveResource(kind)

  • __drv_releasesExclusiveResource(kind)

  • __drv_acquiresExclusiveResourceGlobal(kind, param)

  • __drv_releasesExclusiveResourceGlobal(kind, param)

These composite annotations are used to annotate resource allocation functions which are similar to spin lock wrapper functions in which the resource can have only one owner:

  • The "acquires" forms combine the __drv_neverHold and __drv_acquiresResource annotations.

  • The "releases" forms combine the __drv_mustHold and __drv_releasesResource annotations.

The following list summarizes the behavior that these annotations specify:

__drv_acquiresExclusiveResource(kind)

The kind resource is being acquired and cannot already be held.

__drv_releasesExclusiveResource(kind)

The kind resource is being released and must already be held.

__drv_acquiresExclusiveResourceGlobal(kind,param)

The param instance of the kind global resource is being acquired and cannot already be held.

__drv_releasesExclusiveResourceGlobal(kind,param)

The param instance of the kind global resource is being released and must already be held.

For example, the annotation in Listing 23-22 indicates that the function is acquiring MySpinLock, which must not already be held.

Listing 23-22: Example of a composite annotation for acquiring a spin lock

image from book
 VOID   GetMySpinLock(      __inout        __deref(__drv_acquiresExclusiveResource(MySpinLock))     PKSPIN_LOCK SpinLock,     __out PKIRQL OldIrql     ); 
image from book

Function Type Class Annotations

Drivers commonly define functions that the system calls by using a function pointer that the driver passes to the system. A driver's AddDevice, StartIo, and Cancel functions are examples of this kind of callback function. Many of the annotations that are discussed in this chapter apply to these callback functions.

Properly annotated callback functions significantly help PREfast to check for proper usage. This can be done by declaring functions to be members of a function type class.

Inside Out 

Most common system-defined callback types have a function class. System-defined function type classes for WDM drivers are defined in %wdk%\inc\ddk\Wdm.h. PREfast can detect function type class annotations for WDM drivers. Class annotations for KMDF drivers are defined in %wdk%\inc\wdf\kmdf\wdfroletypes.h. The ability to detect these role types is enabled in Static Driver Verifier and planned for a future version of PREfast.

You can define your own function type classes by using typedef declarations, as described in "Annotations on Function Typedef Declarations" earlier in this chapter.

Annotations for Identifying the Function Type Class of a Function

The __drv_functionClass(name) annotation indicates that the function or function typedef declaration is a member of the named class of functions that is represented by name. This annotation is most useful when applied to both the function and the function pointer type. It can be most easily applied through the use of function typedef declarations.

The __drv_functionClass annotation propagates to the corresponding function pointer typedef declaration and causes PREfast to check that function assignments to and from function pointers use the same function class.

PREfast issues a warning in the following cases:

  • If a function does not have a function class.

  • If a function of the wrong function class is assigned to a function pointer that does have a function pointer class.

To fix this warning, add the appropriate function typedef declaration to your code. The PREfast error message text usually lists the function typedef declaration that you should use.

For example, in Listing 23-23, the __drv_functionClass annotation indicates that this is the function typedef declaration for the DRIVER_INITIALIZE function class. The typedef name and the class name are in different name spaces. The pointer PDRIVER_INITIALIZE is of the class DRIVER_INITIALIZE because it is derived from the DRIVER_INITIALIZE typedef declaration.

Listing 23-23: Example of annotation for a function of a specific function type class

image from book
 typedef __drv_functionClass(DRIVER_INITIALIZE) NTSTATUS DRIVER_INITIALIZE (     __in struct _DRIVER_OBJECT *DriverObject,     __in PUNICODE_STRING RegistryPath     ); typedef DRIVER_INITIALIZE *PDRIVER_INITIALIZE; 
image from book

Annotations for Checking a Function Type Class in a Conditional Expression

A few functions have annotations that apply only when the function is-or is not-called from a particular class of function that is specified by a __drv_functionClass annotation. This case can be annotated by adding a call to isFunctionClass$(name) as part of a conditional expression in a __drv_when annotation.

The isFunctionClass$(name) function determines whether the function belongs to the class that is identified by name:

  • If the function does not belong to the class, isFunctionClass$ returns 0.

  • If the function does belong to the class, isFunctionClass$ returns 1.

For example, isFunctionClass$("DRIVER_INITIALIZE") determines whether the function is a driver initialization routine type. See "Special Functions in Conditions" earlier in this chapter for information about other functions that you can use in conditions.

IoCreateDevice is an example of a special case that applies only to legacy drivers. In a legacy driver, the system allows IoCreateDevice to be called from within the DRIVER_INITIALIZE function and does not require it to explicitly keep track of the resulting device object. The system puts the device object in a location that the driver's Unload function can find, but PREfast cannot detect this and issues a warning.

The annotations in the following example prevent noise that is related to this special case when you run PREfast on a legacy driver:

 __drv_when(!isFunctionClass$("DRIVER_INITIALIZE")     && return == 0,__deref(__drv_allocatesMem(DeviceObject)) 

The return==0 clause is an example of a check for successful execution.

Floating-Point Annotations

For some processor families, particularly x86 processors, using floating point from within kernel code must be done only within the scope of functions that save and restore floating-point state. Violations of this rule can be difficult to find because they cause problems only sporadically at runtime. With the proper use of annotations, PREfast can detect the use of floating point in kernel-mode code and report an error if floating-point state is not properly protected. Floating-point rules are checked only for kernel-mode code.

You can apply the __drv_floatSaved and __drv_floatRestored annotations to a function parameter to indicate what it does with floating-point state. These annotations are already applied to KeSaveFloatingPoint state and KeRestoreFloatingPointState, along with annotations for acquiring and releasing resources to prevent leaks. The similar EngXxx functions are also annotated in this way. However, functions that wrap these functions should also use these annotations.

When PREfast discovers an apparently unprotected use of floating point, it issues a warning. If the entire function is called safely by some calling function, you can annotate the function with __drv_floatUsed, which suppresses the warning and also causes PREfast to check that the caller is safely using the function. Additional levels of __drv_floatUsed can be added as required. PREfast automatically provides the __drv_floatUsed annotation when either the function result or one of the function's parameters is a floating-point type, but you might find it useful to apply the annotation explicitly, as documentation.

For example, in Listing 23-24 the __drv_floatSaved annotation indicates that the floating-point state is stored in the FloatSave parameter of the KeSaveFloatingPointState system function.

Listing 23-24: Example of annotations that indicate where floating-point state is stored

image from book
 NTSTATUS   KeSaveFloatingPointState(     __out         __drv_deref(__drv_floatSaved)           PKFLOATING_SAVE FloatSave     ); 
image from book

In the example in Listing 23-25, the __drv_floatUsed annotation suppresses PREfast warnings about the use of floating-point state by the MyDoesFloatingPoint function. The annotation also causes PREfast to check that any calls to MyDoesFloatingPoint occur in a floating-point-safe context.

Listing 23-25: Example of annotation for a function that uses floating point

image from book
 __drv_floatUsed void     MyDoesFloatingPoint(arguments); 
image from book

IRQL Annotations

All kernel-mode drivers must consider IRQLs. When PREfast analyzes unannotated driver code, it attempts to infer the range of IRQL at which a function could be running and identify any inconsistencies.

IRQL annotations help PREfast make more accurate inferences about the range of IRQL at which a function should run. For example, a function can be annotated with the maximum IRQL at which it can be called. If that function is called at a higher IRQL, PREfast reports an error. The more annotations that are applied to driver functions, the better PREfast can make those inferences and the more accurately it can find errors.

IRQL parameter annotations interact with each other more than other annotations because the IRQL value is set, reset, saved, and restored by various function calls.

Table 23-6 lists the annotations that you can use to indicate correct IRQL for a function and its parameters.

Table 23-6: IRQL Annotations
Open table as spreadsheet

Annotation

Description

__drv_maxIRQL(value)

IRQL value is the maximum IRQL at which the function can be called.

__drv_minIRQL(value)

IRQL value is the minimum IRQL at which the function can be called.

__drv_setsIRQL(value)

The function returns at IRQL value.

__drv_requiresIRQL(value)

The function must be entered at IRQL value.

__drv_raisesIRQL(value)

The function exits at IRQL value, but it can only be called to raise-not lower-the current IRQL.

__drv_savesIRQL

The annotated parameter saves the current IRQL to restore later.

__drv_restoresIRQL

The annotated parameter contains an IRQL value from __drv_savesIRQL that is to be restored when the function returns.

__drv_savesIRQLGlobal(kind, param)

The current IRQL is saved into a location that is internal to PREfast from which the IRQL is to be restored. This annotation is used to annotate a function. The location is identified by kind and further refined by param.

__drv_restoresIRQLGlobal(kind, param)

The IRQL saved by the function annotated with __drv_savesIRQLGlobal is restored from a location that is internal to PREfast.

__drv_minFunctionIRQL(value)

IRQL value is the minimum value to which the function can lower the IRQL.

__drv_maxFunctionIRQL(value)

IRQL value is the maximum value to which the function can raise the IRQL.

__drv_sameIRQL

The annotated function must enter and exit at the same IRQL. The function can change the IRQL, but it must restore the IRQL to its original value before exiting.

__drv_isCancelIRQL

The annotated parameter is the IRQL passed in as part of the call to a DRIVER_CANCEL callback function. This annotation indicates that the function is a utility that is called from Cancel routines and that completes the requirements for DRIVER_CANCEL functions, including release of the cancel spin lock.

__drv_isCancelIRQL is a composite annotation that consists of __drv_useCancelIRQL plus several other annotations that ensure correct behavior of a DRIVER_CANCEL callback utility function.

__drv_useCancelIRQL

The annotated parameter is the IRQL value that should be restored by a DRIVER_CANCEL callback function.

__drv_useCancelIRQL by itself is only occasionally useful, for example, if the rest of the obligations described by __drv_isCancelIRQL have already been fulfilled in some other way.

Annotations for Specifying Maximum and Minimum IRQL

The __drv_maxIRQL(value) and __drv_minIRQL(value) annotations simply indicate that the function should not be called from an IRQL that is higher or lower than the specified value. For example, when PREfast sees a sequence of calls that do not change the IRQL, if it finds one with a __drv_maxIRQL value that is lower than a nearby __drv_minIRQL, PREfast reports a warning on the second call that it encounters. The error might actually occur in the first call-the warning indicates where the other half of the conflict occurred.

If the annotations on a function mention IRQL and do not explicitly apply __drv_maxIRQL, PREfast implicitly applies the __drv_maxIRQL(DISPATCH_LEVEL) annotation, which is typically correct with rare exceptions. Implicitly applying this annotation as the default eliminates a lot of annotation clutter and makes the exceptions more visible.

The __drv_minIRQL(PASSIVE_LEVEL) annotation is always implied because the IRQL can go no lower; consequently, there is no corresponding explicit rule about minimum IRQL. Very few functions have both an upper bound other than DISPATCH_LEVEL and a lower bound other than PASSIVE_LEVEL.

Some functions are called in a context in which the called function cannot safely raise the IRQL above some maximum or, more often, cannot safely lower it below some minimum. The __drv_maxFunctionIRQL and __drv_minFunctionIRQL annotations help PREfast find cases where this occurs unintentionally.

For example, functions of the DRIVER_STARTIO type are annotated with __drv_minFunctionIRQL(DISPATCH_LEVEL). This means that, during the execution of a DRIVER_STARTIO function, it is an error to lower the IRQL below DISPATCH_LEVEL. Other annotations indicate that the function must be entered and exited at DISPATCH_LEVEL.

Annotations for Specifying Explicit IRQL

A __drv_setsIRQL or __drv_requiresIRQL annotation helps PREfast better report an inconsistency that is discovered with ___drv_maxIRQL or __drv_minIRQL because PREfast then knows the IRQL.

Annotations for Raising or Lowering IRQL

The __drv_raisesIRQL annotation is similar to __drv_setsIRQL, but indicates that the function must be used only to raise IRQL and must not be used to lower IRQL, even if the syntax of the function would allow it. KeRaiseIrql is an example of a function that should not be used to lower IRQL.

Annotations for Saving and Restoring IRQL

This section discusses the following annotations for saving and restoring IRQL:

  • _drv_savesIRQL

  • __drv_restoresIRQL

  • __drv_savesIRQLGlobal(kind, param)

  • __drv_restoresIRQL Global(kind, param)

The __drv_savesIRQL and __drv_restoresIRQL annotations indicate that the current IRQL- whether it is known exactly or only approximately-is saved to or restored from the annotated parameter. The locations that are associated with __drv_savesIRQL and __drv_restoresIRQL are presumed to be some form of integer-that is, any integral type that the compiler allows. PREfast attempts to deal with that value as an integer where possible.

Some functions save and restore the IRQL implicitly. For example, ExAcquireFastMutex saves IRQL in an opaque location that is associated with the fast mutex object that the first parameter identifies. The saved IRQL is restored by the corresponding ExReleaseFastMutex for that fast mutex object. You can indicate these actions explicitly by using the __drv_savesIRQLGlobal and __drv_restoresIRQLGlobal annotations. The kind and param parameters indicate where the IRQL value is saved, similar to the resource annotations discussed in "Nonmemory Resource Annotations" earlier in this chapter. You do not need to precisely specify the location where the value is saved as long as the annotations that save and restore the value are consistent.

Annotations for Maintaining the Same IRQL

User-defined functions that change IRQL should be annotated either with __drv_sameIRQL or with one of the other IRQL annotations to indicate that the change in IRQL is expected. In the absence of annotations that indicate any net change in IRQL, PREfast issues a warning for any function that does not exit at the same IRQL at which the function was entered. If the change in IRQL is intended, add the appropriate annotation to suppress the error. If the change in IRQL is not intended, you should correct the code.

The addition of __drv_sameIRQL indicates to other programmers that the original developer consciously considered this behavior to be correct. For example, almost all of the system-defined callback functions are annotated with __drv_sameIRQL because they are expected to exit at the same IRQL at which they were entered. The exceptions are marked appropriately for the action that they take.

Annotations for Saving and Restoring IRQL for I/O Cancellation Routines

The __drv_useCancelIRQL annotation indicates that the annotated parameter is the IRQL value that should be restored by a DRIVER_CANCEL callback. The presence of this annotation indicates that the function is a utility that is called from Cancel routines and that it completes the requirements for DRIVER_CANCEL functions (that is, it discharges the obligation for the caller).

For example, the MyCompleteCurrent function is a utility function that is called from many places to implement cancel functionality. One of the parameters is the IRQL that should be restored by this function. The annotations indicate that the function should meet the requirements of a Cancel function, as shown in the following example:

 VOID MyCompleteCurrent(     __in PDEVICE_EXTENSION Extension,     __in_opt PKSYNCHRONIZE_ROUTINE SynchRoutine,     __in __drv_in(__drv_useCancelIRQL) KIRQL IrqlForRelease,     ); 

Note that __drv_useCancelIRQL is a low-level annotation. For many uses, __drv_isCancelIRQL is a better choice.

IRQL Annotation Examples

The examples in this section show IRQL annotations applied to various system functions.

The maximum IRQL at which a fast mutex can be acquired is APC_LEVEL. Acquiring a fast mutex raises the current IRQL to APC_LEVEL. When the fast mutex is released, the caller must still be at APC_LEVEL and the previous IRQL is restored. The example in Listing 23-26 shows the annotations that enforce these rules.

Listing 23-26: Example of annotations for enforcing maximum IRQL

image from book
 __drv_maxIRQL(APC_LEVEL) __drv_setsIRQL(APC_LEVEL) VOID   ExAcquireFastMutex(     __inout         __drv_out(__drv_savesIRQL             __drv_acquiresResource(FastMutex))     PFAST_MUTEX FastMutex     ); __drv_requiresIRQL(APC_LEVEL) VOID   ExReleaseFastMutex(      __inout         __drv_in(__drv_restoresIRQL              __drv_releasesResource(FastMutex))      PFAST_MUTEX FastMutex      ); 
image from book

In the example in Listing 23-27, the annotations override the default __drv_maxIRQL(DISPATCH_LEVEL) for KeRaiseIrql and specify that KeRaiseIrql can be used only to raise the IRQL.

Listing 23-27: Example of annotations for overriding the default maximum IRQL

image from book
 __drv_maxIRQL(HIGH_LEVEL) VOID   KeRaiseIrql(     __in __drv_in(__drv_raisesIRQL) KIRQL NewIrql,     __out __drv_out_deref(__drv_savesIRQL) PKIRQL OldIrql     ); 
image from book

In the example in listing 23-28, note the association of the saved and restored IRQL with LockHandle, which has no direct association with the saved IRQL value.

Listing 23-28: Example of annotations for saving and restoring IRQL

image from book
 __drv_maxIRQL(DISPATCH_LEVEL) __drv_savesIRQLGlobal(QueuedSpinLock, LockHandle) __drv_setsIRQL(DISPATCH_LEVEL) VOID FASTCALL   KeAcquireInStackQueuedSpinLock (      __in PKSPIN_LOCK SpinLock,      __in __drv_in_deref(          __drv_acquiresExclusiveResource(QueuedSpinLock))      PKLOCK_QUEUE_HANDLE LockHandle     ); __drv_restoresIRQLGlobal(QueuedSpinLock, LockHandle) __drv_requiresIRQL(DISPATCH_LEVEL) VOID FASTCALL   KeReleaseInStackQueuedSpinLock (      __in __drv_in_deref(         __drv_releasesExclusiveResource(QueuedSpinLock))      PKLOCK_QUEUE_HANDLE LockHandle     ); 
image from book

The minimum and maximum levels to which the IRQL can be changed are typically applied to callbacks. In the example in Listing 23-29, the annotations specify that the AddDevice function cannot raise the IRQL at all.

Listing 23-29: Example of annotations that prevent a function from raising IRQL

image from book
 typedef __drv_maxFunctionIRQL(0) __drv_sameIRQL __drv_clearDoInit(yes) __drv_functionClass(DRIVER_ADD_DEVICE) NTSTATUS DRIVER_ADD_DEVICE (     __in struct _DRIVER_OBJECT *DriverObject,     __in struct _DEVICE_OBJECT *PhysicalDeviceObject     ); typedef DRIVER_ADD_DEVICE *PDRIVER_ADD_DEVICE; 
image from book

Tips for Applying IRQL Annotations

Here are some tips for applying IRQL annotations to functions:

  • Annotate the function with whatever IRQL information might be appropriate.

    The additional information helps PREfast in subsequent checking of both the caller and callee. In some cases, adding an annotation is a good way to suppress a false positive.

  • If a function's annotations do not mention IRQL at all, it is likely a utility function that can be called at any IRQL and thus explicitly having no IRQL annotation is the proper annotation.

  • When annotating a function for IRQL, consider how the function might evolve, not just its current implementation.

    For example, a function as implemented might work correctly at a higher IRQL than the designer intended. Although it is tempting to annotate the function based upon what the code actually does, the designer might be aware of future requirements, such as the need to lower maximum IRQL for some future enhancement or pending system requirement. The annotation should be derived from the intention of the function designer, not from the actual implementation of the function.

DO_DEVICE_INITIALIZING Annotation

The __drv_clearDoInit annotation specifies that the annotated function is expected to clear the DO_DEVICE_INITIALIZING bit in the Flags word of the device object. Calling a function that is annotated with __drv_clearDoInit discharges that obligation for the caller.

This annotation should be used in a conditional context when the function returns success, unless the annotation is applied to a function typedef declaration. For an example, see "IRQL Annotation Examples" earlier in this chapter.

Annotations for Interlocked Operands

A large family of functions takes as one of their parameters the address of a variable that should be accessed by using an interlocked processor instruction. These are cache read-through atomic instructions. If the operands are used incorrectly, very subtle bugs can result.

You can apply the __drv_interlocked annotation to a function parameter to identify it as an interlocked operand. System-supplied functions are already annotated for interlocked operands.

PREfast assumes that, if a variable is accessed by any interlocked function, the developer intends to share the variable between threads that could be running on different processors. Thus, any attempt to access or modify that variable without an interlocked operation might be done only in the local processor's cache, which would be potentially incorrect code. Variables in the local stack frame that is used as the interlocked operand are both very unusual and often dangerous, and usually indicate a misuse of the function.

The following example shows the annotation for an InterlockedExchange function. This annotation specifies that the Target parameter must always be accessed by using an inerlocked operation:

 LONG   InterlockedExchange(     __inout __drv_in(__drv_interlocked) PLONG  Target,     __in LONG Value     ); 

Examples of Annotated System Functions

The examples in this section show annotations that are applied to commonly used system functions.

In Listing 23-30, the IoGetDmaAdapter function returns a value through a parameter that should be checked, as well as having other annotations.

Listing 23-30: Example of annotations for IoGetDmaAdapter

image from book
 PDMA_ADAPTER    __drv_maxIRQL(DISPATCH_LEVEL)    IoGetDmaAdapter(      __in PDEVICE_OBJECT PhysicalDeviceObject,      __in PDEVICE_DESCRIPTION DeviceDescription,      __checkReturn        __deref_inout PULONG NumberOfMapRegisters     ); 
image from book

The IoCreateDevice function is generally very simple to use, except that the driver must properly dispose of the created device object-usually by calling IoAttachDeviceToDeviceStack. However, when a legacy driver calls IoCreateDevice from within the driver's DRIVER_INITIALIZE or DRIVER_DISPATCH functions, the device object is put in a location where it can be found, usually during unload. PREfast is unaware of this location.

The annotations in Listing 23-31 help to prevent false positives in PREfast. The device object is similar to memory in that it can be aliased, so the example uses a memory annotation.

Listing 23-31: Example of annotations for IoCreateDevice

image from book
 __drv_maxIRQL(APC_LEVEL) NTSTATUS   __drv_valueIs(<0;==0)   IoCreateDevice(     __in PDRIVER_OBJECT DriverObject,     __in ULONG DeviceExtensionSize,     __in_opt PUNICODE_STRING DeviceName,     __in DEVICE_TYPE DeviceType,     __in ULONG DeviceCharacteristics,     __in BOOLEAN Exclusive,     __out __drv_out(__drv_when(return<0, __null)              __drv_when(return==0, __notnull                  __drv_when(!inFunctionClass$("DRIVER_INITIALIZE")                          && !inFunctionClass$("DRIVER_DISPATCH"),                             __acquiresMemory(Memory))))         PDEVICE_OBJECT *DeviceObject     ); 
image from book

For symmetry with IoCreateDevice, the example in Listing 23-32 shows the annotations for IoAttachDeviceToDeviceStack. Note that this example aliases the device object only when the function is successful. Correct code should check the return value for IoAttachDeviceToDeviceStack and typically should call IoDeleteDevice if it fails. These annotations cause PREfast to enforce those rules.

Listing 23-32: Example of annotations for IoAttachDeviceToDeviceStack

image from book
 PDEVICE_OBJECT __drv_maxIRQL(2) __drv_valueIs(==0;!=0) __checkReturn   IoAttachDeviceToDeviceStack(      __in PDEVICE_OBJECT SourceDevice,      __in          __drv_in(__drv_mustHold(Memory)          __drv_when(return!=0, __drv_aliasesMem))          PDEVICE_OBJECT TargetDevice     ); 
image from book

The example in Listing 23-33 shows annotations for the EngSaveFloatingPointState function, which saves the current kernel floating-point state. Although the function prototype appears simple, this function has fairly complex semantics at the contract level and is easy to misuse. Consequently, the annotations that specify correct usage are also fairly complex.

Listing 23-33: Example of annotations for EngSaveFloatingPointState

image from book
 // EngSaveFloatingPointState      __checkReturn                                // 1      __drv_arg(*pBuffer,          __drv_neverHold(EngFloatState)           // 2          __drv_notPointer)                        // 3      __drv_when(pBuffer==0 || cjBufferSize==0,          __drv_ret(__drv_valueIs(>=0)))           // 4      __drv_when(pBuffer!=0 && cjBufferSize!=0,    // 5          __drv_ret(__drv_valueIs(==0;==1))          __drv_when(return==1,              __drv_ret(__drv_floatSaved)              __drv_arg(pBuffer, __bcount_opt(cjBufferSize)                   __deref __drv_acquiresResource(EngFloatState))          )      ) ULONG EngSaveFloatingPointState(      __out_opt VOID *pBuffer,                    // 6      __in ULONG cjBufferSize                     // 6 ); 
image from book

This example uses the __drv_arg annotation to move the more complex annotations from the parameter list to a block of annotations above the beginning of the function prototype. The simple annotations such as __in are left in their usual position.

The numbers in comments correspond to explanations after the example.

Starting from the beginning of the example in Listing 23-33:

  1. The result value should always be checked or used.

  2. The floating-point state should not be held when the function is called.

  3. The buffer pointer should point directly to memory.

  4. If either parameter is zero, the function returns a positive number (that is, the size of the buffer required to save the floating-point state) or zero (that is, the processor having no hardware floating-point capability).

  5. If both parameters are nonzero, the function returns a BOOLEAN. If it is successful, it saves the floating-point state, fills the buffer, and acquires the floating-point state in the buffer.

  6. The usual __in and __out annotations still apply.




Developing Drivers with the Microsoft Windows Driver Foundation
Developing Drivers with the Windows Driver Foundation (Pro Developer)
ISBN: 0735623740
EAN: 2147483647
Year: 2007
Pages: 224

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net