Windebug专题

5 解决问题案例

!cs、~~[TID](经典死锁)

随手写的:

 

    #include <windows.h >

    

    CRITICAL_SECTION cs1;

    CRITICAL_SECTION cs2;

    

    DWORD __stdcall thread1(LPVOID lp)

    {

             EnterCriticalSection(&cs1);

             Sleep(10);

             EnterCriticalSection(&cs2);

    

             return 0;

    }

    

    DWORD  __stdcall thread2(LPVOID lp)

    {

             EnterCriticalSection(&cs2);

             Sleep(10);

             EnterCriticalSection(&cs1);

    

             return 0;

    }

    

    int main()

    {

             InitializeCriticalSection(&cs1);

             InitializeCriticalSection(&cs2);

    

             CreateThread(NULL, 0, thread1, 0, 0, NULL);

             CreateThread(NULL, 0, thread2, 0, 0, NULL);

    

             system("pause");

             return 0;

    

    }

 

 

运行,生成release版本,去掉pdb,运行,程序停住了,windbg加载到进程,

 

先用~*kb查看下所有的线程堆栈:

 

    0:005> ~*kn

    

       0  Id: 2d68.b3b8 Suspend: 1 Teb: 00772000 Unfrozen

     # ChildEBP RetAddr 

    WARNING: Stack unwind information not available. Following frames may be wrong.

    00 0098fb24 76bdae72 ntdll!ZwWaitForSingleObject+0xc

    01 0098fb38 729dbd40 KERNELBASE!WaitForSingleObject+0x12

    02 0098fbbc 729dc702 MSVCR90!wunlink+0x5bf

    03 0098fbe0 729dc84b MSVCR90!spawnv+0xb7

    04 0098fc18 729dcc71 MSVCR90!spawnve+0x12a

    05 0098fc50 008810a8 MSVCR90!system+0x8e

    06 0098fca0 76a462c4 Test+0x10a8

    07 0098fcb4 776f0fd9 KERNEL32!BaseThreadInitThunk+0x24

    08 0098fcfc 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439

    09 0098fd0c 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404

    

       1  Id: 2d68.aef8 Suspend: 1 Teb: 00775000 Unfrozen

     # ChildEBP RetAddr 

    WARNING: Stack unwind information not available. Following frames may be wrong.

    00 00cefc68 76a462c4 ntdll!NtWaitForWorkViaWorkerFactory+0xc

    01 00cefc7c 776f0fd9 KERNEL32!BaseThreadInitThunk+0x24

    02 00cefcc4 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439

    03 00cefcd4 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404

    

       2  Id: 2d68.8f38 Suspend: 1 Teb: 00778000 Unfrozen

     # ChildEBP RetAddr 

    WARNING: Stack unwind information not available. Following frames may be wrong.

    00 00e2fe94 76a462c4 ntdll!NtWaitForWorkViaWorkerFactory+0xc

    01 00e2fea8 776f0fd9 KERNEL32!BaseThreadInitThunk+0x24

    02 00e2fef0 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439

    03 00e2ff00 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404

    

       3  Id: 2d68.b1b4 Suspend: 1 Teb: 0077b000 Unfrozen

     # ChildEBP RetAddr 

    WARNING: Stack unwind information not available. Following frames may be wrong.

    00 00f6fb80 776b6f0d ntdll!NtWaitForAlertByThreadId+0xc

    01 00f6fbc4 776b6dff ntdll!RtlWaitOnAddress+0x1dd

    02 00f6fc00 776d0045 ntdll!RtlWaitOnAddress+0xcf

    03 00f6fc20 776cff65 ntdll!RtlEnterCriticalSection+0x125

    04 00f6fc2c 0088101d ntdll!RtlEnterCriticalSection+0x45

    05 00f6fc4c 776f0fd9 Test+0x101d

    06 00f6fc94 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439

    07 00f6fca4 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404

    

       4  Id: 2d68.20bc Suspend: 1 Teb: 0077e000 Unfrozen

     # ChildEBP RetAddr 

    WARNING: Stack unwind information not available. Following frames may be wrong.

    00 010ffce0 776b6f0d ntdll!NtWaitForAlertByThreadId+0xc

    01 010ffd24 776b6dff ntdll!RtlWaitOnAddress+0x1dd

    02 010ffd60 776d0045 ntdll!RtlWaitOnAddress+0xcf

    03 010ffd80 776cff65 ntdll!RtlEnterCriticalSection+0x125

    04 010ffd8c 0088104d ntdll!RtlEnterCriticalSection+0x45

    05 010ffdac 776f0fd9 Test+0x104d

    06 010ffdf4 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439

    07 010ffe04 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404

    

    #  5  Id: 2d68.1f98 Suspend: 1 Teb: 00781000 Unfrozen

     # ChildEBP RetAddr 

    WARNING: Stack unwind information not available. Following frames may be wrong.

    00 0153fecc 76a462c4 ntdll!DbgBreakPoint

    01 0153fee0 776f0fd9 KERNEL32!BaseThreadInitThunk+0x24

    02 0153ff28 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439

    03 0153ff38 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404

 

 

 

我们注意到3,4号线程的线程堆栈是从ntdll!RtlEnterCriticalSection中开始的,那么ntdll!RtlEnterCriticalSection又是什么函数的入口呢,首先猜到的是EnterCriticalSection,这个函数是kernel32.dll中的,为了验证猜测,我们用dump查看到kernel32.dll的导出函数:

https://i-blog.csdnimg.cn/blog_migrate/e7b6e95dced00cc8475f70c518678ffc.jpeg

!cs

 

!cs 扩展显示一个或多个临界区(critical section)或者整个临界区树

 

前面说的ntdll!RtlEnterCriticalSection的第一个参数是临界区的地址,事实上用uf反汇编它,可以看到是ret 4,说明就只有一个参数

 

那么,

!cs Address 指定要显示的临界区地址。 如果省略该参数,调试器显示当前进程中所有临界区。!cs -s 如果可能的话,显示每个临界区的初始堆栈回溯。!cs -l 仅显示锁定的临界区。

 

    0:005> !cs -l

    -----------------------------------------

    DebugInfo          = 0x7779f820

    Critical section   = 0x0088338c (Test+0x338C)

    LOCKED

    LockCount          = 0x1

    WaiterWoken        = No

    OwningThread       = 0x0000b1b4

    RecursionCount     = 0x1

    LockSemaphore      = 0xFFFFFFFF

    SpinCount          = 0x020007d0

    -----------------------------------------

    DebugInfo          = 0x7779f840

    Critical section   = 0x00883374 (Test+0x3374)

    LOCKED

    LockCount          = 0x1

    WaiterWoken        = No

    OwningThread       = 0x000020bc

    RecursionCount     = 0x1

    LockSemaphore      = 0xFFFFFFFF

    SpinCount          = 0x020007d0

 

   从底层来讲,关键区机制是通过_RTL_CRITICAL_SECTION来实现的

 

    0:005> dt _RTL_CRITICAL_SECTION

    MSVCR90!_RTL_CRITICAL_SECTION

       +0x000 DebugInfo        : Ptr32 _RTL_CRITICAL_SECTION_DEBUG

       +0x004 LockCount        : Int4B///< 标识关键区的锁状态

       +0x008 RecursionCount   : Int4B///< 记录递归次数,用来支持同一个线程多次进入关键区

       +0x00c OwningThread     : Ptr32 Void///< 记录拥有关键区的线程ID

       +0x010 LockSemaphore    : Ptr32 Void///< 记录这个关键区对应的事件对象,当有线程需要等待这个关键区时,便等待这个事件,这个对象是按需创建的,如果

 

LockSemaphore为空,表示这个关键区没人用过,从没有线程在此等待过

 

+0x014 SpinCount : Uint4B

 

~~[TID]线程 ID 为 TID 的线程。(中括号是必需的,而且在第二个~和左括号间不能有空格)

 

    0:005> ~~[0x0000b1b4]

       3  Id: 2d68.b1b4 Suspend: 1 Teb: 0077b000 Unfrozen

          Start: Test+0x1000 (00881000)

          Priority: 0  Priority class: 32  Affinity: f

    0:005> ~~[0x000020bc]

       4  Id: 2d68.20bc Suspend: 1 Teb: 0077e000 Unfrozen

          Start: Test+0x1030 (00881030)

          Priority: 0  Priority class: 32  Affinity: f

    0:005> ~3kv

     # ChildEBP RetAddr  Args to Child             

    05 00f6fc2c 0088101d 00883374 00881000 76a462c4 ntdll!RtlEnterCriticalSection+0x45 (FPO: [Non-Fpo])

    0:005> ~4kv

     # ChildEBP RetAddr  Args to Child             

    05 010ffd8c 0088104d 0088338c 00881030 76a462c4 ntdll!RtlEnterCriticalSection+0x45 (FPO: [Non-Fpo])

 

这意思就是3号线程等待的临界区 00883374拥有者是4号线程,4号线程等待的临界区 0088338c 拥有者是3号线程

 

这里LockCount为1意思为除了一个线程拥有它外,另外还有一个线程在等待它,它是由EnterCriticalSection增加,LeaveCriticalSection来减小的,比如我再加一点代码:

 

    DWORD  __stdcall thread3(LPVOID lp)

    {

             EnterCriticalSection(&cs2);

             Sleep(10);

             EnterCriticalSection(&cs1);

    

             return 0;

    }

    

    int main()

    {

             InitializeCriticalSection(&cs1);

             InitializeCriticalSection(&cs2);

    

             CreateThread(NULL, 0, thread1, 0, 0, NULL);

             CreateThread(NULL, 0, thread2, 0, 0, NULL);

             CreateThread(NULL, 0, thread3, 0, 0, NULL);

    

             system("pause");

             return 0;

    

    }

 

  这时运行windbg:

 

    0:005> !cs -l

    -----------------------------------------

    DebugInfo          = 0x7779f820

    Critical section   = 0x00063374 (Test+0x3374)

    LOCKED

    LockCount          = 0x2

    WaiterWoken        = No

    OwningThread       = 0x000009f0

    RecursionCount     = 0x1

    LockSemaphore      = 0xFFFFFFFF

    SpinCount          = 0x020007d0

    -----------------------------------------

    DebugInfo          = 0x7779f840

    Critical section   = 0x0006338c (Test+0x338C)

    LOCKED

    LockCount          = 0x1

    WaiterWoken        = No

    OwningThread       = 0x00009a58

    RecursionCount     = 0x1

    LockSemaphore      = 0xFFFFFFFF

    SpinCount          = 0x020007d0

 

可以发现LockCount变成了2,如果临界区是有信号的,则显示NOT LOCKED(值为-1)OwningThread表示拥有这个临界区的线程ID,RecursionCount表示拥有线程调了几次EnterCriticalSection,这其实也影响到了LockCount,如果拥有线程多调用一次EnterCriticalSection,那么 LockCount也会相应加1,因为LockCount标识了任意线程调用EnterCriticalSection请求这个互斥量的次数减1,(所以0-1=-1为NOT LOCKED)当然,前面如果调用了LeaveCriticalSection,那么 LockCount也会相应减1

 

 

!cs starAddress EndAddress指定要搜索临界区的地址范围

 

    0:003> !cs 0x00400000 0x00500000

    -----------------------------------------

    DebugInfo          = 0x7c99e9c0

    Critical section   = 0x00403388 (test2+0x3388)

    LOCKED

    LockCount          = 0x1

    OwningThread       = 0x00001588

    RecursionCount     = 0x1

    LockSemaphore      = 0x34

    SpinCount          = 0x00000000

    -----------------------------------------

    DebugInfo          = 0x7c99e9e0

    Critical section   = 0x00403370 (test2+0x3370)

    LOCKED

    LockCount          = 0x1

    OwningThread       = 0x0000185c

    RecursionCount     = 0x1

    LockSemaphore      = 0x2C

    SpinCount          = 0x00000000

 

!cs -o 对所有显示出来的已锁定的临界区,显示所有者的堆栈。

