调试本机代码常见问题

在 Visual Studio 调试器外部运行程序时如何调试访问冲突?

设置实时调试选项并运行独立程序,直到发生访问冲突。 然后,在”访问冲突”对话框中,你可以单击”取消”以启动调试器 。

如何调试 C++ 访问冲突?

如果在取消引用多个指针的代码行上出现访问冲突,则可能很难辨别引起访问冲突的指针。 在 Visual Studio 中,异常对话框显式命名导致访问冲突的指针。

例如,以下代码中,将出现访问冲突:

#include <iostream>
using namespace std;

class ClassC {
public:
  void printHello() {
    cout << "hello world";
  }
};

class ClassB {
public:
  ClassC* C;
  ClassB() {
    C = new ClassC();
  }
};

class ClassA {
public:
  ClassB* B;
  ClassA() {
    // Uncomment to fix
    // B = new ClassB();
  }
};

int main() {
  ClassA* A = new ClassA();
  A->B->C->printHello();

}

如果在 Visual Studio 中运行此代码,你将看到以下异常对话框:

Screenshot of a Microsoft Visual Studio exception dialog, showing a read access violation for 'A->B was nullptr'. The Break button is selected.

如果无法确定该指针为何导致访问冲突,请对代码进行跟踪,以确保导致该问题的指针被正确处理。 如果它作为参数传递,请确保正确传递且未意外创建浅表副本。 然后为有问题的指针创建一个数据断点,以确保它没有在程序的其他地方被修改,从而验证这些值没有在程序的某个地方被无意地更改。 有关数据断点的详细信息,请参阅 使用断点中的数据断点部分。

如何查明指针是否损坏了内存地址?

检查堆损坏。 大多数内存损坏是由堆损坏引起的。 尝试使用 Global Flags Utility (gflags.exe) 或 pageheap.exe。 请参阅 /windows-hardware/drivers/debugger/gflags-and-pageheap

若要查找修改内存地址的位置:

  1. 在 0x00408000 处设置一个数据断点。 请参阅设置数据更改断点(仅限本机 C++)

  2. 当命中断点时,使用“内存”窗口,以查看从 0x00408000 开始的内存内容。 有关详细信息,请参阅内存窗口

如何查明谁在传递错误的参数值?

若要解决此问题,请执行以下操作:

  1. 在函数的开始处设置一个位置断点。

  2. 右键单击该断点并选择“条件”。

  3. 在“断点条件”对话框中,单击“条件”复选框 。 请参阅高级断点

  4. 在文本框中输入一个表达式,例如 Var==3,此处 Var 是包含错误值的参数名称,3 是传给此参数的错误值。

  5. 选择“为真”单选按钮,单击“确定”按钮 。

  6. 现在再次运行程序。 当 Var 参数的值为 3 时,断点导致程序在函数开始处暂停。

  7. 然后可以使用“调用堆栈”窗口查找调用函数并定位到其源代码。 有关详细信息,请参阅如何:使用“调用堆栈”窗口

当一个函数被调用数百次时,如何确定哪次调用失败了?

示例:程序在调用某函数(如 CnvtV)时失败。 失败以前该程序可能已调用了该函数数百次。 如果我在 CnvtV 上设置一个位置断点,程序在每次调用该函数时都停止,而我不希望这样。 我不知道什么条件导致调用失败,所以无法设置条件断点。 我该怎么办?

在函数上设置断点时可以将“命中次数”字段设置为一个大到永远无法达到的值。 在这种情况中,由于你确信函数 CnvtV 被调用了数百次,所以将“命中次数”设置为 1000 或更高。 然后运行程序,等待调用失败。 程序失败后,打开“断点”窗口并查看断点列表。 将显示您在 CnvtV 上设置的断点,其后跟着命中次数和剩余迭代次数:

CnvtV(int) (no condition) when hit count is equal to 1000 (currently 101)

现在您知道函数在第 101 次调用时失败。 如果重置断点,将命中次数设置为 101,然后再次运行程序,程序将在导致 CnvtV 调用失败的位置停止所有该调用。

可以在哪里查阅 Win32 错误代码?

默认系统安装的 INCLUDE 目录中的 WINERROR.H 包含 Win32 API 函数的错误代码定义。

可以通过在“监视”窗口或“快速监视”对话框中键入错误代码来查阅该代码 。 例如:

0x80000004,hr

如何在逐句执行应用代码时保持焦点?

示例:我的程序存在窗口激活问题。 用调试器逐句通过程序时,因为程序不断失去焦点,所以妨碍了再现问题。 是否有方法可以避免失去焦点?

如果有另一台计算机,请使用远程调试。 可以在远程计算机上运行您的程序,而在主机上运行调试器。 有关详细信息,请参阅如何:选择远程计算机

如何调试 Windows API 函数?

若要在加载了 NT 符号的 Windows API 函数中设置断点:

  • 函数断点中,输入函数名以及函数所在 DLL 的名称(请参阅上下文运算符)。 在 32 位代码中,使用函数名的修饰形式。 例如,若要给”MessageBeep” 设置断点,你必须输入如下内容。

    {,,USER32.DLL}_MessageBeep@4
    

    若要获取修饰名,请参阅查看修饰名

    可以测试修饰名称并在反汇编代码中查看它。 在 Visual Studio 调试器的函数中暂停时,在“代码编辑器”或“调用堆栈”窗口中右键单击该函数,然后选择“转到反汇编”。

  • 在 64 位代码中,可以使用未修饰名。

    {,,USER32.DLL}MessageBeep
    

后续步骤

可以使用以下链接详细了解 Visual Studio 中的本机代码调试: