本节讲述Windbg中常用的断点调试方法,并用一个最简单的例子,来演示断点的使用方法。测试程序代码比较简单,如下: void change(char *psz) { strcpy(psz, "world"); return; }int _tmain(int argc, _TCHAR* argv[]) { char szTest[] = "hello"; change(szTest);printf("%s\n", szTest);system("pause"); return 0; }编译后生成CodeTest.exe和对应的符号文件CodeTest.pdb打开Windbg,启动调试CodeTest.exe,设置好符号文件路径(CodeTest.pdb所在路径和微软符号服务器路径),如下图: Windbg启动调试够中断在ntdll!LdrpDoDebugBreak函数处,此时main函数还没有执行,我们可以在此时下一个函数断点。(之前我们说过,exe中保存pdb的绝对路径,pdb中保存的也是cpp的绝对路径,所以此时windbg应该同时也打开了源码窗口)命令:bp CodeTest!change 在CodeTest模块的change函数上下一个断点,当调用了这个函数时,中断下来,以便于调试。此时,我们用bl命令,可以列出当前的所有断点,如下图: 只有一个断点,就是刚才我们下过的,然后F5(或使用g命令),继续执行。中断到change函数,如下图: 断点命中,接下来可以进行单步调试,看这个函数内部执行的情况,期间也可以用dv命令来观察这个函数内的局部变量的值。以上这些操作,在VS中要比在Windbg中容易完成的多,有些读者可能会问,就这么简单,我们为什么要使用Windbg呢?老实说,到这里,我们所讲述的东西,都是调试最基本的知识,调试方法,也都是最最简单的,并未脱离下断点调试这个节奏。但从现在开始,以后我们接触的调试手段,大多在VS中是很难操作的了。有时候,我们有这样一种需求,我们调试程序的时候,中断到当前函数时,某个变量的值,我们认为它应该是a,但我们看到的却是b,但这个变量初始化的时候,明明是a,为何却变成b了呢? 下面,我们来说说数据断点。还是上面的程序,我们szTest中初始化时是“hello”,我们对这个变量所在地址下一个断点,当有代码修改szText时,我们中断下来,进行调试。先清除一下刚刚的断点,命令为:bc 0 此时,已经没有断点了,我们重新调试,并输入如下命令: 先在main函数中下断点,然后F5执行到main函数入口,然后输入命令: ba w 1 szTest 此断点为数据写入断点,当代码执行写入szTest地址1字节的时候,就会触发该断点。程序中断下来,我们就找到了,修改了szTest内容的代码在何处,接下来,我们F5,如图: 此处对栈内存进行初始化,初始化的内容为CC,此汇编指令含义参见《软件调试基础01--汇编基础》。然后继续F5,如下图: 此时,将szTest所在地址的内容,初始化为字符串“hello”,下图为查看常量区内容: 至此,szTest初始化已经完成,接下来,我们再执行,change函数里也修改了这个地址的内容,所以会中断下来,F5走起。如图: 此处由于change中调用了strcpy,而strcpy中调用了strcat函数,strcat函数内部修改了szTest的第一个字节内容,所以中断下来。此时打印调用堆栈如下: 可以在调用堆栈中发现是change函数中改变了szTest的内容,但是为什么中间没有strcpy函数调用这一栈帧呢?我们调试的debug版本,关闭了所有优化,调用堆栈中为什么没有strcpy?此问题,我不做回答,其实用前面的调用堆栈形成一节的知识,是可以解决这个问题的。快快思考吧。。。^_^本文讲述了两个比较简单的断点使用方法,还有一个比较复杂的断点叫条件断点,以后我们再讲,调试基础系列教程里不做讲解,有兴趣的童鞋可以自行百度。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)