9

通常我必须在 Windows 上调试崩溃的 C++ 程序,以便重现崩溃,但很难确定代码中的指令序列导致崩溃(例如,另一个线程覆盖崩溃线程的内存)。在这种情况下,即使是调用堆栈也无济于事。通常我会通过注释掉源代码部分来缩小崩溃原因的范围,但这非常乏味。

有谁知道一个 Windows 工具,它可以报告或重播崩溃前在所有线程中执行的最后几行源代码行或机器代码指令?即类似于gdb 的反向调试功能或类似于Mutek 的BugTrapper 的东西(不再可用)。我正在寻找一个已发布且稳定的工具(我知道 SoftwareVerify 的 'Bug Validator' 和 Hexray 的 IDA Pro 6.3 Trace Replayer,它们都仍处于封闭测试程序中)。

我已经尝试过的是 WinDbg 跟踪命令wtta @$ra,但这两个命令都有缺点,它们会在几秒钟后自动停止。我需要在崩溃发生之前一直运行的跟踪命令,并跟踪正在运行的程序的所有线程。

注意:不是在寻找旨在修复特定问题的调试工具,例如 gflags、pageheap、内存验证器、Purify 等。我正在寻找已发布且稳定的工具来在指令级别进行跟踪或重放。

4

6 回答 6

9

如果您遇到使用gflagsGFlags 和 PageHeapanother thread overwriting memory of the crashing thread )很有用的情况。它不会告诉您崩溃前已执行的几行代码,而是会准确地告诉您算法覆盖正确分配的内存块的位置。

您首先激活此类检查:

gflags /p /enable your_app.exe /full或者
gflags /p /enable your_app.exe /full /backwards

检查您是否已正确激活
gflags /p

运行您的应用程序并收集转储文件

然后使用 gflags 禁用检查:

gflags /p /disable your_app.exe


更新 1

It does not immediately detect problems like *p = 0; where p is an invalid pointer
至少检测到一些问题。
例如:

#include <stdio.h>
int main(int argc, char *argv[])
{
  int *p = new int;
  printf("1) p=%p\n",p);
  *p = 1;
  delete p;
  printf("2) p=%p\n",p);
  *p = 2;
  printf("Done\n");
  return 0;    
}

当我在启用 gflags 的情况下运行时,我得到一个转储文件并且问题被正确识别:

STACK_TEXT:  
0018ff44 00401215 00000001 03e5dfb8 03dfdf48 mem_alloc_3!main+0x5b [c:\src\tests\test.cpp\mem_alloc\mem_alloc\mem_alloc.3.cpp @ 11]
0018ff88 75f8339a 7efde000 0018ffd4 77bb9ef2 mem_alloc_3!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 586]
0018ff94 77bb9ef2 7efde000 2558d82c 00000000 kernel32!BaseThreadInitThunk+0xe
0018ffd4 77bb9ec5 004013bc 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70
0018ffec 00000000 004013bc 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  ~0s; .ecxr ; kb

FAULTING_SOURCE_CODE:  
     7:   printf("1) p=%p\n",p);
     8:   *p = 1;
     9:   delete p;
    10:   printf("2) p=%p\n",p);
>   11:   *p = 2;
    12:   printf("Done\n");
    13:   return 0;
    14: 
    15: }


更新 2

@fmunkert 的另一个例子:

#include <stdio.h>

int main()
{

        int *p = new int;  
        printf("1) p=%p\n",p);  
        *p = 1;  
        p++;
        printf("2) p=%p\n",p);
        *p =  2;   // <==== Illegal memory access
        printf("Done\n");  
        return 0;

}

gflags /p /enable mem_alloc.3.exe /full /unaligned

STACK_TEXT:  
0018ff44 00401205 00000001 0505ffbe 04ffdf44 mem_alloc_3!main+0x52 [c:\src\tests\test.cpp\mem_alloc\mem_alloc\mem_alloc.3.cpp @ 12]
0018ff88 75f8339a 7efde000 0018ffd4 77bb9ef2 mem_alloc_3!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 586]
0018ff94 77bb9ef2 7efde000 2577c47c 00000000 kernel32!BaseThreadInitThunk+0xe
0018ffd4 77bb9ec5 004013ac 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70
0018ffec 00000000 004013ac 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  ~0s; .ecxr ; kb

