程序只发布版本崩溃 – 如何debugging?

我在这里有一个“Schroedinger的猫”types的问题 – 我的程序(实际上是我的程序的testing套件,但程序仍然)崩溃,但只有在释放模式下构build,只有从命令行启动。 通过穴居人debugging(即讨厌的printf()消息到处),我已经确定了代码崩溃的testing方法,但不幸的是,实际崩溃似乎发生在某个析构函数中,因为我看到的最后一条跟踪消息是其他干净地执行的析构函数。

当我尝试在Visual Studio中运行此程序时,它不会崩溃。 从WinDbg.exe启动时也一样。 从命令行启动时只会发生崩溃。 这是在Windows Vista下发生的,顺便说一下,不幸的是我现在无法访问XP机器来testing。

如果我能让Windows打印出一个堆栈跟踪,或者除了简单地终止程序,就好像它已经完全退出一样,那将是非常好的。 有没有人有任何意见,我怎么可以在这里得到一些更有意义的信息,并希望修复这个错误?

编辑:这个问题确实是由一个越界数组引起的, 我在这篇文章中更多描述了这个数组。 谢谢大家的帮助,find这个问题!

在我见过或听说过的情况下,C或C ++程序在debugging器中运行良好,但在外部运行时失败,原因是一直写到函数本地数组的末尾。 (debugging器把更多的东西放在堆栈上,所以你不太可能覆盖重要的东西。)

当我遇到这样的问题之前,一般是由于variables的初始化。 在debugging模式下,variables和指针会自动初始化为零,但在释放模式下它们不会。 因此,如果你有这样的代码

int* p; .... if (p == 0) { // do stuff } 

在debugging模式下,if中的代码不会被执行,但是在释放模式下,p包含一个未定义的值,这个值不可能是0,所以代码的执行经常会导致崩溃。

我会检查您的代码未初始化的variables。 这也可以适用于数组的内容。

需要注意的事项:

数组超载 – visual studiodebugging器插入可能会停止崩溃的填充。

竞争条件 – 你有多个线程,如果这样的竞争条件许多只显示直接执行应用程序。

链接 – 是你的发布版本拉入正确的库。

试一试:

Minidump – 使用起来非常简单(只需在msdn中查看)就可以为每个线程提供完整的崩溃转储。 您只需将输出加载到visual studio中,就好像在崩溃时正在debugging一样。

到目前为止,没有任何答案试图对debugging发行版应用程序的可用技术给予认真的概述:

  1. 发布和debugging版本的行为有许多不同的原因。 这是一个很好的概述。 这些差异中的每一个都可能导致发布版本中的Debug版本中不存在的错误。

  2. debugging器的存在也可能会改变程序的行为 ,包括发布和debugging版本。 看到这个答案。 简而言之,至lessVisual Studio Debugger在连接到程序时自动使用Debug Heap。 您可以使用环境variables_NO_DEBUG_HEAPclosuresdebugging堆。 您可以在计算机属性中或在Visual Studio中的“项目设置”中指定此项。 这可能会使debugging器附带的重现崩溃。

    更多关于在这里debugging堆腐败。

  3. 如果以前的解决scheme无法正常工作,则需要捕获未处理的exception,并附上死后debugging程序发生崩溃的实例。 您可以使用例如WinDbg, 有关可用性验尸debugging程序的详细信息及其在MSDN上的安装

  4. 你可以改进你的exception处理代码,如果这是一个生产应用程序,你应该:

    一个。 使用std::set_terminate安装自定义终止处理程序

    如果你想在本地debugging这个问题,你可以在终止处理程序中运行一个无限循环,并输出一些文本到控制台,通知你std::terminate已被调用。 然后附加debugging器并检查调用堆栈。 或者按照此答案中所述打印堆栈跟踪。

    在生产应用程序中,您可能希望将错误报告发送回家,理想情况下还有一个小内存转储,可以让您按照此处所述分析问题。

    使用Microsoft的结构化exception处理机制 ,可以让您捕获硬件和软件exception。 请参阅MSDN 。 您可以使用SEH来保护您的代码的一部分,并使用与a)中相同的方法来debugging该问题。 SEH提供了有关从生产应用程序发送错误报告时可以使用的exception的更多信息。

您可以将WinDbg设置为您的事后debugging器。 这将启动debugging器,并在发生崩溃时将其附加到进程。 要安装WinDbg进行事后debugging,请使用/ I选项(注意大写 ):

 windbg /I 

更多细节在这里 。

至于原因,其他的答案显示,它最可能是一个单位variables。

