使用场景:
Win32程序在release模式下编译完成,发送给最终用户使用时,我们的程序有时候也会出现崩溃的情况,这个时候如果能快速定位崩溃原因或提供一些程序崩溃时的状态信息,对我们解决问题将会带来很大的帮助。一般程序崩溃时我们需要搜集的信息包括:系统信息、CPU寄存器信息、堆栈信息、调用堆栈信息、CPU和内存状态、内存当前地址等。调用堆栈是我们最常用到的。
技术方案:
目前我搜集的方法有以下三种,日志记录、dbghelp 、SHE(Structured Exception Handling)。
日志记录
这是最常用的,就是在项目中添加一个日志记录函数,在程序关键位置将需要跟踪的信息输出到日志中。提供一个简单日志输出类(支持跨线程):
.h文件
1 #pragma once 2 //******************************************************************** 3 // 创建时间: 2016/05/12 14:42 4 // 文件名称: GB_Logger.h 5 // 作 者: GP_Fore 6 //******************************************************************** 7 // 功能说明: 提供写日志功能,支持多线程,支持可变形参数操作,支持写日志级别的设置 8 // 接 口: SetLogLevel:设置写日志级别 9 // TraceKeyInfo:忽略日志级别,写关键信息 10 // TraceError:写错误信息 11 // TraceWarning:写警告信息 12 // TraceInfo:写一般信息 13 // 备 注: 在有中文输出的环境下,记者使用setlocale(LC_ALL, "chs");先进行本地化设置。 14 //******************************************************************** 15 #ifndef GB_Logger_H_ 16 #define GB_Logger_H_ 17 #include <Windows.h> 18 #include <typeinfo.h> 19 #include <tchar.h> 20 #define _CRT_SECURE_NO_WARNINGS 21 22 23 24 //日志模块公共变量定义 25 #ifndef GB_Logger_DEFINE 26 #define GB_Logger_DEFINE 27 28 #define WM_USERDEFINEWINDOWSINFO WM_USER+2000 //支持windows消息机制。 29 30 //日志级别的提示信息 31 static const TCHAR * KEYINFOPREFIX = _T(" Key:"); //关键日志前缀 32 static const TCHAR * ERRORPREFIX = _T(" Error:"); //错误日志前缀 33 static const TCHAR * WARNINGPREFIX = _T(" Warning:"); //警告日志前缀 34 static const TCHAR * INFOPREFIX = _T(" Info: "); //一般日志前缀 35 static const int MAX_STR_LEN = 1024; //字符串最大缓存 36 static const long MAX_LOGFILESIZE = 1000000; //日志文件最大。<4G 37 38 extern TCHAR g_LOGDIRECTORYPATH[]; //存放日志目录 39 40 #endif 41 42 43 //日志类型 44 typedef enum EnumLogType 45 { 46 ErrInfo=0, //错误日志信息 47 WarnInfo, //警告日志信息 48 trackingInfo, //一般日志信息 49 DataInfo //数据跟踪信息 50 }; 51 52 typedef enum EnumLogLevel 53 { 54 LogLevelAll = 0, //所有信息都写日志 55 LogLevelMid, //写错误、警告信息 56 LogLevelNormal, //只写错误信息 57 LogLevelStop //不写日志 58 }; 59 60 //static EnumLogType ErrLog = ErrInfo; 61 62 class GB_Logger 63 { 64 private: 65 //默认构造函数 66 GB_Logger(); 67 //析构函数 68 virtual ~GB_Logger(); 69 //写文件操作 70 71 //******************************************************************** 72 // 创建时间: 2016-11-30 16:24:13 73 // 作 者: GP_Fore 74 //******************************************************************** 75 // 函数说明: 获取当前时间字符串。 76 // 参数列表: OUT TCHAR * p_ResultBuffer 77 // 参数列表: int p_SizeOfResultBuffer,为p_ResultBuffer缓冲区对应的字节个数。注意:多字节和双字节的区分,当环境为Unicode时,为缓冲区大小除2。 78 // 返回值: int 79 // 备 注: 80 //******************************************************************** 81 int GetCurrentTimeToTChar(OUT TCHAR* p_ResultBuffer, int p_SizeOfResultBuffer); 82 83 84 public: 85 86 //控制日记记录的级别 87 EnumLogLevel m_nLogLevel; 88 89 90 int WriteStrToLoggerFile(const TCHAR * strInfo); 91 92 //******************************************************************** 93 // 创建时间: 2016-11-29 11:24:37 94 // 作 者: GP_Fore 95 //******************************************************************** 96 // 函数说明: 创建日志文件名称。 97 // 返回值: void 98 // 备 注: 99 //******************************************************************** 100 void GenerateLogName(); 101 102 //******************************************************************** 103 // 创建时间: 2016-11-29 11:21:18 104 // 作 者: GP_Fore 105 //******************************************************************** 106 // 函数说明: 输入日志字符串到日志文件 107 // 参数列表: EnumLogType pLogType 108 // 参数列表: const TCHAR * strInfo 详细日志信息,注:日志长度不超过MAX_STR_LEN(1024)。 109 // 参数列表: ... 110 // 返回值: void 111 // 备 注: 112 //******************************************************************** 113 int TraceLogger(EnumLogType pLogType, const TCHAR * strInfo, ...); 114 //设置日志文件保存目录 115 //******************************************************************** 116 // 创建时间: 2016-11-29 11:24:01 117 // 作 者: GP_Fore 118 //******************************************************************** 119 // 函数说明: 设置日志存储目录。文件夹地址。 120 // 参数列表: const TCHAR * strDirectoryPath 121 // 返回值: int 122 // 备 注: 123 //******************************************************************** 124 int SetLogDirectory(const TCHAR * strDirectoryPath); 125 //将信息发送到指定的windows窗体 126 int SendInfoToWindows(HWND hWnd, const TCHAR * strInfo, ...); 127 //获取唯一日志实例对象。 128 static GB_Logger* GetInstance(); 129 130 131 132 private: 133 134 //日志文件句柄 135 HANDLE m_hFile; 136 //写日志文件流 137 //日志的名称 138 TCHAR m_strCurLogName[MAX_STR_LEN]; 139 //线程同步的临界区变量 140 CRITICAL_SECTION m_cs; 141 //当前实例静态指针 142 static GB_Logger* logger; 143 //防止拷贝构造和赋值操作 144 GB_Logger(const GB_Logger&); 145 GB_Logger& operator=(const GB_Logger&); 146 147 }; 148 149 #endif
.cpp文件
1 #include "stdafx.h" 2 #include "GB_Logger.h" 3 #include <imagehlp.h> 4 #include <time.h> 5 #include <stdarg.h> 6 #include <tchar.h> 7 8 9 10 GB_Logger* GB_Logger::logger = NULL; 11 12 13 TCHAR g_LOGDIRECTORYPATH[MAX_STR_LEN] = {0}; //存放日志目录 14 15 //默认构造函数 16 GB_Logger::GB_Logger() 17 { 18 //初始化 19 //memset(g_LOGDIRECTORYPATH, 0, MAX_STR_LEN); 20 memset(m_strCurLogName, 0, MAX_STR_LEN); 21 22 //设置默认的写日志级别 23 m_nLogLevel = EnumLogLevel::LogLevelNormal; 24 GenerateLogName(); 25 //初始化临界区变量 26 InitializeCriticalSection(&m_cs); 27 28 //创建日志文件名 29 30 } 31 32 //析构函数 33 GB_Logger::~GB_Logger() 34 { 35 //释放临界区 36 DeleteCriticalSection(&m_cs); 37 //关闭文件流 38 if (m_hFile) 39 { 40 CloseHandle(m_hFile); 41 } 42 } 43 44 int GB_Logger::TraceLogger(EnumLogType pLogType, const TCHAR * strInfo, ...) 45 { 46 if (!strInfo) 47 return 0; 48 //TCHAR* str_buffer = (TCHAR *)malloc(sizeof(TCHAR)* MAX_STR_LEN); 49 TCHAR str_Buffer[MAX_STR_LEN] = { 0 }; 50 GetCurrentTimeToTChar(str_Buffer, MAX_STR_LEN); 51 switch (pLogType) 52 { 53 case ErrInfo: 54 if (m_nLogLevel >= EnumLogLevel::LogLevelStop) //不写日志。 55 return 2; 56 lstrcat(str_Buffer, _T(" Error: ")); 57 break; 58 case WarnInfo: 59 if (m_nLogLevel >= EnumLogLevel::LogLevelNormal) //只写错误日志。 60 return 2; 61 lstrcat(str_Buffer, _T(" Warning:")); 62 break; 63 case trackingInfo: 64 if (m_nLogLevel >= EnumLogLevel::LogLevelMid) //当前只记录错误和警告信息。 65 return 2; 66 lstrcat(str_Buffer, _T(" trackingInfo:")); 67 break; 68 default: 69 lstrcat(str_Buffer, _T(":")); 70 return 2; 71 } 72 va_list arg_ptr = NULL; 73 va_start(arg_ptr, strInfo); 74 TCHAR p_Content[MAX_STR_LEN] = { 0 }; 75 _vsntprintf_s(p_Content,sizeof(TCHAR)*MAX_STR_LEN, strInfo, arg_ptr); 76 lstrcat(str_Buffer, p_Content); 77 va_end(arg_ptr); 78 arg_ptr = NULL; 79 WriteStrToLoggerFile(str_Buffer); 80 return 1; 81 } 82 83 //将信息发送到指定的windows窗体 84 int GB_Logger::SendInfoToWindows(HWND hWnd,const TCHAR * strFormat, ...) 85 { 86 //判断当前的写日志级别,若设置只写错误和警告信息则函数返回 87 if (!strFormat) 88 return 0; 89 TCHAR prefix[MAX_STR_LEN] = { 0 }; 90 TCHAR str_Buffer[MAX_STR_LEN] = { 0 }; 91 GetCurrentTimeToTChar(str_Buffer,MAX_STR_LEN); 92 lstrcat(str_Buffer, _T(": ")); 93 va_list arg_ptr = NULL; 94 va_start(arg_ptr, strFormat); //让arg_ptr指向参数列表中的第一参数地址。注意:函数参数是以数据结构,栈的形式存取,从右至左入栈。 95 TCHAR p_Content[MAX_STR_LEN] = { 0 }; 96 _vsntprintf_s(p_Content, MAX_STR_LEN, strFormat, arg_ptr); 97 lstrcat(str_Buffer, p_Content); 98 va_end(arg_ptr); 99 arg_ptr = NULL; 100 ::SendMessage(hWnd, WM_USERDEFINEWINDOWSINFO, 0, (LPARAM)&str_Buffer); //同步 101 return 1; 102 } 103 104 int GB_Logger::SetLogDirectory(const TCHAR * strDirectoryPath) 105 { 106 __try{ 107 //进入临界区 108 EnterCriticalSection(&m_cs); 109 if (m_hFile) 110 { 111 if (CloseHandle(m_hFile) != 0) 112 perror("close file fail!"); 113 else 114 m_hFile = nullptr; 115 } 116 _tcscpy_s(g_LOGDIRECTORYPATH, MAX_STR_LEN, strDirectoryPath); 117 if (0 != _tcslen(g_LOGDIRECTORYPATH)) 118 { 119 lstrcat(g_LOGDIRECTORYPATH, _T("//")); 120 } 121 int hr = CreateDirectory(g_LOGDIRECTORYPATH, NULL); 122 if (hr<0) 123 { 124 return hr; 125 } 126 GenerateLogName(); 127 return 1; 128 } 129 __finally{ 130 LeaveCriticalSection(&m_cs); 131 } 132 } 133 134 //获取系统当前时间 135 int GB_Logger::GetCurrentTimeToTChar(OUT TCHAR* p_ResultBuffer, int p_SizeOfResultBuffer) 136 { 137 time_t curTime; 138 tm pTimeInfo; 139 time(&curTime); 140 localtime_s(&pTimeInfo, &curTime); 141 _stprintf_s(p_ResultBuffer, p_SizeOfResultBuffer, _T("%02d:%02d:%02d"), pTimeInfo.tm_hour, pTimeInfo.tm_min, pTimeInfo.tm_sec); 142 return 1; 143 } 144 145 //写文件操作 146 int GB_Logger::WriteStrToLoggerFile(const TCHAR * strInfo) 147 { 148 if (!strInfo) 149 return 0; 150 try 151 { 152 //进入临界区 153 EnterCriticalSection(&m_cs); 154 //若文件流没有打开,则重新打开 155 if (!m_hFile) 156 { 157 HANDLE hFile; 158 TCHAR stBuffer[1024] = { 0 }; 159 lstrcat(stBuffer, g_LOGDIRECTORYPATH); 160 lstrcat(stBuffer, m_strCurLogName); 161 hFile = CreateFile(stBuffer, //指向文件名的指针 162 GENERIC_WRITE | GENERIC_READ, //访问模式(读/写) 写,读 163 FILE_SHARE_READ, // 共享模式 不共享 164 NULL, //指向安全属性的指针 165 OPEN_ALWAYS, //如何让创建 166 FILE_ATTRIBUTE_NORMAL, //文件属性 167 NULL); //用于复制文件句柄 168 if (hFile == INVALID_HANDLE_VALUE) 169 { 170 AfxMessageBox(_T("创建日志文件失败")); 171 return GetLastError(); 172 } 173 this->m_hFile = hFile; 174 } 175 if (m_hFile) 176 { 177 //写日志信息到文件流 178 TCHAR pLogbuff[MAX_PATH] = { 0 }; 179 _stprintf_s(pLogbuff, _T("%s\n"), strInfo); 180 181 if (SetFilePointer(m_hFile, 0, NULL, FILE_END) == -1) 182 { 183 printf("SetFilePointer error\n"); 184 return 0; 185 } 186 187 DWORD ReturnCharNumber; 188 WriteFile(m_hFile, pLogbuff, _tcslen(pLogbuff)*sizeof(TCHAR), &ReturnCharNumber, NULL); 189 //判断当前日志文件大小,单位是字节。 190 //LONGLONG file_size = 0; 191 //file_size = GetFileSize(m_hFile, NULL); 192 193 LARGE_INTEGER FileSize; 194 GetFileSizeEx(m_hFile, &FileSize); 195 if (FileSize.QuadPart >= MAX_LOGFILESIZE) 196 { 197 CloseHandle(m_hFile); 198 TCHAR str_Buffer[1024] = { 0 }; 199 TCHAR* str_TimeBuffer = (TCHAR *)malloc(sizeof(TCHAR)* MAX_STR_LEN); 200 GetCurrentTimeToTChar(str_TimeBuffer, MAX_STR_LEN); 201 lstrcat(str_Buffer, m_strCurLogName); 202 lstrcat(str_Buffer, str_TimeBuffer); 203 memset(m_strCurLogName, 0, MAX_STR_LEN); 204 lstrcat(m_strCurLogName, str_Buffer); 205 } 206 } 207 208 //离开临界区 209 LeaveCriticalSection(&m_cs); 210 return 1; 211 } 212 //若发生异常,则先离开临界区,防止死锁 213 catch (...) 214 { 215 LeaveCriticalSection(&m_cs); 216 return 0; 217 } 218 return 1; 219 } 220 221 //创建日志文件的名称 222 void GB_Logger::GenerateLogName() 223 { 224 time_t curTime; 225 tm pTimeInfo; 226 time(&curTime); 227 localtime_s(&pTimeInfo, &curTime); 228 TCHAR temp[1024] = { 0 }; 229 //日志的名称如:2013-01-01.log 230 _stprintf_s(temp, _T("%04d-%02d-%02d-%02d.log"), pTimeInfo.tm_year + 1900, pTimeInfo.tm_mon + 1, pTimeInfo.tm_mday,pTimeInfo.tm_hour); 231 if (0 != _tcscmp(m_strCurLogName, temp)) //如果文件名称不存在,创建该文件。 232 { 233 _tcscpy_s(m_strCurLogName, temp); 234 if (m_hFile) 235 CloseHandle(m_hFile); 236 TCHAR temp[1024] = { 0 }; 237 lstrcat(temp, g_LOGDIRECTORYPATH); 238 lstrcat(temp, m_strCurLogName); 239 //以追加的方式打开文件流 240 //_tfopen_s(&m_pFileStream, temp, _T("a+")); 241 } 242 243 } 244 245 GB_Logger* GB_Logger::GetInstance() 246 { 247 if (NULL == logger) 248 { 249 GB_Logger* pLog = new GB_Logger(); 250 logger = pLog; 251 } 252 return logger; 253 }
调用方法:
GB_Logger::GetInstance()->SetLogDirectory(loggerFolderPath+"\\Logger\\"); //设置一个日志保存目录。 GB_Logger::GetInstance()->m_nLogLevel = LogLevelAll; //设置日志级别 GB_Logger::GetInstance()->TraceLogger(trackingInfo, _T("系统开始初始化……!\r\n")); 日志输出。