`
chinamming
  • 浏览: 151500 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

如果一个程序跑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的情况不在本文讨论范围之内,毕竟单步就可以解决问题了。换句话说,本文的方法显然只是在某些特定情况下是比较合理的方法,并不代表应该应用在所有情况下。如果本文的方法能够能给大家提供一种解决问题的思路或者一些启示,那么本文的目的也就达到了。

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的情况不在本文讨论范围之内,毕竟单步就可以解决问题了。换句话说,本文的方法显然只是在某些特定情况下是比较合理的方法,并不代表应该应用在所有情况下。如果本文的方法能够能给大家提供一种解决问题的思路或者一些启示,那么本文的目的也就达到了。

分享到:
评论

相关推荐

    随机起名器批量起名最多一次性一键可以生成10000个

    能一键批量随机生成姓名,最多一次性一键可以生成10000个 能一键批量随机生成姓名,最多一次性一键可以生成10000个 能一键批量随机生成姓名,最多一次性一键可以生成10000个 能一键批量随机生成姓名,最多一次性...

    批量更新历史数据 每10000提交一次

    在这个案例中,选择每10000次提交作为界限,是一个常见的折衷方案,兼顾了性能和内存占用。 在实际操作中,我们可以使用编程语言(如Python、Java或PHP)来实现这个功能。首先,我们需要连接到数据库,然后读取需要...

    想让你的QQ人气每一天增加10000次吗?想的来

    yaode 要的来。天下没有免费的午餐。5QB。先钱。要的加QQ1071752522.下面是图。可以看看。给你刷一个星期。每天10000次。并且免费给一个刷积分的挂

    10000份个co图标 ,程序界面美化资料大全

    总的来说,"10000份个co图标,程序界面美化资料大全" 是一个宝贵的资源库,无论你是UI设计师、开发者还是设计爱好者,都能从中受益匪浅,提升你的图标设计能力和界面美化水平。通过深入学习和实践,你将能够创造出更...

    程序支持mc10000盘点机

    对于MC10000盘点机来说,其内置的操作系统可能是一个轻量级的嵌入式系统,如Linux或者RTOS,这些系统对内存和处理器性能有高度优化,确保设备在低功耗下高效运行。 "DataWedge Readme.htm"和"DataWedge User Guide....

    10000以内的亲密数

    对于每一个数`a`,函数会调用`facsum`函数计算出`a`的所有真因子之和,记为`M`。接着再次调用`facsum`计算`M`的所有真因子之和,记为`b`。如果`b`等于初始的数`a`且`a`小于`M`,那么`a`和`M`就被认为是一对亲密数,...

    1至10000数字 按顺序 一行一个.txt

    1至10000数字 按顺序 一行一个,例如: 1 2 3 4 5 6 7 8 9 10 11 12 ……

    编写程序:计算100-10000之间有多少个素数,并输出所有素数。

    编写程序:计算100-10000之间有多少个素数,并输出所有素数。

    用java实现10000的阶乘(2种方法)

    基本思路是定义一个函数,如果n等于0或1,则返回1;否则,返回n乘以n-1的阶乘。以下是用Java实现的递归方法: ```java public static BigInteger factorialRecursion(int n) { if (n == 0 || n == 1) { return ...

    用java程序怎么实现200ms往数据库中插入10000条数据

    JDBC 是一个 Java API,用于连接和操作数据库。要使用 JDBC,我们需要 imports相关的包,例如 `java.sql.Connection`、`java.sql.DriverManager`、`java.sql.PreparedStatement` 等。 加载数据库驱动 在使用 JDBC ...

    ibm-pc汇编课后习题答案

    2.5 如果在一个程序开始执行以前(CS)=0A7F0H,(如16进制数的最高位为字母,则应在其前加一个0) (IP)=2B40H,试问该程序的第一个字的物理地址是多少? 答:该程序的第一个字的物理地址是0AAA40H。 2.6 在实模式下,...

    scph10000_ps2_

    综合以上信息,我们可以理解这是一个关于PS2,特别是SCPH-10000模型的引导加载程序项目。用户可能正在尝试自定义他们的PS2系统,以实现更高级的功能,比如通过EFI来运行自制软件或备份游戏。这些文件对于了解和操作...

    手写数字识别10000次cnn结果 (.caffemodel)

    手写数字识别10000次cnn结果,后缀名称为.caffemodel的网络模型文件,已经在caffe初探3中生成了若干网络模型文件,在这里我们可以选择迭代10000次的模型文件,里面包含了网络参数。

    10000个科学难题(数学,物理,信息).zip

    《10000个科学难题》是一份涵盖了数学、物理和信息学三大基础学科的珍贵资源,旨在挑战和激发对这些领域有热情的学者和爱好者。这份压缩包中的三个子文件分别对应这三个领域的难题集,分别是《10000个科学难题(数学...

    可以播报26个字母及10000以内的价格的语音播报小程序

    如果价格超过10000,则会逐位读取,确保了大额数字的清晰播报,这对于零售、超市、市场等场合非常实用。 在语音技术方面,这款小程序可能采用了文本转语音(Text-to-Speech, TTS)的技术。TTS是将文字信息转化为可...

    java求10000以内的完数附有结果

    本篇文章将深入探讨完全数的概念及其背后的数学原理,并通过一个简单的Java程序来找出10000以内的所有完全数。 #### 二、完全数的基本概念 1. **定义**:如果一个正整数等于其所有真因子的和,则称这个数为完全数...

    给定一个整数数组,其中元素的取值范围为0到10000,求其中出现次数最多的数

    题目要求处理一个整数数组,其中元素的取值范围限定在0到10000之间,并且需要找到出现频率最高的数值及其出现次数。 #### 二、算法设计与实现 为了高效地解决这一问题,我们可以采用哈希表的思想来计数每个整数出现...

    MachineLP#CodeFun#SMOffer034--第一个只出现一次的字符1

    题目描述在一个字符串(0字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).解

    算10000以内数阶乘的C语言程序

    标题中的“算10000以内数阶乘的C语言程序”指的是使用C语言编写一个计算1到10000之间任意整数阶乘的程序。在数学中,阶乘是一个正整数n与小于等于它的所有正整数的乘积,表示为n!。例如,5的阶乘(5!)是5 x 4 x 3 x...

Global site tag (gtag.js) - Google Analytics