经过几个小时的debugging,终于find问题的原因,这确实是由缓冲区溢出造成的,导致单字节差别:

 char *end = static_cast<char*>(attr->data) + attr->dataSize; 

这是一个fencepost错误(一个错误),并通过以下方法修复:

 char *end = static_cast<char*>(attr->data) + attr->dataSize - 1; 

奇怪的是,我在代码的不同部分对_CrtCheckMemory()进行了几次调用,并且总是返回1.我可以通过设置“return false”来find问题的根源。 在testing用例中调用,然后最终通过反复试验来确定故障的位置。

感谢大家的评论 – 今天我学到了很多关于windbg.exe的知识! 🙂

即使你已经将你的exe作为一个发布版本,你仍然可以生成PDB(程序数据库)文件,这将允许你堆栈跟踪,并做有限的variables检查。 在您的构build设置中,可以select创buildPDB文件。 开启并重新连接。 然后尝试先从IDE运行,看看是否会崩溃。 如果是这样,那么很好 – 你们都会着眼于事物。 如果没有,那么当从命令行运行时,可以执行以下两项操作之一:

  1. 运行EXE,并在崩溃之前执行“附加到进程”(Visual Studio上的“工具”菜单)。
  2. 崩溃后,select启动debugging器的选项。

当被要求指向PDB文件时,浏览find它们。 如果PDB放在与EXE或DLL相同的输出文件夹中,它们可能会被自动拾取。

PDB通过足够的符号信息提供一个链接到源代码,以便能够看到堆栈跟踪,variables等等。您可以检查这些值是否正常,但是请注意,您可能会得到错误的读数,因为优化过程可能仅指事物出现在寄存器中,或事情发生的顺序与您预期的不同。

注意:我在这里假设一个Windows / Visual Studio环境。

为了有一个崩溃转储,你可以分析:

  1. 为您的代码生成pdb文件。
  2. 你重新绑定你的exe和dll加载在同一个地址。
  3. 启用验尸debugging器,如Dr. Watson
  4. 使用诸如故障查找器之类的工具检查崩溃故障地址。

你也应该看看debugging工具的窗口中的工具 。 您可以监视应用程序并查看第二次机会exception之前的所有第一次机会exception。

希望它有帮助…

像这样的崩溃几乎总是造成的,因为IDE通常会将未初始化的variables的内容设置为零,或者其他一些“明智的”值,而在本地运行时,将会得到系统拾取的随机垃圾。

因此,你的错误几乎可以肯定的是,你正在使用的东西就像你正在使用一个指针之前,已经正确初始化,你在IDE中越来越远,因为它没有指向任何地方的危险 – 或价值是由你的错误检查 – 但在发布模式下,它做了一些令人讨厌的事情。

一旦我有一个问题,当你的应用程序的行为类似。 原来是sprintf中的一个令人讨厌的缓冲区溢出。 当然,它运行时与一个debugging器连接。 我做了什么,是安装一个未处理的exceptionfilter( SetUnhandledExceptionFilter ),我只是无限地阻塞(使用WaitForSingleObject在一个虚假的句柄与超时值的INFINITE)。

所以你可以按照以下方式来做:

 long __stdcall MyFilter(EXCEPTION_POINTERS *)
 {
     HANDLE hEvt = :: CreateEventW(0,1,0,0);
    如果(hEvt)
     {
        如果(WAIT_FAILED == :: WaitForSingleObject(hEvt,INFINITE))
         {
             //日志失败
         }
     }

 }
 //你的wmain / WinMain中的某处:
 SetUnhandledExceptionFilter(MyFilter);

然后,我附加了debugging器后,该错误已经performance出自己(GUI程序停止响应)。

然后,您可以转储并在稍后处理:

.dump / ma path_to_dump_file

或者马上debugging。 最简单的方法是跟踪运行时exception处理机器保存的处理器上下文的位置:

sd esp 范围 1003f

命令将search堆栈地址空间的CONTEXTlogging(S)提供search的长度。 我通常使用“l?10000”之东西。 请注意,不要使用非常大的数字作为您通常靠近非扩展exceptionfilter框架后的logging。 1003f是用来捕获处理器状态的标志(我相信它对应于CONTEXT_FULL)的组合。 您的search看起来与此类似:

0:000> sd esp l1000 1003f
0012c160 0001003f 00000000 00000000 00000000?……………

一旦获得结果,请使用cxr命令中的地址:

.cxr 0012c160

这将带你到这个新的上下文,正好在崩溃的时候(你会得到确切的应用程序崩溃时的堆栈跟踪)。 另外,使用:

.exr -1

找出发生了什么exception。

希望它有帮助。

