Making WinDbg Your Friend - Creating Debugger Extensions (编写windbg扩展)

罗翔
2023-12-01

转自http://www.osronline.com/article.cfm?id=52 (The Nt insider)

What can be more fun than trying to scan through your private data structures using WinDbg’s Command window?  Anyone who has tried to walk data structures from within WinDbg will know, it can be done, but it requires a lot of byte counting, lots of cross referencing with your header files, and too much pain.  It reminds me of debugging in the days before symbolic debuggers, where your only way to find out why a system crashed was to open and examine memory locations and put together enough evidence to solve the puzzle.  Wouldn’t it be nice if you could type !mydata from within WinDbg, and have WinDbg walk your data structures, and nicely display your data?  Maybe it would even flag fields that are outside of their expected range.  Or maybe it would search memory looking for a “lost” data structure.  With a little time and effort, your WinDbg can do this.

 

By writing a customized WinDbg Debugger Extension, you can simplify your debugging, and make WinDbg your friend.  Well, you might say, “This sounds like a great idea.  How do I begin writing a debugger extension?  After all, there is very little documentation on this….”.  This article will describe how to write your own debugger extension.  It contains the basic steps and framework required to write a debugger extension. You should be able to take what is in this article, and add the code that has specific knowledge of your data structures to have a fully functional debugger extension. As with almost anything that saves time, this will require some up-front work, but hopefully work that will pay off in the long term. 

 

This article will cover the basics of debugger extensions.  We’ll then look at the WinDbg API, and finally look at an example.  There are also debugger extensions available from the Microsoft Support Tools that will walk various Window NT data structures.  These will not be described in this article

 

Let’s start with basics -What’s a debugger extension?

A debugger extension is a DLL that is loaded into our friend WinDbg that “adds” commands that are available to you, when debugging in WinDbg. These commands usually are used to interpret data structures, and can be invoked from the Command window of WinDbg.  Different debugger extensions add different capabilities.  For example, the Microsoft debugger extension kdextx86.dll adds commands such as:

 

            !devobj <device address>

            !drvobj <driver address>

            !drivers

            !locks

 

To load a debugger extension, make sure the debugger extension’s DLL is in the “\bin” subdirectory under your Microsoft SDK directory (this is where WinDbg looks for it).  Then you can load the DLL from the command window of WinDbg with the following command:

 

>    !load kdextx86

Debugger extension library [E:\mssdk\Bin\NT4Chk\kdextx86] loaded

 

You can also provide a pathname if you want, but I find it easier to put it in the SDK\bin directory and load it from there. 

 

Conversely, to unload it, use the  !unload command:

 

>    !unload kdextx86

 

Note, that if your debugger extension was the last debugger extension loaded, you can just type:

 

>    !unload

 

If you would like general information about using WinDbg for debugging, refer to Chapter 4 of the Windows NT DDK Documentation.  We’re going to assume that you are familiar with WinDbg, and focus now on how to build your own WinDbg Debugger Extension. 

 

 

Initializing with WinDbg

 

The WinDbg debugger extension interface is defined in the header file WDbgExts.h, which can be found in the \Include subdirectory of the SDK.  This file contains definitions that are used by your debugger extension.  The .h file should be included in your Debugger Extension source file.  Note that if you include ntddk.h AND WdbgExts.h in the same module, you’ll get a bunch of compiler redefinition warnings.  What we do is to keep all the WinDbg API code in one source file (which includes WdbgExts.h, but not ntddk.h), and all our data structure parsing in other source files.  For the simple example below, we’ll just keep it all in one source file, but not include ntddk.h.

 

There are two types of Exports that you must define.  One type is fixed-name routines that will be called during initialization.  The other type of exports is routine names that correspond with commands.  For example, if your debugger extension supports a !mydata command, it must export a routine named mydata().  The exports are listed in a .def file.   An example .def file is listed in Figure 1.

 

 

LIBRARY MYDBGEXT

 

DESCRIPTION 'Debugger extension for MyDbgExt’

 

EXPORTS

 

    CheckVersion

    WinDbgExtensionDllInit

    ExtensionApiVersion

    mydata

    showlist

    scanmemory

 

 

Figure 1 — .def file

 

 

CheckVersion()WinDbgExtensionDllInit() and ExtensionApiVersion() are predefined routine names that WinDbg will call to initialize or check version numbers.  The other exports (mydata(), showlist(), scanmemory()) are defined by the writer of the Debugger extension.  Note: these other exports MUST be lower case.  This .def file is compiled with your debugger extension.  Now lets look at each predefined routine.  

 

