• Windbg教程调试非托管程序的基本命令中


    前面的文章调试非托管程序的基本命令上讲到如何在windbg里面启动一个程序并且加载调试符号文件。一旦符号文件加载完毕以后,就可以进行调试了,例如设置断点,查看堆栈信息等等。

     

    因为是刚刚启动程序(main函数还没有机会执行),可以查看源代码了解要设置断点的地方。设置断点可以使用bpbubm来做,其中bp可以根据函数名、指令地址以及源代码文件地址来设置断点。

     

    bp命令是在设置断点过程用的比较多的一个命令,下面的表格演示了它的简单用法:

    命令格式

    示例

    说明

    bp 函数名

    bp Usage

    在函数Usage的入口中断程序的执行。

    bp 指令地址

    bp 010113c0

    在执行地址在010113c0的指令前中断程序的执行。

    bp `源文件地址`

    bp `nativedebug.cpp:21`

    在源代码nativedebug.cpp的第21行设置断点,请注意符号“`”(感叹号键左边的反引号)。

     

     

                                                                                                                           

    如果你有源代码的话,通过windbg的菜单“FileOpen Source File”打开源文件,找到相应的代码行,按下键盘的F9就可以设置断点了(当然前提条件是你已经设置好正确的符号文件,符号文件请参考文章Visual Studio调试之符号文件)。

    看起来好像没有什么特别的,只不过是设置断点的方法比Visual studio复杂一些罢了,不过在windbg中,bp等命令允许在触发断点的时候执行一系列的调试命令。例如中断程序后,打印堆栈,保存内存文件然后退出,或者执行一个小的调试命令脚本程序等等,这个过程与visual studio里面的跟踪断点(Trace Point)非常相像,当然操作起来稍微复杂一些(visual studio的跟踪断点的用法请参考文章Visual Studio调试之断点技巧篇)。 windbg中设置触发断点执行其他命令的方法会在后续的文章里面讲到。

     

    例如在调试本文的示例程序(示例程序在文章调试非托管程序的基本命令上里面),可以执行以下的命令:

    bp Usage

    #

    # 没有输出结果,正所谓没有消息就是好消息,如果断点成功设置,

    # windbg不会显示任何信息。

    #

     

    如果在设置断点时,出现类似下面的消息:

     

    bp UsageA

    #

    # 输出结果

    #

    Bp expression 'UsageA' could not be resolved, adding deferred bp

     

    那么有两个检查步骤,第一是检查符号文件是否正确加载,第二步是检查设置断点的函数名是否真的存在于程序当中。

     

    第一步,检查符号文件是否正确加载,可以使用lm命令查看已加载模块的详细信息,例如在上面的例子中,我们相信UsageA命令应该在模块nativedebug.exe中,可以执行下面的命令来查看nativedebug模块的详细信息(请注意模块名是紧跟在vm选项后面的,没有空格,没有后缀名,也没有蛀牙):

     

    lm vmnativedebug

    #

    # 输出结果

    #

    start    end        module name

    # 注意下面这一行里面的private pdb symbols,说明我们已经加载了正确的符号文件。

    # 至于private的含义,会在以后的文章里面讲到。

    01000000 0101b000   nativedebug C (private pdb symbols) D:\Debuggers\sym\nativedebug.pdb\E873A517513C4CC9BA5C805D1A709F206\nativedebug.pdb

        Loaded symbol image file: nativedebug.exe

    # Image path指明了模块加载的路径,在64位机器上调试程序的时候,

    #这个信息是蛮有用的 。因为你需要知道一些系统模块是在system32还是

    # SysWow64文件夹里加载的。

        Image path: nativedebug.exe

        Image name: nativedebug.exe

        Timestamp:        Sat Feb 20 20:05:20 2010 (4B7FD000)

        CheckSum:         00000000

        ImageSize:        0001B000

        Translations:     0000.04b0 0000.04e4 0409.04b0 0409.04e4

     

    顺便说一下,因为nativedebug是我们自己编译的,有一些版本方面的信息在编译的时候没有加进去。如果你查看一个Windows自带的模块的详细信息的话,你可能会看到类似下面的输出:

     

    lm vmntdll

    #

    # 输出结果

    #

    start    end        module name

    775f0000 7772c000   ntdll      (pdb symbols)          D:\Debuggers\sym\ntdll.pdb\F0164DA71FAF4765B8F3DB4F2D7650EA2\ntdll.pdb

        Loaded symbol image file: ntdll.dll

        Image path: ntdll.dll

        Image name: ntdll.dll

        Timestamp:        Tue Jul 14 09:09:47 2009 (4A5BDADB)

        CheckSum:         0014033F

    ImageSize:        0013C000

    # 模块的版本号,如果你的程序象微软的产品那样有多个版本,而且需要对多个

    # 版本提供技术支持的话,下面的信息对于找到正确版本的符号文件非常非常非常

    # 重要。

        File version:     6.1.7600.16385

        Product version: 6.1.7600.16385

    File flags:       0 (Mask 3F)

    # 模块要求的子系统

        File OS:          40004 NT Win32

        File type:        2.0 Dll

        File date:        00000000.00000000

        Translations:     0409.04b0

        CompanyName:      Microsoft Corporation

        ProductName:      Microsoft® Windows® Operating System

        InternalName:     ntdll.dll

        OriginalFilename: ntdll.dll

    ProductVersion:   6.1.7600.16385

    # 下面只显示了已发布的产品的信息,版本号已经在前面的注释里介绍过了。

    # win7_rtm的意思是当前的模块是从win7_rtm这个源代码分支里编译出来的。

    # 版本分支的概念在团队软件产品开发过程中是一个平常的做法,大部分版本

    # 控制软件都支持代码分支的做法。这个过程解释起来有点复杂,现在你需要

    # 知道的是,如果你现在工作的公司没有采取版本分支的做法,那么祝贺你,

    # 至少在寻找符号文件的过程里,你会比较轻松(不需要考虑分支的影响),

    # 虽然会在后面发布高质量的软件产品你的团队会死的比较难看。

    # 如果你工作的公司正在采取版本分支的做法的话,那么你一定要在正确的分支

    # 下寻找对应版本的符号文件,否则你会死的很难看。

    #

    # 另外,下面一行的输出里还有一个重要的信息没有显示,那就是模块是否为调试版

    # ,还是发布版。与软件分支一样,如果考虑进去,也是一样无法加载到正确的

    # 符号文件的。

    #

    # 如果使用类似微软的方法编译软件,会在后面的文章中讲到。

        FileVersion:      6.1.7600.16385 (win7_rtm.090713-1255)

        FileDescription: NT Layer DLL

    # 这个嘛,地球人都知道。

        LegalCopyright:   © Microsoft Corporation. All rights reserved.

     

    既然知道符号文件已经被正确加载,那么下一步就是确认设置的函数名是否存在于模块中,可以使用x命令来检查符号文件保存的名字信息就是函数名呀,全局变量名之类的信息。如果直接调用x命令,windbg会显示模块里面所有的名字。一般都是使用x加上一个匹配模式来查找指定的名字在模块中是否已定义。比如,为了检查UsageA这个名字在nativedebug.exe模块中是否已定义,可以执行下面的命令来查看(感叹号前面是告诉x命令要在哪一个模块中查找名字,感叹号后面就是要查找的名字):

     

    x nativedebug!UsageA

    #

    # 输出结果没有输出结果

    #

     

    如果x没有找到指定的名字,就不会输出任何信息,否则,会有类似下面的输出:

     

    x nativedebug!Usage

    #

    # 输出结果,前面的地址是函数入口在内存中的地址,而后面则显示了函数的声明信息。

    #

    010113c0 nativedebug!Usage (void)

     

    X命令允许你在查找过程中使用通配符进行匹配,例如,在我们的示例程序中,被用来执行转换的“函数”_ttol不是一个真实的函数,而是一个宏。下面是这个宏的定义:

     

    #ifdef _UNICODE

    #   define _ttol       _wtol

    #else

    #   define _ttol       atol

    #endif

     

    而宏是在编译期间就被编译器扩展,并不会被加到符号文件中去,因此如果你试图使用bp命令在_ttol入口设置断点的话,是会失败的。因此你可以使用类似下面的通配符来查找正确的函数名:

    x MSVCR90D!*tol*

    #

    # 输出结果(注意黄色高亮的名字)

    #

    65cd1bb0 MSVCR90D!__STRINGTOLD (struct _LDOUBLE *, char **, char *, int)

    65d47c80 MSVCR90D!_ld12told (struct _LDBL12 *, struct _LDOUBLE *)

    65cd1900 MSVCR90D!_atoldbl (struct _LDOUBLE *, char *)

    65cd6790 MSVCR90D!_wcstol_l (wchar_t *, wchar_t **, int, struct localeinfo_struct *)

    65cd4400 MSVCR90D!strtol (char *, char **, int)

    65d4bac0 MSVCR90D!__mtold12 (char *, unsigned int, struct _LDBL12 *)

    65cd5030 MSVCR90D!_tolower_l (int, struct localeinfo_struct *)

    65cd6300 MSVCR90D!wcstol (wchar_t *, wchar_t **, int)

    65ca0d50 MSVCR90D!atol (char *)

    65cd4940 MSVCR90D!_strtol_l (char *, char **, int, struct localeinfo_struct *)

    65ca12d0 MSVCR90D!_wtol (wchar_t *)

    65d4a980 MSVCR90D!__wstrgtold12_l (struct _LDBL12 *, wchar_t **, wchar_t *, int, int, int, int, struct localeinfo_struct *)

    65d544e0 MSVCR90D!_ftol (void)

    65cd5010 MSVCR90D!_tolower (int)

    65cd5210 MSVCR90D!tolower (int)

    65ca12f0 MSVCR90D!_wtol_l (wchar_t *, struct localeinfo_struct *)

    65d48fd0 MSVCR90D!__dtold (struct _LDOUBLE *, double *)

    65ce3c30 MSVCR90D!_mbctolower_l (unsigned int, struct localeinfo_struct *)

    65d48dc0 MSVCR90D!__STRINGTOLD_L (struct _LDOUBLE *, char **, char *, int, struct localeinfo_struct *)

    65cd17f0 MSVCR90D!_atoldbl_l (struct _LDOUBLE *, char *, struct localeinfo_struct *)

    65ce3d80 MSVCR90D!_mbctolower (unsigned int)

    65ca0d70 MSVCR90D!_atol_l (char *, struct localeinfo_struct *)

    65d38670 MSVCR90D!__lc_strtolc (struct tagLC_STRINGS *, char *)

    65cdd8e0 MSVCR90D!CPtoLCID (int)

    65d47d40 MSVCR90D!__strgtold12_l (struct _LDBL12 *, char **, char *, int, int, int, int, struct localeinfo_struct *)

    65c6109c MSVCR90D!_imp__FileTimeToLocalFileTime = <no type information>

     

    在上面的输出,可以看到atol_wtolmsvcr90d.dll这个模块中都定义了,而我们现在不是很确定当时程序编译的时候,_UNICODE这个宏是否被定义了。因此我们即可以采用一个笨方法,就是使用bp命令在atol_wtol两个函数入口上都设置断点,运行看看到底程序会中断在哪一个函数上。

     

    或者,可以使用bm命令,bm命令相当于bp命令的扩展,允许用户使用一个通配符设置断点。例如:

     

    bm *tol*

     

    #

    # 输出结果 – Windbg会在所有匹配的函数入口上设置断点。

    # 很多,的确很多,因此请慎用bm命令。

    #

     4: 65cd1bb0 @!"MSVCR90D!__STRINGTOLD"

     5: 65d47c80 @!"MSVCR90D!_ld12told"

     6: 65cd1900 @!"MSVCR90D!_atoldbl"

     7: 65cd6790 @!"MSVCR90D!_wcstol_l"

     8: 65cd4400 @!"MSVCR90D!strtol"

     9: 65d4bac0 @!"MSVCR90D!__mtold12"

     10: 65cd5030 @!"MSVCR90D!_tolower_l"

     11: 65cd6300 @!"MSVCR90D!wcstol"

     12: 65ca0d50 @!"MSVCR90D!atol"

     13: 65cd4940 @!"MSVCR90D!_strtol_l"

     14: 65ca12d0 @!"MSVCR90D!_wtol"

     15: 65d4a980 @!"MSVCR90D!__wstrgtold12_l"

     16: 65d544e0 @!"MSVCR90D!_ftol"

     17: 65cd5010 @!"MSVCR90D!_tolower"

     18: 65cd5210 @!"MSVCR90D!tolower"

     19: 65ca12f0 @!"MSVCR90D!_wtol_l"

     20: 65d48fd0 @!"MSVCR90D!__dtold"

     21: 65ce3c30 @!"MSVCR90D!_mbctolower_l"

     22: 65d48dc0 @!"MSVCR90D!__STRINGTOLD_L"

     23: 65cd17f0 @!"MSVCR90D!_atoldbl_l"

     24: 65ce3d80 @!"MSVCR90D!_mbctolower"

     25: 65ca0d70 @!"MSVCR90D!_atol_l"

     26: 65d38670 @!"MSVCR90D!__lc_strtolc"

     27: 65cdd8e0 @!"MSVCR90D!CPtoLCID"

     28: 65d47d40 @!"MSVCR90D!__strgtold12_l"

     

    设置好断点后,可以使用bl命令(breakpoint list)来查看已经设置好的断点:

     

    bl

    #

    # 输出结果

    # 第一列是断点的编号;

    # 第二列,e表示(enabled),u表示(unresolved),因此如果那一列的值为e,则说明

    # 断点是启用状态,如果为d表示(disabled),则表示禁用状态。如果有u,则基本上

    # 说明这个断点是没有设置成功的,虽然windbg会在后续加载每一个模块的时候,都尝试

    # 根据那个名字设置断点;

    # 后面几列,放在后面的文章讲。

    #

     0 e 010113c0     0001 (0001) 0:**** nativedebug!Usage

     1 eu             0001 (0001) (UsageA)

    # 这个断点没有设置正确

     2 eu             0001 (0001) (`22`)

     4 e 65cd1bb0     0001 (0001) 0:**** MSVCR90D!__STRINGTOLD

     5 e 65d47c80     0001 (0001) 0:**** MSVCR90D!_ld12told

     6 e 65cd1900     0001 (0001) 0:**** MSVCR90D!_atoldbl

     7 e 65cd6790     0001 (0001) 0:**** MSVCR90D!_wcstol_l

     8 e 65cd4400     0001 (0001) 0:**** MSVCR90D!strtol

     9 e 65d4bac0     0001 (0001) 0:**** MSVCR90D!__mtold12

    10 e 65cd5030     0001 (0001) 0:**** MSVCR90D!_tolower_l

    11 e 65cd6300     0001 (0001) 0:**** MSVCR90D!wcstol

    12 e 65ca0d50     0001 (0001) 0:**** MSVCR90D!atol

    13 e 65cd4940     0001 (0001) 0:**** MSVCR90D!_strtol_l

    14 e 65ca12d0     0001 (0001) 0:**** MSVCR90D!_wtol

    15 e 65d4a980     0001 (0001) 0:**** MSVCR90D!__wstrgtold12_l

    16 e 65d544e0     0001 (0001) 0:**** MSVCR90D!_ftol

    17 e 65cd5010     0001 (0001) 0:**** MSVCR90D!_tolower

    18 e 65cd5210     0001 (0001) 0:**** MSVCR90D!tolower

    19 e 65ca12f0     0001 (0001) 0:**** MSVCR90D!_wtol_l

    20 e 65d48fd0     0001 (0001) 0:**** MSVCR90D!__dtold

    21 e 65ce3c30     0001 (0001) 0:**** MSVCR90D!_mbctolower_l

    22 e 65d48dc0     0001 (0001) 0:**** MSVCR90D!__STRINGTOLD_L

    23 e 65cd17f0     0001 (0001) 0:**** MSVCR90D!_atoldbl_l

    24 e 65ce3d80     0001 (0001) 0:**** MSVCR90D!_mbctolower

    25 e 65ca0d70     0001 (0001) 0:**** MSVCR90D!_atol_l

    26 e 65d38670     0001 (0001) 0:**** MSVCR90D!__lc_strtolc

    27 e 65cdd8e0     0001 (0001) 0:**** MSVCR90D!CPtoLCID

    28 e 65d47d40     0001 (0001) 0:**** MSVCR90D!__strgtold12_l

     

    在上面的输出中,可以看到断点12是无效的断点,因此可以使用bcbreakpoint clear)这个命令删除掉这两个断点:

     

    bc 1

    #

    # 没有输出结果没有消息就是好消息

    #

    bc 2

    #

    # 没有输出结果没有消息就是好消息

    #

     

    因此在前面的bm命令中,设置了太多的断点,为了避免在不必要的函数上中断,我们既可以使用bc命令将它们删掉,也可以使用bdbreakpoint disabled)命令将其禁用。因为命令实在太多,所以我们可以使用一个小技巧使用一个范围来禁用一批断点:

     

    bd 4-10

    #

    # 没有输出结果没有消息就是好消息,

    # 这个命令将从断点4到断点10的所有断点都禁用了。

    #

     

    bdbc命令的语法是一样的,既可以根据指定的范围禁用或删除一批断点,也可以根据指定的通配符来操作一批断点,还可以使用一种稀奇古怪的语法来操作断点(这个稀奇古怪的语法会在后面的文章中讲到)。

    设置好断点以后,可以继续进程的运行了,断点触发以后,我们才能查看进程的堆栈以及一些变量的数据。这些内容放在下一篇文章调试非托管程序的基本命令下讲解。

  • 相关阅读:
    关于HDFS默认block块大小
    从计算框架MapReduce看Hadoop1.0和2.0的区别
    Linux的作业管理
    php 随机密码和盐 来自wordpress
    SSH管理(重启 停止 运行 安装)centos7
    卸载iptables 小心了!!怎么关闭和卸载iptables
    mysqld: Out of memory 解决办法(mysql)
    ie6 ie7 ie8 ie9兼容问题终极解决方案
    call_user_func()的参数不能为引用传递 自定义替代方法
    centos 域名硬解析(linux)
  • 原文地址:https://www.cnblogs.com/killmyday/p/1675135.html
Copyright © 2020-2023  润新知