• Visual Studio调试本机代码


    本文讲述本机应用程序的一些常见调试问题和调试技术。 本节阐述的技术属于高级别技术。

    调试优化的代码

    当编译器优化代码时,它将重新定位并重组指令, 这会得到更高效的编译的代码。 由于这种调整,调试器并不总能确定与一组指令对应的源代码。

    优化可能影响到:

    • 本地变量(可被优化器移除或移动到调试器无法识别的位置)。

    • 函数内部的位置(当优化器合并代码块时发生变化的位置)。

    • 调用堆栈上框架的函数名称(如果优化器合并两个函数,则函数名称可能是错误的)。

    但是,假定所有框架都有符号,则在调用堆栈上看到的框架几乎总是正确的。 在下列情况下调用堆栈上的框架将是错误的:有堆栈损坏,有用汇编语言编写的函数,或者有操作系统框架在调用堆栈上没有匹配的符号。全局和静态变量总是正确显示。 结构布局也是这样。 如果您有指向结构的指针而且指针的值是正确的,那么结构的每个成员变量都将显示正确值。出于这些限制原因,只要有可能,就应使用程序的“未优化”版本进行调试。 默认情况下,优化在 Visual C++ 程序的“Debug”配置中关闭,在“Release”配置中打开。

    但是,bug 可能仅在程序的优化版本中出现。 在此情况下,必须调试优化的代码。

    在“Debug”生成配置中打开优化

    1. 创建新项目时,请选择 Win32 Debug目标。 接下来要一直使用 Win32 Debug目标,直至程序已进行全面调试,可以生成 Win32 Release目标为止。 调试器并不优化 Win32 Debug目标。

    2. 在解决方案资源管理器中选择项目。

    3. 在“视图”菜单上,单击“属性页”。

    4. 在“属性页”对话框中,确保在“配置”下拉列表框中选择了 Debug。

    5. 在左边的文件夹视图中,选择 C/C++ 文件夹。

    6. 在“C++”文件夹下选择 Optimization。

    7. 在右边的属性列表中找到“Optimization”。 它旁边的设置可能显示为“Disabled (/Od)”。 选择其他选项(“Minimum Size (/O1)”、“Maximum Speed (/O2)”、“Full Optimization (/Ox)”或“Custom”)之一。

    8. 如果为“Optimization”选择了“Custom”选项,现在便可为属性列表中显示的其他任何属性设置选项。

    调试优化的代码时,请使用“反汇编”窗口以了解实际创建和执行了哪些指令。 设置断点时,需要注意断点可能随指令一起移动。 例如,考虑以下代码:

    for (x=0; x<10; x++)
    

    假定在该行设置了一个断点。 可能希望该断点被命中 10 次,但如果代码进行了优化,则只会命中该断点一次。 因为第一个指令将 x 的值设置为 0。 编译器认定该指令只需执行一次,将其移出循环。 断点随之移动。 而比较和递增 x 的指令仍留在循环内。 当查看“反汇编”窗口时,单步执行单元自动设置为“指令”以允许更大控制,这在逐句通过优化的代码时很有用。

    DebugBreak 和 __debugbreak

    可以在代码中的任意点调用 DebugBreak Win32 函数或 __debugbreak 内部类型。 DebugBreak 和 __debugbreak 具有与在该位置设置断点相同的效果。因为 DebugBreak 是系统函数调用,所以必须安装系统调试符号以确保中断后显示正确的调用堆栈信息。 否则,调试器可能在显示一帧调用堆栈信息后就停止显示。 如果使用 __debugbreak,则不需要符号。

    断言

    断言语句指定在程序的某些特定点应为真的条件。 如果该条件不为真,则断言失败,中断程序的执行,并显示“断言失败”对话框。

    Visual C++ 支持基于下列构造的断言语句:

    • MFC 程序的 MFC 断言。

    • 使用 ATL 的程序的 ATLASSERT。

    • 使用 C 运行库的程序的 CRT 断言。

    • 其他 C/C++ 程序的 ANSI assert 函数。

    断言可以用于:

    • 捕捉逻辑错误。

    • 检查某操作的结果。

    • 测试错误条件,这些错误条件应已处理。

    MFC 和 C 运行库断言

    当调试器由于 MFC 或 C 运行库断言而暂停时,它定位到源文件中的断言发生点(如果源可用)。 断言消息显示在“输出”窗口以及“断言失败”对话框中。 如果希望保存断言消息以供将来参考,可以将断言消息从“输出”窗口复制到某个文本窗口。 “输出”窗口可能还包含其他错误信息。 请仔细检查这些消息,因为它们提供了有关确定断言失败原因的线索。

    通过在代码中大量使用断言,可以在开发期间捕捉许多错误。 为所做的每个假定编写一个断言是很好的规则。 例如,如果假定某个参数不为 NULL,请使用一条断言语句检查该假定。

    _DEBUG

    仅当定义了 _DEBUG 时断言语句才编译。 未定义 _DEBUG 时,编译器将断言作为空语句处理。 因此,断言语句在最终发布程序中系统开销为零;可以在代码中大量使用断言语句,而不影响“Release”版本的性能,并且不必使用 #ifdef 指令。

    使用断言的副作用

    当向代码添加断言时,请确保这些断言没有副作用。 例如,考虑以下断言:

    ASSERT(nM++ > 0); // Don't do this!
    

    因为在程序的“Release”版本中不计算 ASSERT 表达式,所以 nM 在“Debug”版本和“Release”版本中会有不同值。 在 MFC 中,可以使用 VERIFY 宏代替 ASSERT。 在“Release”版本中,VERIFY 计算该表达式,但不检查结果。

    在断言语句中使用函数调用时应特别小心,因为计算函数可能会有意外的副作用。

    ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
    VERIFY ( myFnctn(0)==1 ) // safe
    

    VERIFY 在“Debug”版本和“Release”版本中都调用 myFnctn,因此可以使用它。 但在“Release”版本中仍会有因不必要的函数调用而产生的系统开销。

    调试内联汇编代码

    调试器提供了两个用于调试内联程序集代码的窗口,即“反汇编”窗口和“寄存器”窗口。

    调试内联程序集代码

    1. 使用“反汇编”窗口查看程序集指令。

       

    2. 使用“寄存器”窗口查看寄存器内容。

    MFC 调试技术

    AfxDebugBreak

    MFC 提供特殊的 AfxDebugBreak 函数,以供在源代码中对断点进行硬编码:

    AfxDebugBreak( );
    

    在 Intel 平台上,AfxDebugBreak 将生成以下代码,它在源代码而不是内核代码中中断:

    _asm int 3
    

    在其他平台上,AfxDebugBreak 仅调用 DebugBreak。

    确保在创建发布版本时移除 AfxDebugBreak 语句,或使用 #ifdef _DEBUG 环绕这些语句。

    TRACE 宏

    若要在调试器的“输出”窗口中显示来自程序的消息,可以使用 ATLTRACE 宏或 MFC TRACE 宏。 与断言类似,跟踪宏只在程序的“Debug”版本中起作用,在“Release”版本中编译时将消失。

    下面的示例显示几种 TRACE 宏的用法。 与 printf 类似,TRACE 宏可处理许多参数。

    int x = 1;
    int y = 16;
    float z = 32.0;
    TRACE( "This is a TRACE statement
    " );
    
    TRACE( "The value of x is %d
    ", x );
    
    TRACE( "x = %d and y = %d
    ", x, y );
    
    TRACE( "x = %d and y = %x and z = %f
    ", x, y, z );
    

    TRACE 宏可正确处理 char* 参数和 wchar_t* 参数。 下面的示例说明如何将 TRACE 宏与不同字符串参数类型配合使用。

    TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d
    ", "The number is:", 2);
    
    TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d
    ", L"The number is:", 2);
    
    TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d
    "), _T("The number is:"), 2);
    

    CRT 调试技术

    使用CRT 调试库

    C 运行库提供广泛的调试支持。 若要使用 CRT 调试库之一,必须链接 /DEBUG,并用 /MDd、/MTd 或 /LDd 编译。CRT 调试的主要定义和宏可在 CRTDBG.h 头文件中找到。

    CRT 调试库中的函数编译时带有调试信息(/Z7、/Zd、/Zi、/ZI(调试信息格式)),不进行优化。 某些函数包含断言以验证传递给它们的参数,并且提供源代码。 使用此类源代码,可以单步执行 CRT 函数,以确认这些函数按预期方式工作并检查错误的参数或内存状态。 (某些 CRT 技术是专有技术,不提供用于异常处理、浮点和少数其他例程的源代码。)

    安装 Visual C++ 时,可以选择在硬盘上安装 C 运行库源代码。 如果不安装源代码,将需要 CD-ROM 才能单步执行 CRT 函数。

    用于报告的宏

    可以使用在 CRTDBG.H 中定义的 _RPTn 和 _RPTFn 宏替换 printf 语句进行调试。 未定义 _DEBUG 时,这些宏在发布版本中自动消失,因此不必将它们括在 #ifdef 内。

    表 2

    函数

    _RPT0, _RPT1, _RPT2, _RPT3, _RPT4

    向四个参数输出一个消息字符串和零。

    对于从 _RPT1 到 _RPT4,消息字符串作为参数的 printf 样式的格式化字符串。

    _RPTF0、_RPTF1、_RPTF2、_RPTF4

    与 _RPTn 相同,但这些宏还输出其所在的文件名和行号。

    请看下面的示例:

    #ifdef _DEBUG
        if ( someVar > MAX_SOMEVAR )
            printf( "OVERFLOW! In NameOfThisFunc( ),
                   someVar=%d, otherVar=%d.
    ",
                   someVar, otherVar );
    #endif
    

    该代码将 someVar 和 otherVar 的值输出到 stdout。 可以使用以下对 _RPTF2 的调用报告同样的值另加文件名和行号:

    if (someVar > MAX_SOMEVAR) _RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d
    ", someVar, otherVar );
    

    如果发现某特定应用程序需要调试报告,而 C 运行库提供的宏不提供该报告,则可以编写专门设计的宏来符合您自己的要求。 例如,可以在其中一个头文件中包含以下代码来定义名为 ALERT_IF2 的宏:

     
    #ifndef _DEBUG                  /* For RELEASE builds */
    #define  ALERT_IF2(expr, msg, arg1, arg2)  do {} while (0)
    #else                           /* For DEBUG builds   */
    #define  ALERT_IF2(expr, msg, arg1, arg2) 
        do { 
            if ((expr) && 
                (1 == _CrtDbgReport(_CRT_ERROR, 
                    __FILE__, __LINE__, msg, arg1, arg2))) 
                _CrtDbgBreak( ); 
        } while (0)
    #endif
    

    对 ALERT_IF2 的一个调用可以执行本主题开始处的 printf 代码的所有函数:

    ALERT_IF2(someVar > MAX_SOMEVAR, "OVERFLOW! In NameOfThisFunc( ), 
    someVar=%d, otherVar=%d.
    ", someVar, otherVar );
    

    因为可以方便地更改自定义宏,以便向不同目标报告或多或少的信息(取决于怎样更方便),所以该方法在调试要求不断发展时尤其有用。

    堆分配函数的“Debug”版本

    C 运行库包含堆分配函数的特殊“Debug”版本。 这些函数的名称与发行版本相同,只是追加了“_dbg”。 本主题用 malloc 和 _malloc_dbg 作为示例,描述 CRT 函数的发行版本和 _dbg 版本之间的差异。

    定义 _DEBUG 后,CRT 会将所有 malloc 调用映射到 _malloc_dbg。 因此,不需要用 _malloc_dbg 代替 malloc 来重写代码以获得调试时的好处。

    但您可能希望显式调用 _malloc_dbg。 显式调用 _malloc_dbg 具有一些附加的好处:

    • 跟踪 _CLIENT_BLOCK 类型分配。

    • 存储分配请求所在的源文件和行号。

    如果不希望将 malloc 调用转换为 _malloc_dbg,可以通过定义 _CRTDBG_MAP_ALLOC 来获取源文件信息,而这导致预处理器将对 malloc 的所有调用直接映射到 _malloc_dbg,而不是依赖 malloc 周围的包装。

    若要跟踪客户端块中各种类型的分配,必须直接调用 _malloc_dbg,并将 blockType 参数设置为 _CLIENT_BLOCK。

    未定义 _DEBUG 时,对 malloc 的调用将不受妨碍,并且对 _malloc_dbg 的调用将被解析为 malloc,忽略 _CRTDBG_MAP_ALLOC 的定义,并且不提供与分配请求有关的源文件信息。 因为 malloc 没有块类型参数,所以将对 _CLIENT_BLOCK 类型的请求作为标准分配处理。

    调试本机 DLL

    当调试 DLL 时,可以从以下开始调试:

    • 用于创建调用 DLL 的可执行文件的项目。

    - 或 -

    • 用于创建 DLL 本身的项目。

    如果有用于创建可执行文件的项目,则从该项目开始调试。 然后可以打开 DLL 的源文件,并在该文件中设置断点,即使它不是用于创建可执行文件的项目的一部分。 有关更多信息,请参见断点。

    如果从创建 DLL 的项目开始调试,则必须指定在调试 DLL 时要使用的可执行文件。

    为调试会话指定可执行文件

    1. 在“解决方案资源管理器”中,选择用于创建 DLL 的项目。

    2. 从“视图”菜单中,选定“属性页”。

    3. 在“属性页”对话框中,打开“配置属性”文件夹并选择“调试”类别。

    4. 在“命令”框中,指定用于容器的路径名称。 例如,C:Program FilesMyApplicationMYAPP.EXE。

    5. 在“命令参数”框中,指定用于可执行文件的必要参数。

    如果不在“项目 属性页”对话框中指定可执行文件,则在开始调试时将出现“调试会话的可执行文件”对话框。

    调试注入的代码

    使用Attributes 可以大大简化C++编程。一些Attributes由编译器直接解释。其他Attributes将代码注入程序源代码,然后由编译器编译。这段注入的代码通过减少必须编写的代码量使编程变得更容易。但是,有时错误可能会导致应用程序在执行注入的代码时失败。当这种情况发生时,您可能需要查看注入的代码。Visual Studio提供了两种查看注入代码的方法:

    • 您可以在反汇编窗口中查看注入的代码。
    • 使用/Fx,可以创建包含原始代码和注入代码的合并源文件。

    “反汇编”窗口显示与源代码和属性注入的代码相对应的汇编语言指令。此外,反汇编窗口可以显示源代码注释。

    打开源注释

    在“反汇编”窗口上单击鼠标右键,然后从快捷菜单中选择“显示源代码”。

    如果知道源窗口中属性的位置,可以使用快捷菜单在反汇编窗口中查找注入的代码。

    查看插入的代码

    1. 调试器必须处于中断模式。

    2. 在源代码窗口中,将光标放在要查看其注入代码的特性前面。

    3. 右击并从快捷菜单中选定“转到反汇编”。

      如果特性位置在当前执行点附近,则可以从“调试”菜单选择“反汇编”窗口。

    查看当前执行点处的反汇编代码

      1. 调试器必须处于中断模式。

      2. 从“调试”菜单中选择“窗口”,然后单击“反汇编”。

  • 相关阅读:
    PG
    unzip
    yum
    PG
    SQL
    Grails
    Grails
    Grails
    Chrome
    HTML
  • 原文地址:https://www.cnblogs.com/yilang/p/12467276.html
Copyright © 2020-2023  润新知