!cs -?显示该命令的帮助文本。

 

    0:003> !cs -?

    !cs [-s] [-l] [-o]                   - dump all the active critical sections in the current process.

    !cs [-s] [-o] address                - dump critical section at this address.

    !cs [-s] [-l] [-o] address1 address2 - dump all the active critical sections in this range.

    !cs [-s] [-o] -d address             - dump critical section corresponding to DebugInfo at this address.

    

    "-s" will dump the critical section initialization stack trace if it is available.

    

    "-l" will dump only the locked critical sections.

    

    "-o" will dump the critical section owner's stack.

 

 

示例代码:

 

    #include "windows.h"

    #include <stdio.h>

    #include <tchar.h>

    

    CRITICAL_SECTION g_cs;

    

    DWORD WINAPI ThreadProc1(LPVOID lpParameter)

    {

             EnterCriticalSection(&g_cs);

             printf("thread1\n");

             return 0;

    }

    DWORD WINAPI ThreadProc2(LPVOID lpParameter)

    {

             EnterCriticalSection(&g_cs);

             printf("thread2\n");

             return 0;

    }

    int _tmain(int argc, _TCHAR* argv[])

    {

             InitializeCriticalSection(&g_cs);

             CreateThread(NULL,NULL,ThreadProc1,NULL,NULL,NULL);

             Sleep(1000);

             CreateThread(NULL,NULL,ThreadProc2,NULL,NULL,NULL);

             getchar();

             return 0;

    }

 

编译成release,运行,windbg强制附加:

 

    0:002> ~*kb

    

       0  Id: 10c4.a18 Suspend: 1 Teb: 7ffdf000 Unfrozen

    ChildEBP RetAddr  Args to Child             

    0012fc8c 7c92daea 7c932298 00000018 0012fcf0 ntdll!KiFastSystemCallRet

    0012fc90 7c932298 00000018 0012fcf0 0012fcf0 ntdll!ZwRequestWaitReplyPort+0xc

    0012fcb0 7c872a51 00000000 00260688 0002021d ntdll!CsrClientCallServer+0x8c

    0012fdac 7c872b98 00000003 785bc660 00001000 kernel32!ReadConsoleInternal+0x1be

    0012fe34 7c8018b7 00000003 785bc660 00001000 kernel32!ReadConsoleA+0x3b

    0012fe8c 78588ef2 00000003 785bc660 00001000 kernel32!ReadFile+0x64

    0012fed0 78589376 00000000 785bc660 00001000 MSVCR90!_read_nolock+0x203 [f:\dd\vctools\crt_bld\self_x86\crt\src\read.c @ 233]

    0012ff14 7854efd2 00000000 785bc660 00001000 MSVCR90!_read+0xc0 [f:\dd\vctools\crt_bld\self_x86\crt\src\read.c @ 93]

    0012ff30 7854e4fe 785b73a8 0165941f 00403390 MSVCR90!_filbuf+0x7d [f:\dd\vctools\crt_bld\self_x86\crt\src\_filbuf.c @ 136]

    0012ff68 7854e557 785b73a8 0040108c 00000001 MSVCR90!getc+0xe0 [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetc.c @ 49]

    0012ff70 0040108c 00000001 00001650 004011fa MSVCR90!_fgetchar+0xb [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetchar.c @ 37]

    WARNING: Stack unwind information not available. Following frames may be wrong.

    0012ffc0 7c817077 80000001 03edd9e4 7ffd9000 test2+0x108c

    0012fff0 00000000 00401342 00000000 78746341 kernel32!BaseProcessStart+0x23

    

       1  Id: 10c4.1650 Suspend: 1 Teb: 7ffde000 Unfrozen

    ChildEBP RetAddr  Args to Child             

    0050ff18 7c92df5a 7c939b23 0000002c 00000000 ntdll!KiFastSystemCallRet

    0050ff1c 7c939b23 0000002c 00000000 00000000 ntdll!NtWaitForSingleObject+0xc

    0050ffa4 7c921046 00403370 0040102b 00403370 ntdll!RtlpWaitForCriticalSection+0x132

    0050ffac 0040102b 00403370 7c80b729 00000000 ntdll!RtlEnterCriticalSection+0x46

    WARNING: Stack unwind information not available. Following frames may be wrong.

    0050ffec 00000000 00401020 00000000 00000000 test2+0x102b

    

    #  2  Id: 10c4.1a70 Suspend: 1 Teb: 7ffdd000 Unfrozen

    ChildEBP RetAddr  Args to Child             

    003bffc8 7c972119 00000005 00000004 00000001 ntdll!DbgBreakPoint

    003bfff4 00000000 00000000 00000000 00000000 ntdll!DbgUiRemoteBreakin+0x2d

 

注意到1线程在等待临界区,对临界区继续分析:

 

 

 

    0:002> !cs 00403370

    -----------------------------------------

    Critical section   = 0x00403370 (test2+0x3370)

    DebugInfo          = 0x7c99e9c0

    LOCKED

    LockCount          = 0x1

    OwningThread       = 0x0000155c

    RecursionCount     = 0x1

    LockSemaphore      = 0x2C

    SpinCount          = 0x00000000

    0:002> ~~[0x0000155c]

                        ^ Illegal thread error in '~~[0x0000155c]'

 

竟然是错误的线程,这说明这个破线程没有调用LeaveCriticalSection就挂了!

 

继续分析,找出原因:

 

Application Verifier 是类似pageheap 的工具,使用范围更广。可以通过Application Verifier

方便设定注册表,让操作系统主动地提供关于handle, critical section, heap 等系统层面的调试

信息.

 

在这里有下载:http://www.microsoft.com/en-us/download/details.aspx?id=20028

 

启动Application Verifier 后,添加程序名字,然后勾选Locks。在windbg 中用ctrl+E 打开这个程序重新运行

https://i-blog.csdnimg.cn/blog_migrate/a68da1f34f488c0cea0f0a9ca969192e.jpeg

windbg中断了:

 

    0:000> g

    

    

    =======================================

    VERIFIER STOP 00000200: pid 0xA28: Thread cannot own a critical section.

    

             00000CFC : Thread ID.

             00403370 : Critical section address.

             002A8250 : Critical section debug information address.

             00000000 : Critical section initialization stack trace.

    

    

    =======================================

    This verifier stop is continuable.

    After debugging it use `go' to continue.

    

    =======================================

    

    (a28.cfc): Break instruction exception - code 80000003 (first chance)

    eax=002a82a8 ebx=00387d08 ecx=00000001 edx=01caface esi=00000000 edi=002a82a8

    eip=7c92120e esp=01cafb58 ebp=01cafd5c iopl=0         nv up ei pl nz na po nc

    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

    ntdll!DbgBreakPoint:

    7c92120e cc              int     3

 

原因很明显:Thread cannot own a critical section.线程ID是cfc

 

    0:001> ~~[00000CFC]

    .  1  Id: a28.cfc Suspend: 1 Teb: 7ffde000 Unfrozen

          Start: kernel32!BaseThreadStartThunk (7c8106f9)

          Priority: 0  Priority class: 32  Affinity: f

    0:001> ~1kb

    ChildEBP RetAddr  Args to Child             

    01cafb54 10003b68 11ca2a3c 00000000 002a8250 ntdll!DbgBreakPoint

    01cafd5c 0037c427 003860c8 00000200 00000cfc vrfcore!VerifierStopMessageEx+0x4d1

    01cafe00 0037c47e 00403370 002a8250 fffffffe vfbasics!AVrfpCheckCriticalSection+0x169

    01cafe20 0037c5f2 018e1a90 fffffffe 00000000 vfbasics!AVrfpCheckCriticalSectionSplayNode+0x34

    01caff48 0037c6a9 018e1888 fffffffe 00000000 vfbasics!AVrfpCheckCriticalSectionTree+0x47

    01caff64 00384002 fffffffe 00000000 00000000 vfbasics!AVrfCheckForOrphanedCriticalSections+0x75

    01caff78 00384020 fffffffe 00384314 01f29440 vfbasics!AVrfpCheckThreadTermination+0x11

    01caff80 00384314 01f29440 7c92e920 7c98b127 vfbasics!AVrfpCheckCurrentThreadTermination+0x15

    01caffb4 7c80b729 018e1af8 7c92e920 7c98b127 vfbasics!AVrfpStandardThreadFunction+0x4c

    01caffec 00000000 003842c8 018e1af8 00000000 kernel32!BaseThreadStart+0x37

 

这些破函数我也不认识,但是我认识这些:CheckThreadTermination   CheckCriticalSection!!!这里可以看到,线程1试图退出当前线程,但是由于还拥有没有释放的CriticalSection,所以在激活Application Verifier 后,就会自动产生一个break point exception

内存破坏实例分析

以下实例来自AWD

 

代码:

 

    /*++

    Copyright (c) Advanced Windows Debugging (ISBN 0321374460) from Addison-Wesley Professional.  All rights reserved.

        THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY

        KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE

        IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR

        PURPOSE.

    --*/

    #include "stdafx.h"

    #include <windows.h>

    #include <stdio.h>

    #include <conio.h>

    

    VOID SimulateMemoryCorruption ( ) ;

    

    class CAppInfo

    {

    public:

        CAppInfo(LPWSTR wszAppName, LPWSTR wszVersion)

        {

            m_wszAppName=wszAppName;

            m_wszVersion=wszVersion;

        }

    

        VOID PrintAppInfo()

        {

            wprintf(L"\nFull application Name: %s\n", m_wszAppName);

            wprintf(L"Version: %s\n", m_wszVersion);

        }

    

    private:

        LPWSTR m_wszAppName ;

        LPWSTR m_wszVersion ;

    } ;

    

    CAppInfo* g_AppInfo ;

    

    int __cdecl wmain (int argc, WCHAR* args[])

    {

        wint_t iChar = 0 ;

        g_AppInfo = new CAppInfo(L"Memory Corruption Sample", L"1.0" );

        if(!g_AppInfo)

        {

            return 1;

        }

    

        wprintf(L"Press: \n");

        wprintf(L"    1    To display application information\n");

        wprintf(L"    2    To simulated memory corruption\n");

        wprintf(L"    3    To exit\n\n\n>");

    

        while((iChar=_getwche())!='3')

        {

            switch(iChar)

            {

                case '1':

                   g_AppInfo->PrintAppInfo();

                   break;

    

                case '2':

                  SimulateMemoryCorruption();

                  wprintf(L"\nMemory Corruption completed\n");

                  break;

    

                default:

                  wprintf(L"\nInvalid option\n");

            }

            wprintf(L"\n\n> ");

        }

        return 0;

    }

    

    

    VOID SimulateMemoryCorruption ( )

    {

        char* pszWrite="Corrupt";

        BYTE* p=(BYTE*) g_AppInfo;

        CopyMemory(p, pszWrite, strlen(pszWrite));

    }

 

编译,生成Release,运行,选1再选2再选1,程序崩溃,打开Dump:

 

    0:000> lm

    start    end        module name

    00400000 00406000   test1      (deferred)            

    62c20000 62c29000   lpk        (deferred)            

    73fa0000 7400b000   usp10      (deferred)            

    76300000 7631d000   imm32      (deferred)            

    76d70000 76d92000   apphelp    (deferred)            

    77bd0000 77bd8000   version    (deferred)            

    77d10000 77da0000   user32     (deferred)            

    77da0000 77e49000   advapi32   (deferred)            

    77e50000 77ee3000   rpcrt4     (deferred)            

    77ef0000 77f39000   gdi32      (deferred)            

    77fc0000 77fd1000   secur32    (deferred)            

    78520000 785c3000   msvcr90    (private pdb symbols)  c:\mysymbol\msvcr90.i386.pdb\3ADD2E755BC041BC9149BFBE7C33387C1\msvcr90.i386.pdb

    7c800000 7c91e000   kernel32   (deferred)            

    7c920000 7c9b6000   ntdll      (pdb symbols)          c:\mysymbol\ntdll.pdb\CEFC0863B1F84130A11E0F54180CD21A2\ntdll.pdb

 

加载符号文件:

 

    0:000> .sympath+ D:\Project1\test1\Release

    Symbol search path is: C:\WINDOWS\Symbols;SRV*c:\mysymbol* http://msdl.microsoft.com/download/symbols ;D:\Project1\test1\Release

    WARNING: Whitespace at end of path element

    0:000> .reload /f

    ..............

    Loading unloaded module list

    .........

    0:000> lm

    start    end        module name

    00400000 00406000   test1      (private pdb symbols)  D:\Project1\test1\Release\test1.pdb

    62c20000 62c29000   lpk        (pdb symbols)          C:\WINDOWS\Symbols\dll\lpk.pdb

    73fa0000 7400b000   usp10      (pdb symbols)          c:\mysymbol\usp10.pdb\D4BA2952809F469BB6D1D3AF6B956E6B1\usp10.pdb

    76300000 7631d000   imm32      (pdb symbols)          C:\WINDOWS\Symbols\dll\imm32.pdb

    76d70000 76d92000   apphelp    (pdb symbols)          C:\WINDOWS\Symbols\dll\apphelp.pdb

    77bd0000 77bd8000   version    (pdb symbols)          C:\WINDOWS\Symbols\dll\version.pdb

    77d10000 77da0000   user32     (pdb symbols)          C:\WINDOWS\Symbols\dll\user32.pdb

    77da0000 77e49000   advapi32   (pdb symbols)          c:\mysymbol\advapi32.pdb\F759D3F1C6614313B07C84BC33F02E4D2\advapi32.pdb

    77e50000 77ee3000   rpcrt4     (pdb symbols)          c:\mysymbol\rpcrt4.pdb\1A465C67828242F28A8C70E3B9D5C4772\rpcrt4.pdb

    77ef0000 77f39000   gdi32      (pdb symbols)          c:\mysymbol\gdi32.pdb\372C0F0E08FB456EAB7B4CB2B53E27952\gdi32.pdb

    77fc0000 77fd1000   secur32    (pdb symbols)          c:\mysymbol\secur32.pdb\7867B3F28B5C41CE847895E3FC013DC52\secur32.pdb

    78520000 785c3000   msvcr90    (private pdb symbols)  c:\mysymbol\msvcr90.i386.pdb\3ADD2E755BC041BC9149BFBE7C33387C1\msvcr90.i386.pdb

    7c800000 7c91e000   kernel32   (pdb symbols)          c:\mysymbol\kernel32.pdb\072FF0EB54D24DFAAE9D13885486EE092\kernel32.pdb

    7c920000 7c9b6000   ntdll      (pdb symbols)          c:\mysymbol\ntdll.pdb\CEFC0863B1F84130A11E0F54180CD21A2\ntdll.pdb

 

查看堆栈:

 

    0:000> kb

    ChildEBP RetAddr  Args to Child             

    0012ff20 78556215 785b73c8 004020f4 00000000 msvcr90!_woutput_l+0x94c [f:\dd\vctools\crt_bld\self_x86\crt\src\output.c @ 1624]

    0012ff64 004010ba 004020f4 72726f43 00403380 msvcr90!wprintf+0x73 [f:\dd\vctools\crt_bld\self_x86\crt\src\wprintf.c @ 63]

    0012ff7c 00401252 00000001 00392940 00392998 test1!wmain+0xba [d:\project1\test1\test1\test1.cpp @ 58]

    0012ffc0 7c817077 00300031 0032002d 7ffdc000 test1!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]

    0012fff0 00000000 0040139a 00000000 78746341 kernel32!BaseProcessStart+0x23

 

看下58行代码,使用了   g_AppInfo->PrintAppInfo();

 

我们猜测,g_AppInfo的两个成员无效,因为wprintf调用的就是它们

 

找到g_AppInfo,列出它的成员:

 

    0:000> x test1!*g_*

    00403374 test1!g_AppInfo = 0x00395b10

    004022f0 test1!_load_config_used = struct IMAGE_LOAD_CONFIG_DIRECTORY32_2

    00402098 test1!_imp___amsg_exit = <no type information>

    004014fa test1!_amsg_exit = <no type information>

    0:000> dt CAppInfo 0x00395b10

    test1!CAppInfo

       +0x000 m_wszAppName     : 0x72726f43  "--- memory read error at address 0x72726f43 ---"

       +0x004 m_wszVersion     : 0x00747075  "???"

 

注意,这里dt要用CAppInfo告诉它解析的地址类型

 

那我们再来看看这两个局变量的内容:

 

    0:000> dt CAppInfo 0x00395b10

    test1!CAppInfo

       +0x000 m_wszAppName     : 0x72726f43  "--- memory read error at address 0x72726f43 ---"

       +0x004 m_wszVersion     : 0x00747075  "???"

    0:000> !address 0x72726f43 

        62c29000 : 62c29000 - 11377000

                        Type     00000000

                        Protect  00000001 PAGE_NOACCESS

                        State    00010000 MEM_FREE

                        Usage    RegionUsageFree

    0:000> !address 0x00747075 

        005f0000 : 005f0000 - 001be000

                        Type     00020000 MEM_PRIVATE

                        Protect  00000004 PAGE_READWRITE

                        State    00001000 MEM_COMMIT

                        Usage    RegionUsageIsVAD

 

我们发现第一个变量竟然是不可访问的,找到问题了,

 

    0:000> dc 0x00395b10

    00395b10  72726f43 00747075 00020201 000801c2  Corrupt.........

    00395b20  6c75460a 7061206c 63696c70 6f697461  .Full applicatio

    00395b30  614e206e 203a656d 64657465 4320790a  n Name: eted.y C

    00395b40  7572726f 6f697470 6153206e 656c706d  orruption Sample

    00395b50  0000000a 00000000 00000000 00000000  ................

    00395b60  00000000 00000000 00000000 00000000  ................

    00395b70  00000000 00000000 00000000 00000000  ................

    00395b80  00000000 00000000 00000000 00000000  ................

 

原来第一个变量竟然成了字符串Corrupt,查看代码,我们发现在按2时,程序强行把字符串"Corrupt"写入了

 

 

 

 

 

一些的指导性的建议和策略:

 

1通过命令dc将指针的内存内容转储出来,dc可以将内存内容以双字形式转储出来,如果在输出中看到有任何的字符串,那么可以通过命令da或du把字符串转储出来

 

2通过!address收集关于内存的信息,!address可以告诉你内存的类型(如私有内存),保护级别(读取和写入),状态(已提交或保留)和用途(栈或堆)

 

3.通过dds命令将内存转储为双字或者符号,这有助于将内存和特定的类型关联起来,

 

4.通过dpp命令对指针解引用,并且以双字形式转储出内存的内容,如果有任何一个双字匹配某个符号,那么这个符号也会被显示,如果在指针指向的内存中包含了一个虚函数表,那么

 

这种技术是非常有用的

 

5.通过dpa和dpu将指针指向的内存分别显示为ASII格式和Unicode格式

 

6.如果内存的内容是个很小的数值(4的值数)那么它可能是一个句柄,可以通过!handle来转储这个句柄的信息.

堆的手工分析

大多数(并不是所有)高层的内存管理器都使用了Windows堆管理器,而堆管理器又会使用虚拟内存管理器:

下图给出了在Windows中支持的内存管理器以及它们之间的关系:

https://i-blog.csdnimg.cn/blog_migrate/687a372771389d5aefded94213eaf76a.jpeg

 

 

当进程启动时,堆管理器将自动创建一个新堆,叫做默认的进程堆,但是new/delete运算符以及malloc/free等API仍使用CRT堆来满足它们的内存需求,有些进程还会创建一些额外的堆(通过HeapCreate)以将进程中不同的组件独立开来

 

可以把Windows堆管理器做进一步的划分,如下图:

https://i-blog.csdnimg.cn/blog_migrate/9b6c76205067eec82839fcaedb34da63.jpeg

 

前端分配器

 

前端分配器(Front End Allocator是对后端分配器(Back End Allocator)的一个抽象优化层,在Windows中有两种前端分配器:

 

旁视列表(Look Aside List, LAL)前端分配器

 

低碎片(Low Fragmentation, LF)前端分配器

 

除了Vista外,所有版本Windows在默认情况下都使用LAL前端分配器,下面介绍LAL前端分配器:

 

 

 

LAL是一张表,其中有128项(可以想像成128行),每一项对应于一个单向链表(相当于列),每个单向链表都包含了一组固定大小的空闲堆块,从16字节

 

的大小开始依次递增,(堆块可以想像成i行j列中元素的大小,可能为空,如有值,最小值为16),每个堆块中包含了8字节的堆块元数据用于管理这个堆块,

 

所以,如果用户请求24字节分配,那么LAL前先查找大小为32字节(用户请求24字节+8字节元数据)的空闲堆块,由于LAL的索引0项大小为8字节的空闲堆块

 

(假想成第0行的元素要么为空,要么为8),所以它不会被使用

 

行的元素值是向下递增的(8字节递增),最后一个索引(127)包含了大小为1024字节的空闲堆块,但程序释放一个内存块时,堆管理器将把这块内存标记为空闲的,

 

然后将它放在LAL相应的位置(按大小),下次请求这个大小的内存块(这个大小=用户请求字节+8字节,精确匹配,比如,24的不能分配32的)时,LAL将检查是否存在大小相同的空闲堆块,如果存在,就把这个堆块返回给用户,到目前为止,

 

LAL是满足内存分配需求的最快内存分配方式

 

如果LAL无法满足要求,那么这个请求将被转发到后端分配器:

 

 

后端分配器

 

和LAL同样的,后端分配器也包含了一张空闲列表(Free Lists)这张表和LAL稍有不同,就是空闲列表[2]的堆块大小为16(第三行,而LAL是第二行)之后同样以8字节 行递增

 

同样道理,空闲列表[1]没有被使用(8字节元数据,没有用户使用空间了),同样它也是128项,那么明显最后行为1016字节的堆块,

 

在空闲列表[0]中的堆块大小将大于1016字节而小于虚拟内存分配的限值0x7FFF0(这一行的元素堆大小为自由大小,不需要是8的倍数,但升序排列以获得最大效率)

 

为了更有效的查找,堆管理器而将维持一个空闲列表位图,位图包含128位(对应相应的128行),如行元素全为空,则对应的位为0,否则为1

 

好吧,典型的哈希映射,

 

如果堆管理器还是无法找到某个空闲堆块的大小等于所请求的大小,然后它将使用一种块分割,块分割指堆管理器首先找到一个比请求大小更大的空闲堆块,然后将其对半分割以满足分配请求(是对半分割),

 

所有大于0x7fff0的内存分配请求将被转发到虚拟分配链表(Virtual Allocation List)中,当请求一个很大的内存分配时,堆管理器将向虚拟内存管理器发出请求,并且将相应的分配信息保留在虚拟分配链表中,

堆管理器从何处获得内存?

 

首先,堆管理器将通过Windows虚拟内存管理器来分配一大块内存,然后,这个内存将被分为不同大小的块以满足程序的内存分配请求,当这块内块被耗尽时,堆管理器又将从虚拟内存中分配一大块内存,然后这个过程将重复进行,堆管理器从虚拟内存管理器请求分配的内存块也称为堆段,当堆段最被创建时,堆段中的大部分虚拟内存都是被保留的(Reserve),只有一小部分被提交,当堆管理器耗尽了已提交的空间时,堆段将进一步提交更多的内存,而堆管理器将对新提交的空间进行分割,

 

当一个堆段耗尽所有空间后,堆管理器将创建另一个新的堆段,新堆段大小将是之前堆段的二倍,如果内存不足,然后堆管理器将把这个大小减半,再失败,再减半,直到

 

堆段大小的阈值,这时将返回一个错误给用户,在堆中最多可以同时存在64个堆段,同样,堆管理器弄了个链表来管理它们,

 

 

实例分析

 

先上代码:

 

    #include "stdafx.h"

    #include <windows.h>

    

    void __cdecl wmain (int argc, WCHAR* args[])

    {

        BYTE* pAlloc1=NULL;

        BYTE* pAlloc2=NULL;

        HANDLE hProcessHeap=GetProcessHeap();

    

        pAlloc1=(BYTE*) HeapAlloc(hProcessHeap, 0, 16);

        pAlloc2=(BYTE*) HeapAlloc(hProcessHeap, 0, 1500);

    

        //

        // Use allocated memory

        //

        HeapFree(hProcessHeap, 0, pAlloc1);

        HeapFree(hProcessHeap, 0, pAlloc2);

    }

 

Release版本,Windbg加载符号文件运行:

 

$peb当前进程的进程环境块(PEB)的地址

 

我们把当前的进程环境块解析出来:

 

    0:000> dt _PEB @$peb

    test1!_PEB

       +0x000 InheritedAddressSpace : 0 ''

       +0x001 ReadImageFileExecOptions : 0 ''

       +0x002 BeingDebugged    : 0x1 ''

       +0x003 SpareBool        : 0 ''

       +0x004 Mutant           : 0xffffffff

       +0x008 ImageBaseAddress : 0x00400000

       +0x00c Ldr              : 0x00251ea0 _PEB_LDR_DATA

       +0x010 ProcessParameters : 0x00020000 _RTL_USER_PROCESS_PARAMETERS

       +0x014 SubSystemData    : (null)

       +0x018 ProcessHeap      : 0x00150000

       +0x01c FastPebLock      : 0x7c9a0620 _RTL_CRITICAL_SECTION

       +0x020 SparePtr1        : 0x7c921000

       +0x024 SparePtr2        : 0x7c9210e0

       +0x028 EnvironmentUpdateCount : 1

       +0x02c KernelCallbackTable : (null)

       +0x030 SystemReserved   : [1] 0

       +0x034 ExecuteOptions   : 0y00

       +0x034 SpareBits        : 0y000000000000000000000000000000 (0)

       +0x038 FreeList         : (null)

       +0x03c TlsExpansionCounter : 0

       +0x040 TlsBitmap        : 0x7c9a05e0

       +0x044 TlsBitmapBits    : [2] 7

       +0x04c ReadOnlySharedMemoryBase : 0x7f6f0000

       +0x050 ReadOnlySharedMemoryHeap : 0x7f6f0000

       +0x054 ReadOnlyStaticServerData : 0x7f6f0688  -> (null)

       +0x058 AnsiCodePageData : 0x7ffa0000

       +0x05c OemCodePageData  : 0x7ffa0000

       +0x060 UnicodeCaseTableData : 0x7ffd1000

       +0x064 NumberOfProcessors : 4

       +0x068 NtGlobalFlag     : 0x70

       +0x070 CriticalSectionTimeout : _LARGE_INTEGER 0xffffe86d`079b8000

       +0x078 HeapSegmentReserve : 0x100000

       +0x07c HeapSegmentCommit : 0x2000

       +0x080 HeapDeCommitTotalFreeThreshold : 0x10000

       +0x084 HeapDeCommitFreeBlockThreshold : 0x1000

       +0x088 NumberOfHeaps    : 4

       +0x08c MaximumNumberOfHeaps : 0x10

       +0x090 ProcessHeaps     : 0x7c99ffe0  -> 0x00150000

       +0x094 GdiSharedHandleTable : (null)

       +0x098 ProcessStarterHelper : (null)

       +0x09c GdiDCAttributeList : 0

       +0x0a0 LoaderLock       : 0x7c99e174 _RTL_CRITICAL_SECTION

       +0x0a4 OSMajorVersion   : 5

       +0x0a8 OSMinorVersion   : 1

       +0x0ac OSBuildNumber    : 0xa28

       +0x0ae OSCSDVersion     : 0x300

       +0x0b0 OSPlatformId     : 2

       +0x0b4 ImageSubsystem   : 3

       +0x0b8 ImageSubsystemMajorVersion : 5

       +0x0bc ImageSubsystemMinorVersion : 0

       +0x0c0 ImageProcessAffinityMask : 0

       +0x0c4 GdiHandleBuffer  : [34] 0

       +0x14c PostProcessInitRoutine : (null)

       +0x150 TlsExpansionBitmap : 0x7c9a05d8

       +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 3"

       +0x1f8 ActivationContextData : 0x00140000 _ACTIVATION_CONTEXT_DATA

       +0x1fc ProcessAssemblyStorageMap : 0x00152988 _ASSEMBLY_STORAGE_MAP

       +0x200 SystemDefaultActivationContextData : 0x00130000 _ACTIVATION_CONTEXT_DATA

       +0x204 SystemAssemblyStorageMap : (null)

       +0x208 MinimumStackCommit : 0

       +0x20c FlsCallback      : (null)

       +0x210 FlsListHead      : _LIST_ENTRY [ 0x0 - 0x0 ]

       +0x218 FlsBitmap        : (null)

       +0x21c FlsBitmapBits    : [4] 0

       +0x22c FlsHighIndex     : 0

 

 

