Visual Studio调试之断点技巧篇补遗
讲完Visual Studio调试之断点技巧篇以后,翻翻以前看得一些资料和自己写的一些文章,发现还有几个关于中断程序的技巧在前面的文章里面遗漏了,决定还是在这里总结一下。当然啦,如果你知道这些技巧,忽略这篇文章好了,:)
在程序启动的时候将调试器附加上去
可能有人会对这个问题有一些争议,因为大部分情况下我们只需要在调试器(Debugger)里面直接启动被调试程序(Debuggee)就可以在程序启动前调试程序了。
但有些情况下,你是不能控制被调试程序(Debuggee)在什么时候启动的。例如在DCOM环境里面,DCOM客户端(Client)可以通过调用CoCreateInstanceEx(…, CTX_LOCAL_SERVER, …)启动DCOM服务器(Server),启动DCOM服务器的过程是在COM库中进行的, 你没有办法将在DCOM服务器的WinMain函数之前将你的调试器附加(Attach)上去。Windows提供了一个功能就是在一个程序启动的时候,自动将设置好的调试器附加到这个新启动的程序上去。这里我就是要介绍这个功能:
或者说你需要调试一个Windows服务的启动部分,在服务启动的时候,让调试器附加上去岂不是比在Start()函数里面打印很多跟踪信息要好很多?
在程序启动的时候将调试器附加上去实际上是Windows操作系统提供的功能,呃……当然,调试器之所以能工作,也是CPU和操作系统通力合作的结果,:)。因此你需要修改一些操作系统里面的设置:
1. 打开注册表编辑器(regedit.exe).
2. 找到键值HKEY_LOCAL_MACHINE"SOFTWARE"Microsoft"Windows NT"CurrentVersion"Image File Execution Options"。
3. 新建一个键(Key),键名就是你要调试的程序的文件名,例如notepad.exe。
4. 然后在这个新建的键值(Key)下,在我们的例子里,这个键值是notepad.exe。新建一个字符串值(String Value)Debugger,值设置为你喜欢的调试器:
a. 如果选择Visual Studio的话,就是D:"Program Files"Microsoft Visual Studio 9.0"Common7"IDE"devenv.exe(假设你的Visual Studio安装在D盘)
b. 选择Windbg的话,就是c:"debuggers"windbg.exe(假设你的Windbg拷贝在c:"debuggers"目录里)。
设置好了以后,启动notepad.exe,这个时候你选择的调试器就会被Windows先启动起来,然后在notepad.exe的入口处中断。换句话说,所有的程序,都可以使用这种方法在启动的时候自动激活调试器,当然啦,如果你已经修复了你的问题,不需要调试器在程序启动之前启动的话,在注册表里面删掉那个键值就好了。
如下图所示:
如何设置验尸调试(POSTMORTEM DEBUGGER)
首先先讲一下什么叫做验尸调试,大家应该都有这个体验,当一个程序突然崩溃的时候,Windows会不失时机弹出一个对话框,问你是否要发送错误报告给微软。其实这个对话框叫做Watson, 是Windows自带的一个非常简单的调试器,它的工作就是将程序的内存拷贝下来保存到一个文件当中。这个过程就是验尸调试的准备步骤,因为程序加载进内存以后,术语叫做进程,当进程崩溃的时候,就是意外死亡嘛,当然进程正常结束那叫做自然死亡。一般我们只对意外死亡的进程比较感兴趣,就跟电视里警匪片里面描写的那样,一个无辜的配角死掉了,警察第一步总是要保护现场,带尸体回去 ,让法医验尸。进程意外死亡的时候,Windows区别进程是否是意外死亡的方式很简单,就是看进程里面是否有未处理的异常。Windows也提供了一个选项,可以让你把犯罪现场--进程发生异常的时候的内存保留下来(进程的尸体),你可以在晚一点的时候来慢慢分析这个内存(比如看一看堆栈,一些变量什么的)--这个过程就叫做验尸。
验尸调试对下面这种情况很有帮助:
1. 重现很难重现的bug,比如说你有一个bug是随机出现,但是每次出现都会把程序搞死,与其绞尽脑汁去回忆重现步骤,还不如直接把犯罪现场保留下来,慢慢分析。
启动非托管程序的验尸调试功能
跟在程序启动时将调试器附加上去类似,验尸调试实际上也是Windows提供的功能,启动验尸调试,你需要修改下面的注册表设置:
在""HKEY_LOCAL_MACHINE"Software"Microsoft"Windows NT"CurrentVersion"AeDebug里面,分别创建下面两个键:
名称 |
类型 |
键值 |
Debugger |
REG_SZ |
"C:"Windows"system32"vsjitdebugger.exe" -p %ld -e %ld |
Auto |
REG_SZ |
1 |
如图所示:
里面的键值说明一下:
1. Debugger:指定了用来执行验尸调试的调试器文件名的完整路径,-p和-e是调试器的一些命令行参数,而%ld则是一个占位符,Windows会把死亡的进程的PID替换%ld这个值(读者如果熟悉批处理编程的话,就应该知道%ld实际上是批处理程序的参数的声明方式)。
vsjitdebugger.exe是Visual Studio用来处理验尸调试的调试器名称,如果你安装了Visual Studio,VS的安装程序应该会自动为你设置好这个键。
2. Auto:如果值是1的话,那么windows就会自动在进程死亡的时候,启动调试器;如果为0的话,就会打开一个对话框问你是否要执行验尸调试—但是Windows 7如果是这个选项的话,会直接禁用验尸调试,因此我推荐将Auto的值总是设置成1。
备注:对于偏好windbg的读者,你有两个方案将Windbg设置成默认的验尸调试程序:
1. 直接执行
windbg –I
这个命令,注意I要大写。
2. 将Debugger的键值设置为:
"c:"Debuggers"windbg.exe" -p %ld -e %ld –g
设置好了以后,我们可以写一个测试程序来试一下刚才的设置:
Test.cpp
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[]) { int *p = NULL;
// 触发一个异常 *p = 0;
return 0; } |
使用命令cl.exe /Zi test.cpp编译好程序以后,执行test.exe,你应该就可以看到调试器自动在进程死亡的时候弹出来了。
启动托管程序的验尸调试功能
然而,上面的设置仅对native程序有效,如果要设置托管程序的默认验尸调试器,在""HKEY_LOCAL_MACHINE"Software"Microsoft".NETFramework里面,分别创建下面两个键:
名称 |
类型 |
键值 |
DbgManagedDebugger |
REG_SZ |
"C:"Windows"system32"vsjitdebugger.exe" PID %d APPDOM %d EXTEXT "%s" EVTHDL %d |
DbgJITDebugLaunchSetting |
REG_DWORD |
2 |
使用上面的设置,你应该可以做所有非Winform托管程序的验尸调试了。
启动Winform程序的验尸调试功能
当你的Win form程序崩溃(Crash)的时候,你会发现你设置的默认验尸调试器没有运行起来,原因是因为Win form程序默认禁用了即时调试(JIT Debug)的功能。因此要设置Win form程序的默认验尸调试器,你除了做上面的步骤以外,你还要将Win form程序的即时调试功能打开。打开的方法:
1. 修改你机器的machine.config文件,这样机器上所有的Win form程序都会将这个即时调试功能打开。在<configuration>里面添加下面一行:
<system.windows.forms jitDebugging="true" />
2. 修改单独程序的的app.config文件打开单个Win form程序的即时调试功能。在<configuration>里面添加下面一行:
<system.windows.forms jitDebugging="true" />
例如:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.windows.forms jitDebugging="true" /> </configuration> |
使用Visual Studio的RPC调试功能同时调试COM程序的客户端和服务端
参看我以前的文章:
http://blog.csdn.net/Donjuan/archive/2009/01/23/3851586.aspx