VS2008调试Release程序--Dump文件方式

来源:互联网 发布:淘宝自拍风格技巧 编辑:程序博客网 时间:2024/04/28 23:53

Windows平台下用C++开发应用程序,最不想见到的情况恐怕就是程序崩溃,而要想解决引起问题的bug,最困难的应该就是调试release版本了。因为release版本来就少了很多调试信息,更何况一般都是发布出去由用户使用,crash的现场很难保留和重现。目前有一些方法可以解决:崩溃地址 + MAP文件;MAP文件;SetUnhandledExceptionFilter + Minidump。本文重点解决Minidump方式。Dump文件是进程的内存镜像,可以把程序运行时的状态完整的保存下来。

一、Minidump文件生成

  1、Minidump概念

    minidump(小存储器转储)可以理解为一个dump文件,里面记录了能够帮助调试crash的最小有用信息。实际上,如果你在系统属性 -> 高级 -> 启动和故障恢复 -> 设置 -> 写入调试信息中选择小内存转储(64 KB)”的话,当系统意外停止时都会在C:\Windows\Minidump\路径下生成一个.dmp后缀的文件,这个文件就是minidump文件,只不过这个是内核态的minidump

   我们要生成的是用户态的minidump,文件中包含了程序运行的模块信息、线程信息、堆栈调用信息等。而且为了符合其mini的特性,dump文件是压缩过的。

2、生成minidump文件

通过drwtsn32NTSDCDB等调试工具生成Dump文件, drwtsn32存在的缺点虽然NTSDCDB可以完全解决,但并不是所有的操作系统中都安装了NTSDCDB等调试工具。根据MiniDumpWriteDump接口,完全可以程序自动生成Dump文件。

 

3、  自动生成Minidump文件

当程序遇到未处理异常(主要指非指针造成)导致程序崩溃死,如果在异常发生之前调用了SetUnhandledExceptionFilter()函数,异常交给函数处理。MSDN中描述为:

Issuing SetUnhandledExceptionFilter replaces the existing top-level exception filter for all existing and all future threads in the calling process.

 因而,在程序开始处增加SetUnhandledExceptionFilter()函数,并在函数中利用适当的方法生成Dump文件,即可实现需要的功能。

生成dump文件类(minidump.h)

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #pragma once  
  2.   
  3.    
  4.   
  5. #include <windows.h>  
  6.   
  7. #include <imagehlp.h>  
  8.   
  9. #include <stdlib.h>  
  10.   
  11. #pragma comment(lib, "dbghelp.lib")  
  12.   
  13.    
  14.   
  15. inline BOOL IsDataSectionNeeded(const WCHAR* pModuleName)  
  16.   
  17. {  
  18.   
  19.     if(pModuleName == 0)  
  20.   
  21.     {  
  22.   
  23.        return FALSE;  
  24.   
  25.     }  
  26.   
  27.    
  28.   
  29.     WCHAR szFileName[_MAX_FNAME] = L"";  
  30.   
  31.     _wsplitpath(pModuleName, NULL, NULL, szFileName, NULL);  
  32.   
  33.     if(wcsicmp(szFileName, L"ntdll") == 0)  
  34.   
  35.        return TRUE;  
  36.   
  37.    
  38.   
  39.     return FALSE;   
  40.   
  41. }  
  42.   
  43.    
  44.   
  45. inline BOOL CALLBACK MiniDumpCallback(PVOID                            pParam,   
  46.   
  47.                                   const PMINIDUMP_CALLBACK_INPUT   pInput,   
  48.   
  49.                                   PMINIDUMP_CALLBACK_OUTPUT        pOutput)  
  50.   
  51. {  
  52.   
  53.     if(pInput == 0 || pOutput == 0)  
  54.   
  55.        return FALSE;  
  56.   
  57.    
  58.   
  59.     switch(pInput->CallbackType)  
  60.   
  61.     {  
  62.   
  63.     case ModuleCallback:   
  64.   
  65.        if(pOutput->ModuleWriteFlags & ModuleWriteDataSeg)   
  66.   
  67.    
  68.   
  69.            if(!IsDataSectionNeeded(pInput->Module.FullPath))   
  70.   
  71.    
  72.   
  73.               pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg);   
  74.   
  75.    
  76.   
  77.     case IncludeModuleCallback:  
  78.   
  79.     case IncludeThreadCallback:  
  80.   
  81.     case ThreadCallback:  
  82.   
  83.     case ThreadExCallback:  
  84.   
  85.        return TRUE;  
  86.   
  87.    
  88.   
  89.     default:;  
  90.   
  91.     }  
  92.   
  93.    
  94.   
  95.     return FALSE;  
  96.   
  97. }  
  98.   
  99.    
  100.   
  101. //创建Dump文件  
  102.   
  103. inline void CreateMiniDump(EXCEPTION_POINTERS* pep, LPCTSTR strFileName)  
  104.   
  105. {  
  106.   
  107.     HANDLE hFile = CreateFile(strFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);  
  108.   
  109.     if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE))  
  110.   
  111.     {  
  112.   
  113.        MINIDUMP_EXCEPTION_INFORMATION mdei;  
  114.   
  115.        mdei.ThreadId           = GetCurrentThreadId();  
  116.   
  117.        mdei.ExceptionPointers  = pep;  
  118.   
  119.        mdei.ClientPointers     = FALSE;  
  120.   
  121.        MINIDUMP_CALLBACK_INFORMATION mci;  
  122.   
  123.        mci.CallbackRoutine     = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;  
  124.   
  125.        mci.CallbackParam       = 0;  
  126.   
  127.        MINIDUMP_TYPE mdt       = (MINIDUMP_TYPE)0x0000ffff;  
  128.   
  129.        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &mdei, NULL, &mci);  
  130.   
  131.    
  132.   
  133.        CloseHandle(hFile);   
  134.   
  135.     }  
  136.   
  137. }  
  138.   
  139.    
  140.   
  141. LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)  
  142.   
  143. {  
  144.   
  145.     return NULL;  
  146.   
  147. }  
  148.   
  149.    
  150.   
  151. BOOL PreventSetUnhandledExceptionFilter()  
  152.   
  153. {  
  154.   
  155.     HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));  
  156.   
  157.     if (hKernel32 ==   NULL)  
  158.   
  159.        return FALSE;  
  160.   
  161.    
  162.   
  163.    
  164.   
  165.     void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");  
  166.   
  167.     if(pOrgEntry == NULL)  
  168.   
  169.        return FALSE;  
  170.   
  171.    
  172.   
  173.    
  174.   
  175.     unsigned char newJump[ 100 ];  
  176.   
  177.     DWORD dwOrgEntryAddr = (DWORD) pOrgEntry;  
  178.   
  179.     dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far  
  180.   
  181.    
  182.   
  183.    
  184.   
  185.     void *pNewFunc = &MyDummySetUnhandledExceptionFilter;  
  186.   
  187.     DWORD dwNewEntryAddr = (DWORD) pNewFunc;  
  188.   
  189.     DWORD dwRelativeAddr = dwNewEntryAddr -  dwOrgEntryAddr;  
  190.   
  191.    
  192.   
  193.    
  194.   
  195.     newJump[ 0 ] = 0xE9;  // JMP absolute  
  196.   
  197.     memcpy(&newJump[ 1 ], &dwRelativeAddr, sizeof(pNewFunc));  
  198.   
  199.     SIZE_T bytesWritten;  
  200.   
  201.     BOOL bRet = WriteProcessMemory(GetCurrentProcess(),    pOrgEntry, newJump, sizeof(pNewFunc) + 1, &bytesWritten);  
  202.   
  203.     return bRet;  
  204.   
  205. }  
  206.   
  207.    
  208.   
  209.    
  210.   
  211. LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException)  
  212.   
  213. {  
  214.   
  215.     TCHAR szMbsFile[MAX_PATH] = { 0 };  
  216.   
  217.     ::GetModuleFileName(NULL, szMbsFile, MAX_PATH);  
  218.   
  219.     TCHAR* pFind = _tcsrchr(szMbsFile, '\\');  
  220.   
  221.     if(pFind)  
  222.   
  223.     {  
  224.   
  225.        *(pFind+1) = 0;  
  226.   
  227.        _tcscat(szMbsFile, _T("CreateMiniDump.dmp"));  
  228.   
  229.        CreateMiniDump(pException,szMbsFile);  
  230.   
  231.     }  
  232.   
  233.    
  234.   
  235.    
  236.   
  237.     // TODO: MiniDumpWriteDump  
  238.   
  239.     FatalAppExit(-1,  _T("Fatal Error"));  
  240.   
  241.     return EXCEPTION_CONTINUE_SEARCH;  
  242.   
  243. }  
  244.   
  245.    
  246.   
  247. //运行异常处理  
  248.   
  249. void RunCrashHandler()  
  250.   
  251. {  
  252.   
  253.     SetUnhandledExceptionFilter(UnhandledExceptionFilterEx);  
  254.   
  255.     PreventSetUnhandledExceptionFilter();  
  256.   
  257. }  


 