为什么要使用@$peb呢:windbg帮助文档提到了:

 

When you use a register in an expression, you should add an at sign ( @ ) before the register. This at sign tells the debugger that the following text is the name of a register.

 

If you are using MASM expression syntax, you can omit the at sign for certain very common registers. On x86-based systems, you can omit the at sign for theeax,ebx,ecx,edx,esi,edi,ebp,eip, andefl registers. However, if you specify a less common register without an at sign, the debugger first tries to interpret the text as a hexadecimal number. If the text contains non-hexadecimal characters, the debugger next interprets the text as a symbol. Finally, if the debugger does not find a symbol match, the debugger interprets the number as a register.

 

If you are using C++ expression syntax, the at sign is always required.

 

所以使用时都加上@吧,

 

最前面一排表示的是相对于这个结构首地址的偏移地址,首先我们关心的是位于偏移0x90处的进程堆链表,PEB中这个堆栈表成员是一个指针数组,每个指针都指向一个类型为_HEAP的数据结构:

 

   +0x090 ProcessHeaps     : 0x7c99ffe0  -> 0x00150000

 

转储出来:

 

    0:000> dd 0x7c99ffe0 

    7c99ffe0  00150000 00250000 00260000 00390000

    7c99fff0  00000000 00000000 00000000 00000000

    7c9a0000  00000000 00000000 00000000 00000000

    7c9a0010  00000000 00000000 00000000 00000000

    7c9a0020  05d205d0 00020498 00000001 7c9b6000

    7c9a0030  7ffd1de6 00000001 00000005 00000001

    7c9a0040  fffff739 00000000 003a0043 0057005c

    7c9a0050  004e0049 004f0044 00530057 0073005c

 

从信息来看,进程中共有四个堆,这个可以从最上面的

 

   +0x088 NumberOfHeaps    : 4

 

看出,并且默认进程堆总是位为链表的第一项:其实默认进程堆也可以从这看出:

 

   +0x018 ProcessHeap      : 0x00150000

 

进程中存在多个堆是很正常的,比如C运行时,这个组件在初始化时将创建自己的堆

 

由于程序中使用默认进程堆,因此我们重点对这个堆进行分析,把这个地址按_HEAP结构解析出来:

 

    0:000> dt _HEAP 0x00150000

    ntdll!_HEAP

       +0x000 Entry            : _HEAP_ENTRY

       +0x008 Signature        : 0xeeffeeff

       +0x00c Flags            : 0x50000062

       +0x010 ForceFlags       : 0x40000060

       +0x014 VirtualMemoryThreshold : 0xfe00

       +0x018 SegmentReserve   : 0x100000

       +0x01c SegmentCommit    : 0x2000

       +0x020 DeCommitFreeBlockThreshold : 0x200

       +0x024 DeCommitTotalFreeThreshold : 0x2000

       +0x028 TotalFreeSize    : 0x292

       +0x02c MaximumAllocationSize : 0x7ffdefff

       +0x030 ProcessHeapsListIndex : 1

       +0x032 HeaderValidateLength : 0x608

       +0x034 HeaderValidateCopy : (null)

       +0x038 NextAvailableTagIndex : 0

       +0x03a MaximumTagIndex  : 0

       +0x03c TagEntries       : (null)

       +0x040 UCRSegments      : (null)

       +0x044 UnusedUnCommittedRanges : 0x00150598 _HEAP_UNCOMMMTTED_RANGE

       +0x048 AlignRound       : 0x17

       +0x04c AlignMask        : 0xfffffff8

       +0x050 VirtualAllocdBlocks : _LIST_ENTRY [ 0x150050 - 0x150050 ]

       +0x058 Segments         : [64] 0x00150640 _HEAP_SEGMENT

       +0x158 u                : __unnamed

       +0x168 u2               : __unnamed

       +0x16a AllocatorBackTraceIndex : 0

       +0x16c NonDedicatedListLength : 1

       +0x170 LargeBlocksIndex : (null)

       +0x174 PseudoTagEntries : (null)

    <pre class="cpp" name="code">   +0x178 FreeLists        : [128] _LIST_ENTRY [ 0x152b78 - 0x152b78 ]

 

 

+0x578 LockVariable : 0x00150608 _HEAP_LOCK +0x57c CommitRoutine : (null) +0x580 FrontEndHeap : 0x00150688 +0x584 FrontHeapLockCount : 0 +0x586 FrontEndHeapType : 0x1 '' +0x587 LastSegmentIndex : 0 ''

 

<p>其中空闲列表在这:</p><pre class="cpp" name="code">  +0x178 FreeLists        : [128] _LIST_ENTRY [ 0x152b78 - 0x152b78 ]

 

其中包含一组双向链表,每个链表都包含了固定大小的一组堆块,我们将进一步查看这些空闲链表:

 

 

 

 

 

下一个HeapAlloc调用将分配1500字节的内存,这个大小超出了空闲列表[0]的大小,所以,堆管理器将不得不从堆段中提交更多的内存,为了更好的理解堆段状态,我们可以手动来分析堆段,_HEAP结构中就包含了一组指针指向当前堆中所有活跃进的堆段,这个数组偏移位置是0x58

 

    0:000> dd 0x00150000+0X58

    00150058  00150640 00000000 00000000 00000000

    00150068  00000000 00000000 00000000 00000000

    00150078  00000000 00000000 00000000 00000000

    00150088  00000000 00000000 00000000 00000000

    00150098  00000000 00000000 00000000 00000000

    001500a8  00000000 00000000 00000000 00000000

    001500b8  00000000 00000000 00000000 00000000

 

打印出来的为指向_HEAP_SEGMENT的地址,继续解析这个结构体:

 

    0:000> dt _HEAP_SEGMENT 00150640

    ntdll!_HEAP_SEGMENT

       +0x000 Entry            : _HEAP_ENTRY

       +0x008 Signature        : 0xffeeffee

       +0x00c Flags            : 0

       +0x010 Heap             : 0x00150000 _HEAP

       +0x014 LargestUnCommittedRange : 0xfc000

       +0x018 BaseAddress      : 0x00150000

       +0x01c NumberOfPages    : 0x100

       +0x020 FirstEntry       : 0x00150680 _HEAP_ENTRY

       +0x024 LastValidEntry   : 0x00250000 _HEAP_ENTRY

       +0x028 NumberOfUnCommittedPages : 0xfc

       +0x02c NumberOfUnCommittedRanges : 1

       +0x030 UnCommittedRanges : 0x00150588 _HEAP_UNCOMMMTTED_RANGE

       +0x034 AllocatorBackTraceIndex : 0

       +0x036 Reserved         : 0

       +0x038 LastEntryInSegment : 0x00152dc8 _HEAP_ENTRY

 

 

