调试技巧 —— 如何利用windbg + dump + map分析程序异常

来源:互联网 发布:优化发展环境建议 编辑:程序博客网 时间:2024/04/29 13:43

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

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

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


MiniDump.h


#include <windows.h>#include <tlhelp32.h>//#include "dbghelp.h"//#define DEBUG_DPRINTF1//allow d()//#include "wfun.h"#pragma optimize("y", off)//generate stack frame pointers for all functions - same as /Oy- in the project#pragma warning(disable: 4200)//nonstandard extension used : zero-sized array in struct/union#pragma warning(disable: 4100)//unreferenced formal parameter/*BOOL WINAPI Get_Module_By_Ret_Addr(PBYTE Ret_Addr, PCHAR Module_Name, PBYTE & Module_Addr);int WINAPI Get_Call_Stack(PEXCEPTION_POINTERS pException, PCHAR Str);int  WINAPI Get_Version_Str(PCHAR Str);PCHAR WINAPI Get_Exception_Info(PEXCEPTION_POINTERS pException);void WINAPI Create_Dump(PEXCEPTION_POINTERS pException, BOOL File_Flag, BOOL Show_Flag);*/// In case you don't have dbghelp.h.#ifndef _DBGHELP_typedef struct _MINIDUMP_EXCEPTION_INFORMATION {DWORDThreadId;PEXCEPTION_POINTERSExceptionPointers;BOOLClientPointers;} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;typedef enum _MINIDUMP_TYPE {MiniDumpNormal =0x00000000,MiniDumpWithDataSegs =0x00000001,} MINIDUMP_TYPE;typedefBOOL (WINAPI * MINIDUMP_WRITE_DUMP)(IN HANDLEhProcess,IN DWORDProcessId,IN HANDLEhFile,IN MINIDUMP_TYPEDumpType,IN CONST PMINIDUMP_EXCEPTION_INFORMATIONExceptionParam, OPTIONALIN PVOIDUserStreamParam, OPTIONALIN PVOIDCallbackParam OPTIONAL);#elsetypedefBOOL (WINAPI * MINIDUMP_WRITE_DUMP)(IN HANDLEhProcess,IN DWORDProcessId,IN HANDLEhFile,IN MINIDUMP_TYPEDumpType,IN CONST PMINIDUMP_EXCEPTION_INFORMATIONExceptionParam, OPTIONALIN PMINIDUMP_USER_STREAM_INFORMATIONUserStreamParam, OPTIONALIN PMINIDUMP_CALLBACK_INFORMATIONCallbackParam OPTIONAL);#endif //#ifndef _DBGHELP_// Tool Help functions.typedefHANDLE (WINAPI * CREATE_TOOL_HELP32_SNAPSHOT)(DWORD dwFlags, DWORD th32ProcessID);typedefBOOL (WINAPI * MODULE32_FIRST)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);typedefBOOL (WINAPI * MODULE32_NEST)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);extern void WINAPI Create_Dump(PEXCEPTION_POINTERS pException, BOOL File_Flag, BOOL Show_Flag);extern HMODULEhDbgHelp;extern MINIDUMP_WRITE_DUMPMiniDumpWriteDump_;extern CREATE_TOOL_HELP32_SNAPSHOTCreateToolhelp32Snapshot_;extern MODULE32_FIRSTModule32First_;extern MODULE32_NESTModule32Next_;



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")HMODULEhDbgHelp;MINIDUMP_WRITE_DUMPMiniDumpWriteDump_;CREATE_TOOL_HELP32_SNAPSHOTCreateToolhelp32Snapshot_;MODULE32_FIRSTModule32First_;MODULE32_NESTModule32Next_;#defineDUMP_SIZE_MAX8000//max size of our dump#defineCALL_TRACE_MAX((DUMP_SIZE_MAX - 2000) / (MAX_PATH + 40))//max number of traced calls#defineNL"\r\n"//new lineextern 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.{MODULEENTRY32M = {sizeof(M)};HANDLEhSnapshot;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.{CHARModule_Name[MAX_PATH];PBYTEModule_Addr = 0;PBYTEModule_Addr_1;intStr_Len;typedef struct STACK{STACK *Ebp;PBYTERet_Addr;DWORDParam[0];} STACK, * PSTACK;STACKStack = {0, 0};PSTACKEbp;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 addressStr_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]);}}elseStr_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.{OSVERSIONINFOEXV = {sizeof(OSVERSIONINFOEX)};//EX for NT 5.0 and laterif (!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) = 0x04xxreturn 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.{PCHARStr;intStr_Len;inti;CHARModule_Name[MAX_PATH];PBYTEModule_Addr;HANDLEhFile;FILETIMELast_Write_Time;FILETIMELocal_File_Time;SYSTEMTIMET;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)[i]);// 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.{HANDLEhDump_File;PCHARStr;DWORDBytes;DWORDnLen = 0;CString strDir,strTXTFile,strDMPFile;CString strDate,strTotal;CTimetm = 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_INFORMATIONM;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

具体参考方法如下:

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


SetUnhandledExceptionFilter(CrashReportEx);HMODULEhKernel32;// Try to get MiniDumpWriteDump() address.hDbgHelp = LoadLibrary("DBGHELP.DLL");MiniDumpWriteDump_ = (MINIDUMP_WRITE_DUMP)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");//d("hDbgHelp=%X, MiniDumpWriteDump_=%X", hDbgHelp, MiniDumpWriteDump_);// Try to get Tool Help library functions.hKernel32 = GetModuleHandle("KERNEL32");CreateToolhelp32Snapshot_ = (CREATE_TOOL_HELP32_SNAPSHOT)GetProcAddress(hKernel32, "CreateToolhelp32Snapshot");Module32First_ = (MODULE32_FIRST)GetProcAddress(hKernel32, "Module32First");Module32Next_ = (MODULE32_NEST)GetProcAddress(hKernel32, "Module32Next");


测试代码如下:

class CTestDlg : public CDialog{// Constructionpublic:CTestDlg(CWnd* pParent = NULL);// standard constructorvoid Fun1(char *pszBuffer);void Fun2(char *pszBuffer);void Fun3(char *pszBuffer);};


void CTestDlg::Fun1(char *pszBuffer){Fun2(pszBuffer);}void CTestDlg::Fun2(char *pszBuffer){Fun3(pszBuffer);}void CTestDlg::Fun3(char *pszBuffer){pszBuffer[1] = 0x00;}

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

void CTestDlg::OnOK() {// TODO: Add extra validation hereFun1(NULL);}

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


原创粉丝点击