暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Windbg调试----Windbg入门

一个程序员的修炼之路 2020-10-25
1077

好久没有更新文章了,希望以Windbg系列作为回归。欢迎大家一起讨论和分享。


Windbg简单来说就是一个Windows下对用户态/内核态的程序进行调试,以及对Core Dump文件的分析。对于Crash,资源泄露,死锁等问题的分析,Windbg是一个强有力的利器。

相关资料

本人也是在维护和开发产品的过程中使用过Windbg,但并未对Windbg进行过系统和深入的学习,也通过这一系列的博客来完善自己对Windbg以及周边知识的理解与使用。我也列出自己正在或者即将阅读的书/资料与大家一起分享:

  • << Windows用户态程序高效排错 >>

  • << 软件调试 >>

  • Modern Windows Exploit Development

  • Windbg.org

  • Windbg帮助文档翻译

安装Windbg

由于目前微软官网上并没有单独提供Windbg的下载安装包,可以通过以下两个途径获取:

  • 下载安装WDK

  • 有网友提供了单独的MSI安装包,可以通过Goole搜索获取

Windbg同时也分32位和64位版本,有网友建议是使用32位Windbg调试32位程序,64位Windbg调试64位程序。本人平时使用64位的Windbg,如果需要分析32位的程序/Dump, 使用如下命令进行CPU模式的切换:

    .load wow64exts
    !sw

    Windbg调试程序

    在使用Windbg调试程序之前,先给大家展示下我的测试程序:

      int main()
      {
      char* pStr = (char*)0xa;
      printf("%s\n", pStr);
      return 0;
      }

      以Viusal Studio为例,一般发布给客户的程序,我们采用Release
      模式编译程序,而Release
      模式与Debug
      模式,有个很大的区别: Release模式编译出来的程序默认是不带PDB相关信息的,而Debug模式则有。采用Release模式编译上述代码,生成一个应用程序testforme
      . 然后采用Windbg打开可执行程序testforme.exe
      , Windbg命令窗口打印信息如下:

        Microsoft (R) Windows Debugger Version 6.3.9600.16384 AMD64
        Copyright (c) Microsoft Corporation. All rights reserved.

        CommandLine: C:\Users\Administrator\Desktop\testforme.exe
        Symbol search path is: *** Invalid ***
        ****************************************************************************
        1. Symbol loading may be unreliable without a symbol search path. *
        2. Use .symfix to have the debugger choose a symbol path. *
        3. After setting your symbol path, use .reload to refresh symbol locations. *
        ****************************************************************************
        Executable search path is:
        ModLoad: 00000000`00400000 00000000`00410000 testforme.exe
        ModLoad: 00000000`77390000 00000000`77539000 ntdll.dll
        ModLoad: 00000000`77570000 00000000`776f0000 ntdll32.dll
        ModLoad: 00000000`74420000 00000000`7445f000 C:\Windows\SYSTEM32\wow64.dll
        ModLoad: 00000000`743c0000 00000000`7441c000 C:\Windows\SYSTEM32\wow64win.dll
        ModLoad: 00000000`743b0000 00000000`743b8000 C:\Windows\SYSTEM32\wow64cpu.dll
        (9f8.4d4): Break instruction exception - code 80000003 (first chance)
        *** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll -
        ntdll!CsrSetPriorityClass+0x40:
        00000000`7743cb70 cc int 3

        主要描述了3点信息:

        • 没有设置符号信息的路径,所以找不到符号。这里所说的符号信息就指上述PDB文件,并且在默认Release模式编译出来的程序,会带有一个同名的PDB文件。你也可以通过配置Visual Studio的配置项来决定是否产生PDB文件:

        • 可以看到已经加载的模块,以及这些模块所在的内存区域。比如可以看出testforme.exe的模块位置在内存0x400000
          0x410000

        调试器中断,这时还没有真正去执行testforme的代码,并且可以通过Windbg命令去设置断点,查看已加载模块的信息等操作。

        接着,我们要让程序执行,在命令行下使用g
        命令, 可以看到程序中断了在Access Violation
        ,也就是内存访问错误。

          0:000:x86> g
          (7b8.238): Access violation - code c0000005 (first chance)
          First chance exceptions are reported before any exception handling.
          This exception may be expected and handled.
          *** ERROR: Module load completed but symbols could not be loaded for testforme.exe
          testforme+0x1e88:
          00401e88 803800 cmp byte ptr [eax],0 ds:002b:0000000a=??

          然后我们用kv
          指令查看当前异常处的函数调用栈:

            0:000:x86> kv
            ChildEBP RetAddr Args to Child
            WARNING: Stack unwind information not available. Following frames may be wrong.
            0018fcf0 00812d70 00000000 00810000 0018fd48 testforme+0x1e88
            00000000 00000000 00000000 00000000 00000000 0x812d70

            这个完全看不出来哪里异常了啊,那是因为并没有配置应用程序相应的符号文件,也就是之前所说的PDB文件。

            设置调试Symbol

              0:000:x86> .sympath C:\mysymbols;SRV*C:\symbols*http://msdl.microsoft.com/download/symbols


              Symbol search path is: C:\mysymbols;SRV*C:\symbols*http://msdl.microsoft.com/download/symbols
              Expanded Symbol search path is: c:\mysymbols;srv*c:\symbols*http://msdl.microsoft.com/download/symbols


              ************* Symbol Path validation summary **************
              Response Time (ms) Location
              OK C:\mysymbols
              Deferred SRV*C:\symbols*http://msdl.microsoft.com/download/symbols

              先通过.sympath
              设置符号文件的目录。可以将testforme.pdb
              存放到设置好的符号目录C:\mysymbols
              SRV*C:\symbols*http://msdl.microsoft.com/download/symbols
              明将从http://msdl.microsoft.com/download/symbols
              下载微软的PE文件对应的符号文件,并且缓存到C:\symbols
              目录下。 你也可以通过File->Symbol Search Path
              查看符号目录设置(如下图),当然也可以在这里直接对符号目录进行设置。

              接着调用.reload
              命令重新加载模块的符号信息,然后调用kv
              就可以查看函数异常的函数调用栈了!

                0:000:x86> .reload
                Reloading current modules
                ........
                0:000:x86> kv
                ChildEBP RetAddr Args to Child
                0018fee8 00401085 0040c028 0040b344 00000000 testforme!_output_l+0x7f4 (FPO: [Non-Fpo]) (CONV: cdecl) [f:\sp\vctools\crt_bld\self_x86\crt\src\output.c @ 1648]
                0018ff2c 0040100c 0040b344 0000000a 004012aa testforme!printf+0x73 (FPO: [Non-Fpo]) (CONV: cdecl) [f:\sp\vctools\crt_bld\self_x86\crt\src\printf.c @ 63]
                0018ff88 7700336a 7efde000 0018ffd4 77909f72 testforme!main+0xc (FPO: [0,0,0]) (CONV: cdecl) [d:\vsproject\testforme\testforme\test.cpp @ 11]
                0018ff94 77909f72 7efde000 3cf2f376 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
                0018ffd4 77909f45 00401301 7efde000 ffffffff ntdll32!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
                0018ffec 00000000 00401301 7efde000 00000000 ntdll32!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

                通过函数调用栈,可以清楚的看出异常发生在main
                函数中,在test.cpp
                11
                行处调用了printf
                。熟悉Windows函数栈的同学应该比较清楚,ChildBEP
                在32位程序中表示当前调用栈的栈底指针,并且指向的内存处保存的是上一个栈帧的栈底位置。 Args to Child
                表示当前函数的参数,比如这里printf
                的参数分别为0040b344
                0000000a
                0000000a
                表示测试程序中变量pStr
                的值,而0040b344
                则表示格式化字符串,我们可以使用da
                命令来查看一下:

                  0:000:x86> da 0040b344
                  0040b344 "%s."

                  当然有时候你需要切换到特定的栈(frame),去查看当前frame下的上下文信息,比如局部变量。例如我们现在想去看看main函数下的变量的值,先用kn
                  命令查看栈的编号,然后用.frame
                  切换到main
                  函数那一帧接着用dv -V
                  查看所有的局部变量:

                    0:000:x86> kn
                    # ChildEBP RetAddr
                    00 0018fee8 00401085 testforme!_output_l+0x7f4 [f:\sp\vctools\crt_bld\self_x86\crt\src\output.c @ 1648]
                    01 0018ff2c 0040100c testforme!printf+0x73 [f:\sp\vctools\crt_bld\self_x86\crt\src\printf.c @ 63]
                    02 0018ff88 7700336a testforme!main+0xc [d:\vsproject\testforme\testforme\test.cpp @ 11]
                    03 0018ff94 77909f72 kernel32!BaseThreadInitThunk+0xe
                    04 0018ffd4 77909f45 ntdll32!__RtlUserThreadStart+0x70
                    05 0018ffec 00000000 ntdll32!_RtlUserThreadStart+0x1b
                    0:000:x86> .frame 2
                    02 0018ff88 7700336a testforme!main+0xc [d:\vsproject\testforme\testforme\test.cpp @ 11]
                    0:000:x86> dv V


                    奇怪了为啥dv /V并没有打印出临时变量pStr
                    呢?会不会是因为被优化了?于是就用uf
                    命令反汇编了一下main
                    函数 (如下), 可以看到printf
                    的参数压栈直接用的是常量0A

                      0:000:x86> uf main
                      testforme!main [d:\vsproject\testforme\testforme\test.cpp @ 9]:
                      9 00401000 6a0a push 0Ah
                      11 00401002 6844b34000 push offset testforme!`string' (0040b344)
                      11 00401007 e806000000 call testforme!printf (00401012)
                      11 0040100c 83c408 add esp,8
                      12 0040100f 33c0 xor eax,eax
                      13 00401011 c3 ret

                      所以在调试优化后的程序要注意这些可能的变化,当然如果你想让Release的程序不进行优化,可以在Visual Studio中关闭这个选项,如下图:

                      这里我就不再展示关闭优化后,用windbg调试打印局部变量了,大家可以自己试一试。

                      源码调试

                      习惯于VS调试的同学,可能会觉得Windbg命令调试难记难用(事实上,当你熟悉了之后可能会改变看法)。虽然没有VS的强大的源码调试功能,其实Windbg也提供源码调试的功能。可以通过.srcpath
                      命令或者通过菜单File->Source Search Path
                      去设置源码的目录。

                      现在我将测试源码拷贝到C:\source
                      目录,然后在用Windbg 程序后,设置断点到测试程序的main
                      函数入口处,然后继续执行程序:

                        0:000> bp testforme!main
                        0:000> g

                        这时候你会看到源码文件自动弹出来了,并且中断在main
                        入口处:

                        Windbg 还提供直接打开Source文件的功能File->Open Source File...
                        ,在调试前打开源码文件,可以直接在里面设置断点,调试的快捷键和Visual Studio一样!

                        Windbg工作空间

                        Windbg的工作空间主要表示调试会话的状态、调试器的设置以及窗口布局的设置等。工作空间的使用主要分为以下几点:

                        • 未加载任何调试文件的时候,选择File -> Save Workspace
                          保存默认工作空间,则当每次打开Windbg的时候,将采用这个默认的工作空间

                        • 当调试器已经加载了调试文件的时候,选择File -> Save Workspace
                          将当前工作空间保存为默认工作空间 (这个默认空间仅针对这个调试文件), 则当下次还是调试这个文件的时候,则采用之前保存的默认工作空间。

                        • 可以选择File -> Save Workspace As...
                          保存为命名的工作空间,在以后调试应用程序的时候可以选择File -> Open Workspace
                          去打开指定的工作空间

                        • 以上的工作空间都保存在注册表项HKEY_CURRENT_USER\Software\Microsoft\Windbg\Workspaces
                          里面,你也可以通过File -> Save Worksapce to File...
                          将工作空间保存到文件

                        还有删除工作空间等操作,大家可以自己去熟悉一下。

                        Windbg的命令分类

                        Windbg主要分为3大类的调试命令:

                        • 标准命令 (Standard Command): 这类命令对于所有的调试目标都适用,比如常见的k
                          命令;

                        • 元命令 (Meta-Command):这类目标主要针对特定的目标所做的扩展命令,比如常见的.sympath
                          命令。因为这类命令前面都有一个.
                          ,所以也叫作Dot-Command

                        • 扩展命令 (Extension Command):标准命令和元命令都是Windbg内建的命令,而扩展命令是实现在动态加载的DLL中。这类命令前面都有一个!
                          , 比如常用!analyze -v
                          .

                        顺便这里提一个很实用的命令.hh
                        ,用来在Windbg中打开帮助文档,比如使用.hh k
                        则帮助文档会打开到索引k
                        命令处。


                        文章转载自一个程序员的修炼之路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                        评论