社区应用 最新帖子 精华区 社区服务 会员列表 统计排行 银行

  • 31705阅读
  • 23回复

VC调试技巧大全

级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
调试是一个程序员最基本的技能,其重要性甚至超过学习一门语言。不会调试的程序员就意味着他即使会一门语言,却不能编制出任何好的软件。
      这里我简要的根据自己的经验列出调试中比较常用的技巧,希望对大家有用。本文约定,在选择菜单时,通过/表示分级菜单,例如File/Open表示顶级菜单File的子菜单open。        
        
        1 设置
        为了调试一个程序,首先必须使程序中包含调试信息。一般情况下,一个从AppWizard创建的工程中包含的Debug 
      Configuration自动包含调试信息,但是是不是Debug版本并不是程序包含调试信息的决定因素,程序设计者可以在任意的Configuration中增加调试信息,包括Release版本。
        为了增加调试信息,可以按照下述步骤进行: 
        
        a 打开Project settings对话框(可以通过快捷键ALT+F7打开,也可以通过IDE菜单Project/Settings打开) 
        
        b 选择C/C++页,Category中选择general ,则出现一个Debug Info下拉列表框,可供选择的调试信息方式包括: 
         None:
         没有调试信息 
         Line Numbers Only:
         目标文件或者可执行文件中只包含全局和导出符号以及代码行信息,不包含符号调试信息 
         C7 Compatible: 
         目标文件或者可执行文件中包含行号和所有符号调试信息,包括变量名及类型.函数及原型 等 
         Program Database: 
         创建一个程序库(PDB),包括类型信息和符号调试信息。 
         Program Database for Edit and Continue: 
         除了上面的功能外,这个选项允许对代码进行调试过程中的修改和继续执行。
         这个选项同时使#pragma设置的优化功能无效 
        
        c 选择Link页,选中复选框"Generate Debug Info",这个选项将使连接器把调试信息写进可执行文件和DLL 
      .如果C/C++页中设置了Program Database以上的选项,则Link 
      incrementally可以选择。选中这个选项,将使程序可以在上一次编译的基础上被编译(即增量编译),而不必每次都从头开始编译。 
        
        
        2 断点
        断点是调试器设置的一个代码位置。当程序运行到断点时,程序中断执行,回到调试器。断点是 
      最常用的技巧。调试时,只有设置了断点并使程序回到调试器,才能对程序进行在线调试。
        
        设置断点:可以通过下述方法设置一个断点。首先把光标移动到需要设置断点的代码行上,然后 
        按F9快捷键 
        弹出Breakpoints对话框,方法是按快捷键CTRL+B或ALT+F9,或者通过菜单Edit/Breakpoints打开。打开后点击Break 
      at编辑框的右侧的箭头,选择 合适的位置信息。一般情况下,直接选择line 
      xxx就足够了,如果想设置不是当前位置的断点,可以选择Advanced,然后填写函数、行号和可执行文件信息。 
        去掉断点:把光标移动到给定断点所在的行,再次按F9就可以取消断点。同前面所述,打开Breakpoints对话框后,也可以按照界面提示去掉断点。
        
        条件断点:可以为断点设置一个条件,这样的断点称为条件断点。对于新加的断点,可以单击Conditions按钮,为断点设置一个表达式。当这个表达式发生改变时,程序就 
      被中断。底下设置包括“观察数组或者结构的元素个数”,似乎可以设置一个指针所指向的内存区的大小,但是我设置一个比较的值但是改动 
      范围之外的内存区似乎也导致断点起效。最后一个设置可以让程序先执行多少次然后才到达断点。
        
        数据断点:数据断点只能在Breakpoints对话框中设置。选择“Data”页,就显示了设置数据断点的对话框。在编辑框中输入一个表达式,当这个 
      表达式的值发生变化时,数据断点就到达。一般情况下,这个表达式应该由运算符和全局变量构成,例如:在编辑框中输入 
      g_bFlag这个全局变量的名字,那么当程序中有g_bFlag= !g_bFlag时,程序就将停在这个语句处。
        
        消息断点:VC也支持对Windows消息进行截获。他有两种方式进行截获:窗口消息处理函数和特定消息中断。在Breakpoints对话框中选择Messages页,就可以设置消息断点。如果在上面那个对话框中写入消息处理函数的名字,那么 
      每次消息被这个函数处理,断点就到达(我觉得如果采用普通断点在这个函数中截获,效果应该一样)。如果在底下的下拉 
      列表框选择一个消息,则每次这种消息到达,程序就中断。
        
        
        3 Watch
        VC支持查看变量、表达式和内存的值。所有这些观察都必须是在断点中断的情况下进行。
        观看变量的值最简单,当断点到达时,把光标移动到这个变量上,停留一会就可以看到变量的值。
        VC提供一种被成为Watch的机制来观看变量和表达式的值。在断点状态下,在变量上单击右键,选择Quick Watch, 
      就弹出一个对话框,显示这个变量的值。
        单击Debug工具条上的Watch按钮,就出现一个Watch视图(Watch1,Watch2,Watch3,Watch4),在该视图中输入变量或者表达式,就可以观察 
      变量或者表达式的值。注意:这个表达式不能有副作用,例如++运算符绝对禁止用于这个表达式中,因为这个运算符将修改变量的值,导致 软件的逻辑被破坏。
        
        
        4 Memory
        由于指针指向的数组,Watch只能显示第一个元素的值。为了显示数组的后续内容,或者要显示一片内存的内容,可以使用memory功能。在 
      Debug工具条上点memory按钮,就弹出一个对话框,在其中输入地址,就可以显示该地址指向的内存的内容。
        
        
        5 Varibles
        Debug工具条上的Varibles按钮弹出一个框,显示所有当前执行上下文中可见的变量的值。特别是当前指令涉及的变量,以红色显示。
        
        
        6 寄存器
        Debug工具条上的Reigsters按钮弹出一个框,显示当前的所有寄存器的值。
        
        
        7 进程控制
        VC允许被中断的程序继续运行、单步运行和运行到指定光标处,分别对应快捷键F5、F10/F11和CTRL+F10。各个快捷键功能如下: 
        快捷键 说明 
        F5 继续运行 
        F10 单步,如果涉及到子函数,不进入子函数内部 
        F11 单步,如果涉及到子函数,进入子函数内部 
        CTRL+F10 运行到当前光标处。 
        
        
        8 Call Stack
        调用堆栈反映了当前断点处函数是被那些函数按照什么顺序调用的。单击Debug工具条上的Call stack就显示Call 
      Stack对话框。在CallStack对话框中显示了一个调用系列,最上面的是当前函数,往下依次是调用函数的上级函数。单击这些函数名可以跳到对应的函数中去。
        
        
        9 其他调试手段
        系统提供一系列特殊的函数或者宏来处理Debug版本相关的信息,如下: 
        
        10 宏名/函数名 说明 
        TRACE 使用方法和printf完全一致,他在output框中输出调试信息 
        ASSERT 它接收一个表达式,如果这个表达式为TRUE,则无动作,否则中断当前程序执行。对于系统中出现这个宏 
      导致的中断,应该认为你的函数调用未能满足系统的调用此函数的前提条件。例如,对于一个还没有创建的窗口调用SetWindowText等。 
        VERIFY 和ASSERT功能类似,所不同的是,在Release版本中,ASSERT不计算输入的表达式的值,而VERIFY计算表达式的值。 
        
        关注:
        一个好的程序员不应该把所有的判断交给编译器和调试器,应该在程序中自己加以程序保护和错误定位,具体措施包括: 
        
         对于所有有返回值的函数,都应该检查返回值,除非你确信这个函数调用绝对不会出错,或者不关心它是否出错。 
         一些函数返回错误,需要用其他函数获得错误的具体信息。例如accept返回INVALID_SOCKET表示accept失败,为了查明 
      具体的失败原因,应该立刻用WSAGetLastError获得错误码,并针对性的解决问题。 
         有些函数通过异常机制抛出错误,应该用TRY-CATCH语句来检查错误 
        程序员对于能处理的错误,应该自己在底层处理,对于不能处理的,应该报告给用户让他们决定怎么处理。如果程序出了异常, 
      却不对返回值和其他机制返回的错误信息进行判断,只能是加大了找错误的难度。 
         另外:VC中要编制程序不应该一开始就写cpp/h文件,而应该首先创建一个合适的工程。因为只有这样,VC才能选择合适的编译、连接 
      选项。对于加入到工程中的cpp文件,应该检查是否在第一行显式的包含stdafx.h头文件,这是Microsoft Visual 
      Studio为了加快编译 速度而设置的预编译头文件。在这个#include 
      "stdafx.h"行前面的所有代码将被忽略,所以其他头文件应该在这一行后面被包含。
         对于.c文件,由于不能包含stdafx.h,因此可以通过Project settings把它的预编译头设置为“不使用”,方法是: 
        弹出Project settings对话框 
        选择C/C++ 
        Category选择Precompilation Header 
        选择不使用预编译头。




其他技巧:
      1.在调试状态下怎样查看错误消息(GetLastError())?
      通常可以用GetLastError()得到错误编号然后用FormatMessage(...)得到错误描述。
      这里有一个更直接的办法:在Watch窗口添加@err,hr
      2.怎样知道程序是否有内存泄漏(Memory Leak)?
      在VC开发环境下Press 
      [F5],在调试状态下运行程序,测试有可能出现内存泄漏的操作,关闭程序,在Output窗口查看运行信息.如果出现泄漏,在Output中会有记录。当然,不能完全依靠这种方式来发现程序运行有内存泄漏。
      3.当某一变量满足某种条件时,停止在断点.
      如以下一程序片段:
      2 int iLocation;
      ...
      30 iLocation++
      ...
      要求: 在line30设有断点,并想在iLocation>100 