这里最有用的是FirstEntry,它表示在堆段中的第一个堆块,如果将这个堆块大小得到,那么和第一个堆块地址相加就可以得到下一个堆块,重复这个过程,就可以遍历整个堆段,并可以进一步分析每个堆块的正确性:

 

    0:000> dt _HEAP_ENTRY 0x00150680

    ntdll!_HEAP_ENTRY

       +0x000 Size             : 0x303

       +0x002 PreviousSize     : 8

       +0x000 SubSegmentCode   : 0x00080303

       +0x004 SmallTagIndex    : 0x57 'W'

       +0x005 Flags            : 0x7 ''

       +0x006 UnusedBytes      : 0x18 ''

       +0x007 SegmentIndex     : 0 ''

    0:000> dt _HEAP_ENTRY 0x00150680+(0x303*8)

    ntdll!_HEAP_ENTRY

       +0x000 Size             : 8

       +0x002 PreviousSize     : 0x303

       +0x000 SubSegmentCode   : 0x03030008

       +0x004 SmallTagIndex    : 0x54 'T'

       +0x005 Flags            : 0x7 ''

       +0x006 UnusedBytes      : 0x1e ''

       +0x007 SegmentIndex     : 0 ''

 

 

!heap堆状态

以下以windbg启动calc为调试结果:

!heap

 

!heap 扩展显示堆使用信息,控制堆管理器中的断点,检测泄露的堆块,搜索堆块或者显示页堆(page heap)信息。

 

!heap -h列出当前进程的所有堆:

 

    0:000> !heap -h

    Index   Address  Name      Debugging options enabled

      1:   000a0000

        Segment at 000a0000 to 001a0000 (00003000 bytes committed)

      2:   001a0000

        Segment at 001a0000 to 001b0000 (00006000 bytes committed)

      3:   001b0000

        Segment at 001b0000 to 001c0000 (00003000 bytes committed)

 

 

!heap -v可以观察堆的分配粒度和解除提交阈值:

 

    0:000> !heap 000a0000 -v

    Index   Address  Name      Debugging options enabled

      1:   000a0000

        Segment at 000a0000 to 001a0000 (00003000 bytes committed)

        Flags:                50000062

        ForceFlags:           40000060

        Granularity:          8 bytes

        Segment Reserve:      00100000

        Segment Commit:       00002000

        DeCommit Block Thres: 00000200

        DeCommit Total Thres: 00002000

        Total Free Size:      000000d1

        Max. Allocation Size: 7ffdefff

        Lock Variable at:     000a0608

        Next TagIndex:        0000

        Maximum TagIndex:     0000

        Tag Entries:          00000000

        PsuedoTag Entries:    00000000

        Virtual Alloc List:   000a0050

        UCR FreeList:        000a0598

        FreeList Usage:      00000000 00000000 00000000 00000000

        FreeList[ 00 ] at 000a0178: 000a2980 . 000a2980   (1 block )

 

Segment at 000a0000 to 001a0000 (00003000 bytes committed)指明堆的内存范围和提交字节数

Granularity: 8 bytes指明堆块分配粒度

 

 

 

由于堆管理器使用HEAP结构来记录和维护堆的管理信息,因此我们把这个结构称为堆的管理结构,因为这个结构总是在每个堆的开始处,因此有时也被称为堆的头结构

 

下面显示了_HEAP的结构:

 

    :000> dt ntdll!_HEAP 000a0000

       +0x000 Entry            : _HEAP_ENTRY

       +0x008 Signature        : 0xeeffeeff

       +0x00c Flags            : 0x50000062

       +0x010 ForceFlags       : 0x40000060

       +0x014 VirtualMemoryThreshold : 0xfe00

       +0x018 SegmentReserve   : 0x100000

       +0x01c SegmentCommit    : 0x2000

       +0x020 DeCommitFreeBlockThreshold : 0x200

       +0x024 DeCommitTotalFreeThreshold : 0x2000

       +0x028 TotalFreeSize    : 0xd1

       +0x02c MaximumAllocationSize : 0x7ffdefff

       +0x030 ProcessHeapsListIndex : 1

       +0x032 HeaderValidateLength : 0x608

       +0x034 HeaderValidateCopy : (null)

       +0x038 NextAvailableTagIndex : 0

       +0x03a MaximumTagIndex  : 0

       +0x03c TagEntries       : (null)

       +0x040 UCRSegments      : (null)

       +0x044 UnusedUnCommittedRanges : 0x000a0598 _HEAP_UNCOMMMTTED_RANGE

       +0x048 AlignRound       : 0x17

       +0x04c AlignMask        : 0xfffffff8

       +0x050 VirtualAllocdBlocks : _LIST_ENTRY [ 0xa0050 - 0xa0050 ]

       +0x058 Segments         : [64] 0x000a0640 _HEAP_SEGMENT

       +0x158 u                : __unnamed

       +0x168 u2               : __unnamed

       +0x16a AllocatorBackTraceIndex : 0

       +0x16c NonDedicatedListLength : 1

       +0x170 LargeBlocksIndex : (null)

       +0x174 PseudoTagEntries : (null)

       +0x178 FreeLists        : [128] _LIST_ENTRY [ 0xa2980 - 0xa2980 ]

       +0x578 LockVariable     : 0x000a0608 _HEAP_LOCK

       +0x57c CommitRoutine    : (null)

       +0x580 FrontEndHeap     : 0x000a0688

       +0x584 FrontHeapLockCount : 0

       +0x586 FrontEndHeapType : 0x1 ''

       +0x587 LastSegmentIndex : 0 ''

 

Segments字段用来记录堆中包含的所有段,它是一个数组,每个元素都指向一个HEAP_SEGMENT结构的指针,

 

   +0x058 Segments         : [64] 0x000a0640 _HEAP_SEGMENT

 

LastSegmentIndex的值加1就是段的总个数:

 

 +0x587 LastSegmentIndex : 0 ''

 

说明就一个段:

 

    0:000> dt _HEAP_SEGMENT  0x000a0640

    ntdll!_HEAP_SEGMENT

       +0x000 Entry            : _HEAP_ENTRY

       +0x008 Signature        : 0xffeeffee

       +0x00c Flags            : 0

       +0x010 Heap             : 0x000a0000 _HEAP

       +0x014 LargestUnCommittedRange : 0xfd000

       +0x018 BaseAddress      : 0x000a0000

       +0x01c NumberOfPages    : 0x100

       +0x020 FirstEntry       : 0x000a0680 _HEAP_ENTRY//第一个堆块

       +0x024 LastValidEntry   : 0x001a0000 _HEAP_ENTRY

       +0x028 NumberOfUnCommittedPages : 0xfd

       +0x02c NumberOfUnCommittedRanges : 1

       +0x030 UnCommittedRanges : 0x000a0588 _HEAP_UNCOMMMTTED_RANGE

       +0x034 AllocatorBackTraceIndex : 0

       +0x036 Reserved         : 0

       +0x038 LastEntryInSegment : 0x000a2978 _HEAP_ENTRY//最后一个堆块

 

 

 堆管理器使用_HEAP_ENTRY来描述每个堆块:

 

    0:000> dt _HEAP_ENTRY 0x000a0680

    ntdll!_HEAP_ENTRY

       +0x000 Size             : 0x303//堆块的大小,以分配粒度为单位

       +0x002 PreviousSize     : 8//前一个堆块的大小

       +0x000 SubSegmentCode   : 0x00080303

       +0x004 SmallTagIndex    : 0x60 '`'

       +0x005 Flags            : 0x7 ''//标志

       +0x006 UnusedBytes      : 0x18 ''//因为补齐而多分配的字节数

       +0x007 SegmentIndex     : 0 ''//这个堆块所在段的序号

 

其中Flags字段代表堆块的状态,其值是下列标志位的组合

 

标志        值   含义

HEAP_ENTRY_BUSY        01   该块处于占用状态

HEAP_ENTRY_EXTRA_PRESENT     02   该块存在额外的描述

HEAP_ENTRY_FILL_PATTERN 04   使用固定模式填充堆块

HEAP_ENTRY_VIRTUAL_ALLOC      08   虚拟分配

HEAP_ENTRY_LAST_ENTRY    0X10        这是该段的最后一个块

 

_HEAP_ENTRY的长度固定为8字节长,位于堆块起始处,其后便是堆块的用户数据,也就是说,把HeapAlloc函数

 

返回的地址减去8,就是这个堆块的_HEAP_ENTRY结构的地址

!htrace(跟踪句柄泄漏)

!htrace

 

!htrace(Handle Trace) 扩展用于显示一个或多个句柄的堆栈回溯信息。

 

直接用!htrace -?可以看到简单使用说明:

 

    0:000> !htrace -?

    !htrace [handle [max_traces]]

    !htrace -enable [max_traces]

    !htrace -disable

    !htrace -snapshot

    !htrace -diff

 

Handle 指定要显示堆栈回溯的句柄。如果Handle 为0 或者省略,则显示进程中所有句柄的堆栈回溯。 Process (仅内核模式) 指定要显示句柄的进程。如果Process 为0或者省略,则使用当前进程。用户模式下总是使用当前进程。 Max_Traces 指定要显示的堆栈回溯的最大层数。用户模式下如果省略该参数,则显示目标进程中的所有堆栈回溯。 -enable (仅用户模式) 启用句柄跟踪,并且为-diff 选项使用的初始状态产生第一次句柄信息的快照。

 

-snapshot (仅用户模式) 抓取当前的句柄信息的快照用作-diff 选项的初始状态。.

 

-diff (仅用户模式) 将当前的句柄信息和上一次句柄快照的信息进行对比。显示所有仍然打开的句柄。

 

-disable (仅用户模式;仅Windows Server 2003和之后的系统) 禁止句柄跟踪。在Windows XP中,只有结束目标进程才能禁用句柄跟踪。

 

-? 在调试器命令窗口中显示一些简要的帮助文本。

 

首先需要用!htrace -enable来告诉操作系统启用栈回溯(这是前提)

 

    !htrace -snapshot

    !htrace -diff

    0:000> !htrace -enable

    Handle tracing enabled.

    Handle tracing information snapshot successfully taken.

 

可以看到,-enable是一个两步操作,首先,启动栈回溯(Handle tracing enabled),

 

然后,它根据句柄来抓取进程当前状态的快照(Handle tracing information snapshot successfully taken.)

 

在栈回溯被启用后,windows将立即开始记录所有的句柄创建调用和句柄删除调用,在下一次抓取快照时(-snapshot),!htrace将向操作系统查询所有句柄

 

创建调用和句柄删除调用的栈回溯,并且把它们显示出来,

这时直接用!htrace会输出以下内容:

 

    0:001> !htrace

    --------------------------------------

    Handle = 0x000007bc - CLOSE

    Thread ID = 0x000014fc, Process ID = 0x000013f0

    

    0x7c8135dd: kernel32!GetLongPathNameW+0x00000249

    0x7854287c: MSVCR90!_check_manifest+0x0000009c

    0x78542c22: MSVCR90!__CRTDLL_INIT+0x0000008e

    0x78542d5e: MSVCR90!_CRTDLL_INIT+0x0000001e

    0x7c92118a: ntdll!LdrpCallInitRoutine+0x00000014

    0x7c93b5d2: ntdll!LdrpRunInitializeRoutines+0x00000344

    0x7c93fbdc: ntdll!LdrpInitializeProcess+0x0000114b

    0x7c93fad7: ntdll!_LdrpInitialize+0x00000183

    0x7c92e457: ntdll!KiUserApcDispatcher+0x00000007

    --------------------------------------

    Handle = 0x000007bc - OPEN

    Thread ID = 0x000014fc, Process ID = 0x000013f0

    

    0x7c80ef97: kernel32!FindFirstFileW+0x00000016

    0x7c8135c5: kernel32!GetLongPathNameW+0x00000231

    0x7854287c: MSVCR90!_check_manifest+0x0000009c

    0x78542c22: MSVCR90!__CRTDLL_INIT+0x0000008e

    0x78542d5e: MSVCR90!_CRTDLL_INIT+0x0000001e

    0x7c92118a: ntdll!LdrpCallInitRoutine+0x00000014

    0x7c93b5d2: ntdll!LdrpRunInitializeRoutines+0x00000344

    0x7c93fbdc: ntdll!LdrpInitializeProcess+0x0000114b

    0x7c93fad7: ntdll!_LdrpInitialize+0x00000183

    --------------------------------------

    Handle = 0x000007c0 - CLOSE

    Thread ID = 0x000014fc, Process ID = 0x000013f0

    

    0x7c8135dd: kernel32!GetLongPathNameW+0x00000249

    0x7854287c: MSVCR90!_check_manifest+0x0000009c

    0x78542c22: MSVCR90!__CRTDLL_INIT+0x0000008e

    0x78542d5e: MSVCR90!_CRTDLL_INIT+0x0000001e

    0x7c92118a: ntdll!LdrpCallInitRoutine+0x00000014

    0x7c93b5d2: ntdll!LdrpRunInitializeRoutines+0x000003

    .........................

 

