调试指南4:编写调试器扩展命令

来源:互联网 发布:拉刀长短的数据 编辑:程序博客网 时间:2024/06/15 12:11
 
简介
欢迎来到第四部分。这部分中,我们将稍稍偏离一下实际的调试,深入研究一下怎样建立有用的调试辅助工具。当然,我不会仅仅写一个辅助工具,我将为大家展示一下,有些情况,我们不得不写一些很繁琐的东西。我们会看到这些东西最好自动生成,然后我再向大家介绍怎样自动生成它们。
在我的这些文章中,我经常会在栈或其它位置搜索一些字符串。为什么要这么做呢?人不是电脑,我们能理解的是语言而不是那些数字。许多应用程序甚至驱动都是基于字符串来编写的。我不能说一切都是字符串,但肯定在某些地方会有字符串。如果你仔细考虑一下,这些字符串对程序的运行根本是没用的,这样我们也可以只使用数字而不用字符串吗?有些人可能会说,当暴露一个UI时,最后肯定会用到字符串。其实我不是在说用户接口部分,我想说的是程序内部不必暴露给用户的那部分。开发者也是人,他们会喜欢语言而不是二进制。所以,即使程序内部也会使用字符串来代表有些东西。字符串无处不在,甚至是在驱动程序中。
找到字符串了,然后呢?
字符串会为程序增加额外的可读性。凭经验而言,你可能会在栈上找到字符串,它们让你更好的了解到程序正在干什么。这些字符串有可能是环境字符串、文件名、设备名(COM1,/Device/xxxx,等等)、其它对象名、用户名、GUID等等。这些信息可以更好的指示程序正在干什么以及它在执行代码中的哪部分。
还有另外一个有趣的想法,虽然我不知道这是不是事实。但我相信通常的缓冲区溢出都是由非法字符串造成的。有可能是忘记了字符串最后的那个NULL或者错误的内存分配(比如,API返回的是字符的个数而不是字节的个数)。如果能在程序中找到越界的字符串,就能很轻易的找到是谁创建了它。
从哪里开始?
那就从堆栈开始吧。如果我发现一个陷阱,在“KB”和“DDS ESP”之后要做的就是“DC ESP”。这个命令可以在左边显示DWORD的值在右边显示可打印出来的字符。下面是一个例子:
0:000> dc esp
0006febc 77d43a09 77d43c7d 0006fefc 00000000 .:.w}<.w........
0006fecc 00000000 00000000 00000000 0006ff1c ................
0006fedc 010028e4 0006fefc 00000000 00000000 .(..............
0006feec  00000000 00000000 77e7ad86 00091ee7 ...........w....
0006fefc 001a03e4 00000118 0000ffff bf8a75ed .............u..
0006ff0c 0768a2ca 00000229 00000251 00000000 ..h.)...Q.......
0006ff1c 0006ffc0 01006c54 01000000 00000000 ....Tl..........
0006ff2c  00091ee7 0000000a 00000000 00000000 ................
0:000> dc
0006ff3c 7ffdf000 80543940 f544fc5c 00000044 ....@9T./.D.D...
0006ff4c 00092b28 00092b48 00092b70 00000000 (+..H+..p+......
0006ff5c 00000000 00000000 00000000 00000000 ................
0006ff6c 00000000 00000000 00000000 00000000 ................
0006ff7c 00000000 ffffffff ffffffff ffffffff ................
0006ff8c 00091ee7 00000000 00000001 00272620 ............ &'.
0006ff9c 00272d00 00000000 00000000 0006ff34 .-'.........4...
0006ffac e24296d0 0006ffe0 01006d14 01001888 ..B......m......
0:000>
0006ffbc 00000000 0006fff0 77e814c7 00000000 ...........w....
0006ffcc 00000000 7ffdf000 f544fcf0 0006ffc8 ..........D.....
0006ffdc 80534504 ffffffff 77e94809 77e91210 .ES......H.w...w
0006ffec 00000000 00000000 00000000 01006ae0 .............j..
0006fffc 00000000 78746341 00000020 00000001 ....Actx .......
0007000c 00000654 0000007c 00000000 00000020 T...|....... ...
0007001c 00000000 00000014 00000001 00000003 ................
0007002c 00000034 000000ac 00000001 00000000 4...............
0:000>
0007003c 00000000 00000000 00000000 00000000 ................
0007004c 00000002 00000000 00000000 00000000 ................
0007005c 00000168 00000190 00000000 2d59495b h...........[IY-
0007006c 000002f8 00000032 0000032c 000002b8 ....2...,.......
0007007c 00000010 00000002 0000008c 00000002 ................
0007008c 00000001 000000ac 00000538 00000001 ........8.......
0007009c 00000002 000005e4 00000070 00000001 ........p.......
000700ac 64487353 0000002c 00000001 00000001 SsHd,...........
0:000>
000700bc 00000003 00000002 0000008c 00000001 ................
000700cc 00000000 0000002c 0000005e 0000005e ....,...^...^...
000700dc 00000000 00000000 00000000 00000000 ................
000700ec 00000000 00000000 00000000 00000000 ................
000700fc 00000000 00000002 00000028 00000034 ........(...4...
0007010c 003a0043 0057005c 004e0049 004f0044 C.:./.W.I.N.D.O.
0007011c 00530057 0030002e 0057005c 006e0069 W.S...0./.W.i.n.
0007012c 00780053 005c0073 00000000 00000000 S.x.s./.........
我打开了notepade.exe然后使用调试工具显示了主线程,也是唯一的一个线程的堆栈。堆栈上的东西应该是一些象我们程序中常见的char x[10]之类的局部的数组。但这并不是全部的字符串,还有一些指针也会指向一些局部字符串甚至是传递给其它的函数。比如,CreateFile,它的第一个参数就是一个字符串。
所以,我只能一个个的从内存中寻找可能是字符串的地方,然后使用“DC”(或者“DA”显示ANSI,“DU”显示Unicode)。这个过程很慢很繁琐。调试器也不支持其它工具实现这个功能,所以我需要自己写一个。
自己写一个?
WINDBG支持自定义的DLL,只要这个DLL符合微软DLL的规则并且输出了一些函数。也就是说,它支持插件。人们曾经写过形如!<mydatastructure> <address>的插件命令来根据名字显示数据结构的成员。然而,WINDBG支持“dt”命令。如果有PDB,它就可以实现上面的功能,不需要写任何代码。我们来看一个例子:
0:000> dt ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 SpareBool        : UChar
   +0x004 Mutant           : Ptr32 Void
   +0x008 ImageBaseAddress : Ptr32 Void
   +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
   +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
   +0x014 SubSystemData    : Ptr32 Void
   +0x018 ProcessHeap      : Ptr32 Void
   +0x01c FastPebLock      : Ptr32 _RTL_CRITICAL_SECTION
   +0x020 FastPebLockRoutine : Ptr32 Void
   +0x024 FastPebUnlockRoutine : Ptr32 Void
   +0x028 EnvironmentUpdateCount : Uint4B
   +0x02c KernelCallbackTable : Ptr32 Void
   +0x030 SystemReserved   : [1] Uint4B
   +0x034 ExecuteOptions   : Pos 0, 2 Bits
   +0x034 SpareBits        : Pos 2, 30 Bits
   +0x038 FreeList         : Ptr32 _PEB_FREE_BLOCK
   +0x03c TlsExpansionCounter : Uint4B
   +0x040 TlsBitmap        : Ptr32 Void
   +0x044 TlsBitmapBits    : [2] Uint4B
   +0x04c ReadOnlySharedMemoryBase : Ptr32 Void
   +0x050 ReadOnlySharedMemoryHeap : Ptr32 Void
   +0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void
   +0x058 AnsiCodePageData : Ptr32 Void
   +0x05c OemCodePageData : Ptr32 Void
   +0x060 UnicodeCaseTableData : Ptr32 Void
   +0x064 NumberOfProcessors : Uint4B
   +0x068 NtGlobalFlag     : Uint4B
   +0x070 CriticalSectionTimeout : _LARGE_INTEGER
   +0x078 HeapSegmentReserve : Uint4B
   +0x07c HeapSegmentCommit : Uint4B
   +0x080 HeapDeCommitTotalFreeThreshold : Uint4B
   +0x084 HeapDeCommitFreeBlockThreshold : Uint4B
   +0x088 NumberOfHeaps    : Uint4B
   +0x08c MaximumNumberOfHeaps : Uint4B
   +0x090 ProcessHeaps     : Ptr32 Ptr32 Void
   +0x094 GdiSharedHandleTable : Ptr32 Void
   +0x098 ProcessStarterHelper : Ptr32 Void
   +0x09c GdiDCAttributeList : Uint4B
   +0x0a0 LoaderLock       : Ptr32 Void
   +0x0a4 OSMajorVersion   : Uint4B
   +0x0a8 OSMinorVersion   : Uint4B
   +0x0ac OSBuildNumber    : Uint2B
   +0x0ae OSCSDVersion     : Uint2B
   +0x0b0 OSPlatformId     : Uint4B
   +0x0b4 ImageSubsystem   : Uint4B
   +0x0b8 ImageSubsystemMajorVersion : Uint4B
   +0x0bc ImageSubsystemMinorVersion : Uint4B
   +0x0c0 ImageProcessAffinityMask : Uint4B
   +0x0c4 GdiHandleBuffer : [34] Uint4B
   +0x14c PostProcessInitRoutine : Ptr32
   +0x150 TlsExpansionBitmap : Ptr32 Void
   +0x154 TlsExpansionBitmapBits : [32] Uint4B
   +0x1d4 SessionId        : Uint4B
   +0x1d8 AppCompatFlags   : _ULARGE_INTEGER
   +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER
   +0x1e8 pShimData        : Ptr32 Void
   +0x1ec AppCompatInfo    : Ptr32 Void
   +0x1f0 CSDVersion       : _UNICODE_STRING
   +0x1f8 ActivationContextData : Ptr32 Void
   +0x1fc ProcessAssemblyStorageMap : Ptr32 Void
   +0x200 SystemDefaultActivationContextData : Ptr32 Void
   +0x204 SystemAssemblyStorageMap : Ptr32 Void
   +0x208 MinimumStackCommit : Uint4B
0:000>
上面我们显示了Windows定义的_PEB的成员。下面我们找到自己的PEB然后显示它的内容:
0:000> !teb
TEB at 7ffde000
    ExceptionList:        0006ffb0
    StackBase:            00070000
    StackLimit:           0005f000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 7ffde000
    EnvironmentPointer:   00000000
    ClientId:             00000b80 . 00000f40
    RpcHandle:            00000000
    Tls Storage:          00000000
    PEB Address:          7ffdf000
    LastErrorValue:       0
    LastStatusValue:      c0000034
    Count Owned Locks:    0
    HardErrorMode:        0
0:000> dt ntdll!_PEB 7ffdf000
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
   +0x003 SpareBool        : 0 ''
   +0x004 Mutant           : 0xffffffff
   +0x008 ImageBaseAddress : 0x01000000
   +0x00c Ldr              : 0x00191ea0
   +0x010 ProcessParameters : 0x00020000
   +0x014 SubSystemData    : (null)
   +0x018 ProcessHeap      : 0x00090000
   +0x01c FastPebLock      : 0x77fc49e0
   +0x020 FastPebLockRoutine : 0x77f5b2a0
   +0x024 FastPebUnlockRoutine : 0x77f5b380
   +0x028 EnvironmentUpdateCount : 1
   +0x02c KernelCallbackTable : 0x77d42a38
   +0x030 SystemReserved   : [1] 0
   +0x034 ExecuteOptions   : 0y00
   +0x034 SpareBits        : 0y000000000000000000000000000000 (0)
   +0x038 FreeList         : (null)
   +0x03c TlsExpansionCounter : 0
   +0x040 TlsBitmap        : 0x77fc4680
   +0x044 TlsBitmapBits    : [2] 0x7ff
   +0x04c ReadOnlySharedMemoryBase : 0x7f6f0000
   +0x050 ReadOnlySharedMemoryHeap : 0x7f6f0000
   +0x054 ReadOnlyStaticServerData : 0x7f6f0688 -> (null)
   +0x058 AnsiCodePageData : 0x7ffb0000
   +0x05c OemCodePageData : 0x7ffc1000
   +0x060 UnicodeCaseTableData : 0x7ffd2000
   +0x064 NumberOfProcessors : 1
   +0x068 NtGlobalFlag     : 0x70
   +0x070 CriticalSectionTimeout : _LARGE_INTEGER 0xffffe86d`079b8000
   +0x078 HeapSegmentReserve : 0x100000
   +0x07c HeapSegmentCommit : 0x2000
   +0x080 HeapDeCommitTotalFreeThreshold : 0x10000
   +0x084 HeapDeCommitFreeBlockThreshold : 0x1000
   +0x088 NumberOfHeaps    : 5
   +0x08c MaximumNumberOfHeaps : 0x10
   +0x090 ProcessHeaps     : 0x77fc5a80 -> 0x00090000
   +0x094 GdiSharedHandleTable : 0x00360000
   +0x098 ProcessStarterHelper : (null)
   +0x09c GdiDCAttributeList : 0x14
   +0x0a0 LoaderLock       : 0x77fc1774
   +0x0a4 OSMajorVersion   : 5
   +0x0a8 OSMinorVersion   : 1
   +0x0ac OSBuildNumber    : 0xa28
   +0x0ae OSCSDVersion     : 0x100
   +0x0b0 OSPlatformId     : 2
   +0x0b4 ImageSubsystem   : 2
   +0x0b8 ImageSubsystemMajorVersion : 4
   +0x0bc ImageSubsystemMinorVersion : 0
   +0x0c0 ImageProcessAffinityMask : 0
   +0x0c4 GdiHandleBuffer : [34] 0
   +0x14c PostProcessInitRoutine : (null)
   +0x150 TlsExpansionBitmap : 0x77fc4660
   +0x154 TlsExpansionBitmapBits : [32] 0
   +0x1d4 SessionId        : 0
   +0x1d8 AppCompatFlags   : _ULARGE_INTEGER 0x0
   +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER 0x0
   +0x1e8 pShimData        : (null)
   +0x1ec AppCompatInfo    : (null)
   +0x1f0 CSDVersion       : _UNICODE_STRING "Service Pack 1"
   +0x1f8 ActivationContextData : 0x00080000
   +0x1fc ProcessAssemblyStorageMap : 0x000929a8
   +0x200 SystemDefaultActivationContextData : 0x00070000
   +0x204 SystemAssemblyStorageMap : (null)
   +0x208 MinimumStackCommit : 0
0:000>
如果你还记得,!teb是显示了线程环境内存块。它里面包含了一个进程的PEB结构。可以看到,它可以根据数据类型显示结构内成员的信息。为什么要给你们看这些呢?因为我上面说过,起初人们想使用扩展调试工具显示所有的数据结构,但这并不可行。因为每次加入或更改相关信息时都需要更改调试代码。我们最好用“dt”来实现这一点。
编写扩展
所以我认为最好是写一个!dumpstrings。这个命令会显示一段内存内的所有指针指向的内容。这样就可以使用!dumpstrings esp来显示栈上所有指针指向的字符串。
首先,来看看怎么写一个扩展。WINDBG规定,一个扩展DLL至少需要两个导出函数。下面的函数是从我的代码中摘抄下来的:
/***********************************************************
 * ExtensionApiVersion
 *
 * Purpose: WINDBG will call this function to get the version
 *          of the API
 *
 * Parameters:
 *     Void
 *
 * Return Values:
 *     Pointer to a EXT_API_VERSION structure.
 *
 ***********************************************************/              
LPEXT_API_VERSION WDBGAPI ExtensionApiVersion (void)
{
    return &g_ExtApiVersion;
}
 
 
/***********************************************************
 * WinDbgExtensionDllInit
 *
 * Purpose: WINDBG will call this function to initialize
 *          the API
 *
 * Parameters:
 *     Pointer to the API functions, Major Version, Minor Version
 *
 * Return Values:
 *     Nothing
 *
 ***********************************************************/              
VOID WDBGAPI WinDbgExtensionDllInit (PWINDBG_EXTENSION_APIS 
           lpExtensionApis, USHORT usMajorVersion, 
           USHORT usMinorVersion)
{
     ExtensionApis = *lpExtensionApis;
}
第一个函数是ExtensionApiVersion,这个函数会返回一个版本信息,我们需要做的就是提供WINDBG需要的版本号。下面是g_ExtApiVersion结构:
/***********************************************************
 * Global Variable Needed For Versioning
 ***********************************************************/              
EXT_API_VERSION g_ExtApiVersion = {
         5 ,
         5 ,
         EXT_API_VERSION_NUMBER ,
         0
     } ;
EXT_API_VERSION_NUMBER在wdbgexts.h中声明。根据扩展DLL的种类不同可能会用到其它库文件比如,ntsdexts.h。我现在所作的只是一个兼容CDB和WinDbg的简单的扩展所以只需要windbgexts.h。但这两个头文件都会被包含进来。
那EXT_API_VERSION_NUMBER是怎样声明的呢?在我的系统中,它的声明如下:
#define EXT_API_VERSION_NUMBER   5
 
typedefstruct EXT_API_VERSION {
    USHORT MajorVersion;
    USHORT MinorVersion;
    USHORT Revision;
    USHORT Reserved;
} EXT_API_VERSION, *LPEXT_API_VERSION;
那5,5是怎么来得呢?我跟踪调试了一些其它与WINDBG一起使用的扩展,发现这两个参数是5,5。而一些老版本的WINDBG这两个参数是3,5。关于这个还是有些疑义的,但我们只是需要构建一个基本框架然后实现我们需要的函数。我没有把它看作一个大问题而去深入探究,只要我们的扩展函数能加载上就没有问题了。
WinDbg ExtensionDllInit函数会为应用程序创建一个虚函数表。虚函数表必须有一个特定的名字,也可以说不是必须但如果有的话后面的事情会变得很简单。因为,windbgexts.h中使用一些宏来调用这个结构中的函数。如果不使用同一个名字,那就必须自己创建这些函数调用。下面是我的代码中的全局变量:
/***********************************************************
 * Global Variable Needed For Functions
 ***********************************************************/              
WINDBG_EXTENSION_APIS ExtensionApis = {0};
其实也没什么关系,这些宏只是为了易于函数调用,你当然也可以更改。下面是WINDGBEXTS.H中宏的内容:
#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)
除了这两个API外,也可以增加一个CheckVersion()函数来强制我们的扩展只能应用于某些特定版本的WINDBG。这个对我们来说没有多大用处。我们仅仅实现命令实现函数。第一个命令是“!help”:
/***********************************************************
 * !help
 *
 * Purpose: WINDBG will call this API when the user types !help
 *          
 *
 * Parameters:
 *     N/A
 *
 * Return Values:
 *     N/A
 *
 ***********************************************************/
DECLARE_API (help)
{
    dprintf("Toby's Debug Extensions/n/n");
    dprintf("!dumpstrings <ADDRESS register> - Dumps 20 strings in"/
       "ANSI/UNICODE using this address as a pointer to strings (useful for" /
       "dumping strings on the stack) /n");
       /* String Split so it is readable in this article. */
}
Dprintf();是debug版本的printf()。它可以在调试器中显示一些内容。DECLARE_API(<command>)是一个宏,它简化了API函数的声明。记住一点,传给它的名字就是在调试器中使用的命令的名字。上面的名字叫help,所以在调试器中可以使用!help或者!<dllname>.help。这个函数仅仅为用户显示调试信息。
接下来就是要实现字符串函数。这个函数接受一个参数作为内存地址。我还想让它象现有的其它命令一样,比如执行dc<address>然后再执行dc他会接着显示下面的内存内容。下面是实现代码:

/***********************************************************
 * !dumpstrings
 *
 * Purpose: WINDBG will call this API when the user types !dumpstrings
 *          
 *
 * Parameters:
 *     !dumpstrings or !dumpstrings <ADDRESS register>
 *
 * Return Values:
 *     N/A
 *
 ***********************************************************/
DECLARE_API (dumpstrings)
{
    static ULONG Address = 0;
    ULONG GetAddress, StringAddress, Index = 0, Bytes;
    WCHAR MyString[51] = {0};
    
    
    GetAddress = GetExpression(args);
 
    if(GetAddress != 0)
    {
        Address = GetAddress;
    }
        
    dprintf("STACK   ADDR   STRING /n");
 
    for(Index = 0; Index <4*20; Index+=4)
    {
        memset(MyString, 0, sizeof(MyString));
        
        Bytes = 0;
 
        ReadMemory(Address + Index, &StringAddress, 
                           sizeof(StringAddress), &Bytes);
 
        if(Bytes)
        {
           Bytes = 0;
 
           ReadMemory(StringAddress, MyString, 
                 sizeof(MyString) - 2, &Bytes);
 
           if(Bytes)
           {
              dprintf("%08x : %08x = (UNICODE) /"%ws/"/n", 
                       Address + Index, StringAddress, MyString);
              dprintf("%08x : %08x = (ANSI)    /"%s/"/n", 
                       Address + Index, StringAddress, MyString);
           }
           else
           {
              dprintf("%08x : %08x = Address Not Valid/n", 
                             Address + Index, StringAddress);
           }
        }
        else
        {
           dprintf("%08x : Address Not Valid/n", Address + Index);
        }
    }
 
    Address += Index;
}
可以看到,函数调用的第一个外部函数是GetExpression()。在较新版本的WINDBG中,它的原型为ADDRESS GetExpression(SYMBOLIC STRING)。给它传递一个符号字符串作为参数,比如这个符号字符串可来自于命令的参数,函数会将这个字符串转换为一个地址。在这里,命令的参数保存在args里面,所以我给它传递args。这个函数可以将符号、地址甚至寄存器转换为数字,比如也可以将ESP作为参数。
下面我声明了一个静态变量。如果GetExpression()返回0,有可能没有给命令传递参数。在这种情况下,我会使用这个静态变量。这种情况出现在用户键入!dumpstrings时。它会从上次的位置继续向下显示。在函数调用结束时,我会将Address的值更新为下一次要显示的位置。
下面是调用了dprintf(),这个我前面讲过了。因为地址是4个字节的,所以在循环中我每次都会加4。我想让命令每次显示20个地址,所以循环从0到4×20。
我们不能仅仅指向并显示某块内存然后就返回,因为可能这块内存不属于应用程序的地址空间。所以,WINDBG提供了ReadMemory()函数。(windbgexts.h里面列出了所有API函数的原型,所以当你在API文档中找不到时,你可以直接到这个文件中查看。)这个函数接受4个参数:
ReadMemory(Address In Process To Read,
           Local Variable to store the memory read, 
           size of the local variable, 
           pointer to a DWORD that returns the number 
           of bytes read from the memory location);
程序中我向这个函数传递了一个指针用来接受返回的数据以及一个比特值接受返回数据的大小。如果返回的字节数为0,我们就会向用户显示非法地址;否则,我们从这块内存中读出49个字节(我们的缓冲时51个字节,后面两个放置两个NULL)。如果能成功读到东西,我就会用dprintf()分别使用ANSI和UNICODE显示它们。
这样函数就完成了。下面我们需要建立.DEF文件。这个文件规定了DLL的输出函数。
LIBRARY "TDBG.DLL"
 
EXPORTS
    WinDbgExtensionDllInit
    ExtensionApiVersion
    dumpstrings
    help
接下来,就是要编译这个工程。我从来不用VC++而是用make文件和Visual Slickedit。我已经创建了一个make文件,下面展示了怎样建立它。首先,执行VC安装目录下BIN文件夹里面的VARS32.BAT。然后在源文件所在目录执行nmake。
C:/Programming/Programs/debugext/src/debug>/vcvars32
Setting environment for using Microsoft Visual C++ tools.
C:/Programming/Programs/debugext/src/debug>nmake
 
Microsoft (R) Program Maintenance Utility   Version 6.00.8168.0
Copyright (C) Microsoft Corp 1988-1998. All rights reserved.
 
        cl /nologo /MD /W3 /Oxs /Zi /I "./inc" /D "WIN32" /DLL /D "_WINDOWS"
/Fr./obj/i386// /Fo./obj/i386// /Fd./obj/i386// /c ./tdbg.c
tdbg.c
C:/PROGRA~1/MICROS~2/VC98/INCLUDE/wdbgexts.h(526) : warning C4101: 'li' : unrefe
renced local variable
        link.exe /DLL /nologo /def:tdbg.def /out:../../bin/tdbg.dll /pdb:tdbg.p
db /debug /debugtype:both USER32.LIB KERNEL32.LIB ./obj/i386/tdbg.obj
   Creating library ../../bin/tdbg.lib and object ../../bin/tdbg.exp
        rebase.exe -b 0x00100000 -x ../../bin -a ../../bin/tdbg.dll
 
REBASE: Total Size of mapping 0x00010000
REBASE: Range 0x00100000 -0x00110000
 
C:/Programming/Programs/debugext/src/debug>nmake clean
 
Microsoft (R) Program Maintenance Utility   Version 6.00.8168.0
Copyright (C) Microsoft Corp 1988-1998. All rights reserved.
 
Deleted file - C:/Programming/Programs/debugext/src/debug/obj/i386/tdbg.obj
Deleted file - C:/Programming/Programs/debugext/src/debug/obj/i386/tdbg.sbr
Deleted file - C:/Programming/Programs/debugext/src/debug/obj/i386/vc60.pdb
 
C:/Programming/Programs/debugext/src/debug>
如果想重新编译,可以对源文件进行修改以改变其修改日期或者使用“nmake clean”。现在我们就拥有了这个二进制文件。
Volume in drive C has no label.
 Volume Serial Number is 2CF8-F7B5
 
 Directory of C:/Programming/Programs/debugext/bin
 
03/25/2004 08:56 PM    <DIR>          .
03/25/2004 08:56 PM    <DIR>          ..
03/25/2004 09:53 PM             2,412 tdbg.dbg
03/25/2004 09:53 PM            20,752 tdbg.dll
03/25/2004 09:53 PM             1,044 tdbg.exp
03/25/2004 09:53 PM             2,538 tdbg.lib
03/25/2004 09:53 PM            82,944 tdbg.pdb
               5 File(s)        109,690 bytes
               2 Dir(s) 12,229,009,408 bytes free
将这个文件拷贝到Windbg可以找到的目录,比如windows目录,也可以使用!load和!unload(当创建了一个新的扩展想强制调试器卸载旧的扩展时使用)来指定要加载/卸载的扩展路径。调用函数时可以使用!<dll name>.<function name>或者简单使用<function name>。如果指定了二进制文件名就可以强制WINDBG查找并加载指定的文件,当多个DLL扩展都输出同一个函数时也可以用来区别到底使用哪个DLL。
试试我们的扩展
我们已经创建了一个调试扩展工具,并把它放在了WINDBG可见的目录下。下面我就用!tdbg.dumpstrings esp来显示栈上的字符串。我重新启动notpade.exe,下面是运行情况:
0:000> !tdbg.dumpstrings esp
STACK   ADDR   STRING
0006febc : 77d43a09 = (UNICODE) ""
0006febc : 77d43a09 = (ANSI)    "&#9516;&#9658;"
0006fec0 : 77d43c7d = (UNICODE) ""
0006fec0 : 77d43c7d = (ANSI)    "ïN&#9830;ü&#8729;&#9787;&#9786;"
0006fec4 : 0006fefc = (UNICODE) ""
0006fec4 : 0006fefc = (ANSI)    "&#9616;&#9829;&#8596;"
0006fec8 : 00000000 = Address Not Valid
0006fecc : 00000000 = Address Not Valid
0006fed0 : 00000000 = Address Not Valid
0006fed4 : 00000000 = Address Not Valid
0006fed8 : 0006ff1c = (UNICODE) ""
0006fed8 : 0006ff1c = (ANSI)    "&#9492; &#9824;"
0006fedc : 010028e4 = (UNICODE) ""
0006fedc : 010028e4 = (ANSI)    "à&#9492;uö&#934;&#9500;&#8745;   5î¢"
0006fee0 : 0006fefc = (UNICODE) ""
0006fee0 : 0006fefc = (ANSI)    "&#9616;&#9829;&#8596;"
0006fee4 : 00000000 = Address Not Valid
0006fee8 : 00000000 = Address Not Valid
0006feec : 00000000 = Address Not Valid
0006fef0 : 00000000 = Address Not Valid
0006fef4 : 77e7ad86 = (UNICODE) ""
0006fef4 : 77e7ad86 = (ANSI)    "â|___FCKpd___435amp;#9830;"
0006fef8 : 00091ee8 = (UNICODE) ""
0006fef8 : 00091ee8 = (ANSI)    ""
0006fefc : 001d03de = (UNICODE) ""
0006fefc : 001d03de = (ANSI)    ""
0006ff00 : 00000118 = Address Not Valid
0006ff04 : 0000ffff = Address Not Valid
0006ff08 : bf8a75ed = Address Not Valid
0:000> !tdbg.dumpstrings
STACK   ADDR   STRING
0006ff0c : 077d5cc8 = Address Not Valid
0006ff10 : 000001f3 = Address Not Valid
0006ff14 : 000001df = Address Not Valid
0006ff18 : 00000000 = Address Not Valid
0006ff1c : 0006ffc0 = (UNICODE) ""
0006ff1c : 0006ffc0 = (ANSI)    "&#8801; &#9824;"
0006ff20 : 01006c54 = (UNICODE) ""
0006ff20 : 01006c54 = (ANSI)    "ï&#8801;ëuä9]&#931;uV §&#9604;&#8597;"
0006ff24 : 01000000 = (UNICODE) ""
0006ff24 : 01000000 = (ANSI)    "MZÉ"
0006ff28 : 00000000 = Address Not Valid
0006ff2c : 00091ee8 = (UNICODE) ""
0006ff2c : 00091ee8 = (ANSI)    ""
0006ff30 : 0000000a = Address Not Valid
0006ff34 : 00000000 = Address Not Valid
0006ff38 : 00000000 = Address Not Valid
0006ff3c : 7ffdf000 = (UNICODE) ""
0006ff3c : 7ffdf000 = (ANSI)    ""
0006ff40 : 80543940 = Address Not Valid
0006ff44 : f4910c5c = Address Not Valid
0006ff48 : 00000044 = Address Not Valid
0006ff4c : 00092b30 = (UNICODE) ""
0006ff4c : 00092b30 = (ANSI)    ""
0006ff50 : 00092b50 = (UNICODE) ""
0006ff50 : 00092b50 = (ANSI)    "WinSta0/Default"
0006ff54 : 00092b78 = (UNICODE) ""
0006ff54 : 00092b78 = (ANSI)    "C:/WINDOWS.0/System32/notepad.exe"
0006ff58 : 00000000 = Address Not Valid
0:000> !tdbg.dumpstrings
STACK   ADDR   STRING
0006ff5c : 00000000 = Address Not Valid
0006ff60 : 00000000 = Address Not Valid
0006ff64 : 00000000 = Address Not Valid
0006ff68 : 00000000 = Address Not Valid
0006ff6c : 00000000 = Address Not Valid
0006ff70 : 00000000 = Address Not Valid
0006ff74 : 00000000 = Address Not Valid
0006ff78 : 00000000 = Address Not Valid
0006ff7c : 00000000 = Address Not Valid
0006ff80 : ffffffff = Address Not Valid
0006ff84 : ffffffff = Address Not Valid
0006ff88 : ffffffff = Address Not Valid
0006ff8c : 00091ee8 = (UNICODE) ""
0006ff8c : 00091ee8 = (ANSI)    ""
0006ff90 : 00000000 = Address Not Valid
0006ff94 : 00000001 = Address Not Valid
0006ff98 : 00272620 = (UNICODE) ""
0006ff98 : 00272620 = (ANSI)    "(&'"
0006ff9c : 00272d00 = (UNICODE) ""
0006ff9c : 00272d00 = (ANSI)    "&#9617;-'"
0006ffa0 : 00000000 = Address Not Valid
0006ffa4 : 00000000 = Address Not Valid
0006ffa8 : 0006ff34 = (UNICODE) ""
0006ffa8 : 0006ff34 = (ANSI)    ""
在栈上我们看到了2个字符串。还不错,因为我仅仅是打开notepad而已,我没有做任何其它操作。这只是一个简单应用,大多数情况下从堆栈上找到的都是垃圾,这个无所谓了,因为我们要做的就是找到那些合法的字符串。
总结
我希望大家已经学会了怎样编写调试扩展工具。在以后的文章中我会为大家列出更多的API,然后编写出更多更高级的扩展命令。