如何利用WinDbg找出程序崩溃的位置

 

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

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

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

MiniDump.h

view plain print ?
  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_;  


MiniDump.cpp

view plain print ?
  1. /* 
  2.     Author: Vladimir Sedach. 
  3.  
  4.     Purpose: demo of Call Stack creation by our own means, 
  5.     and with MiniDumpWriteDump() function of DbgHelp.dll. 
  6. */  
  7.   
  8. #include "StdAfx.h"  
  9. #include "MiniDump.h"  
  10. #include <Shlwapi.h>  
  11.   
  12. #pragma comment(lib,"shlwapi.lib")  
  13.   
  14. HMODULE hDbgHelp;  
  15. MINIDUMP_WRITE_DUMP MiniDumpWriteDump_;  
  16.   
  17. CREATE_TOOL_HELP32_SNAPSHOT CreateToolhelp32Snapshot_;  
  18. MODULE32_FIRST  Module32First_;  
  19. MODULE32_NEST   Module32Next_;  
  20.   
  21. #define DUMP_SIZE_MAX   8000    //max size of our dump  
  22. #define CALL_TRACE_MAX  ((DUMP_SIZE_MAX - 2000) / (MAX_PATH + 40))  //max number of traced calls  
  23. #define NL              "\r\n"  //new line  
  24.   
  25. extern CString GetExePath();  
  26.   
  27. //****************************************************************************************  
  28. BOOL WINAPI Get_Module_By_Ret_Addr(PBYTE Ret_Addr, PCHAR Module_Name, PBYTE & Module_Addr)  
  29. //****************************************************************************************  
  30. // Find module by Ret_Addr (address in the module).  
  31. // Return Module_Name (full path) and Module_Addr (start address).  
  32. // Return TRUE if found.  
  33. {  
  34.     MODULEENTRY32   M = {sizeof(M)};  
  35.     HANDLE  hSnapshot;  
  36.   
  37.     Module_Name[0] = 0;  
  38.       
  39.     if (CreateToolhelp32Snapshot_)  
  40.     {  
  41.         hSnapshot = CreateToolhelp32Snapshot_(TH32CS_SNAPMODULE, 0);  
  42.           
  43.         if ((hSnapshot != INVALID_HANDLE_VALUE) &&  
  44.             Module32First_(hSnapshot, &M))  
  45.         {  
  46.             do  
  47.             {  
  48.                 if (DWORD(Ret_Addr - M.modBaseAddr) < M.modBaseSize)  
  49.                 {  
  50.                     lstrcpyn(Module_Name, M.szExePath, MAX_PATH);  
  51.                     Module_Addr = M.modBaseAddr;  
  52.                     break;  
  53.                 }  
  54.             } while (Module32Next_(hSnapshot, &M));  
  55.         }  
  56.   
  57.         CloseHandle(hSnapshot);  
  58.     }  
  59.   
  60.     return !!Module_Name[0];  
  61. //Get_Module_By_Ret_Addr  
  62.   
  63. //******************************************************************  
  64. int WINAPI Get_Call_Stack(PEXCEPTION_POINTERS pException, PCHAR Str)  
  65. //******************************************************************  
  66. // Fill Str with call stack info.  
  67. // pException can be either GetExceptionInformation() or NULL.  
  68. // If pException = NULL - get current call stack.  
  69. {  
  70.     CHAR    Module_Name[MAX_PATH];  
  71.     PBYTE   Module_Addr = 0;  
  72.     PBYTE   Module_Addr_1;  
  73.     int     Str_Len;  
  74.       
  75.     typedef struct STACK  
  76.     {  
  77.         STACK * Ebp;  
  78.         PBYTE   Ret_Addr;  
  79.         DWORD   Param[0];  
  80.     } STACK, * PSTACK;  
  81.   
  82.     STACK   Stack = {0, 0};  
  83.     PSTACK  Ebp;  
  84.   
  85.     if (pException)     //fake frame for exception address  
  86.     {  
  87.         Stack.Ebp = (PSTACK)pException->ContextRecord->Ebp;  
  88.         Stack.Ret_Addr = (PBYTE)pException->ExceptionRecord->ExceptionAddress;  
  89.         Ebp = &Stack;  
  90.     }  
  91.     else  
  92.     {  
  93.         Ebp = (PSTACK)&pException - 1;  //frame addr of Get_Call_Stack()  
  94.   
  95.         // Skip frame of Get_Call_Stack().  
  96.         if (!IsBadReadPtr(Ebp, sizeof(PSTACK)))  
  97.             Ebp = Ebp->Ebp;      //caller ebp  
  98.     }  
  99.   
  100.     Str[0] = 0;  
  101.     Str_Len = 0;  
  102.   
  103.     // Trace CALL_TRACE_MAX calls maximum - not to exceed DUMP_SIZE_MAX.  
  104.     // Break trace on wrong stack frame.  
  105.     for (int Ret_Addr_I = 0;  
  106.         (Ret_Addr_I < CALL_TRACE_MAX) && !IsBadReadPtr(Ebp, sizeof(PSTACK)) && !IsBadCodePtr(FARPROC(Ebp->Ret_Addr));  
  107.         Ret_Addr_I++, Ebp = Ebp->Ebp)  
  108.     {  
  109.         // If module with Ebp->Ret_Addr found.  
  110.         if (Get_Module_By_Ret_Addr(Ebp->Ret_Addr, Module_Name, Module_Addr_1))  
  111.         {  
  112.             if (Module_Addr_1 != Module_Addr)   //new module  
  113.             {  
  114.                 // Save module's address and full path.  
  115.                 Module_Addr = Module_Addr_1;  
  116.                 Str_Len += wsprintf(Str + Str_Len, NL "%08X  %s", Module_Addr, Module_Name);  
  117.             }  
  118.   
  119.             // Save call offset.  
  120.             Str_Len += wsprintf(Str + Str_Len,  
  121.                 NL "  +%08X", Ebp->Ret_Addr - Module_Addr);  
  122.   
  123.             // Save 5 params of the call. We don't know the real number of params.  
  124.             if (pException && !Ret_Addr_I)  //fake frame for exception address  
  125.                 Str_Len += wsprintf(Str + Str_Len, "  Exception Offset");  
  126.             else if (!IsBadReadPtr(Ebp, sizeof(PSTACK) + 5 * sizeof(DWORD)))  
  127.             {  
  128.                 Str_Len += wsprintf(Str + Str_Len, "  (%X, %X, %X, %X, %X)",  
  129.                     Ebp->Param[0], Ebp->Param[1], Ebp->Param[2], Ebp->Param[3], Ebp->Param[4]);  
  130.             }  
  131.         }  
  132.         else  
  133.             Str_Len += wsprintf(Str + Str_Len, NL "%08X", Ebp->Ret_Addr);  
  134.     }  
  135.   
  136.     return Str_Len;  
  137. //Get_Call_Stack  
  138.   
  139. //***********************************  
  140. int WINAPI Get_Version_Str(PCHAR Str)  
  141. //***********************************  
  142. // Fill Str with Windows version.  
  143. {  
  144.     OSVERSIONINFOEX V = {sizeof(OSVERSIONINFOEX)};  //EX for NT 5.0 and later  
  145.   
  146.     if (!GetVersionEx((POSVERSIONINFO)&V))  
  147.     {  
  148.         ZeroMemory(&V, sizeof(V));  
  149.         V.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);  
  150.         GetVersionEx((POSVERSIONINFO)&V);  
  151.     }  
  152.   
  153.     if (V.dwPlatformId != VER_PLATFORM_WIN32_NT)  
  154.         V.dwBuildNumber = LOWORD(V.dwBuildNumber);  //for 9x HIWORD(dwBuildNumber) = 0x04xx  
  155.   
  156.     return wsprintf(Str,  
  157.         NL "Windows:  %d.%d.%d, SP %d.%d, Product Type %d"//SP - service pack, Product Type - VER_NT_WORKSTATION,...  
  158.         V.dwMajorVersion, V.dwMinorVersion, V.dwBuildNumber, V.wServicePackMajor, V.wServicePackMinor/*, V.wProductType*/);  
  159. //Get_Version_Str  
  160.   
  161. //*************************************************************  
  162. PCHAR WINAPI Get_Exception_Info(PEXCEPTION_POINTERS pException)  
  163. //*************************************************************  
  164. // Allocate Str[DUMP_SIZE_MAX] and return Str with dump, if !pException - just return call stack in Str.  
  165. {  
  166.     PCHAR       Str;  
  167.     int         Str_Len;  
  168.     int         i;  
  169.     CHAR        Module_Name[MAX_PATH];  
  170.     PBYTE       Module_Addr;  
  171.     HANDLE      hFile;  
  172.     FILETIME    Last_Write_Time;  
  173.     FILETIME    Local_File_Time;  
  174.     SYSTEMTIME  T;  
  175.       
  176.     Str = new CHAR[DUMP_SIZE_MAX];  
  177.   
  178.     if (!Str)  
  179.         return NULL;  
  180.   
  181.     Str_Len = 0;  
  182.     Str_Len += Get_Version_Str(Str + Str_Len);  
  183.   
  184.     Str_Len += wsprintf(Str + Str_Len, NL "Process:  ");  
  185.     GetModuleFileName(NULL, Str + Str_Len, MAX_PATH);  
  186.     Str_Len = lstrlen(Str);  
  187.   
  188.     // If exception occurred.  
  189.     if (pException)  
  190.     {  
  191.         EXCEPTION_RECORD &  E = *pException->ExceptionRecord;  
  192.         CONTEXT &           C = *pException->ContextRecord;  
  193.   
  194.         // If module with E.ExceptionAddress found - save its path and date.  
  195.         if (Get_Module_By_Ret_Addr((PBYTE)E.ExceptionAddress, Module_Name, Module_Addr))  
  196.         {  
  197.             Str_Len += wsprintf(Str + Str_Len,  
  198.                 NL "Module:  %s", Module_Name);  
  199.   
  200.             if ((hFile = CreateFile(Module_Name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,  
  201.                 FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE)  
  202.             {  
  203.                 if (GetFileTime(hFile, NULL, NULL, &Last_Write_Time))  
  204.                 {  
  205.                     FileTimeToLocalFileTime(&Last_Write_Time, &Local_File_Time);  
  206.                     FileTimeToSystemTime(&Local_File_Time, &T);  
  207.   
  208.                     Str_Len += wsprintf(Str + Str_Len,  
  209.                         NL "Date Modified:  %02d/%02d/%d",  
  210.                         T.wMonth, T.wDay, T.wYear);  
  211.                 }  
  212.                 CloseHandle(hFile);  
  213.             }  
  214.         }  
  215.         else  
  216.         {  
  217.             Str_Len += wsprintf(Str + Str_Len,  
  218.                 NL "Exception Addr:  %08X", E.ExceptionAddress);  
  219.         }  
  220.           
  221.         Str_Len += wsprintf(Str + Str_Len,  
  222.             NL "Exception Code:  %08X", E.ExceptionCode);  
  223.           
  224.         if (E.ExceptionCode == EXCEPTION_ACCESS_VIOLATION)  
  225.         {  
  226.             // Access violation type - Write/Read.  
  227.             Str_Len += wsprintf(Str + Str_Len,  
  228.                 NL "%s Address:  %08X",  
  229.                 (E.ExceptionInformation[0]) ? "Write" : "Read", E.ExceptionInformation[1]);  
  230.         }  
  231.   
  232.         // Save instruction that caused exception.  
  233.         Str_Len += wsprintf(Str + Str_Len, NL "Instruction: ");  
  234.         for (i = 0; i < 16; i++)  
  235.             Str_Len += wsprintf(Str + Str_Len, " %02X"PBYTE(E.ExceptionAddress)[i]);  
  236.   
  237.         // Save registers at exception.  
  238.         Str_Len += wsprintf(Str + Str_Len, NL "Registers:");  
  239.         Str_Len += wsprintf(Str + Str_Len, NL "EAX: %08X  EBX: %08X  ECX: %08X  EDX: %08X", C.Eax, C.Ebx, C.Ecx, C.Edx);  
  240.         Str_Len += wsprintf(Str + Str_Len, NL "ESI: %08X  EDI: %08X  ESP: %08X  EBP: %08X", C.Esi, C.Edi, C.Esp, C.Ebp);  
  241.         Str_Len += wsprintf(Str + Str_Len, NL "EIP: %08X  EFlags: %08X", C.Eip, C.EFlags);  
  242.     } //if (pException)  
  243.       
  244.     // Save call stack info.  
  245.     Str_Len += wsprintf(Str + Str_Len, NL "Call Stack:");  
  246.     Get_Call_Stack(pException, Str + Str_Len);  
  247.   
  248.     if (Str[0] == NL[0])  
  249.         lstrcpy(Str, Str + sizeof(NL) - 1);  
  250.   
  251.     return Str;  
  252. //Get_Exception_Info  
  253.   
  254. //*************************************************************************************  
  255. void WINAPI Create_Dump(PEXCEPTION_POINTERS pException, BOOL File_Flag, BOOL Show_Flag)  
  256. //*************************************************************************************  
  257. // Create dump.   
  258. // pException can be either GetExceptionInformation() or NULL.  
  259. // If File_Flag = TRUE - write dump files (.dmz and .dmp) with the name of the current process.  
  260. // If Show_Flag = TRUE - show message with Get_Exception_Info() dump.  
  261. {  
  262.     HANDLE  hDump_File;  
  263.     PCHAR   Str;  
  264.     DWORD   Bytes;  
  265.     DWORD   nLen = 0;  
  266.   
  267.     CString strDir,strTXTFile,strDMPFile;  
  268.     CString strDate,strTotal;  
  269.     CTime   tm = CTime::GetCurrentTime();  
  270.       
  271.     strDir.Format(_T("%s\\Log"),GetExePath());  
  272.     strTXTFile.Format(_T("%s\\Log\\%04d-%02d-%02d %02d%02d%02d.txt"),GetExePath(),tm.GetYear(),tm.GetMonth(),  
  273.         tm.GetDay(),tm.GetHour(),tm.GetMinute(),tm.GetSecond());  
  274.     strDMPFile.Format(_T("%s\\Log\\%04d-%02d-%02d %02d%02d%02d.dmp"),GetExePath(),tm.GetYear(),tm.GetMonth(),  
  275.         tm.GetDay(),tm.GetHour(),tm.GetMinute(),tm.GetSecond());  
  276.   
  277.     if(!PathFileExists(strDir))  
  278.         CreateDirectory(strDir,NULL);  
  279.   
  280.     Str = Get_Exception_Info(pException);  
  281.   
  282.     //if (Show_Flag && Str)  
  283.     //  MessageBox(NULL, Str, "MiniDump", MB_ICONHAND | MB_OK);  
  284.   
  285.     if (File_Flag)  
  286.     {  
  287.         if (Str)  
  288.         {  
  289.             hDump_File = CreateFile(strTXTFile,  
  290.                 GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);  
  291.               
  292.             nLen = lstrlen(Str);  
  293.             Str[nLen] = '\0';  
  294.   
  295.             WriteFile(hDump_File, Str, lstrlen(Str) + 1, &Bytes, NULL);  
  296.   
  297.             CloseHandle(hDump_File);  
  298.         }  
  299.   
  300.         // If MiniDumpWriteDump() of DbgHelp.dll available.  
  301.         if (MiniDumpWriteDump_)  
  302.         {  
  303.             MINIDUMP_EXCEPTION_INFORMATION  M;  
  304.   
  305.             M.ThreadId = GetCurrentThreadId();  
  306.             M.ExceptionPointers = pException;  
  307.             M.ClientPointers = 0;  
  308.   
  309.             hDump_File = CreateFile(strDMPFile,  
  310.                 GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);  
  311.   
  312.             MiniDumpWriteDump_(GetCurrentProcess(), GetCurrentProcessId(), hDump_File,  
  313.                 MiniDumpNormal, (pException) ? &M : NULL, NULL, NULL);  
  314.   
  315.             CloseHandle(hDump_File);  
  316.         }  
  317.     } //if (File_Flag)  
  318.   
  319.     delete Str;  
  320. //Create_Dump  

具体参考方法如下:

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

view plain print ?
  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");  

测试代码如下:

view plain print ?
  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. };  

view plain print ?
  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. }  

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

view plain print ?
  1. void CTestDlg::OnOK()   
  2. {  
  3.     // TODO: Add extra validation here  
  4.     Fun1(NULL);  
  5. }  

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

如何利用WinDbg找出程序崩溃的位置_第1张图片


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

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

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

如何利用WinDbg找出程序崩溃的位置_第2张图片

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

如何利用WinDbg找出程序崩溃的位置_第3张图片

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

如何利用WinDbg找出程序崩溃的位置_第4张图片


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


测试工程下载地址:

http://download.csdn.net/source/3575167

你可能感兴趣的:(exception,Module,File,null,Path,winapi)