如果一个程序跑10000次只失败一次,你会怎么调试?[转载]

CLR小组中存在着大量的回归测试,这些回归测试会定期执行来发现CLR中的Bug,Developer在Checkin之前,也需要执行这些测试的一部分(大概是10小时左右,如果全部跑的话估计要好几天)。这些测试对于保证CLR的质量是至关重要的。有时候,这些测试会偶尔失败,比如跑100次失败大概一到两次,有些极端的例子甚至是10000次才失败一次。像这种问题通常是很难调试的。在前面调试Bug的神兵利器:通过WinDbg条件断点收集Log这篇文章中,我讲到了如何通过条件断点收集各种信息来判断Bug究竟出在哪里。但是,这个方法还是不太管用,因为它不能够反复执行某个程序。下面我要讲一种技巧可以用来调试类似这样的问题,这种技巧主要适用于下面几种情况:

  1. 在程序出错的时候,某些信息、状态已经丢失,无法通过当前出错时候的状态推断出之前的状态。说的稍微具体一点就是,比如某个变量变成了NULL导致Access
    Violation,但是很难直接推断出为什么这个变量变成了NULL

  2. 程序运行时间较长,很难直接单步调试

  3. 程序较难修改加入打印代码(比如加入新代码并编译非常花时间,或者该程序没有源代码

  4. 该程序运行次数较多的时候才能发现问题,也就是说问题不是每次都出现

#2和#4决定了一步步调试基本上是不可能的。#1和#3则意味着我们必须得使用条件断点来收集信息来判断代码的错误,因为直接调试出错的位置是不可行的。下面了我来讲一下如何用CDB(其实就是WinDbg的无UI版本,WinDbg=CDB+UI)来做到:

  1. 反复执行程序

  2. 当程序出错的时候自动暂停

  3. 通过条件断点收集信息,只保留出错时候的那一次Log

我们先假设我们需要调试的程序叫做Hello.exe,每次出问题的现象是,调用某个函数Hello!Func()的时候,其参数arg为NULL。Arg这个变量是由某个全局变量g_arg传入而来。我们可以通过硬件的数据断点来查看每次将g_arg赋值为NULL的情况(当然了,赋值为NULL并不代表是错误,只有传入Hello!Func的时候为NULL才是错误)。程序一般要跑10000次才可能发现问题。使用下面的命令行可以做到反复收集Func1(Func2、Func3因为类似,这里就不列出了)执行时候的g_arg的值并放入Log文件中,并且如果发现调用Hello!Func的时候arg参数为NULL,则停止程序:

for /L %i in (1, 1, 10000) DO CDB.exe -c "bu
Hello!Func /".echo Inside Hello!Func; dv; .if (poi(arg)!=0) { g } /";
ba w4 Hello!g_arg /“.if
(poi(Hello!g_arg)==0) { .echo g_arg changes to NULL; kb; }/”; g" -G -logo debug.log Hello.exe

我们来简单分析一下:

  1. 一开头的For语句用于执行CDB命令10000次,也就是调试Hello.exe一万次

  2. -c命令指定让CDB在程序开始的时候执行下面的命令
    1. bu Hello!Func “.echo Inside Hello!Func; dv; .if (poi(arg)!=0) { g
      }意思是每次Hello!Func被执行的时候,打印Inside
      Hello!Func,之后打印所有局部变量和参数(包括arg),如果发现arg!=NULL,则继续。注意上面命令中的/”是转义符,代表真正的引号,避免冲突。

    2. ba w4 Hello!g_arg “.if (poi(Hello!g_arg)==0) { .echo g_arg changes to
      NULL; kb; }”意思是每次如果g_arg被修改成NULL,打印出Callstack

    3. g命令表示让程序开始执行

  3. -G:表示让CDB忽略程序结束的时候的Breakpoint,避免CDB在运行结束的时候停下,保证CDB可以持续执行不需要人工干预

  4. -logo
    debug.log:表示让CDB把每次输出的结果放入Debug.log中,并且每次都新建立文件,也就是说,会把上一次覆盖。这正好是我们需要的,因为我们设置了一旦程序错误则停止,那么这一次的Debug.log才是需要保留的

除了用-c指定初始的命令之外,也可以使用-cf来指定一个文件包含任意条CDB命令,如果CDB命令较多,可以采用这种方法。

本文说道的方法是比较有效的,我自己曾经使用过这种方法解决过不少比较棘手的问题。如果碰到了此种需要运行10000次才能重现问题的Bug,不妨试一下本文的方法。

P.S.
看到大家的评论,觉得有些东西需要稍微澄清一下:本文只是提供一种解决问题的思路,主要是在问题较为复杂,并且程序规模较大(>1百万),已有的Log信息并不够用的情况下对于C++程序(没错,CLR是使用C++编写的)采用。对于测试用例而言,输入是固定的,但是输出偶尔错误(举个简单例子,输入是1+1,但是输出99.99%是2,但是0.01%是3),像那种输入1+1输出总是3的情况不在本文讨论范围之内,毕竟单步就可以解决问题了。换句话说,本文的方法显然只是在某些特定情况下是比较合理的方法,并不代表应该应用在所有情况下。如果本文的方法能够能给大家提供一种解决问题的思路或者一些启示,那么本文的目的也就达到了。

时间: 2024-08-11 09:51:10

如果一个程序跑10000次只失败一次,你会怎么调试?[转载]的相关文章

一个程序员创业一年后失败的感悟

内容简介:创业,就必须要找一个方向,基于移动互联的大众化产品,我相信我没有那个实力去做,因此只能找一个行业方向.不管怎样,开始着手做吧,不为别的,只为梦想!这次花光了我打工时的所有积蓄.经历过这的次创业,虽然失败,但不后悔. 2011年,坚决辞去了发展还不错的工作.我觉得,这不是我想要的生活,我我在心理呐喊,我要创业!我自负地认为:我可以实现更高的自我价值.怀着一种为了创业而创业的心态,就这样开始了自己的创业体验. 创业,就必须要找一个方向,基于移动互联的大众化产品,我相信我没有那个实力去做,因

当一个程序员写不出代码了,该怎么办?(转载)

翻译作者:码农网-小峰 转载地址:http://www.codeceo.com/article/what-to-do-programming-sucks.html 原文标题:What Do You Do When Programming Sucks ? 原文地址:https://blog.klimczyk.pl/2017/05/04/what-to-do-when-programming-sucks/ 你已经对着电脑 N 个小时了.不知道该写什么代码,或者一种摔键盘的冲动正在你的胸中酝酿. 咖啡

35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n); (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和; (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n

  35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n): (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和: (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n!): (4)编写测试类E,在测试类E的main方法中使用接口回调的形式来测试实现 接口的类. p

连载《一个程序猿的生命周期》- 36、突破失败的自我感悟

一个程序猿的生命周期 微信平台 口    号:职业交流,职业规划:面对现实,用心去交流.感悟.  公众号:iterlifetime二维码:  百木-ITer职业交流奋斗 群:141588103 注:有些网友怀疑我写的文章的真实性,我可以负责任的告诉大家,99%是真实经历.想法.感悟.否则,浪费自己的时间,也浪费大家的时间,我认为是没有意义的. <一个程序猿的生命周期>也会一直写下去,以10年为界限,目前写的是第一个10年,小标题定为<起航>.以后,会把所有文章重新整理,定制成册.

多次单击快捷方式,只运行一个程序实例

在应用程序安装之后,单击一次快捷方式,就运行一个程序实例,对于资源独占型程序来说,这样是不可以的,比如该程序使用了当前系统的某个端口,当同样的程序再次运行,再次试图占用同一个端口次,会提示"端口已经被占用的"异常.如此,必须在启动应用程序时,必须判断该程序是否已经有一个实例在运行.下面这个类中先判断该程序的实例有没有在运行,使用线程同步类EventWaitHandle(Boolean, EventResetMode, String)及注册正在等待 WaitHandle 的委托方法Reg

python 文件单行循环读取的坑(一个程序中,文件默认只能按行循环读取一次,即使写到另一个循环里,它也只读取一次)

本来写了一个程序,想获取a文件中有,但是b文件中没有的行: 想到的方法是:1.一行一行提取a文件中数据,然后用a文件中的每一行与b文件中的每一行比较, 2.如果找到相同行就继续查找a中的下一行,如果找不到,就把这行保存起来,就是要找的一行 程序写成如下: f = open("file/a.txt","r",newline='\n') ff= open ("file/aa.txt","r",newline='\n') new=o

C#:只运行一个程序

一.通过系统事件 1.实现如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; using System.Runtime.InteropServices; namespace Example { public class SinglonProgram { #region

(小说)那些年,那些事——一个程序员的奋斗史

转自:http://blog.csdn.net/x283930450/article/details/9072223 第01章    段伏枥,一个瘦小,矮小,根本和“帅”这个字粘不上任何关系的普通人 .名字的来源在于其多读了几年书的老爹,总抱着有一天要出书出名乃至于名流千古的 美好理想,但可惜现实总是给予他无情的而又现实的打击,于是就将理想寄望于自己的 儿子,起了个“伏枥”的名,寓意为“老骥伏枥,志在千里”,表达自己不到黄河不死 心的良好的愿望.     只可惜段伏枥这家伙完美地遗传了来自于老爹

从一个程序员到一个销售高手的心路历程

从一个程序员到一个销售高手的心路历程 0.引言 我大学本科读的是理工科,后来毕业以后,我逐渐走上了程 序员的道路.每天面对电脑一行一行的敲代码,这被我们程序员们戏称为“搬砖头”,因为我们所做的事跟民工搬砖头砌墙本质上是相同的,我们也是把一堆代码从 一个地方搬到另一个地方,然后改改让它面子上看起来挺好看,用起来结实耐用就算完工了. 干了6年的技术以后,我放弃了已做的非常 好的技术和积累起来的成绩,转而从0开始去做销售.后来经过自己的努力,我终于成长为一个销售和业绩翻倍高手,在销售和业绩翻倍方面取得