FAULTING_SOURCE_CODE:  
     8:         printf("1) p=%p\n",p);  
     9:         *p = 1;  
    10:         p++;
    11:         printf("2) p=%p\n",p);
>   12:         *p =  2;   // <==== Illegal memory access
    13:         printf("Done\n");  
    14:         return 0;
    15: 
    16: }

不幸的是/unaligned选项可能会导致程序无法正常运行(如何使用 Pageheap.exe):

一些程序假设 8 字节对齐,并且它们停止使用 /unaligned 参数正常工作。Microsoft Internet Explorer 就是这样一种程序。

于 2012-06-06T11:05:39.240 回答
5

我找到了一个解决方案:使用 VMware Workstation 和 Visual Studio 2010 进行“重放调试”。设置它需要花费大量时间,但您会得到一个可以及时向后调试的 Visual Studio C++ 调试器。这是一个演示重放调试如何工作的视频:http: //blogs.vmware.com/workstation/2010/01/replay-debugging-try-it-today.html

该解决方案的一个缺点是 VMware 似乎已停止在最新的 VMware 版本中进行重放调试。此外,似乎只有某些处理器类型支持重放。我没有找到任何支持处理器的完整列表;我在我的三台 PC 上测试了回放功能:回放在 Core i7 200 上不起作用;回放在 Core2 6700 和 Core2 Q9650 上工作。

我真的希望 VMware 重新考虑并在未来的 VMware Workstation 版本中再次引入重放调试,因为这确实为调试增加了一个新的维度。

对于那些感兴趣的人,这里描述了如何设置重放调试环境:

