??xml version="1.0" encoding="utf-8" standalone="yes"?> 2、把优化disableQ否则容易生代码地址偏移Q?/p> 3、用program database产生PDB文gQ?/p> 4、将dll, exe, pdbQ加上版本号Q防止下ơ开发编译时把老的PDB冲掉?/p> 5、发布的版本Q输出到一个固定目录,防止Zؓ删除DebugQ?RelaseD丢失 6、对于已l稳定的版本dll, exeQ编译时Q可优化开启。(虽然偶然有代码地址偏移Q但是还可以接受Q?/p> 7、生成MiniDumpӞ用MiniDumpWithDataSegs方式Q这P全局变量的g可查?/p> q个Ҏ需要自己动手往工程里添加代码了。要实现上面的想法,需要做两g事情Q?span lang="EN-US">1、需要在crash时有Z对程序堆栈进行处理;2、对堆栈信息q行攉?span lang="EN-US">
1?span lang="EN-US">SetUnhandleExceptionFilter函数
Windowsq_下的C++E序异常通常可分ZU:l构化异常(Structured ExceptionQ可以理解ؓ与操作系l相关的异常Q和C++异常。对于结构化异常处理Q?span lang="EN-US">SEHQ,可以扑ֈ很多资料Q在此不l说。对?span lang="EN-US">crash错误Q一般由未被正常捕获的异常引PWindows操作pȝ提供了一?span lang="EN-US">API函数可以在程?span lang="EN-US">crash之前有机会处理这些异常,是SetUnhandleExceptionFilter函数。(C++也有一个类似函?span lang="EN-US">set_terminate可以处理未被捕获?span lang="EN-US">C++异常。)
SetUnhandleExceptionFilter函数声明如下Q?span lang="EN-US">
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
__in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
其中 LPTOP_LEVEL_EXCEPTION_FILTER 定义如下Q?span lang="EN-US">
typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
__in struct _EXCEPTION_POINTERS *ExceptionInfo
);
typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;
单来_SetUnhandleExceptionFilter允许我们讄一个自q函数作ؓ全局SEHqo函数Q当E序crash前会调用我们的函数进行处理。我们可以利用的?_EXCEPTION_POINTERS l构cd的变?span lang="EN-US">ExceptionInfoQ它包含了对异常的描qC及发生异常的U程状态,qo函数可以通过q回不同的值来让系ll运行或退出应用程序?span lang="EN-US">
关于 SetUnhandleExceptionFilter 函数的具体用法和CZ请参?span lang="EN-US">MSDN?span lang="EN-US">
2?span lang="EN-US">StackWalker
现在我们已经有机会可以在crash之前对程序状态信息进行处理了Q只需要生成ƈ保存堆栈信息大功告成了?span lang="EN-US">Windows?span lang="EN-US">dbghelp.dll库提供了一个函数可以得到当前堆栈信息:StackWalk64Q在Win2K以前版本中ؓStackWalkQ。该函数声明如下Q?span lang="EN-US">
BOOL WINAPI StackWalk64(
__in DWORD MachineType,
__in HANDLE hProcess,
__in HANDLE hThread,
__in_out LPSTACKFRAME64 StackFrame,
__in_out PVOID ContextRecord,
__in PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
__in PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
__in PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
__in PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);
该函数的具体用法可以参?span lang="EN-US">MSDN。在q里推荐一个牛人写好的StackWalkerQ可以直接拿来用Q开源的?span lang="EN-US">StackWalker提供了一个基c,l出了几个简单的接口Q可以方便地生成堆栈信息Qƈ且支持一pdVC版本Q非常好用。我们可以自己写一个子c,q载虚函数OnOutputQ就可以堆栈信息输Zؓ特定格式了?span lang="EN-US">StackWalker的地址为:http://www.codeproject.com/KB/threads/StackWalker.aspx?/span>
不过对于Release版本来说Q?span lang="EN-US">StackWalk64函数获得的堆栈信息有可能不完整。如果异常是?span lang="EN-US">MFC的模块抛出,那么获得的堆栈可能缺前面调用模块信息。另外,StackWalk64需要最新的dbghelp.dll文g支持才能工作Q要正确输出crash的函数名和行P需要要pdb文g支持。以上不x可能影响输出信息的完整性和效果Q而对于发布在外的E序Q要带上pdb文g几乎不可能,因此q个Ҏq是有缺憄Q比较适用于本地的release版本调试?span lang="EN-US">
当我们把自己?span lang="EN-US">release版本E序发布出去以后Q一般都是在用户的机器上q行。这U情况下Q对于第四种ҎQ因为需?span lang="EN-US">pdb文g才能够正生成堆栈调用的函数行号及代码行P因此Ҏ四只适用于本?span lang="EN-US">release版的调试Q否则只能生成不完整的堆栈信息。对于前三种ҎQ其实只需要用户告知崩溃地址Q然后在本地查找crash地址可以了Q但是定?span lang="EN-US">crash的过E非怸方便Q如?span lang="EN-US">crash的情冉|较多Q前三种Ҏ都不合适。而且Q前三种Ҏ均不能生成堆栈调用信息,对于debug的作用有限?span lang="EN-US">
下面我们来看一个更加完善的解决Ҏ?span lang="EN-US">
Ҏ五:SetUnhandledExceptionFilter + Minidump
SetUnhandleExceptionFilter函数我们已经介绍q了Q本Ҏ的思\q是要利用我们自q异常处理函数Q来生成minidump文g?span lang="EN-US">
1?span lang="EN-US">Minidump概念
minidumpQ小存储器{储)可以理解Z?span lang="EN-US">dump文gQ里面记录了能够帮助调试crash的最有用信息。实际上Q如果你?pȝ属?span lang="EN-US"> -> 高 -> 启动和故障恢?span lang="EN-US"> -> 讄 -> 写入调试信息 中选择?/span>内存{?span lang="EN-US">(64 KB)?/span>的话Q当pȝ意外停止旉会在C:\Windows\Minidump\路径下生成一?span lang="EN-US">.dmp后缀的文Ӟq个文g是minidump文gQ只不过q个是内核态的minidump?span lang="EN-US">
我们要生成的是用h的minidumpQ文件中包含了程序运行的模块信息、线E信息、堆栈调用信息等。而且ZW合?span lang="EN-US">mini的特性,dump文g是压~过的?span lang="EN-US">
2、生?span lang="EN-US">minidump文g
生成minidump文g?span lang="EN-US">API函数?span lang="EN-US">MiniDumpWriteDumpQ该函数需?span lang="EN-US">dbghelp.lib支持Q其原型如下:
BOOL WINAPI MiniDumpWriteDump(
__in HANDLE hProcess,
__in DWORD ProcessId,
__in HANDLE hFile,
__in MINIDUMP_TYPE DumpType,
__in PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
__in PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
__in PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
在我们的异常处理函数中加入以下代码:
HANDLE hFile = ::CreateFile( _T("E:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if( hFile != INVALID_HANDLE_VALUE)
{
MINIDUMP_EXCEPTION_INFORMATION einfo;
einfo.ThreadId = ::GetCurrentThreadId();
einfo.ExceptionPointers = pExInfo;
einfo.ClientPointers = FALSE;
::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpNormal, &einfo, NULL, NULL);
::CloseHandle(hFile);
}
其中Q?span lang="EN-US">pExInfo变量为异常处理函?span lang="EN-US">PEXCEPTION_POINTERScd的参数。具体请参?span lang="EN-US">MSDN?span lang="EN-US">
3、调?span lang="EN-US">minidump
调试dump文g首先需?span lang="EN-US">pdb文gQ因此我?span lang="EN-US">buildE序旉要设|?span lang="EN-US"> Debug Infomation Format ?span lang="EN-US"> “Program DatabaseQ?span lang="EN-US">/ZiQ?span lang="EN-US">?/span>。其ơ,我们q要保所用的dump文g与源代码?span lang="EN-US">exe?span lang="EN-US">pdb文g版本是一致的Q这要求我们必须l护好程序版本信息?span lang="EN-US">
调试minidump最方便的环境就?span lang="EN-US">VS了,我们只要?span lang="EN-US">.dmp?span lang="EN-US">.exe?span lang="EN-US">.pdb文g攑֜一个\径下Q保证源代码文g的\径与~译时的路径一致就可以了,剩下的就?span lang="EN-US">VS帮我们完成。双?span lang="EN-US">.dmp文g或者在文g打开工程中选择“dump files?/span>Q加?span lang="EN-US">dump文gQ然后按F5q行p直接恢复crash时的现场了,你可以定?span lang="EN-US">crash的代码,可以查看调用堆栈Q可以查看线E和模块信息...一切都跟你讄断点调试一P太强大了Q看个截囑?span lang="EN-US">
需要注意的是,对于release版的E序来说Q很多代码是l过~译器优化过的,因此定位的时候可能会有所偏差Q大家可以考虑讄选项L代码优化?span lang="EN-US">
其他可以调试minidump的工兯?span lang="EN-US">WinDbg{,大家可以查阅相关资料?span lang="EN-US">
]]>
Z试q个API, 参考网上一些资?span lang="EN-US">, 写了一个简单的C++ E序. 目的是当有异常发生的时?span lang="EN-US">, 自动生成Dump文g供之后的分析. 有了Dump文g, 我们可以?span lang="EN-US">WinDBG{调试器来分析异常发生时的情?span lang="EN-US">. 其实q个功能很多软g都有, 比如QQ, 兽世界, {等. 它们在出C异常的时候会弹出一个对话框, 让用戯入异常发生时的情?span lang="EN-US">, 然后把异常的dump文g?span lang="EN-US">email发回, 供开发者们分析修改bug.
不过有一?span lang="EN-US">, q里需要程序的调试W号文g(pdb文g). 对于Debug版来?span lang="EN-US">, 是生成的, 但是Release版来说默认是不生成的. 可以讄VC的编译器, 让它?span lang="EN-US">Release版的时候也生成调试信息. q带来一个新的问?span lang="EN-US">, 因ؓ.pdb里面是保存了源文件的信息?span lang="EN-US">, Z避免泄密, 可以采用VS中的CVPack工具, 从中去除敏感的信?span lang="EN-US">.
E序需要?b>Dbghelp.h ?Dbghelp.lib . 它们可以?span lang="EN-US">MSDN扑ֈ.
//最主要的函?span lang="EN-US">, 生成Dump
static void DumpMiniDump(HANDLE hFile, PEXCEPTION_POINTERS excpInfo)
{
if (excpInfo == NULL) //如果没有传入异常, 比如是在E序里面调用?span lang="EN-US">, 生成一个异?/span>
{
// Generate exception to get proper context in dump
__try
{
OutputDebugString(_T("raising exception\r\n"));
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
}
__except(DumpMiniDump(hFile, GetExceptionInformation()),
EXCEPTION_CONTINUE_EXECUTION)
{
}
}
else
{
OutputDebugString(_T("writing minidump\r\n"));
MINIDUMP_EXCEPTION_INFORMATION eInfo;
eInfo.ThreadId = GetCurrentThreadId(); //把需要的信息添进?/span>
eInfo.ExceptionPointers = excpInfo;
eInfo.ClientPointers = FALSE;
// 调用, 生成Dump. 98不支?span lang="EN-US">
// Dump的类型是型?span lang="EN-US">, 节省I间. 可以参?span lang="EN-US">MSDN生成更详l的Dump.
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
MiniDumpNormal,
excpInfo ? &eInfo : NULL,
NULL,
NULL);
}
}
下面的是E序部分:
int _tmain(int argc, _TCHAR* argv[])
{
// 创徏一?span lang="EN-US">Dump文g
HANDLE hFile = CreateFile( _T("MiniDump.dmp"), GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
int code;
__try
{
// 把自己实现的main函数包装一?span lang="EN-US">, 攑֜try .. except 块中. q样出现了异常可以自动生?span lang="EN-US">dump
main_wrapper(argc, argv);
}
__except( code=GetExceptionCode(), DumpMiniDump(hFile, GetExceptionInformation() ), EXCEPTION_EXECUTE_HANDLER ) //出现了异?span lang="EN-US">, 记录异常?span lang="EN-US">code, 生成dump!!
{
printf("%x\n", code);
wchar_t msg[512];
wsprintf(msg, L"Exception happened. Exception code is %x", code);
MessageBox(NULL, msg, L"Exception", MB_OK); //昄消息l用?/span>
}
CloseHandle( hFile ); //关闭Dump文g
getchar();
return 0;
}
最下面是两个测试的函数, main_wrapper函数调?span lang="EN-US">test1, test1会生成一个异?span lang="EN-US">(非法内存?span lang="EN-US">)
void test1() {
int *p;
p = (int*)0x100;
*p = 0; //?span lang="EN-US">0x100地址, q个是非法的
}
void main_wrapper(int argc, _TCHAR* argv[]) {
test1();
}
q行, 异常被捕获了:
同时, dump文g也生成了:
?span lang="EN-US">WinDBG打开Dump文g, 可以清楚的看出异常出现的情况:
从中可以比较清楚的看到异常发生的情况(Exception code), 异常出现的地址(test1函数, 偏移0x28). 因ؓq次试的是Debug?span lang="EN-US">, 有保存了源代码的.pdb文g, 所?span lang="EN-US">WinDbg把源代码也列出来?span lang="EN-US">. q样可以非常Ҏ的发现问?span lang="EN-US">.
============================================
参?span lang="EN-US">:
DbgHelp中的DumpAPI例子: http://www.debuginfo.com/examples/src/effminidumps/MiniDump.cpp
CrashReport: E序出现异常的时候显C发送错误的对话?span lang="EN-US">, q把Dump文g发送到指定的地址. http://code.google.com/p/crashrpt/
XCrashReport: 与上面的cM的一个开源项?span lang="EN-US">. http://www.codeproject.com/KB/debug/XCrashReportPt1.aspx
W号文gQ?span lang="EN-US">Symbol FilesQ是一个数据信息文Ӟ它包含了应用E序二进制文Ӟ比如Q?span lang="EN-US">EXE?span lang="EN-US">DLL{)调试信息Q专门用来作调试之用Q最l生成的可执行文件在q行时ƈ不需要这个符hӞ但你的程序中所有的变量信息都记录在q个文g中。所以调试应用程序时Q这个文件是非常重要的。用 Visual C++ ?span lang="EN-US"> WinDbg 调试E序旉要用到这个文件?span lang="EN-US">
?span lang="EN-US"> Windows pȝ中,W号文g?span lang="EN-US"> .pdb 为扩展名Q比如:每个 Windows 操作pȝ下有一?span lang="EN-US"> GDI32.dll 文gQ编译器在编译该 DLL 的时候会产生一?span lang="EN-US"> GDI32.pdb 文gQ一旦你拥有了这?span lang="EN-US"> PDB 文gQ那么便可以用它来调试ƈ跟踪?span lang="EN-US"> GDI32.dll 内部。该文g和二q制文g的编译版本密切相养I比如修改?span lang="EN-US"> DLL 的输出函敎ͼ再编译该 DLLQ那么原先的 PDB 文gp时了Q不能再用老的 PDB 文g来做调试工作,而必M用最新的 PDB 文g版本?span lang="EN-US">
Visual C++ ~译代码后会?span lang="EN-US"> Debug 或?span lang="EN-US"> Release 目录下生成一?span lang="EN-US"> PDB 文g。一般情况下Q符h件包括以下的数据信息Q?span lang="EN-US">
二、如何得到和安装W号文g?
安装W号文g的注意事:
如果是手动安装符hӞ有一点很重要Q那是宿主机(Hostt ComputerQ上的符h件必M目标机器Q?span lang="EN-US">Target ComputerQ上?span lang="EN-US"> Windows 版本相匹配?span lang="EN-US">
q里所谓的宿主机指的是q行调试会话的机器,在典型的双系l调试会话环境中Q宿L可以是连接到目标机器的Q何机器。目标机器指的是发生软glg、系l服务、应用程序或操作pȝq行p|的机器。也x需要被调试的机器,它是调试会话x的焦炏V目标机器可以近在咫,也可以位于完全不同的地方。有时我们也目标机器称之ؓ—?/span>被调试者(debuggeeQ,那么与之对应Q宿L则可以称试者(debuggerQ?span lang="EN-US">
三、在 Visual C++ 使用W号文g的方?/span>
?span lang="EN-US"> Visual C++ 6.0 中的使用ҎQ?span lang="EN-US">
?span lang="EN-US"> Visual C++ .NET 2003 中的使用ҎQ?span lang="EN-US">
四、如何?span lang="EN-US"> Release 版本二进制文件对应的 PDB 文g?
?span lang="EN-US"> Visual C++ 6.0 中的ҎQ?span lang="EN-US">
?span lang="EN-US"> Visual C++ .NET 2003 中的ҎQ?span lang="EN-US">
五、关?span lang="EN-US"> Free BuildQ也U?span lang="EN-US"> Retail BuildQ和 Checked BuildQ也U?span lang="EN-US"> Debug BuildQ?/span>
每个Z NT 操作pȝ有两U不同的E序生成模式Q即Q?span lang="EN-US">
Free Build 生成的是最l用L本,针对生成的二q制文gq行了彻底的优化Q禁用了调试断言Qƈ剥离了调试信息。这样一来可执行程序文件更,加蝲更快Q用的内存也更?span lang="EN-US">
Checked Build 生成的是试和调试版本。它包含额外?span lang="EN-US"> Free Build 所没有的错误检查,参数验证和调试信息,Checked Build 有助于隔d跟踪可能D不可预见的行为的问题Q比如内存溢出,不正的讑֤配置。虽?span lang="EN-US"> Checked Build 提供了额外的保护Q但?span lang="EN-US"> Free Build 比较Q它需要更多的内存开销和磁盘空间。由于可执行E序包含W号调试信息Q调试时要执行附加的代码、参数检查和输出调试诊断信息Q从而导致性能下降?span lang="EN-US">
六、系l符h件的更新Ҏ
pȝW号文g?span lang="EN-US"> Windows 操作pȝ依赖的那几个重要?span lang="EN-US"> DLL/SYS 和可执行文g对应的符hӞ常见的比如:gdi32.dll?span lang="EN-US">Kernel32.dll?span lang="EN-US">Kerberos.dll?span lang="EN-US">psapi.dll?span lang="EN-US">user32.dll{,使用 WinDbg 调试Ӟ你就会发现系l符h?span lang="EN-US">(PDB)有多重要Q这些文仉与本地的 OS 密切相关Q比如,Windows 2000 打了SP补丁的话Q那么必L新系l符h件才能进行相兌试,原来的符h件与打补丁后的系l就会不匚wQ怎么办呢? 可以通过|络来更斎ͼ象下面这样在 WinDbg ?span lang="EN-US"> Symbols Path 里面输入路径Q?span lang="EN-US">
SRV*D:\Symbols\websymbols*http://msdl.microsoft.com/download/symbols
Q斜体部分是你在本地保存W号文g的\径)
如果你不是通过代理上网Q那么在你用 WinDbg 打开一个被调试E序后,输入 symchk 回RQ?span lang="EN-US">WinDbg ׃自动的连到微软的|站Ҏ你的机器的情冉|新的 PDB 文gQƈ它保存在上面斜体部分指定的本地路径里,q样你就可以保你的W号文g版本和你机器上的文g版本一致?span lang="EN-US">
如果你是通过代理上网那么你需要配|?span lang="EN-US"> IE 的连接设|。具体方法恕不赘a
void CForDebugDlg::OnOK()
{
for (int i = 0; i < 1000; i++) //A
{
int k = i * 10 - 2; //B
SendTo(k); //C
int tmp = DoSome(i); //D
int j = i / tmp; //E
}
}
执行此函敎ͼE序崩溃?span lang="EN-US">E行,发现此时tmp?span lang="EN-US">0Q假?span lang="EN-US">tmp本不应该?span lang="EN-US">0Q怎么q个时候ؓ0呢?所以最好能够跟t此ơ@环时DoSome函数是如何运行的Q但׃是在循环体内Q如果在E行设|断点,可能需要按F5Q?span lang="EN-US">GOQ许多次。这h要不停的按,很痛苦。?span lang="EN-US">VC6断点修饰条g可以轻易解x问题。步骤如下?span lang="EN-US">
1 Ctrl+B打开断点讄框,如下图:
Figure 1讄高位置断点
2 然后选择D行所在的断点Q然后点?span lang="EN-US">condition按钮Q在弹出对话框的最下面一个编辑框中输入一个很大数目,具体视应用而定Q这?span lang="EN-US">1000够了?span lang="EN-US">
3 ?span lang="EN-US">F5重新q行E序Q程序中断?span lang="EN-US">Ctrl+B打开断点框,发现此断点后跟随一串说明:...487 times remaining。意思是q剩?span lang="EN-US">487ơ没有执行,那就是说执行?span lang="EN-US">513Q?span lang="EN-US">1000Q?span lang="EN-US">487Q次时候出错的。因此,我们按步?span lang="EN-US">2所Ԍ更改此断点的skipơ数,?span lang="EN-US">1000改ؓ513?span lang="EN-US">
4 再次重新q行E序Q程序执行了513ơ@环,然后自动停在断点处。这Ӟ我们可以仔l查?span lang="EN-US">DoSome是如何返?span lang="EN-US">0的。这P你就避免了手指的痛苦Q节省了旉?span lang="EN-US">
再看位置断点其他修饰条g。如Figure 1所C,?span lang="EN-US">“Enter the expression to be evaluated:?/span>下面Q可以输入一些条Ӟ当这些条件满xQ断Ҏ启动。譬如,刚才的程序,我们需?span lang="EN-US">i?span lang="EN-US">100时程序停下来Q我们就可以输入在编辑框中输?span lang="EN-US">“i==
另外Q如果在此编辑框中如果只输入变量名称Q则变量发生改变Ӟ断点才会启动。这Ҏ一个变量何时被修改很方便,特别对一些大E序?span lang="EN-US">
用好位置断点的修饰条Ӟ可以大大方便解决某些问题?span lang="EN-US">
?数据断点Q?span lang="EN-US">Data BreakpointQ?/b>
软g调试q程中,有时会发C些数据会莫名其妙的被修改掉(如一些数l的界写导致覆盖了另外的变量)Q找Z处代码导致这块内存被更改是一件棘手的事情Q如果没有调试器的帮助)。恰当运用数据断点可以快速帮你定位何时何处这个数据被修改。譬如下面一D늨序:
#include "stdafx.h"
#include
int main(int argc, char* argv[])
{
char szName1[10];
char szName2[4];
strcpy(szName1,"shenzhen");
printf("%s\n", szName1); //A
strcpy(szName2, "vckbase"); //B
printf("%s\n", szName1);
printf("%s\n", szName2);
return 0;
}
q段E序的输出是
szName1: shenzhen
szName1: ase
szName2: vckbase
szName1何时被修改呢Q因为没有明昄修改szName1代码。我们可以首先在A行设|普通断点,F5q行E序Q程序停?span lang="EN-US">A行。然后我们再讄一个数据断炏V如下图Q?span lang="EN-US">
Figure 2 数据断点
F5l箋q行Q程序停?span lang="EN-US">B行,说明B处代码修改了szName1?span lang="EN-US">B处明明没有修?span lang="EN-US">szName1呀Q但调试器指明是q一行,一般不会错Q所以还是静下心来看看程序,哦,你发CQ?span lang="EN-US">szName2只有4个字节,?span lang="EN-US">strcpy?span lang="EN-US">7个字节,所以覆写了szName1?span lang="EN-US">
数据断点不只是对变量改变有效Q还可以讄变量是否{于某个倹{譬如,你可以将Figure 2中红圈处改ؓ条g”szName2[0]==''''y''''?那么?span lang="EN-US">szName2W一个字Wؓy时断点就会启动?span lang="EN-US">
可以看出Q数据断点相对位|断点一个很大的区别是不用明指明在哪一行代码设|断炏V?span lang="EN-US">
?其他
1 ?span lang="EN-US">call stackH口中设|断点,选择某个函数Q按F9讄一个断炏V这样可以从深层ơ的函数调用中迅速返回到需要的函数?span lang="EN-US">
2 Set Next StateMent命oQ?span lang="EN-US">debugq程中,右键菜单中的命oQ?span lang="EN-US">
此命令的作用是将E序的指令指针(EIPQ指向不同的代码行。譬如,你正在调试上面那D代码,q行?span lang="EN-US">A行,但你不愿意运?span lang="EN-US">B行和C行代码,q时Q你可以在D行,右键Q然?span lang="EN-US">“Set Next StateMent?/span>。调试器׃会执?span lang="EN-US">B?span lang="EN-US">C行。只要在同一函数内,此指令就可以随意跛_或蟩后执行。灵zM用此功能可以大量节省调试旉?span lang="EN-US">
3 watchH口
watchH口支持丰富的数据格式化功能。如输入0x65,uQ则在右栏显C?span lang="EN-US">101?span lang="EN-US">
实时昄windows API调用的错误:在左栏输?span lang="EN-US">@err,hr?span lang="EN-US">
?span lang="EN-US">watchH口中调用函数。提醒一下,调用完函数后马上?span lang="EN-US">watchH口中清除它Q否则,单步调试时每一步调试器都会调用此函数?span lang="EN-US">
4 messages断点不怎么实用。基本上可以用前面讲q的断点代替?span lang="EN-US">
ȝ
调试最重要的还是你要思考,要猜你的程序可能出错的地方Q然后运用你的调试器来证实你的猜。而熟l用上面这些技巧无疑会加快q个q程。最后,大家如果有关于调试方面的问题Q我乐意参与探讨
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
键中的内ҎҎ处理的异帔R取一U统一的行为和Ҏ?span lang=EN-US>
该键中的
Auto: 军_是自动运行调试器Q还是让用户军_
Debugger: 是指向调试器可执行文件的路径的字W串
-------------------------------------------------------------------------------------------
据观察,在我装了vs2005之后
"Auto"="1"
"Debugger"="\"C:\\WINDOWS\\system32\\vsjitdebugger.exe\" -p %ld -e %ld"
表示自动运行调试器
-------------------------------------------------------------------------------------------
?span lang=EN-US>Windows默认的调试器?span lang=EN-US>Dr.Watson
它捕获应用程序应用程序崩溃状态,q记录日志文ӞDrwtsn32.logQ和q程崩溃转储文gQ?span lang=EN-US>User.dmpQ?span lang=EN-US>
要想查看q|?span lang=EN-US>Dr.WatsonQ可以在q行中输?span lang=EN-US>Drwtsn32.exe?span lang=EN-US>
要想恢复pȝ初始状态则可以q行 Drwtsn32 -i
q样注册表的那两个g变成
"Auto"="1"
"Debugger"="drwtsn32 -p %ld -e %ld -g"
-------------------------------------------------------------------------------------------
3、发生异常的时候ؓ了保留现场堆栈需要冻l进E内其他U程Q所使用的操作越越好?span lang=EN-US>
4、开启另外一个进E对异常q程q行MiniDump?span lang=EN-US>
5、如果要分析崩溃地址是否在某个模块,最好在分析q程里做。或者在模块加蝲时计模块区域以便在异常时快速定位。判断崩溃是否和某个模块相关Q只能通过分析堆栈里的模块来识别,使用StackWalk函数?span lang=EN-US>StackWalk函数里所使用?span lang=EN-US>context参数要用异常参数里?span lang=EN-US>ContextRecordQ这栯分析在崩溃的时候的堆栈Q而不会受后面操作的媄响?span lang=EN-US>
6、如果希望自己是W一个被调用Q可以在挂链之后?span lang=EN-US>APIHook讄SetUnhandledExceptionFilterq回I?span lang=EN-US>
7、在ȝU程的时候,不要?span lang=EN-US>MessageBoxQ会D当前U程因ؓ其他U程挂v而阻歅R?span lang=EN-US>
8、异帔R?span lang=EN-US>SetUnHandledExceptionFliter之外Q还?span lang=EN-US>AddVectoredExceptionHandlerQ相对于前者,后者的优点是能够让异常按照铑ּ触发Q不会被中间节点断开。但?span lang=EN-US>AddVectoredExceptionHandler无法?span lang=EN-US>2K下用。。?span lang=EN-US>
1?/span> E序~译选项
2?/span> 利用VS2005 分析dump文g
3?/span> 常见问题讨论
一?/span>
E序~译选项PDB files contains all debug information like type definition and function prototype. When application crashes, we need the PDB files to analyze the root cause, so make sure these PDB files will be created when building it. You must do the following setting:
C/C++\General\Debug Information Format=Program Database (/Zi).
?/span>1.1 调试信息格式
Linker\Debugging\Generate Program Database File=”Name and location of your PDB files”
?/span>1.2 PDB文g输出路径
PDB文g路径最好设|在同一个文件夹中,q样方便dmp文g调试时调用?/span>
调试Ӟ所有的PDB文g和源文g必须严格匚wQ?/span>the PDB files should be the one generated by build the source codeQ,q存储在一个安全的位置。当客户报告了一个错误时Q你需要这些文件来帮忙以便定位错误于源代码中ƈ解决问题?/span>
二?/span>
VS2005 分析dump文gIn this simple application, there is an unhandled Access Violation Reading exception, because GetNameFromDatabase returns a NULL pointer, and this pointer is passed into IsPrefix and then it’s used directly without NULL pointer checking.
?/span>1.3 演示代码
利用Release模式~译该测试程序,在客h上运行该E序Q将ҎNTSD讄生成相对应的DMP格式文g?/span>
可以使用Visual Studio.Net?/span>NTSD或是其他的调试工具对DMP格式文gq行分析?/span>
l Start Visual Studio.Net
Click File\Open Solution and make sure the files of type is *.dmp then click Open.
?/span>1.3 Open Dump File (GUI)
l Set Symbol Path
Click Tools\Options, Debugging\SymbolsQ增?/span>PDB文g路径。若调试的程序需要微软基库的PDB信息Q可以增加一个\径ؓQ?/span>
http://msdl.microsoft.com/download/symbols
在界面下?/span>Cache Symbol From symbol…选择本地存储q些Symbols的\径?/span>
?/span>1.4 Symbol Path
如果DMP文g没有攑օ本nPDB文g所在目录,也可以在此处增加一个本地目录。点OK后,VS2005从|络中下载所需要的SymbolsQ需要等待一D|间。如果是多次调试同一个程序错误所生成?/span>DMP文gQ可以在对话框中选择“Search the above locations only when symbols are loaded manually”。从而可以节省网l带宽?/span>
l Set Source code path
Open Solution Property Pages and set the source code path.
?/span>1.5 属性菜?/span>
?/span>1.6 Debug Source Files
l Start to Debug the Dump File
Click the Debug menu, it will ask you to save as a solution, save it. Then it will go to the line which caused the crash of your application.
?/span>1.7 调试H口Q定位到源代?/span>
三?/span>
常见问题讨论1?/span> Dump文g攑֜哪里Q?/span>
Dump文g不用非要攑֜你编译出来的位置Q你完全可以建立一个新的文件夹来放它。但若不是存攑֜~译出来的位|,需要将~译生成?/span>PDB文g拯?/span>Dump文g目录Q或是利?/span>VS2005打开Dump文g后,讄PDB文g路径。参照图1.4?/span>
2?/span> 如何恢复当时的现场?
可能你要问,怎么可能Q这?span lang=EN-US>dump文g可是用户发给我的Q我不可能去用户安调试吧?q个恢复现场可不是指的非要到那台机器上去Q而是要把产生dump文g对应的二q制文g拿到?span lang=EN-US>
但是恢复现场需要所有的二进制文仉要对应,你一定要有导致用户崩溃的那些Exe?span lang=EN-US>DLL。既然是你发布的E序Q?span lang=EN-US>Exe文g当然你会有。所以这里只考虑DLLp了?span lang=EN-US>
Dump文g中记录了所?span lang=EN-US>DLL文g的版本号和时间戳Q所以你一定可以同q某U途径拿到它。如果你能从用户那里拿到最好,如果不方便,用户不可能用的是我们q_不常用的操作pȝQ所以找个有对应pȝ的机器一般都会有。但是记住不仅是文g名称要一_q要核对版本和时间戳Q如果不同一h有办法用?span lang=EN-US>
如果客户用了某个Ҏ的补丁怎么办?
其实q个问题也很好解冻I只要它不ȝ阅读堆栈Q就不用它Q调?span lang=EN-US>Dump和运行程序不一P~少一两个DLL没有M问题?span lang=EN-US>
3?/span> 如果真的需要怎么办?
W号文g现在主要是指PDB文g?span lang=EN-US>
如果没有W号文gQ那么调试的时候可能导致堆栈错误?span lang=EN-US>
如果你丢׃q个发布版本中你~译出来的那?/span>exe?/span>DLL?/span>PDBQ那么这个损失是严重的,重新~译出来的版本是不能使用的?/span>
我自qDLL都有了,可是~的是系l的DLL的对?/span>PDB文g怎么办??/span>1.4中已l介l了Ҏ。微软在它的W号数据库上为我们提供了所有的PDB文gQ还有部分非关键DLL。设|好后程序将自动下蝲需要的PDB?/span>DLL文g?/span>
4?/span> 拿到需要的文g了,q些文g应该攑֜哪里Q?/span>
W号数据库中的文件不用动Q把其它?/span>exe?/span>DLL?/span>PDB文g攑֜dump文g目录里就行了?/span>
5?/span> 我用的是VS2005Q明明有源代码,Z么显CZ了?
q个?/span>dump调试的最头痛问题Q代码可能已l改q了Q即使你?/span>SVN拿到当时的版本,旉戳也是错的,VS2005是不让你显CZ码。其实只要在
Tools\OptionsQ?/span>Debugging\General中去?/span>
Require source files to exactly match the original version的复选就行了?/span>
前言Q之前介l了利用VS2005q行Dump文g的调试,功能非常强大。但VS2005是一个大E序Q本文将讨论利用NTSD?span lang=EN-US>Command Line 实现Dump 文g的调试?span lang=EN-US>
1?/span> 载入DMP格式文g
利用CMD打开命o行窗口,切换?span lang=EN-US>NTSD所在目录。利用命令蝲?span lang=EN-US>DMP文gQ?span lang=EN-US>
ntsd –z dumpfileName –y symbolPath –srcpath sourcecodePath
dumpfileNameQ?span lang=EN-US>DMP格式文g路径
symbolPathQ?span lang=EN-US>PDB文g路径
sourcecodePathQ程序的源代码\?span lang=EN-US>
如图1.1Q若载入成功Q将弹出一?span lang=EN-US>NTSDH口Q如?span lang=EN-US>1.2?span lang=EN-US>
?span lang=EN-US>1.1 载入DMP格式文g
?span lang=EN-US>1.2 NTSD界面
U色的圈中显CZBUG的原因,函数IsPrefix 存在错误?/span>0x36是错误相对于函数的偏Ud{这个错误是一?span lang=EN-GB>Access Violation的异常,异常地址?span lang=EN-GB>00401036?/span>
NoteQ?/span>
l 若\径中存在I格Q需要用在\径前后加上引受?/span>
l 若提C错误,Ҏ提示查看是否׃dbghelp.dll不存在导_安装的调试工具包中存在该动态库?/span>
2?/span> 定位与源代码
利用如下命o可以定位错误到源代码中:
lsp –a 500
lsa .
W一行命令用于设|显C的源代码行敎ͼW二行命令将错误定位与错误行。如图:
?span lang=EN-US>1.3 Analysis Result: Line of Source Code Causing Crash
从图中可以看出,W?span lang=EN-US>13hDE序错误的位|。如果希望看到变量的|使用命oQ?span lang=EN-US>
x
若需要详l查看某个变量的|使用命oQ?span lang=EN-US>
?? variableName
?/span>1.4 Analysis Result: Value of Variable Causing Crash
Module Address of Your Application(昄E序模块地址)
Command:
x *!
?span lang=EN-GB>1.5 Application Module Information
It shows all the module address in your application.
Crash Stack TraceQ显C程序调用堆栈)
If you want to trace the stack of application, use this command:
Command:
kb
?span lang=EN-GB>1.6 Application Stack Trace
It shows that function main invokes function IsPrefix then application crashes.
For more commands of NTSD, refer to debugger.chm in Microsoft Debugger Tools package.
1?/span> 打开Dump格式文g
打开WinDbgQ通过菜单[File] à [Open Crash dump] 选择dump文g打开Q也可通过CMD打开Dos命oH口Q切换到WinDbg所在目录,利用命oQ?span lang=EN-US>
WinDbg –z “D:\Lines
-z表示路径
?span lang=EN-US>1.1 利用WinDbg打开dump文g
载入dump文g昄如图Q?span lang=EN-US>
?span lang=EN-US>1.2 WinDbg界面
2?/span> 分析dump文g
若生成的dump文g在本机,dump文g中将包含调试需要的PDB文g及源代码路径Q若不在本机Q可以通过WinDbg菜单[File] à [Symbol File path] ?[Source File Path] 分别讄PDB文g路径和源代码路径。如果程序涉及到DLLQ需要将EXE?span lang=EN-US>DLL所有涉及的PDB、源代码路径都包括。用命令:
!analyze –v
分?span lang=EN-US>dump文gQƈ昄E序崩溃处于的代码行Q?span lang=EN-US>
?span lang=EN-US>1.3 分析dump 文g
如果你的E序什么都不干. 那么在程序出错的时?span lang=EN-US>. drwtsn32.exe 会写一?span lang=EN-US> userdump. ?span lang=EN-US> drwtsn32 有些~点. 比如只能写一?span lang=EN-US> dump 文g. 后面的崩溃写 dump 文g时会覆盖前面?span lang=EN-US>. win2000 下的 drwtsn32 只能写旧式的 dump 文g(往往寸比较?span lang=EN-US>). 有一文章论q的比较清楚:
http://www.debuginfo.com/articles/ntsdwatson.html
使用 ntsd 代替 drwtsn32. ?span lang=EN-US> ntsd 的缺点就是需要安装最新的 windbg. q是一个硬?span lang=EN-US>. 在看?span lang=EN-US> debuginfo.com 的另一文?span lang=EN-US>:
http://www.debuginfo.com/articles/effminidumps.html
之后, 我选择的是在程序出错的时候调?span lang=EN-US> api 自己?span lang=EN-US> minidump. 克服?span lang=EN-US> drwtsn32, ntsd 的缺?span lang=EN-US>.
封装好的函数放C一个头文g?span lang=EN-US>, 包含卛_. 使用的方法很?span lang=EN-US>:
#include <windows.h>
#include "minidump.h"
LONG __stdcall MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
{
CreateMiniDump(pExceptionInfo, "c:\\user.dmp");
return EXCEPTION_EXECUTE_HANDLER;
}
void main()
{
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
*(int*)0=0; // AV
}
q样好?span lang=EN-US>. 注意安装臛_ xp 以上?span lang=EN-US> sdk.
q里用到了一?span lang=EN-US> api SetUnhandledExceptionFilter(), 如果不明白可以搜一?span lang=EN-US> msdn.
q行例子E序出错退Z?span lang=EN-US>, 得C c:\\user.dmp. 可以?span lang=EN-US> windbg {调试器来分析了.
代码从这里下?http://nicoster.googlepages.com/minidump.rar
Q?span lang=EN-US>1Q?/span> 调用abort函数Qƈ且设|了_CALL_REPORTFAULT选项Q这个选项?span lang=EN-US>Release版本是默认设|的Q?span lang=EN-US>
Q?span lang=EN-US>2Q?/span> 启用了运行时安全查选项Qƈ且在软gq行时检查出安全性错误,例如出现~存溢出。(安全查选项 /GS 默认也是打开的)
Q?span lang=EN-US>3Q?/span> 遇到_invalid_parameter错误Q而应用程序又没有d调用
_set_invalid_parameter_handler讄错误捕获函数?span lang=EN-US>
所以结论是Q?span lang=EN-US>VS2005Q?span lang=EN-US>VC8Q编译的E序Q许多错误都不能?span lang=EN-US>SetUnhandledExceptionFilter捕获到。这?span lang=EN-US>CRT相对于前面版本的一个比较大的改变,但是很遗憾,Microsoft却没有在相应的文档明指出?span lang=EN-US>
解决Ҏ
之所以应用程序捕获不到那些异常,原因是因为新版本?span lang=EN-US>CRT实现在异常处理中强制删除所有应用程序先前设|的捕获函数Q如下所C:
/* Make sure any filter already in place is deleted. */
SetUnhandledExceptionFilter(NULL);
UnhandledExceptionFilter(&ExceptionPointers);
解决Ҏ是拦?span lang=EN-US>CRT调用SetUnhandledExceptionFilter函数Q之无效。在X86q_下,可以使用以下代码?span lang=EN-US>
#ifndef _M_IX86
#error "The following code only works for x86!"
#endif
void DisableSetUnhandledExceptionFilter()
{
void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),
"SetUnhandledExceptionFilter");
if (addr)
{
unsigned char code[16];
int size = 0;
code[size++] = 0x33;
code[size++] = 0xC0;
code[size++] = 0xC2;
code[size++] = 0x04;
code[size++] = 0x00;
DWORD dwOldFlag, dwTempFlag;
VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
}
}
在设|自q异常处理函数后,调用DisableSetUnhandledExceptionFilter止CRT讄卛_?span lang=EN-US>
其它讨论
上面通过讄api hookQ解决了?span lang=EN-US>VS2005上的异常捕获问题Q这U虽然不是那?#8220;q净”的解x案,是目前唯一单有效的方式?span lang=EN-US>
虽然也可以通过_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT), signal(SIGABRT, ...), ?span lang=EN-US>_set_invalid_parameter_handler(...) 解决Q?span lang=EN-US>1Q(3Q,但是对于Q?span lang=EN-US>2Q,讄api hook是唯一的方式?span lang=EN-US>