格式一般是句柄值,进程线程, OPEN表示打开句柄的栈回溯,CLOSE表示关闭句柄的栈回溯,那么关键是要找到那些栈回溯打开了句柄,却没有相应的栈回溯来关闭句柄,这可以通过!htrace -diff来实现:

 

    0:000> !htrace -diff

    Handle tracing information snapshot successfully taken.

    0xfa new stack traces since the previous snapshot.

    Ignoring handles that were already closed...

    Outstanding handles opened since the previous snapshot:

    --------------------------------------

    Handle = 0x000005b0 - OPEN

    Thread ID = 0x000007f4, Process ID = 0x000013f0

    

    0x00401657: test1!CServer::GetToken+0x00000047

    0x0040136f: test1!CServer::GetSID+0x0000001f

    0x004010de: test1!ThreadWorker+0x0000007e

    0x7c80b729: kernel32!BaseThreadStart+0x00000037

    --------------------------------------

    Handle = 0x000005b4 - OPEN

    Thread ID = 0x00000c34, Process ID = 0x000013f0

    

    0x00401657: test1!CServer::GetToken+0x00000047

    0x0040136f: test1!CServer::GetSID+0x0000001f

    0x004010de: test1!ThreadWorker+0x0000007e

    0x7c80b729: kernel32!BaseThreadStart+0x00000037

    --------------------------------------

    Handle = 0x000005b8 - OPEN

    Thread ID = 0x00001650, Process ID = 0x000013f0

    

    0x00401657: test1!CServer::GetToken+0x00000047

 

 

很明显了,我们看到在GetToken中有句柄未关闭,所以在使用!htrace时采用的步骤一般是:

 

1. 在重现泄漏问题之前,启用句柄跟踪(!htrace -enable)

 

2.执行重现过程,并且让进程句柄泄漏

 

3.通过!htrace -diff来找出有问题的栈

 

 

自己写了下DML方便使用:

 

    .printf "\n\nfor trace handle leak\n"

    .block

    {

        as ${/v:ScriptName} c:\\cmdtree\\script\\User-Mode\\traceHandle.txt

    }

    

    .printf /D "<link cmd=\"!htrace -enable;ad ${/v:ScriptName};$$><${ScriptName}\">trace handle :<col fg=\"changed\"bg=\"wbg\"><b>snap-enable</b></col></link>\n\n"

    .printf /D "<link cmd=\"!htrace -snapshot;ad ${/v:ScriptName};$$><${ScriptName}\">trace handle :<col fg=\"changed\"bg=\"wbg\"><b>snap-snapshot</b></col></link>\n\n"

    .printf /D "<link cmd=\".echo you should get snapshot more than twice!!!!!!!!!!!!!!!!!!!!!!!!!;!htrace -diff;ad ${/v:ScriptName};$$><${ScriptName}\"\n\n>trace handle :<col fg=\"changed\"bg=\"wbg\"><b>diff</b></col></link>\n\n"

让windbg随进程加载自动启动(映像劫持技术)

1.在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options下,建一个项,比如DNF,就叫DNF.exe,

 

2.再在右边建一个 子项叫

 

Debugger 类型REG_SZ,填上windbg的地址,

 

3.再用windbg -I(大写)设置为默认调试器,之后就可以直接在dnf.exe起来时直接附加上.

https://i-blog.csdnimg.cn/blog_migrate/13800f961d001007cae4173932bf08a4.png

 

 

4.如果想用vs调试,就使用以下:

 

    Windows Registry Editor Version 5.00

    

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\1.exe]

    "debugger"="vsjitdebugger.exe"

 

这时先开1.exe的vs代码,1.exe被拉起后,会弹出选择,选vs代码运行即可

!handle(句柄信息)

!handle

 

!handle 扩展显示目标系统中一个或所有进程拥有的句柄的信息

 

    0:001> !handle

    Handle 4

      Type            Directory

    Handle 8

      Type            File

    Handle c

      Type            File

    Handle 10

      Type            Key

    Handle 14

      Type            ALPC Port

    Handle 18

      Type            Mutant

    Handle 1c

      Type            Key

    Handle 20

      Type            Event

    Handle 24

      Type            Key

    Handle 2c

      Type            Event

    Handle 30

      Type            WindowStation

    Handle 34

      Type            Desktop

    Handle 38

      Type            WindowStation

    Handle 3c

      Type            File

    Handle 84

      Type            Event

    Handle 88

      Type            Event

    Handle 8c

      Type            Event

    Handle 90

      Type            Event

    Handle 94

      Type            Event

    Handle 98

      Type            Event

    Handle 9c

      Type            Directory

    Handle a0

      Type            Event

    Handle a4

      Type            Event

    Handle a8

      Type            File

    Handle ac

      Type            File

    Handle b0

      Type            Event

    Handle b4

      Type            Mutant

    Handle b8

      Type            Event

    Handle bc

      Type            Mutant

    Handle c0

      Type            Section

    Handle c4

      Type            Section

    Handle c8

      Type            Mutant

    Handle cc

      Type            Section

    Handle d0

      Type            Key

    Handle d4

      Type            Key

    Handle d8

      Type            Key

    Handle dc

      Type            Key

    Handle e0

      Type            Key

    Handle e4

      Type            File

    Handle e8

      Type            Section

    Handle f4

      Type            File

    Handle f8

      Type            ALPC Port

    Handle fc

      Type            Mutant

    Handle 100

      Type            Section

    Handle 104

      Type            File

    Handle 10c

      Type            File

    Handle 110

      Type            Key

    Handle 114

      Type            Key

    Handle 11c

      Type            Key

    49 Handles

    Type              Count

    None           2

    Event             12

    Section             5

    File                9

    Directory            2

    Mutant           5

    WindowStation     2

    Key               11

    Desktop           1

 

我们注意到最下面这一部分统计了各种类型句柄的各自数目

 

要得到某个句柄更详细的信息,可以用这个句柄做为参数,再用'f'表示显示最详细的信息:

 

    0:001> !handle 4 f

    Handle 4

      Type            Directory

      Attributes          0x10

      GrantedAccess     0x3:

             None

             Query,Traverse

      HandleCount     60

      PointerCount      99

      Name         \KnownDlls

      No Object Specific Information available

 

    0:001> !handle 84 f

    Handle 84

      Type            Event

      Attributes          0

      GrantedAccess     0x1f0003:

             Delete,ReadControl,WriteDac,WriteOwner,Synch

             QueryState,ModifyState

      HandleCount     2

      PointerCount      3

      Name         <none>

      Object Specific Information

        Event Type Manual Reset

        Event is Waiting

 

重新写一个代码测试Mutex:

 

    0:001> !handle xx f

    Handle 78

      Type            Mutant

      Attributes          0

      GrantedAccess     0x1f0001:

             Delete,ReadControl,WriteDac,WriteOwner,Synch

             QueryState

      HandleCount     2

      PointerCount      3

      Name         <none>

      Object Specific Information

        Mutex is Free

    0:001> !handle xx f

    Handle e0

      Type            Mutant

      Attributes          0

      GrantedAccess     0x1f0001:

             Delete,ReadControl,WriteDac,WriteOwner,Synch

             QueryState

      HandleCount     2

      PointerCount      3

      Name         <none>

      Object Specific Information

        Mutex is Owned

 

再写个代码验证信号量:

 

    0:001> !handle dc f

    Handle dc

      Type            Semaphore

      Attributes          0

      GrantedAccess     0x1f0003:

             Delete,ReadControl,WriteDac,WriteOwner,Synch

             QueryState,ModifyState

      HandleCount     2

      PointerCount      3

      Name         <none>

      Object Specific Information

        Semaphore Count 4

        Semaphore Limit 5

 

 

 

可以看到,上面给出了这个句丙的类型,属性,访问权限以及句柄计数,我们甚至看到这个事件是Manual Reset(人工重置事件)如果是Event Type Auto Reset(自动重置事件),状态是Waiting(未触发),如果是Event is set表示已触发状态,如果Mutex is free 表示互斥量已有信号,如果Mutex is owned表示互斥量已被占用,对于信号量,Semaphore Count 表示可用的信号量,Limit表示总数

 

我们不知道!handle具体用法,可以使用-?来查询

 

    0:001> !handle -?

    !handle [<handle>] [<flags>] [<type>]

      <handle> - Handle to get information about

                 0 or -1 means all handles

      <flags> - Output control flags

                1   - Get type information (default)

                2   - Get basic information

                4   - Get name information

                8   - Get object specific info (where available)

      <type> - Limit query to handles of the given type

    Display information about open handles

 

 

 

当handle很多时,我们也许只想知道所有互斥量的信息:

 

    0:001> !handle 0 1 Mutant

    Handle 78

      Type            Mutant

    Handle e0

      Type            Mutant

    Handle ec

      Type            Mutant

    3 handles of type Mutant

 

1对应flags,至于名字Mutant可以参看windbg帮助说明:

 

Specifies the type of handle that you want to examine. Only handles that match this type are displayed.TypeName is case sensitive. Valid types include Event, Section, File, Port, Directory, SymbolicLink, Mutant, WindowStation, Semaphore, Key, Token, Process, Thread, Desktop, IoCompletion, Timer, Job, and WaitablePort.

 

上面红色标明为区分大小写,怀疑:下面明显是成功的!

 

    0:001> !handle 0 3 mutanT

    Handle 78

      Type            Mutant

      Attributes          0

      GrantedAccess     0x1f0001:

             Delete,ReadControl,WriteDac,WriteOwner,Synch

             QueryState

      HandleCount     2

      PointerCount      3

    Handle e0

      Type            Mutant

      Attributes          0

      GrantedAccess     0x1f0001:

             Delete,ReadControl,WriteDac,WriteOwner,Synch

             QueryState

      HandleCount     2

      PointerCount      3

    Handle ec

      Type            Mutant

      Attributes          0

      GrantedAccess     0x100000:

             Synch

             None

      HandleCount     13

      PointerCount      15

    3 handles of type Mutant

k*实例分析(查看调用栈分析)

    #include "stdafx.h"

    

    int fun0(int i)

    {

             return i;

    };

    

    int fun1(int i)

    {

             return fun0(i);

    }

    

    int _tmain(int argc, _TCHAR* argv[])

    {

    

             fun1(10);

    

             return 0;

    }

 

 

代码如上

 

我们在test!fun1下个断点,g运行,断下来后:

https://i-blog.csdnimg.cn/blog_migrate/91ab98056bbe3d6f6b28960b9bf50012.png

 

我们来观注下蓝色小框的地址是RetAddr,具体指什么,跳转到此处汇编:

https://i-blog.csdnimg.cn/blog_migrate/12fdd1432748e3487515c35edf81f4c9.png

 

很明显了,就是运行函数后的下一条要执行的指令

 

我们再看看ChildEBP的含义:

 

现在打印下ebp值:

 

    0:000> r ebp

    ebp=002ef930

 

很诡异,怎么会是前一个栈的ebp呢,其实我们可以注意到当前汇编是push ebp

 

而这个函数用到的ebp应该是到mov ebp, esp之后,单步调过此处,再看其值:

https://i-blog.csdnimg.cn/blog_migrate/49fd0d7037c378e1d3d28df9d57202a1.png

 

