Developing Drivers with the Windows Driver Foundation (Pro Developer)

How to Use PREfast

You can use PREfast to analyze both kernel-mode drivers and other kernel-mode components. You can also use PREfast to analyze user-mode drivers. PREfast is installed with the WDK. You do not need to take any additional steps to install PREfast.

By default, PREfast analyzes code according to rules for kernel-mode drivers. To analyze a user-mode driver, set the analysis mode to __user_driver, as described in "How to Specify the PREfast Analysis Mode" later in this chapter, or simply ignore any kernel-specific warnings.

This section provides a brief introduction to the PREfast command line and PREfast defect log viewer. If you are already familiar with using PREfast, you might prefer to skip this section.

 Note  Take full advantage of the compiler's error-checking capabilities by compiling code with the /W4 and /WX compiler switches, in addition to using PREfast. PREfast does not enable the /W4 switch, although there is some overlap between errors that /W4 detects and errors that PREfast detects. Most of these errors are uninitialized variables.

How to Specify the PREfast Analysis Mode

Inside Out 

The PREfast analysis mode determines which set of rules PREfast uses when it analyzes code. The analysis-mode annotation that is defined in %wdk%\inc\ddk\driverspecs.h informs PREfast whether a particular body of code is user-mode or kernel-mode code and whether the code is actually a driver. This annotation applies to an entire source file.

The analysis mode can be one of the following annotations:

__kernel_driver

For kernel-mode driver code. This is the default analysis mode.

__kernel_code

For nondriver kernel-mode code.

__user_driver

For user-mode driver code.

__user_code

For nondriver user-mode code.

If the __kernel_driver analysis mode is incorrect for a particular driver, insert the appropriate analysis mode annotation in the source file or appropriate header file just after the relevant header is included and before any function bodies. Ntddk.h and Wdm.h include driverspecs.h, so this annotation can appear anywhere after Ntddk.h or Wdm.h is included.

How to Run PREfast

To run PREfast in a build environment window, type prefast, followed by your usual build command.

When you execute a prefast command, PREfast intercepts the call to the compiler, analyzes the code to be compiled, and writes the results of the analysis to a log file in XML format. PREfast operates separately on each function in the source code. It produces a single combined log for all of the files that are checked in a single run and eliminates duplicate errors and warnings that header files generate. PREfast then calls the regular compiler to produce the usual build output. The resulting object files are the same as those produced by your usual build command.

Tip 

PREfast is designed to analyze 32-bit code or 64-bit code for x64-based systems. When you run PREfast, the appropriate version of PREfast is specified by the WDK build environment. To analyze code for Itanium-based systems, either make a copy of the code and change it as necessary to build in an x64 build environment or use conditional compilation to compile it for the x64 architecture. Then run PREfast in an x64 build environment on the x64 architecture version.

To Run PREfast

  1. Open a build environment window.

  2. Use the cd command to set the default directory as required to build your source code.

    For example, if you are building a driver, you would set the default directory to one that contains a sources file or a dirs file.

  3. Type prefast build, followed by any Build utility parameters that are required to build your code, as shown in the following example:

    • prefast build -cZ

    Inside Out 

    PREfast analyzes the code to be compiled and writes results of the analysis to the log file, which is stored as XML. The Defects.xml default log file is written to %wdk%\tools\pfd. To write the log file to another location, use the /LOG= switch with the prefast command.

How to Build the PREfast Examples

PREfast is installed with a directory of source code examples that contain deliberate errors to trigger various PREfast warnings. You can use the PREfast examples to validate your PREfast installation and to experiment with the PREfast defect log viewer. The \fail_driver subdirectory contains driver source code that illustrates driver-specific rules in more depth.

For comparison to code that triggers warnings, the Boundsexamples.cpp example file contains several functions that do not contain errors and so do not trigger any PREfast warnings. Look in the source code for functions with "_ok" in the function name.

Tip 

Before you build or modify any WDK sample, copy the files to another directory and then work with the copies. This preserves the sample in its original form in case you need it.