WinDbgExtensionDllInit()

This routine (shown in Figure 2) in your debugger extension is called when your extension is loaded by WinDbg using the !load mydbgext.dll command.  This routine is used for WinDbg to pass an address vector of its helper routines to your debugger extension.  These are the functions that you would call from your extension DLL to request some action from WinDbg.  In additions in the calling parameters, WinDbg will tell you whether the kernel is checked or free (if that matters to your debugger extension), and build number of the system. In MajorVersion, it passed 0x0c if the kernel is checked, and 0x0f if the system is free.  The NT Build number is passed in the MinorVersion field.  MajorVersion and MinorVersion can be optionally used by your debugger extension to exhibit different behavior for different versions of NT, or even for checked vs. free builds of the kernel.  We’ll discuss the extension APIs (WINDBG_EXTENSION_APIS) more below.

 

 

WINDBG_EXTENSION_APIS  ExtensionApis;

 

VOID WinDbgExtensionDllInit( PWINDBG_EXTENSION_APIS lpExtensionApis,

                                                         USHORT MajorVersion,

                                                                                                USHORT MinorVersion ) {

 

                ExtensionApis = *lpExtensionApis;

                << any initialization or ver checking that you want to perform>>

                return;

  }

 

Figure 2 — WinDbgExtensionDllInit()

 

 

Note: Its important that the name you choose to store the WINDBG_EXTENSION_APIS structure is “ExtensionApis” as WdbgExts.h expects this name to be used by your Debugger extension.

 

ExtensionApiVersion()

This routine will be called to check the version of the debugger extension.  What we do is to just return the numbers that WinDbg expects so that it won’t get upset.  See the  example of using ExtensionApiVersion() in Figure 3.

 

 

typedef struct EXT_API_VERSION {USHORT  MajorVersion;

           USHORT  MinorVersion;

           USHORT  Revision;

           USHORT  Reserved;

} EXT_API_VERSION, *LPEXT_API_VERSION;

 

EXT_API_VERSION ApiVersion = {3, 5, EXT_API_VERSION_NUMBER, 0};

LPEXT_API_VERSION ExtensionApiVersion(VOID) {

                Return &ApiVersion

}

 

Figure 3—ExtensionApiVersion()

 

 

EXT_API_VERSION_NUMBER is defined in WdbgExt.h and represents the WinDbg version of the API. 

CheckVersion()

This routine is called for each exported command supported by your WinDbg extension DLL.  It is a fairly useless command since there is little use for version checking for EACH command.  Rather, version checking should take place during initialization time.  An example of using CheckVersion() is:

 

ULONG CheckVersion(VOID) {

      Return;

}

 

mydata()

For each command supported by your debugger extension, you’ll need a separate routine with an export for each.  For example, if the debugger extension implements a !mydata command.  It will need to export “mydata” in the .def file as described above.  In the source code for the debugger extension, it will need to include a routine such as the following:

 

      DECLARE_API (mydata) {

                  //

                  // This contains the functionality that

                  // will be executed when someone

                  // types !mydata in WinDbg. 

                  //

                  << custom functionality >>

      }

 

WINDBG_EXTENSION_APIS

As we mentioned above, during initialization, WinDbg will pass a vector of helper routines to the debugger extension.  This structure has a prototype like that shown in Figure 4.

 

 

typedef struct _WINDBG_EXTENSION_APIS {

    ULONG                                                              nSize;

    PWINDBG_OUTPUT_ROUTINE                  lpOutputRoutine;

    PWINDBG_GET_EXPRESSION                    lpGetExpressionRoutine;

    PWINDBG_GET_SYMBOL                           lpGetSymbolRoutine;

    PWINDBG_DISASM                                      lpDisasmRoutine;

    PWINDBG_CHECK_CONTROL_C              lpCheckControlCRoutine;

    PWINDBG_READ_PROCESS_MEMORY_ROUTINE    lpReadProcessMemoryRoutine;

    PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE  lpWriteProcessMemoryRoutine;

    PWINDBG_GET_THREAD_CONTEXT_ROUTINE     lpGetThreadContextRoutine;

    PWINDBG_SET_THREAD_CONTEXT_ROUTINE     lpSetThreadContextRoutine;

    PWINDBG_IOCTL_ROUTINE                      lpIoctlRoutine;

    PWINDBG_STACKTRACE_ROUTINE          lpStackTraceRoutine;

} WINDBG_EXTENSION_APIS, *PWINDBG_EXTENSION_APIS;

 

 