关于获取诊断信息的问题,您是否尝试过使用adplus.vbs作为WinDbg.exe的替代方法? 要附加到正在运行的进程,请使用

 adplus.vbs -crash -p <process_id> 

或者在发生崩溃的情况下快速启动应用程序:

 adplus.vbs -crash -sc your_app.exe 

有关adplus.vbs的完整信息,请访问: http : //support.microsoft.com/kb/286350

附带debugging程序的Ntdll.dll

从IDE或WinDbg启动一个程序,而不是从命令行/桌面启动它的一个小差别是,当启动一个附加的debugging器(即IDE或WinDbg)ntdll.dll使用不同的堆实现,执行一些小validation在内存分配/释放上。

您可能会在ntdll.dll中的意外的用户断点上读取一些相关的信息。 可能能够帮助您识别问题的一个工具是PageHeap.exe 。

崩溃分析

你没有写出你正在经历的“崩溃”。 一旦程序崩溃,并提供给微软的错误信息,你应该能够点击技术信息,并至less检查exception代码,并通过一些努力,甚至可以进行事后分析(见Heisenbug :WinApi程序在某些计算机上崩溃)的指示)

Vista SP1实际上有一个非常好的崩溃转储生成器内置到系统中。 不幸的是,它默认情况下是不打开的!

看到这篇文章: http : //msdn.microsoft.com/en-us/library/bb787181(VS.85).aspx

这种方法的好处是不需要在受影响的系统上安装额外的软件。 抓住它,撕开它,宝贝!

有时候会发生这种情况,因为在“assert”macros中包含了重要的操作。 正如你可能知道的,“assert”只在debugging模式下才能评估expression式。

GCC曾经发生过一次类似的事情。 事实certificate,这是一个过于激进的优化,只有在创build最终版本时才能启用,而不是在开发过程中启用。

那么,说实话,这是我的错,而不是gcc的,因为我没有注意到我的代码依赖于这样的事实,即特定的优化不会被完成。

我花了很多时间来追踪,因为我在一个新闻组上发问,有人让我思考。 所以,让我来回报一下,以防万一这件事发生在你身上。

我发现这篇文章对你的场景很有用。 ISTR的编译器选项有些过时了。 查看你的Visual Studio项目选项,看看如何为你的发布版本生成pdb文件等等。

有可能会发生在debugging器之外,而不是内部。 在debugging器中运行通常不会改变应用程序行为。 我会检查控制台和IDE之间的环境差异。 另外,显然,编译版本没有优化和debugging信息,看看是否会影响行为。 最后,请查看其他人在此提出的验尸debugging工具,通常可以从中得到一些线索。

debugging版本构build可能是一个痛苦,因为优化改变了代码的执行顺序。 它真的可以混淆!

至less缩小问题的一种技术是使用MessageBox()来显示快速的语句,说明你的代码到底是什么部分(“Starting Foo()”,“Starting Foo2()”); 开始把它们放在你怀疑的代码区域的函数的顶部(你在崩溃的时候做了什么?)。 当你可以知道哪个函数时,将消息框更改为代码块,甚至是该函数中的单个行,直到将其缩小到几行为止。 然后,您可以开始打印出variables的值,以查看在崩溃时它们处于什么状态。

尝试使用_CrtCheckMemory()来查看分配的内存在哪个状态。 如果一切顺利, _CrtCheckMemory返回TRUE ,否则返回FALSE

您可以在Global Flags启用的情况下运行软件(查看适用于Windows的debugging工具)。 这通常会帮助解决问题。

当发生exception时,使程序生成一个小型转储,然后在debugging器(例如WinDbg)中打开它。 要查看的关键函数:MiniDumpWriteDump,SetUnhandledExceptionFilter

根据我的经验,这是最大的记忆腐败问题。

例如 :

 char a[8]; memset(&a[0], 0, 16); : /*use array a doing some thing */ 

在运行代码时,在debugging模式下很可能是正常的。

但在发布时,这可能会崩溃。

对于我来说,翻身记忆的地方太难了。

使用像Visual Leak Detector (windows)或valgrind (linux)这样的工具是更明智的select。

debugging这样的错误的一个好方法是为您的debugging版本启用优化。

我有这个错误,甚至当试图!干净! 我的项目。 所以我从Release目录中手动删除了obj文件,之后就构build得很好。

我同意Rolf。 因为可重复性非常重要,所以不应该有非debugging模式。 你所有的构build应该是可debugging的。 有两个目标来debugging不止你的debugging负载加倍。 只要运送“debugging模式”版本,除非它是不可用的。 在这种情况下,使其可用。