To Build the PREfast Examples

  1. Open a build environment window.

  2. Make the PREfast Samples directory the default directory.

    For example, if C:\winddk is the WDK installation directory and you want to build the examples for PREfast with driver-specific rules, type the following at the command prompt:

    • cd C:\winddk\tools\pfd\samples

  3. Type a prefast build command such as the following to build the examples:

    • prefast build -cZ

    The command window output in Listing 23-1 shows the results of building PREfast samples. The errors reflect deliberate errors in the examples.

    Listing 23-1: Building PREfast samples-command window output

    C:\WINDDK\tools\pfd\samples>prefast build -cZ ------------------------------------------------------------- Microsoft (R) PREfast Version 8.0.xxxxx. Copyright (C) Microsoft Corporation. All rights reserved. ------------------------------------------------------------- BUILD: Compile and Link for x86 BUILD: Start time: Mon Dec 04 14:37:10 2006 BUILD: Examining c:\winddk\tools\pfd\samples directory for files to compile c:\winddk\tools\pfd\samples BUILD: Compiling c:\winddk\tools\pfd\samples directory _NT_TARGET_VERSION SET TO WINXP Compiling - bounds-examples.cpp Compiling - pft-example1.cpp Compiling - pft-example2.cpp Compiling - pft-example3.cpp Compiling - precedence-examples.cpp Compiling - hresult-examples.cpp Compiling - drivers-examples.cpp Compiling - bounds-examples.cpp Compiling - pft-example1.cpp Compiling - pft-example2.cpp Compiling - pft-example3.cpp Compiling - precedence-examples.cpp Compiling - hresult-examples.cpp Compiling - drivers-examples.cpp Compiling - generating code... Building Library - objchk_wxp_x86\i386\prefastexamples.lib BUILD: Finish time: Mon Dec 04 14:37:19 2006 BUILD: Done 16 files compiled 1 library built ------------------------------------------------------------- Removing duplicate defects from the log... ------------------------------------------------------------- PREfast reported 31 defects during execution of the command. ------------------------------------------------------------- Enter PREFAST LIST to list the defect log as text within the console. Enter PREFAST VIEW to display the defect log user interface.

How to Display PREfast Results

You can display the results of the PREfast analysis in one of the following ways:

PREfast Defect Log Viewer

The PREfast defect log viewer provides a graphical user interface that you can use to review PREfast output, to filter output so you can show or hide particular messages, and to view annotated source code so you can see the analysis path that produced a given warning.

To Display PREfast Results in the PREfast Defect Log Viewer

  1. Run PREfast on your source code, as described earlier in this section.

  2. In the command window, type the following:

    • prefast view

    PREfast displays the PREfast defect log in a Message List screen.

Message List Screen

Figure 23-1 shows with the unfiltered PREfast results of building the examples in the Message List screen. The version number at the top of the screen indicates the version of PREfast that displays the log.

Figure 23-1: PREfast Message List screen

In the Message List screen, you can:

View Annotated Source Screen

If you double-click a message in the Message List screen, the View Annotated Source screen appears, as shown in Figure 23-2. The View Annotated Source screen displays annotated source code for the error that triggered that message, with a few lines of code before and after, for context.

Figure 23-2: PREfast View Annotated Source screen

In the View Annotated Source screen, you can:

Tip 

For detailed information about warnings, click the warning number. In the PREfast viewer, the text "warning nnnn" is a hyperlink to the PREfast for Drivers documentation in the WDK. For many warnings, the documentation provides significant insights about the precise nature of the warning and often suggests how to fix the problem. If you are unfamiliar with a particular warning number, read the documentation-it can save you a lot of time.

Message List Screen in Filter View

If you click Filter in the Message List screen, a list of messages that can be filtered appears above the list of messages that was generated by building your code, as shown in Figure 23-3.

Figure 23-3: PREfast Message List screen in filter view

In the Message List screen in filter view, you can:

You can also double-click a message to display the View Annotated Code screen for that message, just as you can when filters are not visible.

Tips for Filtering PREfast Results