Figure 4—WINDBG_EXTENSION_APIS

 

For ease of use, there are also several #defines in WDbgExts.h that make using these APIs easier.  Typically, these defines (see Figure 5) are what we use to call the helper routines. 

 

#define dprintf                       (ExtensionApis.lpOutputRoutine)

#define GetExpression         (ExtensionApis.lpGetExpressionRoutine)

#define GetSymbol               (ExtensionApis.lpGetSymbolRoutine)

#define Disassm                    (ExtensionApis.lpDisasmRoutine)

#define CheckControlC        (ExtensionApis.lpCheckControlCRoutine)

#define ReadMemory           (ExtensionApis.lpReadProcessMemoryRoutine)

#define WriteMemory          (ExtensionApis.lpWriteProcessMemoryRoutine)

#define GetContext               (ExtensionApis.lpGetThreadContextRoutine)

#define SetContext               (ExtensionApis.lpSetThreadContextRoutine)

#define Ioctl                           (ExtensionApis.lpIoctlRoutine)

#define StackTrace               (ExtensionApis.lpStackTraceRoutine)

 

Figure 5—WDbgExts.h

 

 

Above, we mentioned that WdbgExts.h expects you to use “ExtensionApis” as your name for the WINDBG_ EXTENSION_APIS structure.  You’ll see why in the #defines in the table above.  These #defines hardwire the name of the structure to be “ExtensionApis”. 

 

dprintf - Takes DbgPrint style parameters, and results in a message being displayed to the WinDbg command window.

 