便于调试的代码风格:
不用全局变量
所有变量都要初始化,成员变量在构造函数中初始化
尽量使用const
详尽的注释

VC++编译选项:
总是使用/W4警告级别
在调试版本里总是使用/GZ编译选项,用来发现在Release版本中才有的错误
没有警告的编译:保证在编译后没有任何警告,但是在消除警告前要进行仔细检查

调试方法:
1、使用 Assert(原则:尽量简单)
assert只在debug下生效,release下不会被编译。
例子:
char* strcpy(char* dest,char* source)
{
assert(source!=0);
assert(dest!=0);
char* returnstring = dest;
while((*dest++ = *source++)!= ‘\0’)
{
;
}
return returnstring;

2、防御性的编程
例子:
char* strcpy(char* dest,char* source)
{
if(source == 0)
{
assert(false);
reutrn 0;
}
if(dest == 0)
{
assert(false);
return 0;
}
char* returnstring = dest;
while((*dest++ = *source++)!= ‘\0’)
{
;
}
return returnstring;

3、使用Trace
以下的例子只能在debug中显示,
例子:
a)、TRACECString csTest = “test”;
TRACE(“CString is %s\n”,csTest);
b)、ATLTRACE
c)、afxDump
CTime time = CTime::GetCurrentTime();
#ifdef _DEBUG
afxDump << time << “\n”;
#endif
4、用GetLastError来检测返回值,通过得到错误代码来分析错误原因
5、把错误信息记录到文件中
异常处理
程序设计时一定要考虑到异常如何处理,当错误发生后,不应简单的报告错误并退出程序,应当尽可能的想办法恢复到出错前的状态或者让程序从头开始运行,并且对于某些错误,应该能够容错,即允许错误的存在,但是程序还是能够正常完成任务。
调试技巧
1、VC++中F5进行调试运行
a)、在output Debug窗口中可以看到用TRACE打印的信息
b)、 Call Stack窗口中能看到程序的调用堆栈
2、当Debug版本运行时发生崩溃,选择retry进行调试,通过看Call Stack分析出错的位置及原因
3、使用映射文件调试
a)、创建映射文件:Project settings中link项,选中Generate mapfile,输出程序代码地址:/MAPINFO: 
LINES,得到引出序号:/MAPINFO: EXPORTS。
b)、程序发布时,应该把所有模块的映射文件都存档。
c)、查看映射文件:见” 通过崩溃地址找出源代码的出错行”文件。
4、可以调试的Release版本
Project settings中C++项的Debug Info选择为Program Database,Link项的Debug中选择Debug 
Info和Microsoft format。
5、查看API的错误码,在watch窗口输入@err可以查看或者@err,hr,其中”,hr”表示错误码的说明。
6、Set Next Statement:该功能可以直接跳转到指定的代码行执行,一般用来测试异常处理的代码。
7、调试内存变量的变化:当内存发生变化时停下来。
常见错误
1、在函数返回的时候程序崩溃的原因
a)、写自动变量越界
b)、函数原型不匹配
2、MFC
a)、使用错误的函数原型处理用户定义消息
正确的函数原型为:
afx_msg LRESULT OnMyMessage(WPARAM wParam,LPARAM lParam);
3、谨慎使用TerminateThread:使用TerminateThread会造成资源泄漏,不到万不得已,不要使用。
4、使用_beginthreadex,不要使用Create Thread来常见线程。
WindowS 调试
1.Windows跟踪语句:
(1)TRACE(_T(“Warning (FunctionName):Object %s not found.\n”),objectName);
在输出的调试窗口会输出结果。跟踪信息输出到输出窗口output window中。[调试版本中使用]
(2)C++的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可以指定将报告输出到哪个文件中。
(3)MFC中的跟踪语句
       区别:使用TRACE宏时,需要使用_T宏来格式化参数以正确解决Unicode的校正,反之,在TRACEn类型的宏中,不必使用_T宏。
       TRACE(_T("ssss\n"));
       TRACE2("%d r %d l\n",1,2);
(4)异常和返回值
C++程序中可以使用异常和返回值来返回状态信息。C语言返回一个函数的状态的最好方法就是它的返回值
Try
{
//code may fail
}
Catch
{
//handle the failure
}
只有在抛出异常的时候会有开销!Catch块有一些开销,但是try块有很少的开销!只能在调试版中处理异常,并弹出MessageBox,发布版不处理异常,为的就是优化。
     try
     {
       int *pInt = 0;
       *pInt =42;
     }
     catch(...)
     {
       MessageBox(_T("Exception was caught!"),_T("Exception test"),MB_OK);   
     }
      (5)ANSI C++ 运行时刻函数库跟踪
具体有:C语言的stderr和C++语言的clog流,在Windows程序中没有任何效果!
(6)OutputDebugString (_T(“trace   debug info!\n”));[调试版本中使用]
       如果只想在调试版本中使用OutputDebugString,可以使用下面得宏来实现:
#ifdef   _DEBUG
#define OutputTraceString(text)   OutputDebugString(text)
#else
#define OutputTraceString(text)   ((void) (0))
#endif
使用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文件。在project& #61664;settingsLinkCategoryDebug 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).
 调试
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中输入导致崩溃的地址。
创 建映射文件的方法:ProjectSettingsLinkGenerate 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),在ToolsOptionsDebug
中选择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




关键词: C++ 调试 技巧 编程
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 沙发  发表于: 2011-09-08
利用windbg + dump + map分析程序异常

之前碰到论坛里有几个好友,说程序不时的崩溃,什么xxoo不能read的! 如果光要是这个内存地址,估计你会疯掉~~

所以分享一下基本的调试技巧,需要准备的工具有WinDbg + VC6.0,

下面是自己整理的一份自动生成DUMP文件的源代码,只需要添加到工程即可,源代码如下:

MiniDump.h

  1. #include <windows.h>  
  2. #include <tlhelp32.h>  
  3.   
  4. //#include "dbghelp.h"  
  5. //#define DEBUG_DPRINTF     1   //allow d()  
  6. //#include "wfun.h"  
  7.   
  8. #pragma optimize("y", off)      //generate stack frame pointers for all functions - same as /Oy- in the project  
  9. #pragma warning(disable: 4200)  //nonstandard extension used : zero-sized array in struct/union  
  10. #pragma warning(disable: 4100)  //unreferenced formal parameter  
  11.   
  12. /*BOOL WINAPI Get_Module_By_Ret_Addr(PBYTE Ret_Addr, PCHAR Module_Name, PBYTE & Module_Addr); 
  13. int  WINAPI Get_Call_Stack(PEXCEPTION_POINTERS pException, PCHAR Str); 
  14. int  WINAPI Get_Version_Str(PCHAR Str); 
  15. PCHAR WINAPI Get_Exception_Info(PEXCEPTION_POINTERS pException); 
  16. void WINAPI Create_Dump(PEXCEPTION_POINTERS pException, BOOL File_Flag, BOOL Show_Flag);*/  
  17.   
  18. // In case you don't have dbghelp.h.  
  19. #ifndef _DBGHELP_  
  20.   
  21. typedef struct _MINIDUMP_EXCEPTION_INFORMATION {  
  22.     DWORD   ThreadId;  
  23.     PEXCEPTION_POINTERS ExceptionPointers;  
  24.     BOOL    ClientPointers;  
  25. } MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;  
  26.   
  27. typedef enum _MINIDUMP_TYPE {  
  28.     MiniDumpNormal =            0x00000000,  
  29.         MiniDumpWithDataSegs =      0x00000001,  
  30. } MINIDUMP_TYPE;  
  31.   
  32. typedef BOOL (WINAPI * MINIDUMP_WRITE_DUMP)(  
  33.                                             IN HANDLE           hProcess,  
  34.                                             IN DWORD            ProcessId,  
  35.                                             IN HANDLE           hFile,  
  36.                                             IN MINIDUMP_TYPE    DumpType,  
  37.                                             IN CONST PMINIDUMP_EXCEPTION_INFORMATION    ExceptionParam, OPTIONAL  
  38.                                             IN PVOID                                    UserStreamParam, OPTIONAL  
  39.                                             IN PVOID                                    CallbackParam OPTIONAL  
  40.                                             );  
  41.   
  42. #else  
  43.   
  44. typedef BOOL (WINAPI * MINIDUMP_WRITE_DUMP)(  
  45.                                             IN HANDLE           hProcess,  
  46.                                             IN DWORD            ProcessId,  
  47.                                             IN HANDLE           hFile,  
  48.                                             IN MINIDUMP_TYPE    DumpType,  
  49.                                             IN CONST PMINIDUMP_EXCEPTION_INFORMATION    ExceptionParam, OPTIONAL  
  50.                                             IN PMINIDUMP_USER_STREAM_INFORMATION        UserStreamParam, OPTIONAL  
  51.                                             IN PMINIDUMP_CALLBACK_INFORMATION           CallbackParam OPTIONAL  
  52.                                             );  
  53. #endif //#ifndef _DBGHELP_  
  54.   
  55. // Tool Help functions.  
  56. typedef HANDLE (WINAPI * CREATE_TOOL_HELP32_SNAPSHOT)(DWORD dwFlags, DWORD th32ProcessID);  
  57. typedef BOOL (WINAPI * MODULE32_FIRST)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);  
  58. typedef BOOL (WINAPI * MODULE32_NEST)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);  
  59.   
  60.   
  61. extern void WINAPI Create_Dump(PEXCEPTION_POINTERS pException, BOOL File_Flag, BOOL Show_Flag);  
  62.   
  63. extern HMODULE  hDbgHelp;  
  64. extern MINIDUMP_WRITE_DUMP  MiniDumpWriteDump_;  
  65.   
  66. extern CREATE_TOOL_HELP32_SNAPSHOT  CreateToolhelp32Snapshot_;  
  67. extern MODULE32_FIRST   Module32First_;  
  68. extern MODULE32_NEST    Module32Next_;  
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 板凳  发表于: 2011-09-08
MiniDump.cpp