Filtering results does not prevent PREfast from finding errors-it simplifies the list of results in the PREfast viewer so you can work with them more effectively. After you fix the errors shown in the filtered results, you should always run PREfast again and change the way in which results are filtered, so you can see and fix other, less critical errors.

Take advantage of predefined filters

The PREfast drivers_recommended filter displays messages for serious errors in both general-purpose code and driver code. These messages identify errors that tend to be genuine rather than messages that might not represent actual errors in code, which are often referred to as false positives or "noise." The drivers_only filter displays messages only for errors that apply specifically to drivers. If you have a limited amount of time to fix errors that PREfast detects in your driver, use one of these predefined filters and concentrate on fixing the errors that they display.

Hide individual messages if necessary

You might want to hide individual messages for several reasons: you or your development team might think that the risk associated with a message is acceptably low or the noise is unacceptably high, your product ship cycle might allow fixing only the most critical errors, or the messages simply might be irrelevant to your project.

For example, certain PREfast warnings that apply to kernel-mode drivers are also triggered by user-mode drivers. If you are testing a user-mode driver, you might want to hide kernel-mode driver messages such as the following:

Warning 28110: Drivers must protect floating point hardware state. See use of float <expression> Warning 28111: The IRQL where the floating point state was saved does not match the current IRQL (for this restore operation) Warning 28146: Kernel mode drivers should use ntstrsafe.h, not strsafe.h

To hide an individual message, clear its check box in the message filter pane as described in Figure 23-3, "PREfast Message List Screen in filtered view."

PREfast Defect Log Text Output

You can use the prefast list command to display the contents of the PREfast defect log as text output in the build environment command window. This command is useful if you need only a short list of errors and do not need access to annotated source code-for example, to see the effect of fixing errors that PREfast found in a previous run. The prefast list output consists of the same information that is shown in the PREfast defect log viewer, in a form suitable for pasting into files or bug reports.

To Display PREfast Results as Text Output

  1. Run PREfast on your source code, as described earlier in this section.

  2. At the command prompt, type the following:

    • prefast list

    PREfast displays the message list in the command window.

The example in Listing 23-2 shows text output for the first few messages from building the PREfast examples.

Listing 23-2: Building PREfast examples-prefast list command

C:\WINDDK\tools\pfd\samples>prefast list -------------------------------------------------------------------- Microsoft (R) PREfast Version 8.0.58804. Copyright (C) Microsoft Corporation. All rights reserved. -------------------------------------------------------------------- Contents of defect log: C:\Documents and Settings\<username>\ApplicationData\Microsoft\PFD\defects.xml -------------------------------------------------------------------- c:\winddk\tools\pfd\samples\bounds-examples.cpp (45): warning 6029: Possible buffer overrun in call to 'fgets': use of unchecked value 'line_length' FUNCTION: read_size (40) c:\winddk\tools\pfd\samples\bounds-examples.cpp (54): warning 6057: Buffer overrun due to number of characters/number of bytes mismatch in call to 'wcsncpy' FUNCTION: unicode_misuse (51) c:\winddk\tools\pfd\samples\bounds-examples.cpp (62): warning 6201: Index '10' is out of valid index range '0' to '9' for possibly stack allocated buffer 'arr' FUNCTION: constant_index (59) c:\winddk\tools\pfd\samples\drivers-examples.cpp (23): warning 28125: The function 'ProbeForRead' must be called from within a try/except block: The requirement might be conditional. FUNCTION: drivers_test2 (21)

Examples of PREfast Results

This section shows a few simple code examples and describes solutions for common errors that PREfast can detect in source code. These examples are shown without source code annotations, so you can see what PREfast detects in unannotated code.

Example 1: Uninitialized Variables and NULL Pointers

Inside Out 

The PREfast samples in the WDK have deliberate errors and follow bad coding practices to show how PREfast responds to errors and bad coding practices. The test function in %wdk%\tools\pfd\samples\pft-example2.cpp triggers several PREfast warnings related to uninitialized variables and NULL pointers. Although the errors in this example are easy to see just by reading the code, they illustrate errors that might be difficult to see in more complex code, where PREfast might help you find a bug.

Figure 23-4 shows the PREfast analysis path for one of the PREfast warnings related to the pointer variable p in a function named test. Statements in the analysis path are in bold type.

12 void test() 13 { PREfast analysis path begins 14 int *p, a; 15 s *ps, c; 16 17 if (a) 18 { 19 p = &a; 20 } 21 else 22 { 23 ps = (strut s*)malloc(sizeof(struct s)); 24 } 25 26 if (p) 27 { 28 PS = &c; 29 } 30 31 *p; pft-example2.cpp(31) : warning 6011: Dereferencing NULL pointer'p'. Found in function 'test' Path includes 8 statements on the following lines: 14 14 15 15 17 23 26 31 32 a = (((ps)))->a; 33 34 return; 35 }

Figure 23-4: Example 1-Uninitialized variables and NULL pointers

This function declares several variables but does not initialize them, and the function fails to branch appropriately. In this particular code path, the test at line 17 fails because the a variable is not initialized, which is reported in another warning message that is not shown in Figure 23-4. Line 19 therefore fails to execute, leaving the p variable uninitialized.

Although p is tested at line 26, no code handles the case in which the test fails, so execution continues at line 31, which dereferences p and triggers a PREfast warning that p could be NULL. A NULL pointer can also trigger warnings about uninitialized variables, as it does for p in this function. To eliminate this warning, you would add logic to prevent dereferencing p if it happens to be NULL.

Example 2: Implicit Order of Evaluation

Code that relies on implicit order of evaluation can contain bugs that are difficult to find. PREfast detects cases where the implicit order of evaluation might produce results that are different from what the programmer intended.

Figure 23-5 shows a simple example.

10 int unclearIntent(int a, int b, int c) 11 { PREfast analysis path begins 12 if (a & b == c) return 1; pfdsbs7.c(12) : warning 6281: Incorrect order of operations: relational operators have higher precedence than bitwise operators. Found in function 'unclearIntent' 12 13 return 0; 14 }

Figure 23-5: Example 2-Implicit order of evaluation

According to the rules of operator precedence in C, the (a & b == c) expression is interpreted as (a & (b == c)) because the == logical equals operator has higher precedence than bitwise AND (&). Therefore, this function compares b with c and then masks the result with a, which tests whether a is even or odd. If this is the intended result, the function is correct as written, but parentheses would help to make the programmer's intention more clear, as would a comment in the code.

If the programmer intended to mask a with b and compare the result with c, the function is incorrect. Parentheses are required to force evaluation of the as ((a & b) == c) expression.

Example 3: Calling a Function at Incorrect IRQL

The IRQL at which a driver function runs determines which kernel-mode functions it can call and whether it can access paged memory, use kernel-dispatcher objects, or take other actions that might cause a page fault. For example, some DDI functions require that the caller be running at DISPATCH_LEVEL, whereas others cannot be called safely if the caller is running at any IRQL higher than PASSIVE_LEVEL.

Many of the DDI functions that drivers must call are affected by IRQL, sometimes in subtle ways. For example, if your driver sets a flag on the basis of a string and the flag is in a data structure that needs protected access, you might be tempted to write code that resembles the example shown in Figure 23-6. In this example, calling ExAcquireFastMutex resets the current IRQL to APC_LEVEL before acquiring the mutex. However, RtlCompareUnicodeString should be called at PASSIVE_LEVEL, so PREfast displays a warning.

11 void IsFlagset( 12 IN PUNICODE_STRING s) 13 { PREfast analysis path begins 14 ExAcquireFastMutex(&mutex); 15 16 if (RtlCompareUnicodeString(s, &t, TRUE) == 0) { IRQL.c(16) : warning 28121: The function 'RtlCompareUnicodeString' is not permitted to be called at the current IRQ level. The current level is too high: IRQL was last set to 1 at line 14. The level might have been inferred from the function signature. Found in function 'IsFlagSet' Path includes 2 statements on the following lines: 14 16 17 MyGlobal.FlagIsSet = 1; 18 } 19 20 ExReleaseFastMutex(&mutex); 21 }