GetExpression – Interprets a symbolic expression input by the user in the command window.  It takes as its only parameter, a pointer to a character string used to extract the expression.  Typically, this would be “args” (a pointer to the command line typed by the user after the !mydata string.  So, if someone typed: !mydata fe123456, from your mydata() routine, you could take “args” and pass it to GetExpression() to return the address corresponding to the string fe123456.  Note: if you use GetExpression() to do a symbol lookup, you need to be careful because different versions of WinDbg will produce different results.  For example, if someone typed “!mydata MyGlobalData”, you can use GetExpression() to resolve MyDLL!MyGlobalData().  For older versions of WinDbg, it will resolve it as *MyGlobalData(), while newer versions will resolve it as & MyGlobalData().  It’s just something to watch out for.

 

GetSymbol – Given an address, this function finds the nearest symbol.  GetSymbol() expects the first parameter to be an address, and the second parameter to be a pointer to a character string where WinDbg will store the symbol name.  The third parameter is a pointer to a location where WinDbg will store the offset between the address of the symbol, and the address that we gave it in the first parameter of the call.

 

Disassm – Disassembles instructions.  The first parameter is a pointer to the address you want to disassemble.  The second parameter is a pointer to the output buffer where you want the disassembly string to be stored. 

 

CheckControlC – Checks to see if the user has entered Ctrl-C.  There are no parameters passed to this function.  It returns 0 if NO Ctrl-C has been typed, and is non-zero if a Ctrl-C was typed.  If you have places in your code where a lot of data is transferred between the debugger and debuggee (such as scanning memory), you should put a CheckControlC() in the loop to give the user a chance of aborting the command. 

 

ReadMemory() – Reads from virtual memory.  This routine reads memory from your debuggee system.  The first parameter represents the virtual address to read.  The second parameter is a pointer a buffer where ReadMemory() will store the data.  The third parameter represents how much memory you want to read, and the fourth parameter is a pointer a ULONG representing how much data was actually read. 

 

WriteMemory() – Writes into virtual memory.  The write parameters are basically the same as the ReadMemory() parameters.

 

GetContext() – Gets context for a processor.  The first parameter is the context.  The second parameter is a pointer to where the context should be stored.  The third parameter is the size of the context.

 

SetContext() – Sets the context for a processor.  The parameters are the same as the GetContext().

 

Ioctl – There are multiple types of Ioctls.  See WdbgExts.h for complete list (look for IG_*).  The first parameter of the call is the Ioctl value.  The second parameter is the pointer to a buffer where the resulting data should be stored, and the third parameter is the length of the buffer.

 

StackTrace() – Performs a stack trace.  Refer to WdbgExts.h for a list of parameters.

 

In addition to these helper functions, there are several global data structures that are available from within your debugger extension when called in your command routine:

 

HANDLE hCurrentProcess             // Handle for the current process

HANDLE hCurrentThread             // Handle for the current thread

ULONG dwCurrentPc                   // Program counter

ULONG dwProcessor                   // Current processor

PCSTR args                                   // Command line arguments

 

The most useful of these (at least for our debugger extensions) is args.  This will be a pointer to the command string following !mydata

 

Example

Let’s put it all together in the following example.  This example implements a simple debugger extension that implements the WinDbg command:

 

!mydata <address of the FOO data structure>

 

What it will do is lookup and print the pFoo->Flags field of the supplied pFoo address.  Note this is a very simple example, but illustrates the basic structure of a debugger extension (see Figure 6).

 

#include <wdbgexts.h>

EXT_API_VERSION        ApiVersion = { 3, 5, EXT_API_VERSION_NUMBER, 0 };

WINDBG_EXTENSION_APIS  ExtensionApis;

 

typedef struct FOO {

    SHORT Type;

    USHORT Size;

    ULONG MdlAddress;

    ULONG Flags;

} FOO, *PFOO;

 

//

// API routine for !mydata command

//

DECLARE_API (mydata) {

    ULONG pFoo, fooFlags, bRead;

    if (!args || !*args) {

 

        dprintf("!mydata: No argument found\n");

        return;

    }

    dprintf("Debug command !mydata, args=%s\n",args);

    pFoo=GetExpression(args);

    if (!pFoo) {

        dprintf("!mydata: Arguement not a valid address\n");

        return;

    }

    ReadMemory(pFoo + FIELD_OFFSET(FOO, Flags), &fooFlags, sizeof(fooFlags), &bRead);

    if (bRead != sizeof(fooFlags)) {

                dprintf(“Error reading data\n”)

    } else {

dprintf("!mydata: Content of pFoo->flags is %x\n",fooFlags);

    }

    return;

}

 

//

// WinDbgExtensionDllInit: KERNEL DEBUGGER EXTENSION INITIALIZATION

//

VOID WinDbgExtensionDllInit(

    PWINDBG_EXTENSION_APIS lpExtensionApis,

    USHORT MajorVersion,

    USHORT MinorVersion)  { 

   

    ExtensionApis = *lpExtensionApis;

 

    return;

}

 

//

// Check Version routine

//

VOID CheckVersion(VOID) {

                return;

}

 

//

// API version routine

//

LPEXT_API_VERSION ExtensionApiVersion(VOID) {

    return &ApiVersion;

 

 

Figure 6—Simple Debugger Extension

 

 

Once our debugger extension is loaded into WinDbg, we will be called in mydata() when someone types !mydata.  The routine uses GetExpression() to get the address that follows “!mydata” on the command line.  It then needs to compute the address of the pFoo->Flags field by adding the offset of ->Flags to the address that was supplied on the command line.  Once we have the address that we want to read, we use the ReadMemory() to retrieve the contents of the address, and display the results.

 

Note: once you have the address of pFoo from GetExpression(), you cannot do something like this:

 

fooFlags = pFoo->Flags

 

You must use ReadMemory() to read the contents of Virtual Addresses.  So to get fooFlags, you must use the following:

 

      ReadMemory(pFoo + FIELD_OFFSET(FOO, Flags), &fooFlags, sizeof(fooFlags), &bRead);

 

You could also read the whole FOO structure into memory as follows:

 

       FOO localFoo;

            …

       ReadMemory(pFoo, &localFoo, sizeof(localFoo), &bRead);

       fooFlags = localFoo.Flags;

 

Summary

Hopefully, this will give you a framework to get started with your debugger extension.  In summary, we’ll list some of the key tricks and tips that will help you as you implement your debugger extension.

 

·         ·         All commands must be lower case. 

 

·         ·         You have an address you want to access, you can’t just read the contents of the address.  You must use ReadMemory() to retrieve the contents of an address.

 

·         ·         If you are accessing a field of a structure, add FIELD_OFFSET to the base address of the structure before calling ReadMemory().  You can’t just do Flags = PFoo->Flags.

 

·         ·         If accessing multiple data structure fields, it is easier to read the whole structure into local memory (e.g. ReadMemory (pFoo, &localFoo, sizeof(localFoo), 0);  ).  Then you can access the fields of the data structure using localFoo.Flags, localFoo.Type, etc.

 

·         ·         Default extension DLLs are loaded from MSSDK\bin

 

·         ·         See MSDN Visual Studio Library Samples for a sample Extension DLL.

 

·         ·         Different versions of WinDbg behave differently

 

·         ·         Different symbol lookup behavior – GetExpression() for driver!Foo for older WinDbg’s returns *Foo, unlike new WinDbg’s that return &Foo.

·         ·         CheckControlC() doesn’t seem to work for newer WinDbg’s


 类似资料: