compress generated minidumps
[public/netxms.git] / src / libnetxms / seh.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2017 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU Lesser General Public License as published
7 ** by the Free Software Foundation; either version 3 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU Lesser General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: seh.cpp
20 **
21 **/
22
23 #include "libnetxms.h"
24 #include <dbghelp.h>
25 #include "StackWalker.h"
26
27 #pragma warning(disable : 4482)
28
29 /**
30 * Customized StackWalker class
31 */
32 class NxStackWalker : public StackWalker
33 {
34 protected:
35 virtual void OnOutput(LPCSTR pszText);
36 virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName) { }
37 virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) { }
38
39 public:
40 NxStackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess)
41 : StackWalker(options, szSymPath, dwProcessId, hProcess) { }
42 };
43
44
45 //
46 // Static data
47 //
48
49 static BOOL (*m_pfExceptionHandler)(EXCEPTION_POINTERS *) = NULL;
50 static void (*m_pfWriter)(const TCHAR *pszText) = NULL;
51 static HANDLE m_hExceptionLock = INVALID_HANDLE_VALUE;
52 static TCHAR m_szBaseProcessName[64] = _T("netxms");
53 static TCHAR m_szDumpDir[MAX_PATH] = _T("C:\\");
54 static DWORD m_dwLogMessageCode = 0;
55 static BOOL m_printToScreen = FALSE;
56 static BOOL m_writeFullDump = FALSE;
57
58 /**
59 * Output for stack walker
60 */
61 void NxStackWalker::OnOutput(LPCSTR pszText)
62 {
63 if (m_pfWriter != NULL)
64 {
65 m_pfWriter(_T(" "));
66 #ifdef UNICODE
67 WCHAR *wtext = WideStringFromMBString(pszText);
68 m_pfWriter(wtext);
69 free(wtext);
70 #else
71 m_pfWriter(pszText);
72 #endif
73 }
74 else
75 {
76 fputs(" ", stdout);
77 fputs(pszText, stdout);
78 }
79 }
80
81 /**
82 * Initialize SEH functions
83 */
84 void SEHInit()
85 {
86 m_hExceptionLock = CreateMutex(NULL, FALSE, NULL);
87 }
88
89 /**
90 * Set exception handler
91 */
92 void LIBNETXMS_EXPORTABLE SetExceptionHandler(BOOL (*pfHandler)(EXCEPTION_POINTERS *),
93 void (*pfWriter)(const TCHAR *), const TCHAR *pszDumpDir,
94 const TCHAR *pszBaseProcessName, DWORD dwLogMsgCode,
95 BOOL writeFullDump, BOOL printToScreen)
96 {
97 m_pfExceptionHandler = pfHandler;
98 m_pfWriter = pfWriter;
99 if (pszBaseProcessName != NULL)
100 _tcslcpy(m_szBaseProcessName, pszBaseProcessName, 64);
101 if (pszDumpDir != NULL)
102 _tcslcpy(m_szDumpDir, pszDumpDir, MAX_PATH);
103 m_dwLogMessageCode = dwLogMsgCode;
104 m_writeFullDump = writeFullDump;
105 m_printToScreen = printToScreen;
106 }
107
108 /**
109 * Get exception name from code
110 */
111 TCHAR LIBNETXMS_EXPORTABLE *SEHExceptionName(DWORD code)
112 {
113 static struct
114 {
115 DWORD code;
116 TCHAR *name;
117 } exceptionList[] =
118 {
119 { EXCEPTION_ACCESS_VIOLATION, _T("Access violation") },
120 { EXCEPTION_ARRAY_BOUNDS_EXCEEDED, _T("Array bounds exceeded") },
121 { EXCEPTION_BREAKPOINT, _T("Breakpoint") },
122 { EXCEPTION_DATATYPE_MISALIGNMENT, _T("Alignment error") },
123 { EXCEPTION_FLT_DENORMAL_OPERAND, _T("FLT_DENORMAL_OPERAND") },
124 { EXCEPTION_FLT_DIVIDE_BY_ZERO, _T("FLT_DIVIDE_BY_ZERO") },
125 { EXCEPTION_FLT_INEXACT_RESULT, _T("FLT_INEXACT_RESULT") },
126 { EXCEPTION_FLT_INVALID_OPERATION, _T("FLT_INVALID_OPERATION") },
127 { EXCEPTION_FLT_OVERFLOW, _T("Floating point overflow") },
128 { EXCEPTION_FLT_STACK_CHECK, _T("FLT_STACK_CHECK") },
129 { EXCEPTION_FLT_UNDERFLOW, _T("Floating point underflow") },
130 { EXCEPTION_ILLEGAL_INSTRUCTION, _T("Illegal instruction") },
131 { EXCEPTION_IN_PAGE_ERROR, _T("Page error") },
132 { EXCEPTION_INT_DIVIDE_BY_ZERO, _T("Divide by zero") },
133 { EXCEPTION_INT_OVERFLOW, _T("Integer overflow") },
134 { EXCEPTION_INVALID_DISPOSITION, _T("Invalid disposition") },
135 { EXCEPTION_NONCONTINUABLE_EXCEPTION, _T("Non-continuable exception") },
136 { EXCEPTION_PRIV_INSTRUCTION, _T("Priveledged instruction") },
137 { EXCEPTION_SINGLE_STEP, _T("Single step") },
138 { EXCEPTION_STACK_OVERFLOW, _T("Stack overflow") },
139 { 0, NULL }
140 };
141 int i;
142
143 for(i = 0; exceptionList[i].name != NULL; i++)
144 if (code == exceptionList[i].code)
145 return exceptionList[i].name;
146
147 return _T("Unknown");
148 }
149
150 /**
151 * Default exception handler for console applications
152 */
153 BOOL LIBNETXMS_EXPORTABLE SEHDefaultConsoleHandler(EXCEPTION_POINTERS *pInfo)
154 {
155 _tprintf(_T("EXCEPTION: %08X (%s) at %p\n"),
156 pInfo->ExceptionRecord->ExceptionCode,
157 SEHExceptionName(pInfo->ExceptionRecord->ExceptionCode),
158 pInfo->ExceptionRecord->ExceptionAddress);
159 #ifdef _X86_
160 _tprintf(_T("\nRegister information:\n")
161 _T(" eax=%08X ebx=%08X ecx=%08X edx=%08X\n")
162 _T(" esi=%08X edi=%08X ebp=%08X esp=%08X\n")
163 _T(" cs=%04X ds=%04X es=%04X ss=%04X fs=%04X gs=%04X flags=%08X\n"),
164 pInfo->ContextRecord->Eax, pInfo->ContextRecord->Ebx,
165 pInfo->ContextRecord->Ecx, pInfo->ContextRecord->Edx,
166 pInfo->ContextRecord->Esi, pInfo->ContextRecord->Edi,
167 pInfo->ContextRecord->Ebp, pInfo->ContextRecord->Esp,
168 pInfo->ContextRecord->SegCs, pInfo->ContextRecord->SegDs,
169 pInfo->ContextRecord->SegEs, pInfo->ContextRecord->SegSs,
170 pInfo->ContextRecord->SegFs, pInfo->ContextRecord->SegGs,
171 pInfo->ContextRecord->EFlags);
172 #endif
173
174 _tprintf(_T("\nCall stack:\n"));
175 NxStackWalker sw(NxStackWalker::StackWalkOptions::OptionsAll, NULL, GetCurrentProcessId(), GetCurrentProcess());
176 sw.ShowCallstack(GetCurrentThread(), pInfo->ContextRecord);
177
178 _tprintf(_T("\nPROCESS TERMINATED\n"));
179 return TRUE;
180 }
181
182 /**
183 * Show call stack
184 */
185 void LIBNETXMS_EXPORTABLE SEHShowCallStack(CONTEXT *pCtx)
186 {
187 NxStackWalker sw(NxStackWalker::StackWalkOptions::OptionsAll, NULL, GetCurrentProcessId(), GetCurrentProcess());
188 sw.ShowCallstack(GetCurrentThread(), pCtx);
189 }
190
191 /**
192 * Exception handler
193 */
194 int LIBNETXMS_EXPORTABLE ___ExceptionHandler(EXCEPTION_POINTERS *pInfo)
195 {
196 if ((m_pfExceptionHandler != NULL) &&
197 (pInfo->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT) &&
198 (pInfo->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP))
199 {
200 // Only one exception handler can be executed at a time
201 // We will never release mutex because __except block
202 // will call ExitProcess anyway and we want to log only first
203 // exception in case of multiple simultaneous exceptions
204 WaitForSingleObject(m_hExceptionLock, INFINITE);
205 return m_pfExceptionHandler(pInfo) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
206 }
207 return EXCEPTION_CONTINUE_SEARCH;
208 }
209
210 /**
211 * Starter for threads
212 */
213 THREAD_RESULT LIBNETXMS_EXPORTABLE THREAD_CALL SEHThreadStarter(void *pArg)
214 {
215 __try
216 {
217 ((THREAD_START_DATA *)pArg)->start_address(((THREAD_START_DATA *)pArg)->args);
218 free(pArg);
219 }
220 __except(___ExceptionHandler((EXCEPTION_POINTERS *)_exception_info()))
221 {
222 ExitProcess(99);
223 }
224 return THREAD_OK;
225 }
226
227 /*
228 * Windows service exception handling
229 * ****************************************************
230 */
231
232 /**
233 * Exception info file handle
234 */
235 static FILE *m_pExInfoFile = NULL;
236
237 /**
238 * Writer for SEHShowCallStack()
239 */
240 void LIBNETXMS_EXPORTABLE SEHServiceExceptionDataWriter(const TCHAR *pszText)
241 {
242 if (m_pExInfoFile != NULL)
243 _fputts(pszText, m_pExInfoFile);
244 }
245
246 /**
247 * Exception handler
248 */
249 BOOL LIBNETXMS_EXPORTABLE SEHServiceExceptionHandler(EXCEPTION_POINTERS *pInfo)
250 {
251 TCHAR szWindowsVersion[256] = _T("ERROR"), szInfoFile[MAX_PATH], szDumpFile[MAX_PATH], szProcNameUppercase[64];
252 HANDLE hFile;
253 time_t t;
254 SYSTEM_INFO sysInfo;
255
256 t = time(NULL);
257 _tcscpy(szProcNameUppercase, m_szBaseProcessName);
258 _tcsupr(szProcNameUppercase);
259
260 // Create info file
261 _sntprintf(szInfoFile, MAX_PATH, _T("%s\\%s-%d-%u.info"),
262 m_szDumpDir, m_szBaseProcessName, GetCurrentProcessId(), (DWORD)t);
263 m_pExInfoFile = _tfopen(szInfoFile, _T("w"));
264 if (m_pExInfoFile != NULL)
265 {
266 _ftprintf(m_pExInfoFile, _T("%s CRASH DUMP\n%s\n"), szProcNameUppercase, _tctime(&t));
267 #ifdef _M_IX86
268 _ftprintf(m_pExInfoFile, _T("EXCEPTION: %08X (%s) at %08X\n"),
269 #else
270 _ftprintf(m_pExInfoFile, _T("EXCEPTION: %08X (%s) at %016I64X\n"),
271 #endif
272 pInfo->ExceptionRecord->ExceptionCode,
273 SEHExceptionName(pInfo->ExceptionRecord->ExceptionCode),
274 (ULONG_PTR)pInfo->ExceptionRecord->ExceptionAddress);
275
276 // NetXMS and OS version
277 GetWindowsVersionString(szWindowsVersion, 256);
278 _ftprintf(m_pExInfoFile,
279 _T("\nNetXMS Version: ") NETXMS_VERSION_STRING
280 _T("\nNetXMS Build Tag: ") NETXMS_BUILD_TAG
281 _T("\nOS Version: %s\n"), szWindowsVersion);
282
283 // Processor architecture
284 _ftprintf(m_pExInfoFile, _T("Processor architecture: "));
285 GetSystemInfo(&sysInfo);
286 switch(sysInfo.wProcessorArchitecture)
287 {
288 case PROCESSOR_ARCHITECTURE_INTEL:
289 _ftprintf(m_pExInfoFile, _T("Intel x86\n"));
290 break;
291 case PROCESSOR_ARCHITECTURE_MIPS:
292 _ftprintf(m_pExInfoFile, _T("MIPS\n"));
293 break;
294 case PROCESSOR_ARCHITECTURE_ALPHA:
295 _ftprintf(m_pExInfoFile, _T("ALPHA\n"));
296 break;
297 case PROCESSOR_ARCHITECTURE_PPC:
298 _ftprintf(m_pExInfoFile, _T("PowerPC\n"));
299 break;
300 case PROCESSOR_ARCHITECTURE_IA64:
301 _ftprintf(m_pExInfoFile, _T("Intel IA-64\n"));
302 break;
303 case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
304 _ftprintf(m_pExInfoFile, _T("Intel x86 on Win64\n"));
305 break;
306 case PROCESSOR_ARCHITECTURE_AMD64:
307 _ftprintf(m_pExInfoFile, _T("AMD64 (Intel EM64T)\n"));
308 break;
309 default:
310 _ftprintf(m_pExInfoFile, _T("UNKNOWN\n"));
311 break;
312 }
313
314 #ifdef _X86_
315 _ftprintf(m_pExInfoFile, _T("\nRegister information:\n")
316 _T(" eax=%08X ebx=%08X ecx=%08X edx=%08X\n")
317 _T(" esi=%08X edi=%08X ebp=%08X esp=%08X\n")
318 _T(" cs=%04X ds=%04X es=%04X ss=%04X fs=%04X gs=%04X flags=%08X\n"),
319 pInfo->ContextRecord->Eax, pInfo->ContextRecord->Ebx,
320 pInfo->ContextRecord->Ecx, pInfo->ContextRecord->Edx,
321 pInfo->ContextRecord->Esi, pInfo->ContextRecord->Edi,
322 pInfo->ContextRecord->Ebp, pInfo->ContextRecord->Esp,
323 pInfo->ContextRecord->SegCs, pInfo->ContextRecord->SegDs,
324 pInfo->ContextRecord->SegEs, pInfo->ContextRecord->SegSs,
325 pInfo->ContextRecord->SegFs, pInfo->ContextRecord->SegGs,
326 pInfo->ContextRecord->EFlags);
327 #endif
328
329 _ftprintf(m_pExInfoFile, _T("\nCall stack:\n"));
330 SEHShowCallStack(pInfo->ContextRecord);
331
332 fclose(m_pExInfoFile);
333 }
334
335 // Create minidump
336 _sntprintf(szDumpFile, MAX_PATH, _T("%s\\%s-%u-%u.mdmp"),
337 m_szDumpDir, m_szBaseProcessName, GetCurrentProcessId(), (DWORD)t);
338 hFile = CreateFile(szDumpFile, GENERIC_WRITE, 0, NULL,
339 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
340 if (hFile != INVALID_HANDLE_VALUE)
341 {
342 MINIDUMP_EXCEPTION_INFORMATION mei;
343 mei.ThreadId = GetCurrentThreadId();
344 mei.ExceptionPointers = pInfo;
345 mei.ClientPointers = FALSE;
346
347 static const char *comments = "Version=" NETXMS_VERSION_STRING_A "; BuildTag=" NETXMS_BUILD_TAG_A;
348 MINIDUMP_USER_STREAM us;
349 us.Type = CommentStreamA;
350 us.Buffer = (void*)comments;
351 us.BufferSize = static_cast<ULONG>(strlen(comments) + 1);
352
353 MINIDUMP_USER_STREAM_INFORMATION usi;
354 usi.UserStreamCount = 1;
355 usi.UserStreamArray = &us;
356
357 if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
358 static_cast<MINIDUMP_TYPE>((m_writeFullDump ? MiniDumpWithFullMemory : MiniDumpNormal) | MiniDumpWithHandleData | MiniDumpWithProcessThreadData),
359 &mei, &usi, NULL))
360 {
361 CloseHandle(hFile);
362 if (DeflateFile(szDumpFile))
363 DeleteFile(szDumpFile);
364 }
365 else
366 {
367 CloseHandle(hFile);
368 DeleteFile(szDumpFile);
369 }
370 }
371
372 // Write event log
373 nxlog_write(m_dwLogMessageCode, EVENTLOG_ERROR_TYPE, "xsxss",
374 pInfo->ExceptionRecord->ExceptionCode,
375 SEHExceptionName(pInfo->ExceptionRecord->ExceptionCode),
376 pInfo->ExceptionRecord->ExceptionAddress,
377 szInfoFile, szDumpFile);
378
379 if (m_printToScreen)
380 {
381 _tprintf(_T("\n\n*************************************************************\n")
382 #ifdef _M_IX86
383 _T("EXCEPTION: %08X (%s) at %08X\nPROCESS TERMINATED"),
384 #else
385 _T("EXCEPTION: %08X (%s) at %016I64X\nPROCESS TERMINATED"),
386 #endif
387 pInfo->ExceptionRecord->ExceptionCode,
388 SEHExceptionName(pInfo->ExceptionRecord->ExceptionCode),
389 (ULONG_PTR)pInfo->ExceptionRecord->ExceptionAddress);
390 }
391
392 return TRUE; // Terminate process
393 }