Figure 23-6: Example 3-Calling a function at incorrect IRQL

Note the "IRQL was last set to 1 on line 14" statement. It is easy to see this in a brief example, but this information might prove to be very important in a longer, more complex function body.

The solution is to move the RtlCompareUnicodeString call before ExAcquireFastMutex and then to test the result after acquiring the mutex. The corrected code would appear as shown in Listing 23-3.

Listing 23-3: Corrected code example that calls ExAcquireFastMutex at correct IRQL

void IsFlagSet( IN PUNICODE_STRING s) { int tmp = 0; if (RtlCompareUnicodeString(s, &t, TRUE) == 0) { tmp = 1; } ExAcquireFastMutex(&mutex); if (tmp) { MyGlobal.FlagIsSet = 1; } ExReleaseFastMutex(&mutex); }

Example 4: Valid Error Reported in the Wrong Place

PREfast often reports a valid error in one location that is actually caused by code in another location. Calling a function at an incorrect IRQL is a good example. When PREfast analyzes a code path, it attempts to infer the range of IRQL at which a function could be running and identify any inconsistencies, as this example shows. PREfast proceeds as if the programmer specifically intended each change to the IRQL. If the IRQL is incorrect for a subsequent function call, PREfast warns about the function call, and not about the earlier change in the IRQL.

Tip 

IRQL annotations such as __drv_requiresIRQL help PREfast to make more accurate inferences about the range of IRQLs at which a function should run. See "IRQL Annotations" later in this chapter for details.

For example, assume that a function named Y is intended to be called at DISPATCH_LEVEL. The Y function calls two DDI functions that have specific IRQL requirements: KeDelayExecutionThread must be called at APC_LEVEL, and KeReleaseSpinLockFromDpcLevel must be called at DISPATCH_LEVEL.

When PREfast starts to analyze the Y function, the call to KeAcquireSpinLockAtDpcLevel causes PREfast to assume that the code should be executing at DISPATCH_LEVEL and therefore to issue a warning that KeDelayExecutionThread is being called at an IRQL that is too high, as shown in Figure 23-7:

17 void Y(void) 18 //This routine will be always called at DISPATCH_LEVEL 19 { PREfast analysis path begins 20 LARGE_INTEGER SleepTime; 21 KeAcquireSpinLockAtDpcLevel(&spinLock); 22 23 if(some_condition) { 24 //Driver performs I/O to the hardware and decides 25 //to get a response or check the state. 26 27 KeDelayExecutionThread(Kernelmode, FALSE, &SleepTime); xy.c(27) : warning 28123: The function KeDelayExecutionThread is not permitted to be called at a high IRQ level. Prior function calls are inconsistent with this constraint: It may be that the error is actually in some prior call that limited the range. Minimum legal IRQL was last set to 2 at line 21. Found in function 'Y' Path includes 4 statements on the following lines: 20 21 23 27 28 } 29 KeReleaseSpinLockFromDpcLevel(&spinLock); 30 }

Figure 23-7: Example 4-Valid error reported in the wrong place

As PREfast continues to analyze the Y function, it now assumes that the code should be executing at APC_LEVEL because of the call to KeDelayExecutionThread. PREfast then encounters KeReleaseSpinLockFromDpcLevel, which must be called at DISPATCH_LEVEL, and so it issues the following warning that the function is being called at an IRQL that is too low:

xy.c(29) : warning 28122: The function KefReleaseSpinLockFromDpcLevel is not permitted to be called at a low IRQ level. Prior function calls are inconsistent with this constraint: It may be that the error is actually in some prior call that limited the range. Maximum legal IRQL was last set to 1 at line 27 Found in function 'Y' Path includes 5 statements on the following lines: 20 21 23 27 29

To correct this situation, consider one of the following solutions in your code:

Example 5: Function Type Class Mismatch

PREfast is more strict than the compiler when it attempts to match the assignments of callback functions to function pointers. It does this by giving each callback function a "type class."

