編寫WinDbg擴展

介紹

  歡迎來到這個調試系列的第4篇。在這篇中,我們會稍微偏離一點實際的調試,而去研究一下對調試有幫助的東西。我絕對不會爲了寫調試擴展而去寫,寫這些東西必須要有一些原因,否則會變得很乏味。一旦你覺得你很願意去做這件事,那將會是自然而然的事情。

  在我進行調試的時候,我總是去檢查棧和其他地方來尋找某些字符串。爲什麼我要這樣做呢?我們不是電腦,我們懂語言,而不是數字。而許多程序甚至驅動程序都是以字符串爲基礎寫的,這並不是說一切都是字符串,但總有一些字符串。如果你想的話,你也完全可以不用字符串,你可以只用數字。但是如果你要展現一個UI界面的話,也應該有一些字符串,不可能全部都用數字。程序員也是人,也要用某種語言去交流,而不是用二進制去交流。因此,程序內部會使用到很多字符串來代表一些東西,甚至驅動程序也是。

 

有字符串又怎樣呢?

  它們讓程序的可讀性更強,這是最重要的一條。可能你在棧中找到了一個字符串,你會知道程序到底在幹什麼。這些字符串可能是環境字符串,文件名,設備名(COM1,\Device\xxx,etc),對象名,用戶名,GUIDs,等等。這些信息能很好的幫助你跟蹤程序。

  另外一個有趣的是,我們知道最常見的緩衝區溢出都要歸咎於不當的字符串操作。可能是忘記了最後的NULL,也可能是錯誤的判斷了API的返回信息。這樣的話,如果程序確實出現了這種情況,追蹤將會變得更簡單。

 

我們該如何開始

  我們從棧開始。當我們出錯中斷下來,首先就是使用”kb”和”DDS ESP”,然後就是使用”DC ESP”。讓我們來看看例子。

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.\.........

我打開了notepad.exe,然後中斷下來,Dump出主線程的棧。棧中的字符串都是局部數據,例如char x[10];。另外其他地方也會存儲一些字符串,然後用指針來引用他們,或者直接通過參數傳遞,比如CreateFile的第一個參數就是接收一個字符串。

  那麼,我們通常都該做些什麼呢?我會搜索棧,然後找一些可能的字符串,使用DCDADU命令顯示其中的字符串。但是這是件很乏味,並且很慢的事情。並且沒有任何調試器命令可以幫助做這些事情,因此我只能自己寫。

 

如何自己寫?

  WinDbg可以使用一些符合微軟規範了DLL,也就是說你可以寫插件。你可以寫類似於這樣的插件!<mydatastructure> <address>Dump出數據結構中的成員。然後,WinDbg提供了”dt”命令,如果你有PDB文件的話,它能幫助你完成這樣的功能。

  使用”dt <yourdll>!<your data structure>”可以把結構中的名稱都打印出來。我們來看一個例子。

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>

上面我們Dump出了_PEB結構中的信息。下面我們來找到自己的PEB然後Dump

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這樣的命令來Dump出棧中的信息。

  首先,我們來看看怎樣編寫擴展。WinDbg需要你至少導出兩個函數。如下:

/***********************************************************

 * 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,就是返回一個版本號,我們只要提供一個版本號就行了。下面是g_ExtApiVersion的聲明:

/***********************************************************

 * Global Variable Needed For Versioning

 ***********************************************************/              

EXT_API_VERSION g_ExtApiVersion = {

         5 ,

         5 ,

         EXT_API_VERSION_NUMBER ,

         0

     } ;

EXT_API_VERSION_NUMBERwdbexts.h有聲明。要注意的是,還有其他很多調試擴展DLL,比如ntsdexts.h。這裏我們只對現在版本的CDBWinDbg調試器作講解。我這裏使用的也是windbgexts.h,而不是ntsdexts.h。如果你看頭文件的話,你會注意到這兩個你都有。

  下面是EXT_API_VERSION_NUMBER在我係統上的顯示:

#define EXT_API_VERSION_NUMBER   5

 

typedef struct EXT_API_VERSION {

    USHORT  MajorVersion;

    USHORT  MinorVersion;

    USHORT  Revision;

    USHORT  Reserved;

} EXT_API_VERSION, *LPEXT_API_VERSION;

那麼55是怎麼得到的呢?這是我從WinDbg其他的調試擴展中找到的。我還發現老版本的WinDbg使用的是35。這些都只要根據實際情況填寫就可以了,然後我們需要編寫一個框架,然後編寫我們的命令。

  WinDbgExtensionDllInit這個API只是給你一個虛擬函數表,這個表命名爲一個固定的名字,當然不是必須的,但會讓你編寫起來更簡單。原因是windbgexts.h包含了一些宏,可以調用這個結構中的函數,如果不使用相同的名字,這些宏你就無法使用了。下面是我全局變量:

/***********************************************************

 * Global Variable Needed For Functions

 ***********************************************************/              

WINDBG_EXTENSION_APIS ExtensionApis = {0};

這個結構並不大,使用宏讓你的工作變得更見簡單。下面是WINDBGEXTS.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();就是調試輸出,和printf()一樣,它將信息打印到debugger中。DECLARE_API(<command>)可以幫助你很簡單的聲明一個API名字。記住,這個函數名字和你在調試器中使用命令的名字一樣。在這個例子中,我們就使用!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參數,就會取ESP中的值。

  我定義了一個靜態變量,如果GetExpression()返回0,可能就是沒有參數,這種情況下,就可以使用靜態變量中存儲的值繼續後面的工作,並且會在這個值之後繼續顯示,這就完成了上面說的功能,函數的最後我都會存儲這個Address

  下面使用的函數是dprintf(),上面我已經解釋過了,和printf一樣。在每次循環中,我都會將地址+4,然後從04*20打印20個地址中的值。

  但是你不能簡單的使用這個地址,因爲你並不在程序的進程空間中,因此,WINDBG提供ReadMemory這樣一個函數,這個函數有4個參數(如果你在MSDN中找不到某個API的話,你可以到windbgexts.h中去找原型)

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);

因此,我們把程序中內存的指針傳遞過去,還有一個存放數據的指針用來返回,最後是返回的字節數。如果沒有字節返回,我們只要打印出不正確的地址信息即可;如果有字節返回,我們就可以用這個地址讀取49個字節(我們使用了51是因爲最後要放兩個NULL,支持Unicode)。如果成功讀取,就可以使用dprintf()ANSIUNICODE方式打印出字符串,如果返回0字節,就打印錯誤信息。

  下面我們需要建立一個.DEF文件來導出函數。

LIBRARY "TDBG.DLL"

 

EXPORTS

    WinDbgExtensionDllInit

    ExtensionApiVersion

    dumpstrings

help

 

下面我們生成這個工程。我喜歡使用make files,並且我喜歡使用Visual Slickedit作爲編輯器。我從來不使用VC++IDE。因此,在這個工程中,我創建了一個makefile。首先,運行VC++BIN目錄中的VCVARS32.BAT,我喜歡把它放到C:\方便使用,然後在源代碼目錄下使用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能找到的地方就可以了。然後你就可以用!load!unload來加載和卸載這個擴展。最後,你就可以使用!<dll name>.<function name>!<function name>了,加上DLL的名字會強制WINDBG找這個DLL中的相應函數,不同DLL可能使用相同的名字,所以這樣就可以區分開了。

 

我們來試試成果

我們已經創建了這個調試擴展,將它放到WINDBG可以加載的目錄中後,就可以使用!tdbg.dumpstrings  espDump棧中所有的字符串了。下面我要Dump棧中所有字符串的指針了,我們來看看會發生什麼。

0:000> !tdbg.dumpstrings esp

STACK   ADDR   STRING

0006febc : 77d43a09 = (UNICODE) ""

0006febc : 77d43a09 = (ANSI)    "┬►"

0006fec0 : 77d43c7d = (UNICODE) ""

0006fec0 : 77d43c7d = (ANSI)    "�N♦�∙☻☺"

0006fec4 : 0006fefc = (UNICODE) ""

0006fec4 : 0006fefc = (ANSI)    "▐♥↔"

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)    "└ ♠"

0006fedc : 010028e4 = (UNICODE) ""

0006fedc : 010028e4 = (ANSI)    "�└u�Φ├∩   5�"

0006fee0 : 0006fefc = (UNICODE) ""

0006fee0 : 0006fefc = (ANSI)    "▐♥↔"

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)    "�|$♦"

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)    "≡ ♠"

0006ff20 : 01006c54 = (UNICODE) ""

0006ff20 : 01006c54 = (ANSI)    "�≡�u�9]ΣuV �▄↕"

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)    "░-'"

0006ffa0 : 00000000 =  Address Not Valid

0006ffa4 : 00000000 =  Address Not Valid

0006ffa8 : 0006ff34 = (UNICODE) ""

0006ffa8 : 0006ff34 = (ANSI)    ""

我們在棧中找到了兩個字符串,看起來不錯。你會得到很多垃圾和沒用的信息,還有你想得到的字符串信息。

 

總結

  我希望你很高興自己學習編寫調試擴展,並且這個例子會給你帶來幫助。另外還有其他一些API你可以使用,後面的教程可能也會解釋一些,基本的操作這裏都已經講述了。將來,我們會探索更多的高級調試命令。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章