[Windbg笔记] 调试偶发性Bug
时间:2010-08-28 来源:Binhua Liu
[Windbg笔记] 调试偶发性Bug
前言
很久之前在网上看到两篇不错的windbg文章——<调试Bug的神兵利器:通过WinDbg条件断点收集Log >, <如果一个程序跑10000次只失败一次,你会怎么调试?>。最近重读一下,并做了一些家庭作业,总算弄得比较清楚了。于是把自己写的调试小程序和小改进贴出来,权当笔记。相关知识推荐还是阅读2篇原文和Windbg help文档。
调试场景
这种调试方法可以用在很难重现的bug上,通过不断重复运行程序来捕获bug,并且通过在关键的代码和数据上设置断点来打印调试信息帮助定位bug。我认为另外一个用途是可以用在自动化测试上,通过脚本自动起调试程序,运行过程中打印调试Log,一旦遇到希望捕获的异常,就创建dump file并和Log一起上传到服务器。话不多说,马上开始,我写了一个小程序WindbgTest1.exe模拟一个难以重现的bug,代码如下:
#include "stdafx.h" #include "Windows.h" int GetQuotient(int a) { int dividend=rand()%100; return a/dividend; } int Test() { int a; int result; SYSTEMTIME localTime; GetLocalTime(&localTime); srand(localTime.wMilliseconds); a=rand()%100; if(a>=90) return GetQuotient(a); else return a; } int _tmain(int argc, _TCHAR* argv[]) { int reslut=Test(); return 0; }
Test函数在0-99之间产生一个随机数,如果随机数大于等于90,则调用GetQuotient函数,GetQuotient函数随机在0-99之间产生一个被除数。显然,如果被除数是0,将发生除零异常。异常产生的概率为0.1%。
调试
我们通过运行下面这行命令来捕捉bug:
for /l %i in (1,1,1000) do CDB.exe -c "bu WindbgTest1!Test \".echo Inside Test Function;dv;g\"; bu WindbgTest1!GetQuotient \".echo Inside GetQuotient Function;dv;g\"; sxe -c \"dv;kp;.dump /ma c:\\test.dmp\" dz; g" -G -logo c:\debug.log "D:\Code\WindbgTest1\Debug\WindbgTest1.exe"
我们来解释一下这行命令,这个命令包含好几层嵌套,只要搞清楚了命令的结构和每部分的功能,事实上不是很难。
for /l %i in (1,1,1000) do {command}
这是一个cmd命令,意思为重复执行{command}一千遍,所以我们上面的命令虽然很长,但后面部分都属于这个for语句的{command}部分
CDB.exe -c "{commands}" -G -logo c:\debug.log "D:\Code\WindbgTest1\Debug\WindbgTest1.exe"
这个命令指在CDB.exe下调试WindbgTest1.exe,CDB.exe是windbg的命令行版本。-G表示CDB忽略程序结束的时候的Breakpoint,这样调试完成后会自动退出,然后for语句可以马上起下一次调试。-logo c:\debug.log 指把调试信息打印到debug.log文件中,由于调试不断重复,这个文件也会不断被覆盖,只保留最后一次即bug发生的那次的log。
关键是 –c “{commands}” ,这是用来设置调试启动时的初始命令。相当于我们用Windbg调试时,加载.exe文件后,程序运行前的进入的断点,我们在这时进行断点的设置,符号文件的加载等初始化命令,这里也是同样的道理。下面我们就来看看"{commands}”中的内容:
bu WindbgTest1!Test \".echo Inside Test Function;dv;g\";
bu表示设置一个Unresolved Breakpoints断点,WindbgTest1!Test为设置断点的地址,双引号(由于外层还有双引号,所以要加上转义符)中的命令是指一旦断点被触发后会自动执行的命令--首先打印一段话”Inside Test Function”,然后dv命令打印局部变量,最后g语句让程序继续执行。最后的g语句是必须的,因为如果没有g语句,程序就停留在该断点不会继续往下执行了。
bu WindbgTest1!GetQuotient \".echo Inside GetQuotient Function;dv;g\";
同上
sxe -c \"dv;kp;.dump /ma c:\\test.dmp\" dz;
该语句是我做的小改进,用来在程序遭遇到期望的异常后会自动产生dump file。sxe [–c “commands”] {exception} 表示当遭遇到指定的first chance的{exception}后,执行commands中的命令。dz表示除零异常,遭遇这个异常后,首先dv打印局部变量,kp打印堆栈,最后.dump /ma c:\test.dmp 创建一个min dump。由于没有g语句,程序停留在这个断点上,另外一个选择是,上传dump和log,然后执行退出命令,再起下一次调试。
最后
本文涉及到的命令只是做了初略的讲解,由于本人功底尚浅,还以官方help文档为准。