Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Writing a kd/WinDbg Debugger Extension DLL


December 1998/Writing a kd/WinDbg Debugger Extension DLL


The birth of NT required not only a new 32-bit API (Win32), but a new 32-bit debugging framework. The developers of NT needed debugging support well before any fancy IDE-based debuggers were available, and at least two of those early debuggers have survived to this day: kd and WinDbg (the latter obviously an abbreviation of “Windows debugger,” but often pronounced “wind bag” by its less affectionate users). kd tends to be the choice of hard-core driver developers; it can handle multi-processor machines and debug even drivers that initialize inconveniently early in the boot process. WinDbg provides source-level debugging, doesn’t require a separate remote debugging machine, and is available on more Win32 platforms than kd (which is available only for NT).

Despite their differences, kd and WinDbg share not only a similar command syntax, but also a framework for adding custom commands via DLLs. You can create a DLL that performs some custom debugging task, and access it from within kd or WinDbg as though it were simply a new debugger command. You can get WinDbg, header files, and documentation for creating debugger extension DLLs from Microsoft’s Platform SDK (currently available at http://msdn.microsoft.com/developer/sdk/platform.htm). Unfortunately, the documentation for creating these extensions is outdated, incomplete, and riddled with inaccuracies. In this article, I attempt to remedy some of the deficiencies of the documentation, and explain the design and use of debugger extensions so that you will be able to more effectively debug your code.

I’ll focus on writing debugger extensions for kernel-mode code, because extensions are typically most useful when debugging device drivers. You can use the same interface for user-mode debugging, but you should be aware that many of the low-level services provided by the debugger function properly only when debugging kernel-mode code. The example extensions presented assume you have the NT DDK and are debugging NT drivers on an Intel x86 platform.

Accessing Debugger Extension DLLs

Debugger extensions are simply functions exported from a standard DLL, each of which has the same, well-defined prototype. The debugger calls these functions when the user invokes them with a command line of the form:

![dll_name.]<function_name> [arguments]

Here, dll_name is the name of your extension DLL (without the filename extension), function_name is the exported name of the extension routine in your DLL to invoke, and arguments is an optional set of arguments to pass to your function. Debugger extension routines are often referred to as “bang commands” in reference to the bang (!) character used on the command line.

If you’ve used WinDbg to debug device drivers, chances are you’ve already used common bang commands like !trap, !process, and !irp. These commands are really just extension routines exported by the DLL kdextx86.dll that ships with the debugger. I won’t talk about the specific functionality provided by this common extension DLL, but you can get information on these routines by typing !kdextx86.? at the debugger command prompt.

The first time you access a function in a debugger extension DLL, WinDbg uses LoadLibrary() to load the DLL, and GetProcAddress() to obtain the address of the desired function. The debugger will keep the DLL loaded from that point on, so you don’t have to specify the DLL name when you invoke its commands. You can find more usage information in the WinDbg help.

wdbgexts.h

The interface between the debugger and your extension DLL is defined in wdbgexts.h, a part of the Microsoft’s Platform SDK. This file provides structure definitions, function prototypes, and some helpful macros and inline functions for creating debugger extension DLLs. If you’ve looked into extension DLLs in the past, chances are you’ve happened upon the DDK’s few examples. These examples include a file named ntkdexts.h, which defines an older kd-specific interface for writing extensions. If you try to use those extensions under WinDbg, you’ll find they fault when invoked because WinDbg doesn’t understand their format.

Although WinDbg does not understand how to invoke extensions written for the old kd interface, kd understands how to invoke extensions written for the WinDbg interface. So even if you’re still using kd, the WinDbg extension interface is the way to go.

Writing Framework Routines

As explained earlier, your debugger extension DLL will export a function for each new custom command you want to add to the debugger. However, besides functions that implement specific commands, your extension DLL must also implement certain functions that support only the framework for communicating with the debugger. That framework involves just three exported functions in your DLL: WinDbgExtensionDllInit(), ExtensionApiVersion(), and CheckVersion().

WinDbgExtensionDllInit(). The debugger identifies your DLL as a valid debugger extension DLL by locating a function called WinDbgExtensionDllInit(), which should have the following prototype:

VOID WDBGAPI WinDbgExtensionDllInit(
    PWINDBG_EXTENSION_APIS lpExtensionApis,
    USHORT                 MajorVersion,
    USHORT                 MinorVersion
    );

After loading your DLL, the debugger passes the address of a WINDBG_EXTENSIONS_APIS structure, which contains pointers to functions (explained in more detail later) your extension DLL can use to perform tasks such as reading and writing the memory of the process being debugged. You would typically store this pointer in a global variable for later use.

The MajorVersion and MinorVersion parameters aren’t really version numbers per se; MajorVersion will be 0x0C if the target system is running a checked version of NT, or 0x0F if running the free version. MinorVersion is the build number of the specific version of NT being debugged. Common values would be 1381 for NT 4.0, and 1057 for NT 3.51.

ExtensionApiVersion(). The debugger calls this function in your DLL to verify that you expect the same version of the extension API as it offers. You can implement ExtensionApiVersion() like this:

/* this struct defined in wdbgexts.h
 * typedef struct EXT_API_VERSION {
 *     USHORT  MajorVersion;  // 3
 *     USHORT  MinorVersion;  // 5
 *     USHORT  Revision;      // EXT_API_VERSION_NUMBER
 *     USHORT  Reserved;
 * } EXT_API_VERSION, *LPEXT_API_VERSION;
 */

EXT_API_VERSION Version = { 3, 5, EXT_API_VERSION_NUMBER, 0 };
LPEXT_API_VERSION WDBGAPI ExtensionApiVersion()
{    return &Version;   }

The debugger expects that MajorVersion will be 3, MinorVersion will be 5, and Revision will be EXT_API_VERSION_NUMBER (defined in wdbgexts.h). The MajorVersion and MinorVersion are apparently a reference to NT 3.5, but the debuggers that ship with NT 4.0 expect these values as well. Reserved should be 0.

CheckVersion(). This function is optional; if you export a function named CheckVersion() from your DLL, the debugger will call it before every call to an extension routine in your DLL. The necessity of this function is not entirely clear to me; the standard kernel extension DLL kdextx86.dll implements it, and checks to see that a free build of kdextx86.dll is not used when debugging a checked version of NT, and vice versa. Since this could just as easily be done in WinDbgExtensionDllInit(), I’m not convinced there’s a compelling reason to implement this function.

If you do have some need to implement it, it should be defined to either return a ULONG or VOID, depending on whom you ask. The WinDbg help file describes it returning VOID, but the PWINDBG_CHECK_VERSION type definition in wdbgexts.h has it returning a ULONG. I’m more apt to believe the header file, but there’s no documentation on what should be returned. Since it doesn’t appear as though the return value is used for anything, it shouldn’t make much difference anyway.

Writing Extension Routines

The three previously mentioned functions have fixed names you must use, but you can give any name to the functions you write to implement debugger commands. The only requirement is that each function name be lowercase, and that the function itself have a specific prototype:

VOID myextension(
    HANDLE hCurrentProcess,
    HANDLE hCurrentThread,
    ULONG dwCurrentPc,
    ULONG dwProcessor,
    PCSTR args
    );

The first four parameters give you information about the state of the machine at the time the debugger seized control of the system. hCurrentProcess and hCurrentThread are handles to the current process and thread, though these values are pretty much useless when doing kernel or crash dump debugging. dwCurrentPc is the value of the program counter (EIP register on Intel platforms), and dwProcessor is the index of the current processor. The last parameter, args, is a pointer to the command-line arguments the user passed to your extension, if any.

wdbgexts.h provides a macro called DECLARE_API() that, given a function name, declares that function with the correct prototype for an extension DLL function:

#include <wdbgexts.h>
DECLARE_API(myextension)
{
    // code here...
}

Calling Debugger Functions

As mentioned earlier, the debugger passes your initialization function a structure that contains pointers to several functions. Besides providing routines for basic tasks like reading and writing memory, performing stack traces, and evaluating expressions, these functions also include some more powerful, low-level routines. Unfortunately, these functions are only partially documented in the help files that come with WinDbg. Worse than that, approximately half of the function descriptions contain blatant errors relating to the syntax and usage of the routine.

If your WinDbgExtensionDllInit() stored the pointer to the WINDBG_EXTENSION_APIS structure in a global variable, you can use that to later invoke debugger functions. For example, if you used a global variable named ExtensionApis, you could write code like this:

// print a message to the debug console
ExtensionApis.lpOutputRoutine ("Hello, Debugger!\n");

wdbgexts.h defines a set of macros for invoking the debugger functions whose pointers reside in the WINDBG_EXTENSION_APIS structure. They all assume that WinDbgExtensionDllInit() stored the pointer to that structure in a global variable with a specific name: ExtensionApis. For example, the following two calls are equivalent:

#include <wdbgext.h>
/* ... */
ExtensionApis.lpOutputRoutine ("Hello, Debugger\n");
// now use macro defined in wdbgext.h
dprintf("Hello, Debugger\n");

The rest of these articles will use these macros instead of directly referring to the members in the WINDBG_EXTENSION_APIS structure; remember that this assumes you’ve stored the pointer to that structure in a global variable named ExtensionApis. Unfortunately, the header file sometimes uses VC++ __inline functions, which may render the file useless for non-Microsoft C compilers.

Table 1 lists the basic debugger functions supplied in the WINDBG_EXTENSION_APIS structure. The last function there is Ioctl(), which is analogous to Win32’s DeviceIoControl() in that it lets you specify a function code that selects between a variety of available routines. Along with the function code, you pass in the address and size of a buffer whose format depends on the function code.

The available function codes are defined in wdbgext.h, and have names such as IG_READ_IO_SPACE and IG_GET_KERNEL_VERSION. Many of these function codes have associated inline functions that wrap the Ioctl() call and supply a more natural calling style. For example, ReadIoSpace() is actually a thin wrapper around the IG_READ_IO_SPACE Ioctl(). The documented functions are listed in Table 2.

There are a handful of control codes defined in wdbgexts.h for which no documentation at all exists. Some of these have corresponding macros for invoking them; they are listed in Table 3.

Sample Debugger Extensions

The sample debugger extension functions in ext.c (Listing 1) exercise many of the debugger functions. Though you can infer how to use the basic ones from their names, there are a few items that aren’t so intuitive that you’ll have to look out for.

help() is a pretty straightforward routine. It shows which extensions are available in the extension DLL with usage information. The debugger will invoke this routine if you type !wdjext.help or !wdjext.? from the command line.

dis() is a debugger extension function that disassembles a single instruction at a given address. It uses GetSymbol() and Disassm(), both of which require a buffer to write into. Unfortunately, these functions don’t allow you to specify the length of the buffer you supply, and they will blindly overwrite the supplied buffer if it isn’t large enough for what they have to write.

dis() also demonstrates GetExpression(), which is great for converting user input to manageable values. For example, dis() expects to receive a parameter that specifies a code address, but the user is free to specify it using an expression of registers, symbols, and values. GetExpression() takes input such as “eip+0x8” or “MyFunctionName” and returns the numeric value of the given expression. (GetExpression() skips any leading white space, and stops evaluating when it hits any white space after an expression.)

One other neat trick with GetExpression() is that you can use it as a quick way to grab a register’s value. For example, if you need the contents of EBP for some operation, you could just use:

EbpValue = GetExpression("ebp");

mdl() is representative of the kind of extension routine you’ll probably be writing most often. Its sole purpose is to dump out a Memory Descriptor List (MDL) to the debug console in a formatted manner. Having routines that can dump out your device driver’s private data structures, such as device extensions and internal lists and queues, can make a huge difference when debugging your driver from a crash dump or on a customer’s machine. As mdl() shows, it’s really just a matter of parsing the input with GetExpression(), using ReadMemory() to access the data itself, and applying a little internal knowledge of your structures to make the output useful.

eflags() is a simple routine that prints out the names of the flags set in the EFLAGS register of the current processor. Although it could have used GetExpression("efl") to get the EFLAGS register value, I obtained the whole CONTEXT structure for the processor to point out a problem you might run into. The problem is that wdbgexts.h provides a GetContext() function for getting the processor context (which includes such items as the EFLAGS register), but it works only under kd and not under WinDbg.

I’ve written a replacement function called WdjGetContext() in wdjext.h (Listing 2) that uses the debugger’s ReadControlSpace() to fill in a CONTEXT structure; this technique works even under WinDbg. Although the WinDbg help file documents the existence of ReadControlSpace(), it doesn’t tell you how you might go about using it. It turns out that the contents of control space are described by the structure KPROCESSOR_STATE, which is defined in \ddk\src\krnldbg\kdapis\windbgkd.h:

typedef struct _KPROCESSOR_STATE {
    struct _CONTEXT ContextFrame;
    struct _KSPECIAL_REGISTERS SpecialRegisters;
} KPROCESSOR_STATE, *PKPROCESSOR_STATE;

As you can see, KPROCESSOR_STATE contains a complete CONTEXT record, along with a KSPECIAL_REGISTERS structure, which together supply the values of all of the processor’s registers. WdjGetContext() uses ReadControlSpace() to simply read the appropriate offset and size from this structure to obtain a processor’s context.

Back in ext.c (Listing 1), special_regs() is an extension routine that writes the contents of various special register values such as the Global Descriptor Table Register (GDTR) and the DRx debug registers to the debug console. It uses WdjGetSpecialRegisters(), which, like WdjGetContext(), relies on ReadControlSpace() to get the values of these registers from WinDbg.

getkrnlver() is an extension routine that prints out a lot of information about the system being debugged, using another inline function in wdjext.h (Listing 2), WdjGetKernelVersion(). This function, too, is just a wrapper, this one around the IG_GET_KERNEL_VERSION Ioctl().

ReadMsr() and WriteMsr() are two undocumented debugger functions not used in the examples. They provide access to the Model Specific Registers (MSR) of the various Intel chips (and clones). MSRs let you access internal processor information, and modify certain behaviors of the chip. These registers are machine-specific, and this functionality is probably useful to only a small number of really low-level developers. If you want to play around with this functionality, you can find a lot of information on MSRs and just about everything else related to the Intel architecture at www.sandpile.org/.

I also didn’t use the last inline function defined in wdjext.h (Listing 2), WdjWriteControlSpace(). This is simply the inverse of the ReadControlSpace() function, and is a wrapper for the IG_WRITE_CONTROL_SPACE Ioctl().

Building the Extension Routines

The complete set of sources for building the example extension DLL consists of: ext.c (Listing 1), wdjext.h (Listing 2), wdjext.c (Listing 3), wdjext.def, sources, and makefile (all included in this month’s code archive). The code is designed to be built using build, the tool included with the DDK. Just type build -cZ to generate wdjext.dll, which you should copy to a directory the debugger can find at runtime, such as
%SYSTEMROOT%\System32. The debugger will automatically load the extension DLL the first time you invoke one of its functions, such as:

!wdjext.?

To make your own extension DLL, simply copy the sample project and do the following:

1. Replace the routines in ext.c with your own extensions.

2. Change the TARGETNAME directive in the sources file to the name of your DLL.

3. Create a new .def file with the same base name as your DLL, and add your extension routines to the list of exports.

When you’re testing your own extensions, you’ll undoubtedly uncover problems that will cause you to rebuild your DLL. To copy the new version over the old, you’ll have to unload the old version from WinDbg with the !unload command. You can then copy the new DLL over the old, and invoke your rebuilt extensions without leaving the debugger.

Summary

I hope I’ve been able to give you the basic information you need to begin experimenting with creating extension DLLs for your own driver projects. While most of the time you’ll be interested in doing basic things like dumping out the contents of your device extension, or checking the integrity of lists and queues, the extension API provided by WinDbg and kd offers some powerful features that can shed light on the tough problems driver writers come across in the field.

You can find the latest version of WinDbg, and links to related articles, at Microsoft’s WinDbg home page at http://msdn.microsoft.com/developer/sdk/windbg.htm.

If you find more bugs with WinDbg, or have feature requests, send them both to Microsoft (through the links at the WinDbg home page) and to [email protected]. The people at OSR Open Systems Resources, Inc. (www.osr.com) have volunteered to assimilate all bug reports and suggestions and forward them directly to the WinDbg maintainers.

Myk Willis is a software engineer with Citrix Systems, Inc. in Fort Lauderdale, Florida, where he is involved with the evolution of the Citrix WinFrame and MetaFrame server platforms. He can be reached at [email protected].

Get Source Code


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.