![]() |
Windows 调试指南
Windows 调试指南
第一章 跟踪 段晓冬 1.Windows跟踪语句: (1)TRACE(_T(“Warning (FunctionName):Object %s not found.\n”),objectName); 跟踪信息输出到输出窗口output window中。[调试版本中使用] (2) OutputDebugString (_T(“trace debug info!\n”));[调试版本中使用] 如果只想在调试版本中使用OutputDebugString,可以使用下面得宏来实现: #ifdef _DEBUG #define OutputTraceString(text) OutputDebugString(text) #else #define OutputTraceString(text) ((void) (0)) #endif 2.ANSI C++ 运行时刻函数库跟踪 具体有:C语言的stderr和C++语言的clog流,在Windows程序中没有任何效果! 3.VC++的C运行时刻库函数跟踪语句 ANSI C 运行时刻库函数没有跟踪语句,但是VC++的C运行时刻库函数有。可使用_RPTn或_RPTFn调试报告宏,但是你必须在程序中引用crtdbg.h,并利用C运行时刻函数库链接: _RPT0(reportType,format); _RPT1(reportType,format,arg1); _RPT2(reportType,format,arg1,arg2); _RPTF0(reportType,format); _RPTF1(reportType,format,arg1); _RPTF2(reportType,format,arg1,arg2); 例子: int a =1000,b=2000; int *p =&a; int *q = &b; _RPTF2(_CRT_WARN,"%x ,%x",p,q); //直接在outputwindow中输出 _RPTF2(_CRT_ERROR,"%x ,%x",p,q); //弹出ERROR对话框 _RPTF2( _CRT_ASSERT,"%x ,%x",p,q); //弹出AEESRT对话框 reportType:_CRT_WARN _CRT_ERROR _CRT_ASSERT 其中_CRT_WARN用于跟踪语句,_RPTFn宏报告了源码文件名和调用这些宏的行号。[调试版本中使用] 可以使用_CrtSetReportMode函数改变默认输出设置,(比如输出到调试器输出窗口,文件,消息框中),_CrtSetReportFile可以指定将报告输出到哪个文件中。 4.MFC中的跟踪语句 区别:使用TRACE宏时,需要使用_T宏来格式化参数以正确解决Unicode的校正,反之,在TRACEn类型的宏中,不必使用_T宏。 例子1-1: TRACE(_T("ssss\n")); TRACE2("%d r %d l\n",1,2); 5.使用Trace实用程序 使用Tools->MFC Tracer->Enable tracing选项打开跟踪输出功能。如果你选择了“Main message dispatch“,MFC使用下面的代码跟踪所有窗口: //from Wincore.cpp #ifdef _DEBUG if(afxTraceFlags&traceWinMsg) _AfxTraceMsg(_T(“Wndpro”),&pThreadState->m_lastSentMsg); #endif 也可以使用”WM_COMMAND dispatch”选项通过MFC命令消息的路线进行跟踪!Tracer只影响MFC跟踪语句的输出! 6.使用AfxOutputDebugString AfxOutputDebugString宏使用和OutputDebugString一样的语法。 7.使用CObiect::Dump CObject类有一个转储(dump)虚拟函数,继承于它的所有子类函数都可以重载这个函数,输出它们的值。 例1-2:用下列语句输出CObject派生类pObject的值。afxDump是预定义的全局变量CDumpContext,注意CDumpContext对最一般的内建数据类型及CObject的指针和引用支持插入操作符(<<)。有几个CObject派生的类也有定义的插入操作符,CPoint,CSize,CRect,CString,CTime和CTimeSpan。 #if _DEBUG AfxDump(pObiect); pObject->Dump(afxDump); afxDump<<pObject; #endif 例1-3: afxDump<<_T(“Warning:This object doesn’t seem right:\n”)<<pObject; 8.使用AfxDump AfxDump是MFC中相当于cerr流的跟踪语句,所以你可以直接向它输出跟踪消息。 TRACE宏由afxDump实现,afxDump由AfxOutputDebugString实现。而AfxOutputDebugString在调试版中由_RPT0宏实现。可以使用下面的方法将afxDump重定向。 #ifdef _DEBUG CFile dumpFile; //必须为全局变量 dumpFile.Open(_T("dump.log"),CFile::modeWrite|CFile::modeCreate); afxDump.m_pFile = &dumpFile; #endif 9.使用AfxDumpStack 可以使用AfxDumpStack函数输出一个调用栈: void AFXAPI AfxDumpStack(DWORD dwTarget = AFX_STACK_DUMP_TARGET_DEFAULT); 参数:dwTarget决定在调试和发布版本中输出到什么地方,可以输出到TRACE宏,OutputDebugString或到剪贴板。如果使用AFX_STACK_DUMP_TARGET_TRACE,含义是在调试版中输出到TRACE宏,而在发布版本中没有输出!如果你希望在发布版本中输出,可使用AFX_STACK_DUMP_TARGET_ODS选项,而且必须在路径中有Imagehlp.dll文件。在projectsettingsLinkCategoryDebug Both formats。 10.ATL跟踪语句 最基本的类型是AtlTrace函数: inline void_cdecl AtlTrace(LPCTSTR format……); ATLTRACE (format……); 和MFC TRACE宏一样,它使用一个512字节固定大小的缓冲区,如果它的参数需要一个大于512字节的文本缓冲区,会导致一个出错的断言。实际上,它是使用API函数OutputDebugString实现的,因此它的输出不能改变到其他目标。 ATL跟踪语句的另一个选择AtlTrace2函数: Inline void _cedecl AtlTrace2(DWORD category,UINT level,LPCTSTR format,…); ATLTRACE(category,level,format); 该函数增加了一个参数跟踪类别(category)(例如,atlTraceCOM,atlTraceWindowing和atlTraceControl)和一个参数严格级别。类别值是位掩码,ATL自身使用介于0-2的级别值,0级指最严格的级别。 和AtlTrace函数类似,AtlTrace2函数只能用在调试版本中! 11.VC++消息Pragma Pagama是一个编译时的跟踪语句,可用来警告在预处理过程中发现的潜在的编连(build)问题 12.处理长字符串:[例如处理SQL语句] (1)MFC下[只能调试版] TRACE(longString); //assert if _tcslen(longString)>511,最大是512 #ifdef _DEBUG afxDump<<longString;//dosen’t assert for long strings 不需要判断 #endif (2)ATL下[只能调试版] ATLTRACE(longString); //assert if _tcslen(longString)>511,最大是512 #ifdef _DEBUG OutputDebugString(longString); //dosen’t assert for long strings 不需要判断 #endif 13.处理大量的跟踪输出。 如果跟踪消息数据产生的速度超过输出窗口处理的速度,那么消息会塞满缓冲区,数据会丢失。 简单方法:在输出大量数据的代码段,例如对象转储函数时候,调用 Sleep API函数。例如:Sleep(100). 第二章 异常和返回值 C++程序中可以使用异常和返回值来返回状态信息。C语言返回一个函数的状态的最好方法就是它的返回值 Try { //code may fail } Catch { //handle the failure } 只有在抛出异常的时候会有开销!Catch块有一些开销,但是try块有很少的开销! 例子2-1只能在调试版中处理异常,并弹出MessageBox,发布版不处理异常,为的就是优化。 try { int *pInt = 0; *pInt =42; } catch(...) { MessageBox(_T("Exception was caught!"),_T("Exception test"),MB_OK); } 第三章 调试 1.当错误发生得时候,调用GetLastError()得到相应得错误码 错误码得位域有固定的格式,,在C:\Program Files\Microsoft Visual Studio\VC98\Include\Winerror.h中有详细的说明: // // Values are 32 bit values layed out as follows: // // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +---+-+-+-----------------------+-------------------------------+ // |Sev|C|R| Facility | Code | // +---+-+-+-----------------------+-------------------------------+ // // Sev - is the severity code 安全代码 // // 00 – Success 0-安全 // 01 – Informational 1-信息 // 10 – Warning 2-警告 // 11 – Error 3-错误 // C - is the Customer code flag 客户代码:0-Microsoft定义的,1-客户定义的 // R - is a reserved bit 保留位必须是0 // Facility - is the facility code工具-Microsoft 定义的 // Code - is the facility's status code 工具状态代码-Microsoft或客户定义 工具:更好的查看错误代码的方法,Tools-》Error Lookup;在VC++调试器的watch窗口中输入@ERR监视GetLastError的返回数值,ERR是调试器用来显示最新错误码的一个虚拟寄存器。还可以用 @ERR,hr将错误码转换为文本格式 技巧:在调试中,按F11键,Alt+8显示反汇编窗口,在Edit菜单中选择Go To命令,在GoTo对话框的Go to what中选择Address选项,在Enter address expression中输入导致崩溃的地址。 创建映射文件的方法:ProjectSettingsLinkGenerate mapfile.即可在工程的Debug文件下看到。映射文件里面所有的公共符号都使用混合名字。可使用VC++的名字解析工具(Undname)将混合名字转换到原始名字。你可以在“开始””运行””cmd”输入 C:\Documents and Settings\zhangzhongping>cd C:\Program Files\Microsoft Visual Studio\Common\Tools 输入: C:\ProgramFiles\MicrosoftVisualStudio\Common\Tools>Undname ?RandException@@YGXHHHH@Z Microsoft(R) Windows NT(R) Operating System UNDNAME Version 5.00.1768.1Copyright (C) Microsoft Corp. 1981-1998 输出: >> ?RandException@@YGXHHHH@Z == RandException 当输入-f时,显示整个函数的原型!查看映射文件的时候,若出现重载函数,名字解析工具很有用! 输入: C:\ProgramFiles\MicrosoftVisualStudio\Common\Tools>Undname-f ?RandException@@YGXHHHH@Z Microsoft(R) Windows NT(R) Operating System UNDNAME Version 5.00.1768.1Copyright (C) Microsoft Corp. 1981-1998 输出: >> ?RandException@@YGXHHHH@Z == void __stdcall RandException(int,int,int,int) 使用MFC和ATL的DEF文件: MFC的DEF文件在 C:\ProgramFiles\MicrosoftVisual Studio\VC98\MFC\SRC\Intel目录下。 ATL的DEF文件在 C:\Program Files\Microsoft Visual Studio\VC98\ATL\SRC目录下。 如果用户运行自己的程序出现:The ordinal 6880 could not be located in the dynamic link Library MFC42.dll信息。你可查看MFC42.DEF中对应的6880号函数:?ScreenToClient@CWnd@@QBEXPAUtagRECT@@@Z @ 6880 NONAME.然后可以用名字解析工具解析混合名字如下: 输入: C:\Program Files\Microsoft Visual Studio\Common\Tools>Undname -f ?ScreenToClient@CWnd@@QBEXPAUtagRECT@@@Z @ 6880 NONAME Microsoft(R) Windows NT(R) Operating System UNDNAME Version 5.00.1768.1Copyright (C) Microsoft Corp. 1981-1998 输出: >> ?ScreenToClient@CWnd@@QBEXPAUtagRECT@@@Z == public: void __thiscall CWnd::ScreenToClient(struct tagRECT *)const >> @ == @ >> 6880 == 6880 >> NONAME == NONAME 使用依赖关系浏览工具: VC++依赖关系浏览工具(COMMON\TOOLS\Depends.exe) 第四章 内存泄漏的调试 在VC中我们使用_CrtDumpMemoryLeaks()来检测内存泄漏。 例子: void CTRACEDlg::OnButton1() { int *pLeak = new int; //故意造成内存泄漏 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF); //delete pLeak; //pLeak = NULL; _CrtDumpMemoryLeaks();//调用此函数在output window的Debug窗口中显示内存泄漏的地址的数据。 } Debug窗口中显示: Detected memory leaks! Dumping objects -> C:\ProgramFiles\MicrosoftVisualStudio\MyProjects\TRACE\TRACEDlg.cpp(181) : {89} normal block at 0x004213B0, 4 bytes long. Data: < > CD CD CD CD Object dump complete. 注意:0xCD表示已经分配的数据 0xDD表示已经释放的数据 0xFD表示被保护的数据 第四章 断言 1.断言的特征: (1)发现运行时刻错误(用户输入错误,资源分配错误,文件系统错误,硬件错误或其他类型错误) (2)断言中的布尔表达式显示的是某个对象或者状态的有效性,而不是正确性 (3)断言在条件编译后只存在调试版本里面,特别是,断言只在_DEBUG符号定义后,才能编译! (4)断言不能包含程序代码,也不能有副作用,不能修改程序变量,也比能调用修改程序变量的函数 (5)断言只是给程序员提供有用信息的 2.断言的类型 (1)ANSI C断言 Void assert(expression) 包含在assert.h头文件中(最好不用assert) 原因:*当文件名太长的化,对话框显示的路径将会被截至掉! *函数是由ANSI NDEBUG函数驱动的,如果定义了NDEBUG后,断言就被取消! 如果要启用JIT调试(Just-in-time),在ToolsOptionsDebug 中选择Just-in-time debugging,默认也会勾选上OLE RPC debugging 单击“重试(R)”就会显示出错误所在的标记行。 (2)C运行时刻函数库断言 _ASSERT(Boolean Expression) (crtdbg.h)[不用] _ASSERTE(Boolean Expression) (crtdbg.h)[经常用这个] _ASSERTE宏更能给出更多简洁,有用的信息,显示了断言! (3)MFC库中的断言 ASSERT(Boolean expression) ASSERT宏和_ASSERT宏显示的消息框相同。VERIFY(Boolean expression) VERIFY 中的BOOL表达式在发布版本中被保留了下来。它简化了对返回值的判断! CString str; VERIFY(str.LoadString(IDS_STRING));//不要用VERIFY宏 ASSERT_VALID宏,被用来决定一个指向CObject派生类的对象的指针是否有效。ASSERT(pObjectDerivedFromCObject);主要是在使用CObject派生类对象之前调用,检查对象的有效性。 ASSERT_KINDOF(className,pObjectDerivedFromCObject); ASSERT_POINTER(pointer,pointerType); ASSERT_NULL_OR_POINTER(pointer,pointerType); AfxlsValidAddress(const void*memoryAddress,UINT memoryBytes,BOOL isWriteable = TRUE); BOOL AfxlsValidString (LPCSTR string, int stringLength = -1); (4)ATL断言 如果你使用ATL,crtdbg.h就包含在atlbase.h中。在任何ATL代码中,ATLASSERT才是你的选择,在atldef.h中你会发现ATLASSERT是_ASSERT的一个别名。 优点:在ATL程序中使用ATLASSERT可以让你使用自己的断言。 (5)考虑使用_ASSERTE(FALSE)来简化防御性的编程和断言的结合,要想得到描述性的断言消息,考虑使用_ASSERTE(“Problem description.”==0). _ASSERTE("This is the object requires the MM_TEXT mapping mode" == 0); If (!expression) { //handle error _ASSERT(FALSE); … } If(FAILED(SomeFunction())) { //handle error _ASSERT(FALSE) } (6)考虑使用_CrtSetReportMode和_CrtSetReportFile |
所有的时间均为北京时间。 现在的时间是 10:47 PM. |