在下面的描述中,“本地调试”是指 Visual Studio 和 VMware 安装在同一台 PC 上。“远程调试”是指Visual Studio 和VMware 安装在不同的PC 上。

  • 在主机系统上安装 Visual Studio 2010 SP1。

  • 确保 Visual Studio 已配置为使用 Microsoft 的符号服务器。(在“工具 | 选项 | 调试 | 符号”下)。

  • 在主机系统上,安装“Windows 调试工具”。

  • 安装 VMware 工作站 7.1。(8.0 版不再包含重放调试功能)。这还将在 Visual Studio 中安装一个插件。

  • 使用 Windows XP SP3 在 VMware 上安装虚拟机 (VM)。

  • 如果正在测试的应用程序是调试版本,请在 VM 上安装 Visual Studio 调试 DLL。(有关如何执行此操作的说明,请参阅http://msdn.microsoft.com/en-us/library/dd293568.aspx ,但使用“调试”配置而不是“发布”)。

  • 将主机“Debugging Tools for Windows”目录下的“gflags.exe”复制到VM,在VM上运行gflags.exe,在“System Registry”选项卡下选择“Disable paging of kernel stacks”,点击OK。重新启动虚拟机。

  • 将被测应用程序的所有 EXE 和 DLL 文件复制到 VM 并确保您可以启动应用程序并重现问题。

  • 关闭 VM 并创建快照(通过 VMware Workstation 中的上下文菜单项“Take Snapshot”)。

  • (仅用于远程调试:)在 Visual Studio PC 上启动以下命令并输入任意密码:

    C:\Program Files\VMware\VMware Workstation\Visual Studio Integrated Debugger\dclProxy.exe主机名

    主机名替换为 PC 的名称。

  • (仅用于远程调试:)为 VM 手动创建记录。即登录到 VM 的操作系统,开始记录(通过上下文菜单“记录”),运行被测应用程序并执行重现问题所需的操作。然后停止并保存录音。

  • 启动 Visual Studio 并转到“VMware | 选项 | 在 VM 中重放调试 | 常规”,并设置以下值:

    • “本地或远程”必须设置为“本地”以进行本地调试或设置为“远程”以进行远程调试。
    • “虚拟机”必须设置为 VM 的 .vmx 文件的路径。
    • “远程机器密码”必须设置为您上面使用的密码(仅用于远程调试)。
    • “Recording to Replay”必须设置为您之前使用 VMware 创建的录制名称。
    • “Host Executable Search Path”必须设置为一个目录,在该目录中保存被测应用程序所需的 DLL,以及 Visual Studio 显示正确堆栈跟踪所需的 DLL。

    按“应用”。

  • 转到“VMware | 选项 | 在 VM 中重放调试 | 预记录事件”,并设置以下值:

    • “用于录制的基本快照”:先前创建的快照的名称。

    按“确定”。

  • (对于本地调试:)在 Visual Studio 中,选择“VMware | Create Recording for Replay”;这将重新启动 VM。登录到 VM,运行被测应用程序并执行重现问题所需的操作。然后停止并保存录音。

  • 选择“VMware | 开始重放调试”。VMware 现在会自动重新启动 VM 和被测应用程序并重播记录的操作。等到应用程序崩溃;Visual Studio 调试器然后自动变为活动状态。

  • 在 Visual Studio 调试器中,将断点设置到您认为应用程序在崩溃之前所在的位置。然后,选择“VMware | 反向继续”。调试器现在向后运行到断点。此操作可能需要一些时间,因为 VM 将重新启动并重播,直到达到断点。(您可以通过在记录场景时在崩溃发生前几秒钟添加快照来加快此操作。您可以在重放调试期间添加额外的快照。)

  • 一旦 VMware 将 VM 重播到断点,您可以使用“Step Over”和“Step Into”从断点向前推进,即重播记录的事件历史记录,直到您可以确定原因你的应用程序崩溃了。

更多信息:http ://www.replaydebugging.com/

于 2012-06-07T01:14:31.790 回答
2

使用 BMC 的 AppSight 怎么样?

我们在以前的公司使用过它(抱歉,我花了一段时间才记住这个名字),它被用来研究崩溃等。ISTR 你运行它,然后运行你的软件,它在一个日志文件中记录了发生的一切您可以稍后查看。

它绝对适用于 Windows,因为这就是我使用的。

这很可能是您正在寻找的东西?

于 2012-06-14T11:10:35.183 回答
2

我会在程序运行时附加 WinDbg,并在它调试崩溃或异常时执行 minidump:

.dump /ma c:\mem.dmp // c:\mem.dmp could be any other location you desire

我将为您的应用程序启用 gflags,无论是从 WinDbg 中的命令行:

!gflag +ust

记得在之后删除这个标志!

然后你可以运行一个自动的异常分析:

!analyze -v

这可能会告诉您它认为导致崩溃的原因,您可以转储所有线程的调用堆栈:

~* kb

如果看到任何可疑之处,您可以切换线程并进一步检查:

~x s

您可以检查异常上下文记录:

.ecxr

关于如何从 catch 块中恢复调用堆栈有一个很好的链接:http://blogs.msdn.com/b/slavao/archive/2005/01/30/363428.aspx 还有这个:http:// blogs.msdn.com/b/jmstall/archive/2005/01/18/355697.aspx

这里的主要内容是附加了 windbg,您应该能够检查所有线程和调用堆栈的状态,您还可以在 Visual Studio 中打开 minidump:http: //msdn.microsoft.com/en-us/ library/windows/desktop/ee416349%28v=vs.85%29.aspx#Analysis_of_a_minidump如果您更喜欢 Visual Studio 进行导航,您可以在 Windbg 中打开相同的转储以使用其分析工具和 Visual Studio 来导航代码。希望这可以帮助。

于 2012-06-06T12:15:27.210 回答
1

gdb 不是开箱即用地提供此功能吗?

自从我使用它已经有一段时间了,但我记得它可以运行一个程序直到它崩溃,然后在调试器中为你重播这些步骤。

此外,设置您自己的日志记录应用程序会很简单,它可以输出您选择的任何数量的数据,并且可以通过命令行参数激活到 exe?

您现在可以设置它来解决您遇到的崩溃,或者只是覆盖基础知识,然后在修复错误或添加新功能时对其进行扩展。优点是您将能够准确捕获您认为有用的数据,甚至可以指定日志记录级别以避免被噪音淹没?

于 2012-06-12T12:57:12.743 回答
-1

不完全确定这是否是您想要的,但“u”将反汇编当前线程上当前 IP 寄存器中的最后一条指令。这将向您显示最后运行的指令,并且您通常可以通过反汇编代码来确定不同寄存器的值。大多数情况下,这是一个缓慢而艰难的过程,但它为您提供了几乎 100% 的准确度(除非出现一些奇怪的硬件问题,或者非常奇怪的代码问题)。我过去曾使用这种方法来弄清楚为什么当我没有源代码时某些事情会被取消。

如果您查看 windbg 帮助文件,您会发现更多信息。

于 2012-06-15T04:06:26.780 回答