comments written to minidump file as ANSI string
[public/netxms.git] / src / libnetxms / seh.cpp
CommitLineData
5039dede
AK
1/*
2** NetXMS - Network Management System
74f9df1c 3** Copyright (C) 2003-2017 Victor Kirhenshtein
5039dede
AK
4**
5** This program is free software; you can redistribute it and/or modify
68f384ea
VK
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
5039dede
AK
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**
68f384ea 15** You should have received a copy of the GNU Lesser General Public License
5039dede
AK
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
3973f74b
VK
27#pragma warning(disable : 4482)
28
bfdba2df
VK
29/**
30 * Customized StackWalker class
31 */
5039dede
AK
32class NxStackWalker : public StackWalker
33{
34protected:
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
39public:
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
49static BOOL (*m_pfExceptionHandler)(EXCEPTION_POINTERS *) = NULL;
6d738067 50static void (*m_pfWriter)(const TCHAR *pszText) = NULL;
5039dede 51static HANDLE m_hExceptionLock = INVALID_HANDLE_VALUE;
6d738067
VK
52static TCHAR m_szBaseProcessName[64] = _T("netxms");
53static TCHAR m_szDumpDir[MAX_PATH] = _T("C:\\");
54static DWORD m_dwLogMessageCode = 0;
cbc777ee
VK
55static BOOL m_printToScreen = FALSE;
56static BOOL m_writeFullDump = FALSE;
5039dede 57
74f9df1c
VK
58/**
59 * Output for stack walker
60 */
5039dede
AK
61void NxStackWalker::OnOutput(LPCSTR pszText)
62{
63 if (m_pfWriter != NULL)
64 {
6d738067
VK
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
5039dede
AK
73 }
74 else
75 {
76 fputs(" ", stdout);
77 fputs(pszText, stdout);
78 }
79}
80
94b00ace
VK
81/**
82 * Initialize SEH functions
83 */
84void SEHInit()
5039dede
AK
85{
86 m_hExceptionLock = CreateMutex(NULL, FALSE, NULL);
87}
88
eeb2f8d0
VK
89/**
90 * Set exception handler
91 */
5039dede 92void LIBNETXMS_EXPORTABLE SetExceptionHandler(BOOL (*pfHandler)(EXCEPTION_POINTERS *),
6d738067 93 void (*pfWriter)(const TCHAR *), const TCHAR *pszDumpDir,
cbc777ee
VK
94 const TCHAR *pszBaseProcessName, DWORD dwLogMsgCode,
95 BOOL writeFullDump, BOOL printToScreen)
5039dede
AK
96{
97 m_pfExceptionHandler = pfHandler;
98 m_pfWriter = pfWriter;
6d738067 99 if (pszBaseProcessName != NULL)
3a4c13cc 100 _tcslcpy(m_szBaseProcessName, pszBaseProcessName, 64);
6d738067 101 if (pszDumpDir != NULL)
3a4c13cc 102 _tcslcpy(m_szDumpDir, pszDumpDir, MAX_PATH);
6d738067 103 m_dwLogMessageCode = dwLogMsgCode;
cbc777ee
VK
104 m_writeFullDump = writeFullDump;
105 m_printToScreen = printToScreen;
5039dede
AK
106}
107
eeb2f8d0
VK
108/**
109 * Get exception name from code
110 */
5039dede
AK
111TCHAR 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
eeb2f8d0
VK
150/**
151 * Default exception handler for console applications
152 */
5039dede
AK
153BOOL 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
94b00ace
VK
182/**
183 * Show call stack
184 */
5039dede
AK
185void LIBNETXMS_EXPORTABLE SEHShowCallStack(CONTEXT *pCtx)
186{
187 NxStackWalker sw(NxStackWalker::StackWalkOptions::OptionsAll, NULL, GetCurrentProcessId(), GetCurrentProcess());
188 sw.ShowCallstack(GetCurrentThread(), pCtx);
189}
190
94b00ace
VK
191/**
192 * Exception handler
193 */
5039dede
AK
194int 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
94b00ace
VK
210/**
211 * Starter for threads
212 */
5039dede
AK
213THREAD_RESULT LIBNETXMS_EXPORTABLE THREAD_CALL SEHThreadStarter(void *pArg)
214{
215 __try
216 {
217 ((THREAD_START_DATA *)pArg)->start_address(((THREAD_START_DATA *)pArg)->args);
0df58041 218 free(pArg);
5039dede
AK
219 }
220 __except(___ExceptionHandler((EXCEPTION_POINTERS *)_exception_info()))
221 {
222 ExitProcess(99);
223 }
224 return THREAD_OK;
225}
6d738067 226
6d738067
VK
227/*
228 * Windows service exception handling
229 * ****************************************************
230 */
231
94b00ace
VK
232/**
233 * Exception info file handle
234 */
6d738067
VK
235static FILE *m_pExInfoFile = NULL;
236
94b00ace
VK
237/**
238 * Writer for SEHShowCallStack()
239 */
6d738067
VK
240void LIBNETXMS_EXPORTABLE SEHServiceExceptionDataWriter(const TCHAR *pszText)
241{
242 if (m_pExInfoFile != NULL)
243 _fputts(pszText, m_pExInfoFile);
244}
245
94b00ace
VK
246/**
247 * Exception handler
248 */
6d738067
VK
249BOOL 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;
6d738067
VK
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 {
b14592e6 266 _ftprintf(m_pExInfoFile, _T("%s CRASH DUMP\n%s\n"), szProcNameUppercase, _tctime(&t));
bfe69cd8 267#ifdef _M_IX86
6d738067 268 _ftprintf(m_pExInfoFile, _T("EXCEPTION: %08X (%s) at %08X\n"),
bfe69cd8
VK
269#else
270 _ftprintf(m_pExInfoFile, _T("EXCEPTION: %08X (%s) at %016I64X\n"),
271#endif
6d738067
VK
272 pInfo->ExceptionRecord->ExceptionCode,
273 SEHExceptionName(pInfo->ExceptionRecord->ExceptionCode),
f1c7358a 274 (ULONG_PTR)pInfo->ExceptionRecord->ExceptionAddress);
6d738067
VK
275
276 // NetXMS and OS version
277 GetWindowsVersionString(szWindowsVersion, 256);
74f9df1c
VK
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);
6d738067
VK
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
3af85b5e 336 _sntprintf(szDumpFile, MAX_PATH, _T("%s\\%s-%u-%u.mdmp"),
6d738067
VK
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 {
3af85b5e 342 MINIDUMP_EXCEPTION_INFORMATION mei;
6d738067
VK
343 mei.ThreadId = GetCurrentThreadId();
344 mei.ExceptionPointers = pInfo;
345 mei.ClientPointers = FALSE;
3af85b5e 346
db63f792 347 static const char *comments = "Version=" NETXMS_VERSION_STRING_A "; BuildTag=" NETXMS_BUILD_TAG_A;
3af85b5e 348 MINIDUMP_USER_STREAM us;
db63f792 349 us.Type = CommentStreamA;
3af85b5e 350 us.Buffer = (void*)comments;
db63f792 351 us.BufferSize = static_cast<ULONG>(strlen(comments) + 1);
3af85b5e
VK
352
353 MINIDUMP_USER_STREAM_INFORMATION usi;
354 usi.UserStreamCount = 1;
355 usi.UserStreamArray = &us;
356
6d738067 357 MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
3af85b5e
VK
358 static_cast<MINIDUMP_TYPE>((m_writeFullDump ? MiniDumpWithFullMemory : MiniDumpNormal) | MiniDumpWithHandleData | MiniDumpWithProcessThreadData),
359 &mei, &usi, NULL);
6d738067
VK
360 CloseHandle(hFile);
361 }
362
363 // Write event log
364 nxlog_write(m_dwLogMessageCode, EVENTLOG_ERROR_TYPE, "xsxss",
365 pInfo->ExceptionRecord->ExceptionCode,
366 SEHExceptionName(pInfo->ExceptionRecord->ExceptionCode),
367 pInfo->ExceptionRecord->ExceptionAddress,
368 szInfoFile, szDumpFile);
369
cbc777ee 370 if (m_printToScreen)
6d738067
VK
371 {
372 _tprintf(_T("\n\n*************************************************************\n")
bfe69cd8 373#ifdef _M_IX86
6d738067 374 _T("EXCEPTION: %08X (%s) at %08X\nPROCESS TERMINATED"),
bfe69cd8
VK
375#else
376 _T("EXCEPTION: %08X (%s) at %016I64X\nPROCESS TERMINATED"),
377#endif
6d738067
VK
378 pInfo->ExceptionRecord->ExceptionCode,
379 SEHExceptionName(pInfo->ExceptionRecord->ExceptionCode),
f1c7358a 380 (ULONG_PTR)pInfo->ExceptionRecord->ExceptionAddress);
6d738067
VK
381 }
382
383 return TRUE; // Terminate process
384}