也就是ChildEbp就是当前函数需要使用的EBP,不要被ChildEbp的child字面意思搞晕了~~~~

 

 

有函数调用的栈中的情况:

 

https://i-blog.csdnimg.cn/blog_migrate/e1e9169580065c988b8ccc34f7bcade7.jpeg

上图中上面是低地址,下面是高地址,保存的EBP就是前一个EBP,它的地址就是当前的EBP

 

在内存中的一系列的值是可以被识别出来的, 这些值表示当前栈中的某个地址, 并且在这些值之后是一个可执行的地址.

 

    0:000> lm

    start    end        module name

    011e0000 011fb000   test     C (private pdb symbols)  E:\test\Debug\test.pdb

    53ca0000 53dc3000   MSVCR90D   (deferred)            

    756f0000 7573b000   KERNELBASE   (deferred)  

 

再看下转存的栈

https://i-blog.csdnimg.cn/blog_migrate/97ef04a13461580f781de7aa1dfb2dc4.png

 

可以看到,蓝色的地址和最右排地址相差无几,极可能是当前栈的某个地址,后面接着红色部分是在test模块内的,所以应该是返回地址

 

以后为重启了次程序运行结果,我们可以打印出kb看到:

 

    0:000> kb

    ChildEBP RetAddr  Args to Child             

    0029fbb4 011f1417 0000000a 0029fd64 00000000 test!fun0+0xb [e:\test\test\test.cpp @ 7]

    0029fc8c 011f1465 0000000a 00000000 00000000 test!fun1+0x27 [e:\test\test\test.cpp @ 13]

    0029fd64 011f19f8 00000001 00331b90 00334800 test!wmain+0x25 [e:\test\test\test.cpp @ 19]

    0029fdb4 011f183f 0029fdc8 75aaed6c 7ffdd000 test!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]

    0029fdbc 75aaed6c 7ffdd000 0029fe08 7757377b test!wmainCRTStartup+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 403]

    0029fdc8 7757377b 7ffdd000 764a1a3e 00000000 kernel32!BaseThreadInitThunk+0xe

    0029fe08 7757374e 011f107d 7ffdd000 00000000 ntdll!__RtlUserThreadStart+0x70

    0029fe20 00000000 011f107d 7ffdd000 00000000 ntdll!_RtlUserThreadStart+0x1b

 

这是fun1用到的ebp,按图就知道,保存的EBP和返回地址是连续的.

 

后面的test!fun1+0x27其实是通过寻找上一行的返回地址(011f1417)得来的,可以使用ln 011f1417查看.

 

 

 特别注意是栈是向低地址增加,dd 后一个EBP的值就是前一个EBP

 

    0:000> p

    eax=00000002 ebx=7efde000 ecx=00000001 edx=00000001 esi=00000000 edi=0034fed8

    eip=00c13613 esp=0034fde4 ebp=0034fed8 iopl=0         nv up ei pl nz na po nc

    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202

    test1!wmain+0x33:

    00c13613 51              push    ecx

    

    0:000> r esp

    esp=0034fde0

 

 

push ecx后,esp减了4

 

另外经常用到的指令如kn 100

 

kn用于显示帧号码,100表示层级,一般不会到100,所以就是显示实际的层级

 

    0:000> kn 100

     # ChildEBP RetAddr 

    00 010ff860 776fb2e4 ntdll!LdrpDoDebuggerBreak+0x2b

    01 010ffa98 776f7744 ntdll!LdrpInitializeProcess+0x14dc

    02 010ffaec 776f7590 ntdll!_LdrpInitialize+0x178

    03 010ffaf4 00000000 ntdll!LdrInitializeThunk+0x10

 

  

 

又如:

 

~*kv 1

 

让每个线程只显示前一帧

 

    :000> ~*kv 1

    

    .  0  Id: ae14.5c8 Suspend: 1 Teb: 00ff5000 Unfrozen

     # ChildEBP RetAddr  Args to Child             

    00 010ff860 776fb2e4 a7e95f8e ffffffff 00000000 ntdll!LdrpDoDebuggerBreak+0x2b (FPO: [Non-Fpo])

    

       1  Id: ae14.b410 Suspend: 1 Teb: 00ff8000 Unfrozen

     # ChildEBP RetAddr  Args to Child             

    00 013cf88c 776c6a61 000000b8 013fc388 00000010 ntdll!NtWaitForWorkViaWorkerFactory+0xc (FPO: [5,0,0])

    

       2  Id: ae14.5ed0 Suspend: 1 Teb: 00ffb000 Unfrozen

     # ChildEBP RetAddr  Args to Child             

    00 015ef96c 776c6a61 000000b8 013fc638 00000010 ntdll!NtWaitForWorkViaWorkerFactory+0xc (FPO: [5,0,0])

!dh、!lmi(显示PE头信息)

!dh

 

!dh:扩展显示指定映像的头部

 

-h 在调试器命令窗口中显示该扩展命令的帮助文本。

 

    0:004> !dh -h

    Usage: dh [options] address

    

    Dumps headers from an image based at address

    

    Options:

    

       -a      Dump everything

       -f      Dump file headers

       -s      Dump section headers

 

也就这三个属性,默认是使用-a

 

    0:004> !dh ntdll

    

    File Type: DLL

    FILE HEADER VALUES

         14C machine (i386)

           5 number of sections

    4EC49B60 time date stamp Thu Nov 17 13:28:00 2011

           0 file pointer to symbol table

           0 number of symbols

          E0 size of optional header

        2102 characteristics

                Executable

                32 bit word machine

                DLL

    OPTIONAL HEADER VALUES

         10B magic #

        9.00 linker version

       D5000 size of code

       63200 size of initialized data

           0 size of uninitialized data

           0 address of entry point

        1000 base of code

             ----- new -----

    775a0000 image base

        1000 section alignment

         200 file alignment

           3 subsystem (Windows CUI)

        6.01 operating system version

        6.01 image version

        6.01 subsystem version

      13C000 size of image

         400 size of headers

      141016 checksum

    00040000 size of stack reserve

    00001000 size of stack commit

    00100000 size of heap reserve

    00001000 size of heap commit

       36190 [    F018] address [size] of Export Directory

           0 [       0] address [size] of Import Directory

       E0000 [   560D8] address [size] of Resource Directory

           0 [       0] address [size] of Exception Directory

      137000 [    3918] address [size] of Security Directory

      137000 [    4C50] address [size] of Base Relocation Directory

       D5D5C [      38] address [size] of Debug Directory

           0 [       0] address [size] of Description Directory

           0 [       0] address [size] of Special Directory

           0 [       0] address [size] of Thread Storage Directory

       1E0A8 [      40] address [size] of Load Configuration Directory

           0 [       0] address [size] of Bound Import Directory

           0 [       0] address [size] of Import Address Table Directory

           0 [       0] address [size] of Delay Import Directory

           0 [       0] address [size] of COR20 Header Directory

           0 [       0] address [size] of Reserved Directory

    SECTION HEADER #1

       .text name

       D4DBA virtual size

        1000 virtual address

       D4E00 size of raw data

         400 file pointer to raw data

           0 file pointer to relocation table

           0 file pointer to line numbers

           0 number of relocations

           0 number of line numbers

    60000020 flags

             Code

             (no align specified)

             Execute Read

    Debug Directories(2)

             Type       Size     Address  Pointer

             cv           22       d5d98    d5198 Format: RSDS, guid, 2, ntdll.pdb

             (    10)       4       d5d94    d5194

    SECTION HEADER #2

          RT name

         1DC virtual size

       D6000 virtual address

         200 size of raw data

       D5200 file pointer to raw data

           0 file pointer to relocation table

           0 file pointer to line numbers

           0 number of relocations

           0 number of line numbers

    60000020 flags

             Code

             (no align specified)

             Execute Read

    SECTION HEADER #3

       .data name

        8064 virtual size

       D7000 virtual address

        6C00 size of raw data

       D5400 file pointer to raw data

           0 file pointer to relocation table

           0 file pointer to line numbers

           0 number of relocations

           0 number of line numbers

    C0000040 flags

             Initialized Data

             (no align specified)

             Read Write

    SECTION HEADER #4

       .rsrc name

       560D8 virtual size

       E0000 virtual address

       56200 size of raw data

       DC000 file pointer to raw data

           0 file pointer to relocation table

           0 file pointer to line numbers

           0 number of relocations

           0 number of line numbers

    40000040 flags

             Initialized Data

             (no align specified)

             Read Only

    SECTION HEADER #5

      .reloc name

        4C50 virtual size

      137000 virtual address

        4E00 size of raw data

      132200 file pointer to raw data

           0 file pointer to relocation table

           0 file pointer to line numbers

           0 number of relocations

           0 number of line numbers

    42000040 flags

             Initialized Data

             Discardable

             (no align specified)

             Read Only

 

 

可以比对LoadPE工具,可以发现完全一样:

 

 

!lmi

 

!lmi 扩展显示某个模块的详细信息

 

    0:004> !lmi ntdll

    Loaded Module Info: [ntdll]

             Module: ntdll

       Base Address: 775a0000

         Image Name: C:\Windows\SYSTEM32\ntdll.dll

       Machine Type: 332 (I386)

         Time Stamp: 4ec49b60 Thu Nov 17 13:28:00 2011

               Size: 13c000

           CheckSum: 141016

    Characteristics: 2102 

    Debug Data Dirs: Type  Size     VA  Pointer

                 CODEVIEW    22, d5d98,   d5198 RSDS - GUID: {093D2CD7-F95B-4CC6-B531-8D405CC31566}

                   Age: 2, Pdb: ntdll.pdb

                    CLSID     4, d5d94,   d5194 [Data not mapped]

         Image Type: FILE     - Image read successfully from debugger.

                     C:\Windows\SYSTEM32\ntdll.dll

        Symbol Type: EXPORT   - PDB not found

        Load Report: export symbols

获取结构体大小(常用的两种方式)

两种方式:

 

dt -v:详细输出。这会输出结构的总大小和字段数量这样的附加信息。当它和-y选项一起使用时,所有的符号都会被显示出来,即使他们没有任何关联的类型信息。

 

    0:000> dt -v _image_nt_headers

    OGame!_IMAGE_NT_HEADERS

    struct _IMAGE_NT_HEADERS, 3 elements, 0xf8 bytes

       +0x000 Signature        : Uint4B

       +0x004 FileHeader       : struct _IMAGE_FILE_HEADER, 7 elements, 0x14 bytes

       +0x018 OptionalHeader   : struct _IMAGE_OPTIONAL_HEADER, 31 elements, 0xe0 bytes

 

 

或C++方式:

 

    0:000> ?? @@c++(sizeof(_image_nt_headers))

    unsigned int 0xf8

 

    0:000> ?? sizeof(_image_nt_headers)

    unsigned int 0xf8

s、#(搜索字符串、地址、汇编)

s(Search Memory)

s 命令搜索内存查找指定模板

 

1.  寻找内存泄露的线索。比如知道当前内存泄漏的内容是一些固定的字符串,就可以在

   DLL 区域搜索这些字符串出现的地址,然后再搜索这些地址用到什么代码中,找出这些

    内存是在什么地方开始分配的。

2.  寻找错误代码的根源。比如知道当前程序返回了 0x80074015  这样的一个代码,但是不

    知道这个代码是由哪一个内层函数返回的。就可以在代码区搜索 0x80074015,找到可能

    返回这个代码的函数。

 

比如我的代码中有个char* g_char = "I am string";全局字符串变量

 

我想要查找,这个肯定是放在.rdata段中,所以

 

    0:000> !dh -s 01270000

    

    SECTION HEADER #3

      .rdata name

        1D11 virtual size

       15000 virtual address

        1E00 size of raw data

        3A00 file pointer to raw data

           0 file pointer to relocation table

           0 file pointer to line numbers

           0 number of relocations

           0 number of line numbers

    40000040 flags

             Initialized Data

             (no align specified)

             Read Only

 

 

 

