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