/*
    Author:    Vladimir Sedach.


    Purpose: demo of Call Stack creation by our own means,
    and with MiniDumpWriteDump() function of DbgHelp.dll.
*/


#include "StdAfx.h"
#include "MiniDump.h"
#include <Shlwapi.h>


#pragma comment(lib,"shlwapi.lib")


HMODULE    hDbgHelp;
MINIDUMP_WRITE_DUMP    MiniDumpWriteDump_;


CREATE_TOOL_HELP32_SNAPSHOT    CreateToolhelp32Snapshot_;
MODULE32_FIRST    Module32First_;
MODULE32_NEST    Module32Next_;


#define    DUMP_SIZE_MAX    8000    //max size of our dump
#define    CALL_TRACE_MAX    ((DUMP_SIZE_MAX - 2000) / (MAX_PATH + 40))    //max number of traced calls
#define    NL                "\r\n"    //new line


extern CString GetExePath();


//****************************************************************************************
BOOL WINAPI Get_Module_By_Ret_Addr(PBYTE Ret_Addr, PCHAR Module_Name, PBYTE & Module_Addr)
//****************************************************************************************
// Find module by Ret_Addr (address in the module).
// Return Module_Name (full path) and Module_Addr (start address).
// Return TRUE if found.
{
    MODULEENTRY32    M = {sizeof(M)};
    HANDLE    hSnapshot;


    Module_Name[0] = 0;
    
    if (CreateToolhelp32Snapshot_)
    {
        hSnapshot = CreateToolhelp32Snapshot_(TH32CS_SNAPMODULE, 0);
        
        if ((hSnapshot != INVALID_HANDLE_VALUE) &&
            Module32First_(hSnapshot, &M))
        {
            do
            {
                if (DWORD(Ret_Addr - M.modBaseAddr) < M.modBaseSize)
                {
                    lstrcpyn(Module_Name, M.szExePath, MAX_PATH);
                    Module_Addr = M.modBaseAddr;
                    break;
                }
            } while (Module32Next_(hSnapshot, &M));
        }


        CloseHandle(hSnapshot);
    }


    return !!Module_Name[0];
} //Get_Module_By_Ret_Addr


//******************************************************************
int WINAPI Get_Call_Stack(PEXCEPTION_POINTERS pException, PCHAR Str)
//******************************************************************
// Fill Str with call stack info.
// pException can be either GetExceptionInformation() or NULL.
// If pException = NULL - get current call stack.
{
    CHAR    Module_Name[MAX_PATH];
    PBYTE    Module_Addr = 0;
    PBYTE    Module_Addr_1;
    int        Str_Len;
    
    typedef struct STACK
    {
        STACK *    Ebp;
        PBYTE    Ret_Addr;
        DWORD    Param[0];
    } STACK, * PSTACK;


    STACK    Stack = {0, 0};
    PSTACK    Ebp;


    if (pException)        //fake frame for exception address
    {
        Stack.Ebp = (PSTACK)pException->ContextRecord->Ebp;
        Stack.Ret_Addr = (PBYTE)pException->ExceptionRecord->ExceptionAddress;
        Ebp = &Stack;
    }
    else
    {
        Ebp = (PSTACK)&pException - 1;    //frame addr of Get_Call_Stack()


        // Skip frame of Get_Call_Stack().
        if (!IsBadReadPtr(Ebp, sizeof(PSTACK)))
            Ebp = Ebp->Ebp;        //caller ebp
    }


    Str[0] = 0;
    Str_Len = 0;


    // Trace CALL_TRACE_MAX calls maximum - not to exceed DUMP_SIZE_MAX.
    // Break trace on wrong stack frame.
    for (int Ret_Addr_I = 0;
        (Ret_Addr_I < CALL_TRACE_MAX) && !IsBadReadPtr(Ebp, sizeof(PSTACK)) && !IsBadCodePtr(FARPROC(Ebp->Ret_Addr));
        Ret_Addr_I++, Ebp = Ebp->Ebp)
    {
        // If module with Ebp->Ret_Addr found.
        if (Get_Module_By_Ret_Addr(Ebp->Ret_Addr, Module_Name, Module_Addr_1))
        {
            if (Module_Addr_1 != Module_Addr)    //new module
            {
                // Save module's address and full path.
                Module_Addr = Module_Addr_1;
                Str_Len += wsprintf(Str + Str_Len, NL "%08X  %s", Module_Addr, Module_Name);
            }


            // Save call offset.
            Str_Len += wsprintf(Str + Str_Len,
                NL "  +%08X", Ebp->Ret_Addr - Module_Addr);


            // Save 5 params of the call. We don't know the real number of params.
            if (pException && !Ret_Addr_I)    //fake frame for exception address
                Str_Len += wsprintf(Str + Str_Len, "  Exception Offset");
            else if (!IsBadReadPtr(Ebp, sizeof(PSTACK) + 5 * sizeof(DWORD)))
            {
                Str_Len += wsprintf(Str + Str_Len, "  (%X, %X, %X, %X, %X)",
                    Ebp->Param[0], Ebp->Param[1], Ebp->Param[2], Ebp->Param[3], Ebp->Param[4]);
            }
        }
        else
            Str_Len += wsprintf(Str + Str_Len, NL "%08X", Ebp->Ret_Addr);
    }


    return Str_Len;
} //Get_Call_Stack


//***********************************
int WINAPI Get_Version_Str(PCHAR Str)
//***********************************
// Fill Str with Windows version.
{
    OSVERSIONINFOEX    V = {sizeof(OSVERSIONINFOEX)};    //EX for NT 5.0 and later


    if (!GetVersionEx((POSVERSIONINFO)&V))
    {
        ZeroMemory(&V, sizeof(V));
        V.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        GetVersionEx((POSVERSIONINFO)&V);
    }


    if (V.dwPlatformId != VER_PLATFORM_WIN32_NT)
        V.dwBuildNumber = LOWORD(V.dwBuildNumber);    //for 9x HIWORD(dwBuildNumber) = 0x04xx


    return wsprintf(Str,
        NL "Windows:  %d.%d.%d, SP %d.%d, Product Type %d",    //SP - service pack, Product Type - VER_NT_WORKSTATION,...
        V.dwMajorVersion, V.dwMinorVersion, V.dwBuildNumber, V.wServicePackMajor, V.wServicePackMinor/*, V.wProductType*/);
} //Get_Version_Str