//字符串搜索--------------------------------------------------------------------------------------------------------------------------------------------------

 

s-sa 和s-su 命令搜索未指定的ASCII和Unicode字符串。这在检查某段内存是否包含可打印字符时有用。

 

s-a 和s-u 命令分别用来搜索指定的ASCII和Unicode字符串。这些字符串不一定要null结尾

 

    0:000> s -sa 01270000+15000 L1D11

    0128573c  "I am string"

    01285868  "bad allocation"

    012858e0  "Stack around _alloca corrupted"

    01285908  "Local variable used before initi"

    01285928  "alization"

    0128593c  "Stack memory corruption"

    01285958  "Cast to smaller type causing los"

    0:000> s -a 01270000+15000 L1D11 "I am"

    0128573c  49 20 61 6d 20 73 74 72-69 6e 67 00 25 73 0a 00  I am string.%s..

 

 

可以使用flag [1]在搜索输出中仅显示匹配项的地址。该选项在使用.foreach把输出通过管道传递给其他命令作为输入时很有用。 

 

注意有个[]号

 

    0:000> s -[1]a 01270000+15000 L1D11 "I am"

    0x0128573c

    0:000> .foreach (addr {s -[1]a 01270000+15000 L1D11 "I am"}){da ${addr}}

    0128573c  "I am string"

 

 

// 内存地址搜索------------------------------------------------------------------------------------------------------------------------------

 

搜索printf的调用地址

 

    009614d4 test1!printf = <no type information>

    009682c8 test1!_imp__printf = <no type information>

    0:000> s -[1]d 00961431 L130000 009682c8

    0x0096145d

    

    0:000> u 0096145d L2

    test1!wmain+0x2d [d:\windbg\test1\test1.cpp @ 23]:

    0096145d c8829600        enter   9682h,0

    00961461 83c408          add     esp,8

    0:000> ub 00961461 L1

    test1!wmain+0x2b [d:\windbg\test1\test1.cpp @ 23]:

    0096145b ff15c8829600    call    dword ptr [test1!_imp__printf (009682c8)]

 

(反汇编)

在反汇编代码中搜索符合指定模板的数据

 

    0:001> lm m test1

    start    end        module name

    00a50000 00a6b000   test1    C (private pdb symbols)  C:\Program Files (x86)\Debugging Tools for Windows (x86)\sym\test1.pdb\0142AD0EED1143078D35079F6E0C70AB5\test1.pdb

    0:001> # test1!_imp__printf 00a50000

    test1!printf:

    00a614d4 ff25c882a600    jmp     dword ptr [test1!_imp__printf (00a682c8)]

    0:001> # test1!_imp__printf   ///<接着上次搜索结果地址后搜索

    test1!ThreadProc+0x58 [d:\windbg\test1\test1.cpp @ 23]:

    00a61b48 ff15c882a600    call    dword ptr [test1!_imp__printf (00a682c8)]

    0:001> # test1!_imp__printf

    test1!wmain+0x4d [d:\windbg\test1\test1.cpp @ 33]:

    00a630dd ff15c882a600    call    dword ptr [test1!_imp__printf (00a682c8)]

 

 

地址也可以直接使用函数名替代

 

    0:001> # *test1!_imp__printf* test1!wmain

    test1!wmain+0x4d [d:\windbg\test1\test1.cpp @ 33]:

    00a630dd ff15c882a600    call    dword ptr [test1!_imp__printf (00a682c8)]

!thread、.thread(内核)

!thread扩展显示目标系统中线程包括ETHREAD块在内的摘要信息。该命令只能在内核模式调试下使用

 

!thread [-p] [-t] [Address [Flags]]

 

 

-p 显示拥有该线程的进程的摘要信息。 -t 包含这个选项时,Address是线程ID,而不是线程地址。 Address 指定目标机上线程的16进制地址。如果Address为-1或省略,则表示当前线程。 Flags 指定显示的详细级别。Flags可以是下面这些位的任意组合。如果Flags为0,只会显示最少量的信息。默认为0x6:

 

Bit 1 (0x2)

    显示线程的等待状态。

Bit 2 (0x4)

    如果不和Bit 1(0x2)一起使用则不会起作用。如果和Bit 1一起使用,线程会和调用堆栈一起显示出来。

Bit 3 (0x8)

    (Windows XP和之后) 

 

    在每个函数的显示信息中加入返回地址、堆栈指针、以及bsp寄存器的值(在Itanium系统中),但是不显示函数的参数。

Bit 4 (0x10)

    (Windows XP和之后) 在这个命令持续期间,将进程上下文设置为拥有指定线程的那个进程。这回使得线程调用堆栈的显示更加精确。

 

显示当前线程的详细信息:

 

    kd> !thread -1 6

    THREAD 821ec390  Cid 06e8.06e4  Teb: 7ffdd000 Win32Thread: 00000000 RUNNING on processor 0

    IRP List:

        82265a38: (0006,0094) Flags: 00000a00  Mdl: 81e91b68

    Not impersonating

    DeviceMap                 e19c40c8

    Owning Process            0       Image:         <Unknown>

    Attached Process          821f5da0       Image:         test.exe

    Wait Start TickCount      21156          Ticks: 1 (0:00:00:00.015)

    Context Switch Count      22            

    UserTime                  00:00:00.000

    KernelTime                00:00:00.031

    Win32 Start Address test (0x00401356)

    Start Address kernel32!BaseProcessStartThunk (0x7c8106f5)

    Stack Init b2325000 Current b2324b84 Base b2325000 Limit b2322000 Call 0

    Priority 8 BasePriority 8 PriorityDecrement 0 DecrementCount 0

    ChildEBP RetAddr  Args to Child             

    b2324c80 80580982 82265aa8 00000000 82265a38 nt!IopfCallDriver+0x31 (FPO: [0,0,0])

    b2324c94 8057e4c9 81e6a518 82265a38 822272d8 nt!IopSynchronousServiceTail+0x70 (FPO: [7,0,4])

    b2324d38 8054261c 00000038 00000000 00000000 nt!NtWriteFile+0x5d7 (FPO: [Non-Fpo])

    b2324d38 7c92e4f4 00000038 00000000 00000000 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ b2324d64)

    0012fee0 7c92df6c 7c810e86 00000038 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])

    0012fee4 7c810e86 00000038 00000000 00000000 ntdll!ZwWriteFile+0xc (FPO: [9,0,0])

    0012ff44 00401070 00000038 0012ff60 0000000a kernel32!WriteFile+0xf7 (FPO: [Non-Fpo])

    WARNING: Stack unwind information not available. Following frames may be wrong.

    0012ff7c 0040120e 00000001 003d3ef8 003d2eb8 test+0x1070

    0012ffc0 7c817067 00310031 00330031 7ffde000 test+0x120e

    0012fff0 00000000 00401356 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])

 

 

.thread 命令指定哪个线程用作寄存器上下文。

和.process有点相似

 

当前线程:

 

    kd> .thread

    Implicit thread is now 80553740

 

.thread /r /p xxx同样是切换到指定的线程,但.thread同时可以切换回中断的线程上下文

 

    kd> .thread /p /r 81e64da8 

    Implicit thread is now 81e64da8

    Implicit process is now 821f5da0

    .cache forcedecodeuser done

    Loading User Symbols

    .........

    kd> kv

      *** Stack trace for last set context - .thread/.cxr resets it

    ChildEBP RetAddr  Args to Child             

    b29b6cb8 80504836 81e64e18 81e64da8 804fc068 nt!KiSwapContext+0x2f (FPO: [Uses EBP] [0,0,4])

    b29b6cc4 804fc068 00000000 b29b6d1c 00000000 nt!KiSwapThread+0x8a (FPO: [0,0,0])

    b29b6cec 805c1750 00000001 00000006 004db801 nt!KeWaitForSingleObject+0x1c2 (FPO: [5,5,4])

    b29b6d50 8054261c 00000010 00000000 b29b6d1c nt!NtWaitForSingleObject+0x9a (FPO: [Non-Fpo])

    b29b6d50 7c92e4f4 00000010 00000000 b29b6d1c nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ b29b6d64)

    003cfa70 7c92df3c 7c8025db 00000010 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])

    003cfa74 7c8025db 00000010 00000000 003cfaa8 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0])

    003cfad8 7c802542 00000010 00002710 00000000 kernel32!WaitForSingleObjectEx+0xa8 (FPO: [Non-Fpo])

    003cfaec 7c875f27 00000010 00002710 00000000 kernel32!WaitForSingleObject+0x12 (FPO: [2,0,0])

    003cffb4 7c80b713 00000000 00610072 006f0074 kernel32!ConsoleIMERoutine+0xf4 (FPO: [1,300,4])

    003cffec 00000000 7c875e33 00000000 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])

    kd> .thread

    Implicit thread is now 821ec390

    kd> kv

    ChildEBP RetAddr  Args to Child             

    b2324c80 80580982 82265aa8 00000000 82265a38 nt!IopfCallDriver+0x31 (FPO: [0,0,0])

    b2324c94 8057e4c9 81e6a518 82265a38 822272d8 nt!IopSynchronousServiceTail+0x70 (FPO: [7,0,4])

    b2324d38 8054261c 00000038 00000000 00000000 nt!NtWriteFile+0x5d7 (FPO: [Non-Fpo])

    b2324d38 7c92e4f4 00000038 00000000 00000000 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ b2324d64)

    0012fee0 7c92df6c 7c810e86 00000038 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])

    0012fee4 7c810e86 00000038 00000000 00000000 ntdll!ZwWriteFile+0xc (FPO: [9,0,0])

    *** ERROR: Module load completed but symbols could not be loaded for test.exe

    0012ff44 00401070 00000038 0012ff60 0000000a kernel32!WriteFile+0xf7 (FPO: [Non-Fpo])

    WARNING: Stack unwind information not available. Following frames may be wrong.

    0012ff7c 0040120e 00000001 003d3ef8 003d2eb8 test+0x1070

0012ffc0 7c817067 00310031 00330031 7ffde000 test+0x120e

!process显示进程(内核)

!process 0 0 显示进程列表:

 

    kd> !process 0 0

    **** NT ACTIVE PROCESS DUMP ****

    PROCESS 825b7830  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000

        DirBase: 02b40020  ObjectTable: e1003e00  HandleCount: 254.

        Image: System

    

    PROCESS 8241d490  SessionId: none  Cid: 0178    Peb: 7ffdf000  ParentCid: 0004

        DirBase: 02b40040  ObjectTable: e148a4a0  HandleCount:  19.

        Image: smss.exe

    

    PROCESS 824d6268  SessionId: 0  Cid: 0264    Peb: 7ffd4000  ParentCid: 0178

        DirBase: 02b40060  ObjectTable: e148fa18  HandleCount: 383.

        Image: csrss.exe

    ....

 

!process XXX显示指定进程的所有信息, !process XXX 0显示指定进程的基本信息

 

XXX可以为EPROCESS或进程ID

 

    kd> !process @$proc 0

    PROCESS 825b7830  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000

        DirBase: 02b40020  ObjectTable: e1003e00  HandleCount: 254.

        Image: System

    

    kd> !process 4 0

    Searching for Process with Cid == 4

    Cid Handle table at e1005000 with 366 Entries in use

    PROCESS 825b7830  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000

        DirBase: 02b40020  ObjectTable: e1003e00  HandleCount: 254.

        Image: System

 

!process 0 0 XXX.exe查找进程

 

    kd> !process 0 0  smss.exe

    PROCESS 8241d490  SessionId: none  Cid: 0178    Peb: 7ffdf000  ParentCid: 0004

        DirBase: 02b40040  ObjectTable: e148a4a0  HandleCount:  19.

        Image: smss.exe

    

    kd> !process 0 0 system

    PROCESS 825b7830  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000

        DirBase: 02b40020  ObjectTable: e1003e00  HandleCount: 254.

        Image: System

 

 

注意只有sytem,没有sytem.exe!!!

 

kd> !process 0 0 system.exe

上述命令是找不到的.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值