A type class in PREfast serves in the role of a type but goes beyond the concept of a type as defined by C and is not related to a class as defined by C++. When PREfast discovers either a type class mismatch or a function type that does not have a type class, it generates an error. PREfast also uses the type class to apply checks that are specific to a particular function type without incorrectly applying them to functions that simply happen to look like that function type.

Warning 28155 identifies a typical function type class error:

This warning indicates that an assignment to a pointer for a particular function pointer did not match the expected type. An example might be an attempt to assign a Cancel routine to a StartIo function pointer, which the C compiler allows but PREfast does not. Typically, the assignment is correct, but the function is not known to be of any specific function class.

Figure 23-8 shows an example of this error.

2124 Void MyCancel( struct _DEVICE OBJECT *Deviceobject, 2125 struct _IRP *Irp) 2126 { 2127 //... 2128 } ... 3130 Irp->CancelRoutine = MyCancel; fun.c(3130) : warning C28155: The function being assigned or passed should be a DRIVER_CANCEL function: Add the declaration 'DRIVER_CANCEL MyCancel;' before the current first declaration of MyCancel. Found in function 'MyCancel' 3130

Figure 23-8: Example 5-Function type class mismatch

To fix this error, you would add DRIVER_CANCEL MyCancel; before line 2124 to instruct PREfast that the MyCancel function is a cancel routine. This suppresses Warning 28155 and causes PREfast to check that the function meets the requirements of a cancel routine.

See "Annotations on Function Typedef Declarations" and "Function Type Class Annotations" later in this chapter for more information about function typedef declarations.

Example 6: Incorrect Enumerated Type

The C compiler's type checking is not strict enough to detect an incorrect enumerated type. Unfortunately, using an incorrect enumerated type in driver code can cause problems that are difficult to find and solve. PREfast detects enumerated type mismatches in driver code and issues a warning about each mismatch. PREfast type mismatch warnings vary somewhat according to the function that is being analyzed, but all PREfast type mismatch warnings identify potential problems that should be investigated and fixed.

For example, a common error when calling the KeWaitXxx routines-KeWaitForSingleObject, KeWaitForMultipleObjects, and KeWaitForMutexObject-is to transpose the WaitReason and WaitMode parameters, which take enumerators of the KWAIT_REASON and KPROCESSOR_MODE types, respectively.

Figure 23-9 shows the PREfast warning for a call to KeWaitForSingleObject in which the WaitReason and WaitMode parameters are transposed.

2784 status = KeWaitForSingleobject(&event, kewait.c(2784) : warning 28139: The argument 'KernelMode' should exactly match the type 'enum _KWAIT_REASON': Some functions permit limited arithmetic on the argument type, others do not. This usually indicates that an enum formal was not passed a member of the enum, but may be used for other types as well. Found in function 'PcIDrvSendIrpSynchronously' 2784 2785 Kernelmode, 2786 Executive, 2787 FALSE, 2788 NULL, 2789 );

Figure 23-9: Example 6-Incorrect enumerated type

PREfast issues the same warning for the argument Executive because it is not of the KPROCESSOR_MODE type.

The enumeration values most commonly passed in a KeWaitXxx call are KWAIT_REASON Executive and KPROCESSOR_MODE KernelMode. Both of these values evaluate to zero, so they are numerically interchangeable. If the driver code transposes them in the function call, a type mismatch occurs for each parameter but, without strict type checking, the mismatch is invisible to the compiler. If the values are Executive and KernelMode, the mismatch is also invisible to the system.

Problems arise when these parameters are transposed with enumeration values that are not numerically interchangeable, such as Executive and UserMode, thus causing the driver to wait in a mode that the programmer did not intend.

This example shows how PREfast can help to prevent a bug when it is used early in the development process. If the values that are incorrectly interchanged are nonzero, this coding error will appear as a bug at some time in testing. Some variations of this coding error can cause the bug to appear as a bug check in the kernel or in an unrelated driver, making the bug very difficult to recognize and especially difficult to find in your driver. PREfast detects this bug, which saves testing and debugging time that would be needed to find the problem. Fixing the bug takes only a moment because the PREfast warning is specific and flags the error close to where it occurs in your code.

Категории