//*************************************************************
PCHAR WINAPI Get_Exception_Info(PEXCEPTION_POINTERS pException)
//*************************************************************
// Allocate Str[DUMP_SIZE_MAX] and return Str with dump, if !pException - just return call stack in Str.
{
    PCHAR        Str;
    int            Str_Len;
    int            i;
    CHAR        Module_Name[MAX_PATH];
    PBYTE        Module_Addr;
    HANDLE        hFile;
    FILETIME    Last_Write_Time;
    FILETIME    Local_File_Time;
    SYSTEMTIME    T;
    
    Str = new CHAR[DUMP_SIZE_MAX];


    if (!Str)
        return NULL;


    Str_Len = 0;
    Str_Len += Get_Version_Str(Str + Str_Len);


    Str_Len += wsprintf(Str + Str_Len, NL "Process:  ");
    GetModuleFileName(NULL, Str + Str_Len, MAX_PATH);
    Str_Len = lstrlen(Str);


    // If exception occurred.
    if (pException)
    {
        EXCEPTION_RECORD &    E = *pException->ExceptionRecord;
        CONTEXT &            C = *pException->ContextRecord;


        // If module with E.ExceptionAddress found - save its path and date.
        if (Get_Module_By_Ret_Addr((PBYTE)E.ExceptionAddress, Module_Name, Module_Addr))
        {
            Str_Len += wsprintf(Str + Str_Len,
                NL "Module:  %s", Module_Name);


            if ((hFile = CreateFile(Module_Name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE)
            {
                if (GetFileTime(hFile, NULL, NULL, &Last_Write_Time))
                {
                    FileTimeToLocalFileTime(&Last_Write_Time, &Local_File_Time);
                    FileTimeToSystemTime(&Local_File_Time, &T);


                    Str_Len += wsprintf(Str + Str_Len,
                        NL "Date Modified:  %02d/%02d/%d",
                        T.wMonth, T.wDay, T.wYear);
                }
                CloseHandle(hFile);
            }
        }
        else
        {
            Str_Len += wsprintf(Str + Str_Len,
                NL "Exception Addr:  %08X", E.ExceptionAddress);
        }
        
        Str_Len += wsprintf(Str + Str_Len,
            NL "Exception Code:  %08X", E.ExceptionCode);
        
        if (E.ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
        {
            // Access violation type - Write/Read.
            Str_Len += wsprintf(Str + Str_Len,
                NL "%s Address:  %08X",
                (E.ExceptionInformation[0]) ? "Write" : "Read", E.ExceptionInformation[1]);
        }


        // Save instruction that caused exception.
        Str_Len += wsprintf(Str + Str_Len, NL "Instruction: ");
        for (i = 0; i < 16; i++)
            Str_Len += wsprintf(Str + Str_Len, " %02X", PBYTE(E.ExceptionAddress));


        // Save registers at exception.
        Str_Len += wsprintf(Str + Str_Len, NL "Registers:");
        Str_Len += wsprintf(Str + Str_Len, NL "EAX: %08X  EBX: %08X  ECX: %08X  EDX: %08X", C.Eax, C.Ebx, C.Ecx, C.Edx);
        Str_Len += wsprintf(Str + Str_Len, NL "ESI: %08X  EDI: %08X  ESP: %08X  EBP: %08X", C.Esi, C.Edi, C.Esp, C.Ebp);
        Str_Len += wsprintf(Str + Str_Len, NL "EIP: %08X  EFlags: %08X", C.Eip, C.EFlags);
    } //if (pException)
    
    // Save call stack info.
    Str_Len += wsprintf(Str + Str_Len, NL "Call Stack:");
    Get_Call_Stack(pException, Str + Str_Len);


    if (Str[0] == NL[0])
        lstrcpy(Str, Str + sizeof(NL) - 1);


    return Str;
} //Get_Exception_Info


//*************************************************************************************
void WINAPI Create_Dump(PEXCEPTION_POINTERS pException, BOOL File_Flag, BOOL Show_Flag)
//*************************************************************************************
// Create dump.
// pException can be either GetExceptionInformation() or NULL.
// If File_Flag = TRUE - write dump files (.dmz and .dmp) with the name of the current process.
// If Show_Flag = TRUE - show message with Get_Exception_Info() dump.
{
    HANDLE    hDump_File;
    PCHAR    Str;
    DWORD    Bytes;
    DWORD    nLen = 0;


    CString strDir,strTXTFile,strDMPFile;
    CString strDate,strTotal;
    CTime    tm = CTime::GetCurrentTime();
    
    strDir.Format(_T("%s\\Log"),GetExePath());
    strTXTFile.Format(_T("%s\\Log\\%04d-%02d-%02d %02d%02d%02d.txt"),GetExePath(),tm.GetYear(),tm.GetMonth(),
        tm.GetDay(),tm.GetHour(),tm.GetMinute(),tm.GetSecond());
    strDMPFile.Format(_T("%s\\Log\\%04d-%02d-%02d %02d%02d%02d.dmp"),GetExePath(),tm.GetYear(),tm.GetMonth(),
        tm.GetDay(),tm.GetHour(),tm.GetMinute(),tm.GetSecond());


    if(!PathFileExists(strDir))
        CreateDirectory(strDir,NULL);


    Str = Get_Exception_Info(pException);


    //if (Show_Flag && Str)
    //    MessageBox(NULL, Str, "MiniDump", MB_ICONHAND | MB_OK);


    if (File_Flag)
    {
        if (Str)
        {
            hDump_File = CreateFile(strTXTFile,
                GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
            
            nLen = lstrlen(Str);
            Str[nLen] = '\0';


            WriteFile(hDump_File, Str, lstrlen(Str) + 1, &Bytes, NULL);


            CloseHandle(hDump_File);
        }


        // If MiniDumpWriteDump() of DbgHelp.dll available.
        if (MiniDumpWriteDump_)
        {
            MINIDUMP_EXCEPTION_INFORMATION    M;


            M.ThreadId = GetCurrentThreadId();
            M.ExceptionPointers = pException;
            M.ClientPointers = 0;


            hDump_File = CreateFile(strDMPFile,
                GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);


            MiniDumpWriteDump_(GetCurrentProcess(), GetCurrentProcessId(), hDump_File,
                MiniDumpNormal, (pException) ? &M : NULL, NULL, NULL);


            CloseHandle(hDump_File);
        }
    } //if (File_Flag)


    delete Str;
} //Create_Dump


QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 地板  发表于: 2011-09-08
具体参考方法如下:

1、在CXXDlg::OnInitDialog()中添加这样一段:

  1. SetUnhandledExceptionFilter(CrashReportEx);  
  2. HMODULE hKernel32;  
  3.   
  4. // Try to get MiniDumpWriteDump() address.  
  5. hDbgHelp = LoadLibrary("DBGHELP.DLL");  
  6. MiniDumpWriteDump_ = (MINIDUMP_WRITE_DUMP)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");  
  7. //  d("hDbgHelp=%X, MiniDumpWriteDump_=%X", hDbgHelp, MiniDumpWriteDump_);  
  8.   
  9. // Try to get Tool Help library functions.  
  10. hKernel32 = GetModuleHandle("KERNEL32");  
  11. CreateToolhelp32Snapshot_ = (CREATE_TOOL_HELP32_SNAPSHOT)GetProcAddress(hKernel32, "CreateToolhelp32Snapshot");  
  12. Module32First_ = (MODULE32_FIRST)GetProcAddress(hKernel32, "Module32First");  
  13. Module32Next_ = (MODULE32_NEST)GetProcAddress(hKernel32, "Module32Next");  

测试代码如下:

  1. class CTestDlg : public CDialog  
  2. {  
  3. // Construction  
  4. public:  
  5.     CTestDlg(CWnd* pParent = NULL); // standard constructor  
  6.   
  7.     void Fun1(char *pszBuffer);  
  8.     void Fun2(char *pszBuffer);  
  9.     void Fun3(char *pszBuffer);  
  10. };  

  1. void CTestDlg::Fun1(char *pszBuffer)  
  2. {  
  3.     Fun2(pszBuffer);  
  4. }  
  5.   
  6. void CTestDlg::Fun2(char *pszBuffer)  
  7. {  
  8.     Fun3(pszBuffer);  
  9. }  
  10.   
  11. void CTestDlg::Fun3(char *pszBuffer)  
  12. {  
  13.     pszBuffer[1] = 0x00;  
  14. }  

我们在双击确定按钮时的响应代码如下:

  1. void CTestDlg::OnOK()   
  2. {  
  3.     // TODO: Add extra validation here  
  4.     Fun1(NULL);  
  5. }  
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 4楼 发表于: 2011-09-08

2、设置VC编译选项,勾选生成MAP和Debug Info:




3、将编译生成的Release目录中的pdb、map文件保存起来,以后调试会用到:

4、运行程序,单击确定按钮出现异常后自动重启,并创建一个Log文件夹,里面生成dump文件:

5、我们打开WinDbg,设置一下pdb路径(File \ Symbol File Path):

6、用WiinDbg打开dump文件(File \ Open Crash Dump)

7、输入命令!analyze -v,等待几秒后会打印出错误信息,函数调用栈如下图:



OK ,这样我们就能在发布版本的程序中,准确的定位到哪个函数出了问题,所以发布程序时,一定要记得生成pdb、map文件,不然客户运行出错的话,你不死也残!


测试工程下载地址:
http://download.csdn.net/source/3575167
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 5楼 发表于: 2011-10-28
C++程序跟踪调试(ATLTRACE2)
在Visual Studio2005中提供了ATLTRACE2宏,用于调试C++程序,但如何使用该宏,有关的资料较少,本文简单介绍ATLTRACE2宏的使用。在Visual Studio2005中其实ATLTRACE2 就是ATLTRACE,在ATL的程序中已定义如下,
#ifndef ATLTRACE
#define ATLTRACE ATL::CTraceFileAndLineInfo(__FILE__, __LINE__)
#define ATLTRACE2 ATLTRACE
#endif
所以在Visual Studio2005中ATLTRACE2和ATLTRACE通用,MSDN上定义的该宏的功能是:
Reports warnings to an output device, such as the debugger window, according to the indicated flags and levels.(按指定的调试标志和级别在调试输出窗口中报告程序跟踪消息)
用法有两个:
1、ATLTRACE2( exp );
2、ATLTRACE2(
DWORD category,
UINT level,
LPCSTR lpszFormat,
...
);
用法1十分简单,只要输入一个字符串变量或输入一个字符串,被调试的程序就会在调试窗口输出调试信息,如:ATLTRACE2(“My Trace Message:%d”,1)在调试输出窗口输出“MyTrace Message:1”信息。第二种用法就比较复杂了,但功能会比第一种用法强的多,下面重点介绍第二种用法。
首先介绍第二种用法的参数。
Category 一个跟踪调试标志对象(后面详细介绍)
Level 跟踪调试级别(后面详细介绍)
lpszFormat 欲在调试窗口输出的格式化字符串,类似于第一种用法中的exp参数(字符串变量或一个字符串)。
第二种用法要如下三方要素协调动作才能在调试窗口输出跟踪调试信息,一是ATLTRACE2宏,二是Category调试跟踪对象,三是Visual Studio2005的“ATL/MFC跟踪工具”,下面就详细介绍这三方面因素。
1、  ATLTRACE2宏
该宏前面已经介绍,在Visual Studio2005 C++程序中欲跟踪调试的地方输入ATLTRACE2(......)就可以了。
2、  category跟踪调试对象
为方便跟踪调试C++程序,在ATL/MFC程序中定义了CTraceCategory类,该类用于管理跟踪调试信息,ATLTRACE2的第一个参数就是它。在使用它之前要定义一个CTraceCategory类的全局对象(全局是必须的),如:
        CTraceCategory MY_CATEGORY( "MyCategoryName", 2 );就定义了一个CTraceCategory类的实例MY_CATAEGORY,调试跟踪对象名是MyCateGoryName,跟踪调试级别是1(MyCateGoryName和跟踪调试级别在“ATL/MFC跟踪工具”中要用到)。跟踪调试级别共0、1、2、3、4,5个级别。系统已经定义了一些CTraceCategory类的实例,供调试跟踪时使用,已经定义的CTraceCategory类的实例如下:(来自MSDN)
atlTraceGeneral
Reports on all ATL applications. The default.
atlTraceCOM
Reports on COM methods.
atlTraceQI
Reports on QueryInterface calls.
atlTraceRegistrar
Reports on the registration of objects.
atlTraceRefcount
Reports on changing reference count.
atlTraceWindowing
Reports on windows methods; for example, reports an invalid message map ID.
atlTraceControls
Reports on controls; for example, reports when a control or its window is destroyed.
atlTraceHosting
Reports hosting messages; for example, reports when a client in a container is activated.
atlTraceDBClient
Reports on OLE DB Consumer Template; for example, when a call to GetData fails, the output can contain the HRESULT.
atlTraceDBProvider
Reports on OLE DB Provider Template; for example, reports if the creation of a column failed.
atlTraceSnapin
Reports for MMC SnapIn application.
atlTraceNotImpl
Reports that the indicated function is not implemented.
atlTraceAllocation
Reports messages printed by the memory debugging tools in atldbgmem.h.
MFC Trace Flags
MFC Category
Description
traceAppMsg
General purpose, MFC messages. Always recommended.
traceDumpContext
Messages from CDumpContext.
traceWinMsg
Messages from MFC's message handling code.
traceMemory
Messages from MFC's memory management code.
traceCmdRouting
Messages from MFC's Windows command routing code.
traceHtml
Messages from MFC's DHTML dialog support.
traceSocket
Messages from MFC's socket support.
traceOle
Messages from MFC's OLE support.
traceDatabase
Messages from MFC's database support.
traceInternet
Messages from MFC's Internet support.
以上这些跟踪调试实例,系统已经定义,我们也可以直接使用,如:
ATLTRACE2(atlTraceQI,3,“this is a atlTraceQI example“);因这些跟踪调试对象系统已经定义,我们在使用时无须再进行定义,可直接使用了。
    3、  ATL/MFC跟踪工具
Visual Studio2005中提供了ATL/MFC跟踪工具这个外挂程序,专门用于系统跟踪调试,假设我们已经定义一个跟踪调试对CTraceCategory MY_CATEGORY( L"MyCategoryName", 2 );在需要进行跟踪调试的地方输入:
ATLTRACE2(MY_CATEGORY,1,”this is an ATLTRACE2 example”);,以上语句前插入断点或输入DebugBreak();语句,然后启动调试应用程序 ,待程序在断点停下来后选择“工具”-〉“ATL/MFC跟踪工具 ”弹出如下的对话框:




    看到这个对话框了吗,看到左边跟踪列表框中的“MyCategoryName”了吗,他就是我们在
CTraceCategory MY_CATEGORY( L"MyCategoryName", 2 );中创建类的实例中的第一个变量。右边分“进程”、“模块”、“类别”三个层次对欲跟踪调试的对象进行定义,进程是整个程序,模块是程序的某个模块,类别就是我们在程序中定义的跟踪调试对象,单击左边MyCategoryName项,在类别模块中选中“启用”,跟踪消息数量下面自动落在中间位置(位置可调),左边第一位置是0,依次是1、2、3、4,代表5个调试级别.
因为CTraceCategory MY_CATEGORY( L"MyCategoryName", 2 );中跟踪调试级别为2,故此落在此条中间位置,这就是定义跟踪调试对象时构造函数第二个参数的作用。一切都定义好后选择右边的“应用”,回到应用程序继续执行可以看到程序的调试输出窗口输出了“this is an ATLTRACE2 example”信息,因为我们定义跟踪调试对象时,级别是2级,故此在ATLTRACE2(MY_CATEGORY,...宏中,级别是2(包括2)以下的跟踪调试信息都将在调试输出窗口中输出,高于级别的不输出。
    以上介绍了ATLTRACE2宏的功能,该宏需要相应的跟踪调试标志对象和ATL/MFC跟踪工具的支持才能很好的工作,应用起来较复杂,但功能也很强大,是跟踪调试C++程序的利器。
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 6楼 发表于: 2011-10-28
TRACE的用法总结
TRACE()宏一般是用在mfc中的,用于将调试信息输出到vs的输出窗口中(这是关键), 这在使用vs作为开发工具的时候,是非常方便的。





然而在开发一般c++程序时,却貌似无法获得这样的便利,其实只要经过几个步骤同样可以实现:


一:#include<atltrace.h>        //此头文件包含ATLTRACE宏,而其实MFC做的就是#define TRACE ATLTRACE


二:使用ATLTRACE("error");


三:包含atl链接库:atlsd.lib ( debug )或者 atls.lib





trace详细教程:


   TRACE宏对于VC下程序调试来说是很有用的东西,有着类似printf的功能;该宏仅仅在程序的DEBUG版本中出现,当RELEASE的时候该宏就完全消息了,从而帮助你调式也在RELEASE的时候减少代码量。


使用非常简单,格式如下:


TRACE("DDDDDDDDDDD");


TRACE("wewe%d",333);


同样还存在TRACE0,TRACE1,TRACE2。。。分别对应0,1,2。。个参数


TRACE信息输出到VC IDE环境的输出窗口(该窗口是你编译项目出错提示的哪个窗口),但仅限于你在VC中运行你的DEBUG版本的程序。


TRACE信息还可以使用DEBUGVIEW来捕获到。这种情况下,你不能在VC的IDE环境中运行你的程序,而将BUILD好的DEBUG版本的程序单独运行,这个时候可以在DEBUGVIEW的窗口看到DEBUGVIE格式的输出了。


VC中TRACE的用法有以下四种:


1:


TRACE   ,就是不带动态参数输出字符串,   类似C的printf("输出字符串");
    
2:


TRACE   中的字符串可以带一个参数输出   , 类似C的printf("...%d",变量);


3:


TRACE   可以带两个参数输出,类似C的printf("...%d...%f",变量1,变量2);


4:


TRACE 可以带三个参数输出,类似C的printf("...%d,%d,%d",变量1,变量2,变量3);


TRACE 宏有点象我们以前在C语言中用的Printf函数,使程序在运行过程中输出一些调试信息,使我们能了解程序的一些状态。但有一点不同的是:




TRACE 宏只有在调试状态下才有所输出,而以前用的Printf 函数在任何情况下都有输出。和Printf 函数一样,TRACE函数可以接受多个参数如:


int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement/n" );
TRACE( "The value of x is %d/n", x );
TRACE( "x = %d and y = %d/n", x, y );
TRACE( "x = %d and y = %x and z = %f/n", x, y, z );


要注意的是TRACE宏只对Debug 版本的工程产生作用,在Release 版本的工程中,TRACE宏将被忽略。
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 7楼 发表于: 2012-01-17
《调试软件》随书工具介绍
调试软件


http://advdbg.org/books/swdbg/index.aspx





在写作《软件调试》的过程中,我使用了很多工具,包括十几种调试器和上百个不同用途的观察和监视工具。因为现有的工具不能满足写作书中某些内容的需要,于是我还专门开发了一些工具,以下是其中的几个:


符号文件观察器——SymView
用户态转储文件观察器——UdmpView
与内核调试引擎对话的交谈器——KdTalker
监视和记录CPU执行轨迹的CpuWhere
观察GDT、IDT的SoZoomer
您在接受以下协议的情况下,可以自由使用以上工具:


这些工具是免费提供给《软件调试》一书的读者的。如果你没有购买这本书,那么你应该感谢那些已经购买了这本书的朋友。
这些工具可能存在瑕疵,不管是否是工具自身的原因,这些工具的作者都不对使用这些工具导致的任何直接或者间接损失负任何责任。
在将以上工具公开前,我还需要对它们做一些测试和完善工作。比如使其适合更多的平台(CPU和OS),使其界面更友好等。所以我会逐步公开以上工具,请大家耐心等待!





虽然本书的重点不是编程语言和如何编写代码,但是为了不流于空泛,我们还是给出了大量的代码实例。这些实例可以分为如下几类:






用于演示相关调试技术的工作原理和实现方法,比如软件断点、硬件断点等。
充当被调试目标的靶子程序。
用来模拟各种错误的反例。
为本书开发的工具程序在工具栏目中有详细介绍和下载。


编译方法
所有示例程序都是使用C/C++语言编写的。大多数程序都可以使用Visual C++ 6.0编译器编译。个别程序需要使用Visual Studio 2005编译。如果程序的项目文件是以DSW/DSP为后缀的,那么便是VC6的项目文件。如果是SLN或PROJ后缀,那么便需要Visual Studio 2005或者2008。 如果您在编译这些程序时遇到困难,那么请先到问答栏目寻找是否已经存在有关的问答,如果没有那么请通过意见反馈栏目中给出的方式给我们联系。


目录结构
所有示例程序是按照右图所示的目录结构来存放的。BIN目录用来存放编译出的可执行文件和符号文件。源程序文件和项目文件保存在每一章的子目录中。编译时产生的临时文件统一放在与BIN目录并行的TEMP目录(没有画出,会自动创建)中。


下载
首先,以下程序都是与《软件调试》一书中的内容密切相关的,因此您应该在阅读书中内容后按照书中的指导使用这些程序。在任何情况下,作者不对因为使用以下程序而导致的任何直接和间接后果承担任何责任。


第二,您合法阅读和使用以下压缩包中的源程序文件和二进制文件的前提条件是您购买了《软件调试》一书。
示例程序的源程序文件和项目文件(2,365KB):swdbgbk_src.zip
示例程序的可执行文件和PDB文件(11,825KB):swdbgbk_bin.zip
试验使用的转储文件和有关程序文件(157KB):swdbgbk_dmp.zip
程序清单
在《软件调试》的附录A中列出了示例程序的清单,其中也包括了为本书开发的部分工具(见工具栏目)。


程序名称


用途


正文


Err2Fail.exe


演示在特定条件下才表现出来的错误


1.6.1


AccKernel.exe


从用户空间访问内核空间


2.5.2


AcsVio.exe


写代码段导致非法访问异常


2.5


ProtSeg.exe


应用程序代码不可以直接修改段寄存器


2.5


Fault.exe


使用结构化异常器处理除零异常后恢复程序运行


3.3.3


B2BStep.exe


分支到分支单步执行


4.3.4


HiInt3.exe


在代码中插入断点指令


4.1.1


DataBP.exe


手工设置数据断点


4.2.8


TryInt1.exe


在用户态代码中插入INT 1指令会违反保护规则


4.3.1


CpuWhere.exe


使用CPU的调试存储机制记录CPU的执行路线


5.4


Bts.sys


支持CpuWhere的驱动程序


5.4


LBR.dll


使用分支记录功能的WinDBG扩展模块


5.2.3


McaViewer.exe


读取MCA寄存器


6.3.2


Breakout.exe


试验应用程序自己调用DbgUiRemoteBreakin的效果


10.6.4


DebString


用于验证OutputDebugString API的工作原理


10.7


EvtFilter.exe


用于试验VC调试器的异常处理选项


10.5.5


HungWnd.exe


用于观察被中断到调试器后的程序窗口


10.6.9


MiniDbgee.exe


用作调试目标的简单Win32程序


10.4.2


TinyDbge.exe


用作调试目标的简单控制台程序


10.4.2


TinyDbgr.exe


使用调试API编写的简单调试器


10.4.2


SEH_Excp.exe


探索SEH的异常处理


11.4.3


SEH_Trmt.exe


探索SEH的终结处理


11.4.2


SEH_Mix.exe


嵌套使用SEH的异常处理和终结处理


11.4.6


VEH.exe


演示向量化异常处理器的用法


11.5.3


JitDbgr.exe


一个简单的JIT调试器


12.5.3


UdmpView.exe


读取和解析用户态转储文件


12.9.4


UEF.exe


触发未处理异常的控制台程序


12.1


UefWin32.exe


触发未处理异常的窗口程序


12.1


UefSndThrd.exe


在第2个线程总触发未处理异常的控制台程序


12.1


UefSrvc.exe


触发未处理异常的系统服务程序


12.1


UefCSharp.exe


触发未处理异常的.Net程序


12.1


UefSilent.exe


不显示应用程序错误对话框


12.4


ErrorMode.exe


观察SetErrorMode API的效果


13.6.1


HiCLFS.exe


使用CLFS API创建日志文件和读写日志记录


15.6


Crimson.exe


演示Crimson API的用法


16.9


ETW.exe


演示使用编程方法控制NT Kernel Logger


16.7.2


RawLog.exe


不使用清单文件而直接输出日志信息


16.9


KdTalker.exe


与内核调试引擎的对话程序


18.5.7


Verifiee.exe


探索程序验证器的分析目标


19.4.1


AllcStk.exe


演示栈的创建过程和栈溢出


22.2.3


BoAttack.exe


缓冲区溢出攻击的基本原理


22.10.2


BufOvr.exe


存在缓冲区溢出错误的小程序


22.10.1


CallConv.exe


包含各种函数调用协议的小程序


22.7


CallCV64.exe


演示64位系统下的函数调用协议


22.7.6


CheckESP.exe


不遵守栈平衡原则的小程序


22.6


HiStack.exe


用于观察栈的小程序


22.3.3


LocalVar.exe


用于观察局部变量的小程序


22.4.1


SecChk.exe


演示编译器的安全检查功能


22.11


StackChk.exe


演示栈检查函数的工作原理


22.8.3


StackOver.exe


通过死循环导致栈溢出的小程序


22.8.2


StkUFlow.exe


存在栈下溢错误的小程序


22.9


MemLeak.exe


使用CRT的调试支持自动转储内存泄漏


23.15


FreCheck.exe


用于分析释放堆块时触发的堆检查


23.8.3


HeapHFC


演示Win32堆的释放检查(HFC)机制


23.6.2


HeapMfc


演示内存泄漏的MFC程序


23.7


HeapOver


演示发生在堆上的缓冲区溢出


23.8


HiHeap.exe


用来分析基本内存分配和释放操作的控制台程序


23.3


SBHeap.exe


使用CRT的小堆块堆


23.11.2


Interop.exe


用于分析在同一个程序中使用两种异常处理机制


24.7


SehComp.exe


用于分析SEH异常处理编译方法的调试目标


24.5.2


SehRaw.exe


手工注册异常处理器


24.4.1


VC8Win32.exe


用于分析异常处理有关的安全问题


24.6


HiWorld.exe


VC2005产生的典型Windows程序


25.4.1


PdbFairy.exe


直接读取PDB文件的小程序


25.6.5


Sig2Time.exe


将PDB文件中的时间戳转换为时间


25.8


SymOption.exe


试验不同的符号文件选项


25.2


SymView.exe


符号文件观察器


25.6.8


D4D.dll


演示可调试设计的DLL模块


27.4


D4dTest.exe


使用D4D.dll的测试程序


27.4


PerfView.exe


演示性能监视程序的工作原理


27.5.3


MulThrds.exe


用于演示线程控制命令的调试目标


30.13.1


Q&A





以下《软件调试》的读者可能会问到的一些问题。简要罗列和回答如下。如果您有其它问题或者觉得回答不够全面,那么可以通过意见反馈栏目列出的方式进一步与作者和其他读者进行讨论。


为什么要重视软件调试?
在作者看来,当一个软件工程师掌握了基本的软件知识和概念后,那么接下来要学习的最重要技能便是软件调试。根本原因有两个:
使用调试技术可以更好的控制软件。相对于硬件,软件更加复杂、多变,有时甚至狡黠、事故和邪恶。大的方面讲,计算机历史上的重大灾难大多是软件导致的。从小的方面讲,用户遇到的崩溃、病毒入侵、没有响应等,也大多是由软件导致的。人类在控制和驾驭软件方面的能力还在发展的过程中。目前阶段,以调试器为核心的调试技术是征服软件的最强大武器。举例来说,目前有太多的糟糕软件完全不把自己当作“仆人”,它们自作主张,借助强大的计算机硬件恣意妄为,这时最有效的方法就是使用调试器将其擒获。当我们使用调试器来自由操纵桀骜的软件和被调试的计算机系统时,我们才真正找回了主人的尊严。如果我们不得不重新安装整个系统,那么就好像是为了赶走一个糟糕的仆人而重建“家园”。
使用调试技术可以更精确、更直接认识的软件。当我们一步步的跟踪一个软件时,我们对它的认识精确到了指令的级别。指令是软件的本质,是承载思维的载体,软件的好坏和善恶忠奸都在它的指令中,所以在指令一级的认识软件方式对于软件工程师来说是非常重要的。因为时间关系,我们可能无法跟踪软件的每一条语句或者指令,但是我们有必要仔细跟踪关键的操作和可能存在问题的代码片段。
终上,软件调试技术对于认识和控制软件都有着重要作用。一旦掌握了这门技术,便可以在很多方面获益:


侦错,即定位和修正瑕疵。
学习新的软硬件技术。
安全。以研究Rootkit著称的Joanna Rutkowska在接受采访时曾经说她的计算机系统不安装任何“安全软件”,但是她安装了调试器,并且说“MS Kernel Debugger can be very useful tool for system compromise detection”
很多资深的计算机专家都非常重视软件调试,比如以开创MSDN杂志的Under The Hood专栏著称的Matt Pietriek在给本书的短评中就说,调试器是所有系统程序员必须掌握的工具。尽管他没有说所有程序员,但这并不意味着,其他程序员学习调试器没有价值。而只是说,系统程序的复杂度和重要性,要求我们必须用调试器这样的强大工具。编写应用软件时,徒手或者土办法还能应付。对于其他程序员来说,既然有更好和更高效的工具,那我们为什么不用呢?


《Linux内核源代码情景分析》的两位作者在他们的《嵌入式系统》一书中谈到调试曾经说,在软件开发的“生命周期”中,程序调试(debug)以及调试手段的重要性是“怎么强调也不为过”的。


这本书为什么这么厚?
简单来说,因为太多的内容需要写。


软件调试如此重要,但是太多人对它还知之甚少,而且很难找到好的资料来学习。在写这本书之前,我仔细搜索了的关于调试的中外图书。但是没能找到一本我理想中的关于软件调试的书。


我希望这本书具有一般性,探讨调试的一般原理和思想,但是又不空泛,对于关键的技术应该讲透、讲彻底。
我希望这本书有理论价值,但又不脱离实践,讲的应该是当代主流的软硬件环境和工具,而不是多年前的,也不是实验性的,讨论的应该是“真家伙”,面对的应该“真难题”。因为调试软件是打仗,纸上谈兵似的东西没有用。
我希望这本书介绍必要的软硬件基础。打仗要熟悉地形,调试软件要熟悉软件所运行的基础环境,最起码的就是CPU、操作系统和编译器。如果对这三样东西模棱两可,那么打仗时难免晕头转向。
发现还没有这样一本书之后,与其等待,不如自己来,于是我决定写这样一本书。按照上面的“希望”有了构思和框架后,漫长的写作开始了。大约每完成一篇,我会统计一下已经达到的厚度和可能的厚度。在完成第3篇后,我开始有意识的控制篇幅。在重构第3篇时,删除了长达60页的整整一章,那一章花了不只一个月时间,这次心痛的体验让我更重视后面的写作计划。


在开始编辑这本书时,我们也考虑这本书的厚度,排版尽可能紧凑,在不影响阅读的情况下,有效利用空间。比如略微缩小行距,增大字体。在编辑的后期,所有页数统计出来后,我和编辑还是做了一个痛苦的决定,再删!于是又删减了大约70页正文和一个20多页的附录,成为目前的1006页。


这本书为什么没有详细讲Linux有关的内容?
正如前面谈到的,写作这本书的一个基本原则是,有一般性而且有不流于空泛。因此我们选择例子时尽可能使其具有更广泛的代表性。考虑到Windows操作系统的流行性,我们选择了以Windows系统作为本书的操作系统实例。


这本书是否适合初学者阅读?
初学者是个很模糊的概念,因此这个问题很难直接回答。我建议有这个问题的朋友先在网络上免费阅读本书的前言和样章。然后自己判断是否适合阅读这本书。


在做2.7.5节的实验时,遇到的情况为什么和书中讲的不一样?
您使用的系统可能启用了CPU的物理地址扩展(即PAE)功能,如果是这样,那么请您参阅《使用WinDBG观察启用PAE后的分页机制》一文来做这个实验。如果是仍然遇到困难,那么请发电子邮件和我联系或者把问题发到高端调试网站中《软件调试》答疑论坛中。


在VS2002/2003/2005/2008下重新编译第3章的Fault程序后,程序为什么不断的循环?
这是因为编译器在编译除法语句nResult=nDividend/nDivisor时,将除数先放到了寄存器中,这样在异常处理中虽然将除数改为非零,但是寄存器中的除数还是零。请参考下面的讨论做修改: 关于第三章Fault演示程序的疑问。



解读PDB文件的魔码(Magic Code)——表25-6来之不易
http://advdbg.org/books/swdbg/f_pdb_magic.aspx





PDB是Windows系统中使用最多的符号文件格式。最先是由Visual C++的始祖Visual C++ 1.0引入的,时间大约为1992年。


PDB文件的内部格式是不公开的。总的来说,PDB文件是二进制格式。但是有趣的是,在它的起始处总是有一段可读的文本信息。可以用type命令或者任何文本文件编辑器来观察这段内容,比如使用Type命令可以观察到:


c:\dig\dbg\author\code\bin\Debug>type kdtalker.pdb


Microsoft C/C++ program database 2.00


上面显示的这句话被称为PDB签名,它位于每个PDB文件的最开始处,以0x1A结束。因为0x1A在ASCII码中代表文本文件的结束,所以Type命令遇到这个字符后就会停下来,不会再继续显示后面的二进制内容。


如果使用记事本打开,那么可以看到下图所示的景象:


  


可见除了Type命令显示的签名外,后面还有两个可读的ASCII码,即第二行的JG。这个JG代表什么呢?事实上,它就是我们要说的PDB魔码(Magic Code)。它紧跟在PDB签名后面,长度为四个字节。


上面的PDB文件是VC6产生的,被称为PDB2.0,如果观察VS 2003/2005/2008产生的PDB7.0的文件,那么会发现它的PDB魔码为DS。


为了理解PDB魔码的含义,我开始了艰苦的搜索。搜索很久后,仍一无所获。关于PDB内部格式的文章实在是少。仔细阅读了《Undocumented Windows 2000 Secrets》,也没有答案。但是在搜索的过程中,我发现了Andy Penell的博客。他在名为《“PDB过时了”意味着什么》的短文中介绍了PDB签名。


http://blogs.msdn.com/andypennell/archive/2005/12/09/502267.aspx


读了这篇短文,我立刻意识到Andy可能是给我答案的专家。于是我在这篇博客的评论中先向他提了一个问题,并随便提到DS:


Monday, April 23, 2007 11:11 PM by Raymond Zhang


Andy, could you advise what does MSF mean in the signature. As I know, the signaure is followed by DS.


Andy很快就回复了,对MSF的解释与我猜测的相同。最重要的是他把我暗带的问题也回答了,令我喜出望外:
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 8楼 发表于: 2012-01-22
VC调试方法大全


一、调试基础



调试快捷键



F5: 开始调试



Shift+F5: 停止调试



F10: 调试到下一句,这里是单步跟踪



F11: 调试到下一句,跟进函数内部



Shift+F11: 从当前函数中跳出



Ctrl+F10: 调试到光标所在位置



F9: 设置(取消)断点



Alt+F9: 高级断点设置



跟踪调试



1、 尽量使用快捷键时行调试



2、 观察调试信息



3、 高级中断设置



异常调试



重试->取消->调试



函数堆栈,用variables或者call stack 窗口



Release调试



1、 经常测试你的Debug和Release版本



2、 不要移除调试代码,如用ASSERT, TRACE等。



3、 初始化变量,特别是全局变量,malloc的内存,new的内存



4、 当你移除某个资源时,确保你移除了所有跟这个资源相关的申明(主要是在resouce.h文中)



5、 使用3或者4级的警告级编译你的代码,并确保没有警告,project->setting->c/c++->warninglevel(中文版是项目->属性->C/C++->常规->警告等级)



6、 _debug改成NDEBUG进行调试,project->setting->C/C++->Preprocessordefinitions(中文版是项目->属性->C/C++->预处理器->预处理定义)(这里是debug和Release编译的重要不同之一)



7、 在Release中调试源代码,project->setting->C/C++->debug info选择programDataBase(中文版是项目->属性->C/C++->常规->调试信息格式->用于“编辑并继续”的程序数据库),project->setting->link选上Generate debug info(中文版是项目->属性->链接器->调试->生成调试信息)



8、 走读代码,特别关注堆栈和指针



二、TRACE



当选择了Debug目标,并且afxTraceEnabled变量被置为TRUE时,TRACE宏也就随之被激活了。但在程序的Release版本中,它们是被完全禁止的。下面是一个典型的TRACE语句:





int nCount =9;



CString strDesc("total");



TRACE("Count =%d,Description =%s\n",nCount,strDesc);







可以看到,TRACE语句的工作方式有点像C语言中的printf语句,TRACE宏参数的个数是可变的,因此使用起来非常容易。如果查看MFC的源代码,你根本找不到TRACE宏,而只能看到TRACE0、TRACE1、TRACE2和TRACE3宏,它们的参数分别为0、1、2、3。



个人总结:最近看网络编程是碰到了TRACE语句,不知道在哪里输出,查了一晚上资料也没找出来,今天终于找到了,方法如下:



1.在MFC中加入TRACE语句



2.在TOOLS->MFCTRACER中选择 “ENABLE TRACING”点击OK



3.进行调试运行,GO(F5)(特别注意:不是执行‘!’以前之所以不能看到TRACE内容,是因为不是调试执行,而是‘!’了,切记,切记)



4.然后就会在OUTPUT中的DEBUG窗口中看到TRACE内容了,调试执行会自动从BUILD窗口跳到DEBUG窗口,在那里就看到TRACE的内容了,^_^



以下是找的TRACE的详细介绍:



==============================



TRACE宏对于VC下程序调试来说是很有用的东西,有着类似printf的功能;该宏仅仅在程序的DEBUG版本中出现,当RELEASE的时候该宏就完全消失了,从而帮助你调式也在RELEASE的时候减少代码量。



使用非常简单,格式如下:



TRACE("DDDDDDDDDDD");



TRACE("wewe%d",333);



同样还存在TRACE0,TRACE1,TRACE2。。。分别对应0,1,2。。个参数



TRACE信息输出到VC IDE环境的输出窗口(该窗口是你编译项目出错提示的哪个窗口),但仅限于你在VC中运行你的DEBUG版本的程序。



TRACE信息还可以使用DEBUGVIEW来捕获到。这种情况下,你不能在VC的IDE环境中运行你的程序,而将BUILD好的DEBUG版本的程序单独运行,这个时候可以在DEBUGVIEW的窗口看到DEBUGVIE格式的输出了。



VC中TRACE的用法有以下四种:



TRACE1 ,就是不带动态参数输出字符串, 类似C的printf("输出字符串");



TRACE2: 中的字符串可以带一个参数输出 ,类似C的printf("...%d",变量);



TRACE3:可以带两个参数输出,类似C的printf("...%d...%f",变量1,变量2);



TRACE4 可以带三个参数输出,类似C的printf("...%d,%d,%d",变量1,变量2,变量3);



TRACE 宏有点象我们以前在C语言中用的Printf函数,使程序在运行过程中输出一些调试信息,使我们能了解程序的一些状态。但有一点不同的是:
TRACE 宏只有在调试状态下才有所输出,而以前用的Printf 函数在任何情况下都有输出。和Printf 函数一样,TRACE函数可以接受多个参数如:



int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );
TRACE( "The value of x is %d\n", x );
TRACE( "x = %d and y = %d\n", x, y );
TRACE( "x = %d and y = %x and z = %f\n", x, y, z );



要注意的是TRACE宏只对Debug 版本的工程产生作用,在Release 版本的工程中,TRACE宏将被忽略。



三、ASSERT



如果你设计了一个函数,该函数需要一个指向文档对象的指针做参数,但是你却错误地用一个视图指针调用了这个函数。这个假的地址将导致视数据的破坏。现在,这种类型的问题可以被完全避免,只要在该函数的开始处实现一个ASSERT测试,用来检测该指针是否真正指向一个文档对象。一般来讲,编程者在每个函数的开始处均应例行公事地使用assertion。ASSERT宏将会判断表达式,如果一个表达式为真,执行将继续,否则,程序将显示一条消息并且暂停,你可以选择忽视这条错误并继续、终止这个程序或者是跳到Debug器中。下面一例演示了如何使用一个ASSERT宏去验证一个语句。



void foo(char p, int size )



{



ASSERT( p != 0 ); //确认缓冲区的指针是有效的



ASSERT( ( size >= 100 ); //确认缓冲区至少有100个字节



// Do the foo calculation



}



这些语句不产生任何代码,除非—DEBUG处理器标志被设置。Visual C++只在Debug版本设置这些标志,而在Release版本不定义这些标志。当—DEBUG被定义时,两个assertions将产生如下代码:



//ASSERT( p!= 0 );



do{



if( !(p !=0) && AfxAssertFailedLine(—FILE—,—LINE—) )



AfxDebugBreak();



}while(0);



//ASSERT((size 〉= 100);



do{



if(!(size 〉= 100) &&AfxAssertFailedLine(—FILE—,—LINE—))



AfxDebugBreak();



}while(0);





Do-while循环将整个assertion封装在一个单独的程序块中,使得编译器编译起来很舒畅。If语句将求取表达式的值并且当结果为零时调用AfxAssertFailedLine()函数。这个函数将弹出一个对话框,其中提供三个选项“取消、重试或忽略”,当你选取“重试”时,它将返回TRUE。重试将导致对AfxDebugBreak()函数的调用,从而激活调试器。



AfxAssertFailedLine()是一个未正式公布的函数,它的功能就是显示一个消息框。该函数的源代码驻留在afxasert.cpp中。函数中的—FILE—和—LINE—语句是处理器标志,它们分别指定了源文件名和当前的行号。





AfxAssertFailedLine()是一个未正式公布的函数,它的功能就是显示一个消息框。该函数的源代码驻留在afxasert.cpp中。函数中的—FILE—和—LINE—语句是处理器标志,它们分别指定了源文件名和当前的行号。





四、VERIFY





因为assertion只能在程序的Debug版本中起作用,在表达式中不可以包含赋值语句、增加语句(++)或者是减少语句(--),因为,这些语句实际改变数据。可有时你可能想要验证一个能动的表达式,使用一个赋值语句。那么就到了用VERIFY宏来替代ASSERT。例如:



voidfoo(char p, int size )



{



char q;



VERIFY(q = p);



ASSERT((size 〉= 100);



//Do the foo calculation



//Do the foo calculation



}





在Debug模式下,ASSERT和VERIFY是一回事,但是在Release模式下,VERIFY宏仍然测试表达式而assertion却不起任何作用。可以说,在Release模式下,ASSERT语句被删除了。





请注意,如果你在一个ASSERT语句中错误地使用了一个能动的表达式,编译器将不做任何警告地忽略它。在Release模式下,该表达式就会被无声息地删除掉,这将会导致程序的错误运行。由于Release版的程序通常不包含Debug信息,这类错误将很难被发现。



五、VC高级调试方法-条件及数据断点的设定



(一)位置断点(LocationBreakpoint
大家最常用的断点是普通的位置断点,在源程序的某一行按F9就设置了一个位置断点。但对于很多问题,这种朴素的断点作用有限。譬如下面这段代码:



void CForDebugDlg::OnOK()



{



for(int i = 0; i < 1000; i++) //A



{



intk = i * 10 - 2; //B



SendTo(k); //C



inttmp = DoSome(i); //D



Trace0("这里要输出的内容”);//在这里可以输出一些有用的信息,你也可以输出I的值,都是可以的



intj = i / tmp; //E



}



}



//其实我们还可以用其他方法调式也是一样的,你可以用TRACE0宏来输出循环中的每一个结果,我们也可以在debug中看见输出的结果,当出现问题时,输出的结果可能就不一样了,我们可以分析一下debug中的结果找出问题的所在



执行此函数,程序崩溃于E行,发现此时tmp为0,假设tmp本不应该为0,怎么这个时候为0呢?所以最好能够跟踪此次循环时DoSome函数是如何运行的,但由于是在循环体内,如果在E行设置断点,可能需要按F5(GO)许多次。这样手要不停的按,很痛苦。使用VC6断点修饰条件就可以轻易解决此问题。步骤如下。
1 Ctrl+B打开断点设置框,如下图:





Figure 1设置高级位置断点
2 然后选择D行所在的断点,然后点击condition按钮,在弹出对话框的最下面一个编辑框中输入一个很大数目,具体视应用而定,这里1000就够了。
3 按F5重新运行程序,程序中断。Ctrl+B打开断点框,发现此断点后跟随一串说明:...487 times remaining。意思是还剩下487次没有执行,那就是说执行到513(1000-487)次时候出错的。因此,我们按步骤2所讲,更改此断点的skip次数,将1000改为513。
4 再次重新运行程序,程序执行了513次循环,然后自动停在断点处。这时,我们就可以仔细查看DoSome是如何返回0的。这样,你就避免了手指的痛苦,节省了时间。
再看位置断点其他修饰条件。如Figure 1所示,在“Enter the expression to be evaluated:”下面,可以输入一些条件,当这些条件满足时,断点才启动。譬如,刚才的程序,我们需要i为100时程序停下来,我们就可以输入在编辑框中输入“i==100”
另外,如果在此编辑框中如果只输入变量名称,则变量发生改变时,断点才会启动。这对检测一个变量何时被修改很方便,特别对一些大程序。
用好位置断点的修饰条件,可以大大方便解决某些问题。
二) 数据断点(DataBreakpoint
软件调试过程中,有时会发现一些数据会莫名其妙的被修改掉(如一些数组的越界写导致覆盖了另外的变量),找出何处代码导致这块内存被更改是一件棘手的事情(如果没有调试器的帮助)。恰当运用数据断点可以快速帮你定位何时何处这个数据被修改。譬如下面一段程序:



#include "stdafx.h"



#include <string.h>



int main(int argc, char* argv[])



{



charszName1[10];



charszName2[4];



strcpy(szName1,"shenzhen");



printf("%s\n",szName1); //A





strcpy(szName2,"vckbase"); //B



printf("%s\n",szName1);



printf("%s\n",szName2);



return0;



}



这段程序的输出是



szName1: shenzhen



szName1:ase



szName2:vckbase



首先我给你分析一下为什么会是这样的结果呢!首先你在strcpy(szName1,"shenzhen");这个地方F9设置一个断点,然后F5运行程序,这是程序会断到我们设置的断点,如下图





看到了吧,问题出现的原因就在这里,系统给szName2分配的地址是0x0012ff70这里是4个字节,然后呢,在0x0012ff70后面4个字节处,开始分配szName1这10个字节,也就是在0x0012ff74处开始分配10个字节,



F10单步跟踪,来到printf("%s\n", szName1)这一行,如下图





szName1分配的空间已经附上了值.



F10走到下一个printf("%s\n", szName1) 看下图,





因为szName1 和szName2分配的空间是连续的,所以给szName2赋值超过所容纳的字节时就开始覆盖szName1的内容了,所以说当我们在输出结果的时候就出现我们想不到的结果了,



那么怎么去调试呢,下面是具体的方法



szName1何时被修改呢?因为没有明显的修改szName1代码。我们可以首先在A行设置普通断点,F5运行程序,程序停在A行。然后我们再设置一个数据断点。如下图:





Figure 2 数据断点
F5继续运行,程序停在B行,说明B处代码修改了szName1。B处明明没有修改szName1呀?但调试器指明是这一行,一般不会错,所以还是静下心来看看程序,哦,你发现了:szName2只有4个字节,而strcpy了7个字节,所以覆写了szName1。
数据断点不只是对变量改变有效,还可以设置变量是否等于某个值。譬如,你可以将Figure 2中红圈处改为条件”szName2[0]==''''y''''“,那么当szName2第一个字符为y时断点就会启动。
可以看出,数据断点相对位置断点一个很大的区别是不用明确指明在哪一行代码设置断点。



三) 其他
1 在call stack窗口中设置断点,选择某个函数,按F9设置一个断点。这样可以从深层次的函数调用中迅速返回到需要的函数。
2 Set Next StateMent命令(debug过程中,右键菜单中的命令)
此命令的作用是将程序的指令指针(EIP)指向不同的代码行。譬如,你正在调试上面那段代码,运行在A行,但你不愿意运行B行和C行代码,这时,你就可以在D行,右键,然后“Set Next StateMent”。调试器就不会执行B、C行。只要在同一函数内,此指令就可以随意跳前或跳后执行。灵活使用此功能可以大量节省调试时间。
3 watch窗口
watch窗口支持丰富的数据格式化功能。如输入0x65,u,则在右栏显示101。
实时显示windows API调用的错误:在左栏输入@err,hr。
在watch窗口中调用函数。提醒一下,调用完函数后马上在watch窗口中清除它,否则,单步调试时每一步调试器都会调用此函数。
4 messages断点不怎么实用。基本上可以用前面讲述的断点代替。
六。VC调试环境设置



为了调试一个程序,首先必须使程序中包含调试信息。一般情况下,一个从AppWizard创建的工程中包含的Debug Configuration自动包含调试信息,但是是不是Debug版本并不是程序包含调试信息的决定因素,程序设计者可以在任意的Configuration中增加调试信息,包括Release版本。



为了增加调试信息,可以按照下述步骤进行:



 



打开Projectsettings对话框(可以通过快捷键ALT+F7打开,也可以通过IDE菜单Project/Settings打开)



选择C/C++页,Category中选择general ,则出现一个Debug Info下拉列表框,可供选择的调试信息 方式包括:



 



 



  命令行 Project settings 说明



无 None 没有调试信息



/Zd Line Numbers Only 目标文件或者可执行文件中只包含全局和导出符号以及代码行信息,不包含符号调试信息



/Z7 C7.0- Compatible 目标文件或者可执行文件中包含行号和所有符号调试信息,包括变量名及类型,函数及原型等



/Zi Program Database 创建一个程序库(PDB),包括类型信息和符号调试信息。



/ZI Program Databasefor



Edit and Continue 除了前面/Zi的功能外,这个选项允许对代码进行调试过程中的修改和继续执行。这个选项同时使#pragma设置的优化功能无效



选择Link页,选中复选框"Generate DebugInfo",这个选项将使连接器把调试信息写进可执行文件和DLL



如果C/C++页中设置了Program Database以上的选项,则Link incrementally可以选择。选中这个选项,将使程序可以在上一次编译的基础上被编译(即增量编译),而不必每次都从头开始编译。


QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
发帖
16
金币
2
威望
2
贡献值
0
元宝
0
只看该作者 9楼 发表于: 2012-02-05
这么好的帖子不顶就太对不起楼主了








广西影视网 gxy4.com
描述
快速回复

您目前还是游客,请 登录注册
如果您在写长篇帖子又不马上发表,建议存为草稿