//测试实现文件

// 一个有函数调用的类

//

class CrashTest

{

public:

    void Test()

    {

       Crash();

    }

 

private:

    void Crash()

    {

           strcpy(NULL,"adfadfg");

    }

};

 

int _tmain(int argc_TCHARargv[])

{

    //设置异常处理函数

    RunCrashHandler();

 

    CrashTest test;

    test.Test();

    getchar();

    return 0;

}

 

注意事项

1、需要配置debug选项,在C/C++选项à常规à调试信息格式(设置为程序数据库(/Zi));在连接器选项—>调试à生成调试信息(设置为是)C/C++选项à优化à禁用。(参见下图)

2  可执行文件(exe)必须找到dbghelp.dll,才能生成Dump文件。这个DLL可以从调试工具包中找到。

3*.exe*.pdb*.dumpdbghelp.dll 这四个文件需要放在同一目录下才好调试,双击dump文件时,就可以自动关联到出错代码位置。

4、为了获取更多更深入的调试信息,需要把程序优化开关设置成禁用。

5 当异常代码定位成功以后,如果无法阻止异常的产生,可以用 __try 结构包装异常代码,__try  try 不同,前者可以捕获非法指针产生的异常。

__try {

// 会异常的函数

}

__except( EXCEPTION_EXECUTE_HANDLER ){

// 异常处理

}

二、调试Minidump文件

  1. 双击minidump文件(*.dmp)。默认会启动vs2008。
  2. 菜单Tools/Options, Debugging/Symbols,增加PDB文件路径。注:如果minidump文件与pdb文件在同一目录,就不用设置这个了。
  3. 若调试的程序需要微软基础库的PDB信息,可以增加一个路径为:
  4. http://msdl.microsoft.com/download/symbols
  5. 在界面下方Cache Symbol From symbol…选择本地存储这些Symbols的路径。 注:如果本地已存储过微软基础库的pdb,就直接按照此步操作设置本地路径,不必执行上一步操作了。
  6. 设置代码路径:

设置代码路径:

刚打开的dmp工程,进入解决方案的属性。在这里输入源程序的代码路径。注:一定是sln所在的路径,而不是vcproj的路径!



按F5,debug吧。

0 0
原创粉丝点击