1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#if defined(WIN32) && defined(CRASH_HANDLER)
24
25#include "crashhandler.h"
26#include <framework/global.h>
27#include <framework/core/application.h>
28#include <framework/core/resourcemanager.h>
29
30#include <winsock2.h>
31#include <windows.h>
32#include <process.h>
33
34#ifdef _MSC_VER
35
36#pragma warning (push)
37#pragma warning (disable:4091) // warning C4091: 'typedef ': ignored on left of '' when no variable is declared
38#include <imagehlp.h>
39#pragma warning (pop)
40
41#else
42
43#include <imagehlp.h>
44
45#endif
46
47bool quiet_crash = false;
48
49const char *getExceptionName(DWORD exceptionCode)
50{
51 switch (exceptionCode) {
52 case EXCEPTION_ACCESS_VIOLATION: return "Access violation";
53 case EXCEPTION_DATATYPE_MISALIGNMENT: return "Datatype misalignment";
54 case EXCEPTION_BREAKPOINT: return "Breakpoint";
55 case EXCEPTION_SINGLE_STEP: return "Single step";
56 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "Array bounds exceeded";
57 case EXCEPTION_FLT_DENORMAL_OPERAND: return "Float denormal operand";
58 case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "Float divide by zero";
59 case EXCEPTION_FLT_INEXACT_RESULT: return "Float inexact result";
60 case EXCEPTION_FLT_INVALID_OPERATION: return "Float invalid operation";
61 case EXCEPTION_FLT_OVERFLOW: return "Float overflow";
62 case EXCEPTION_FLT_STACK_CHECK: return "Float stack check";
63 case EXCEPTION_FLT_UNDERFLOW: return "Float underflow";
64 case EXCEPTION_INT_DIVIDE_BY_ZERO: return "Integer divide by zero";
65 case EXCEPTION_INT_OVERFLOW: return "Integer overflow";
66 case EXCEPTION_PRIV_INSTRUCTION: return "Privileged instruction";
67 case EXCEPTION_IN_PAGE_ERROR: return "In page error";
68 case EXCEPTION_ILLEGAL_INSTRUCTION: return "Illegal instruction";
69 case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "Noncontinuable exception";
70 case EXCEPTION_STACK_OVERFLOW: return "Stack overflow";
71 case EXCEPTION_INVALID_DISPOSITION: return "Invalid disposition";
72 case EXCEPTION_GUARD_PAGE: return "Guard page";
73 case EXCEPTION_INVALID_HANDLE: return "Invalid handle";
74 }
75 return "Unknown exception";
76}
77
78void Stacktrace(LPEXCEPTION_POINTERS e, std::stringstream& ss)
79{
80 PIMAGEHLP_SYMBOL pSym;
81 STACKFRAME sf;
82 HANDLE process, thread;
83 ULONG_PTR dwModBase, Disp;
84 BOOL more = FALSE;
85 DWORD machineType;
86 int count = 0;
87 char modname[MAX_PATH];
88 char symBuffer[sizeof(IMAGEHLP_SYMBOL) + 255];
89
90 pSym = (PIMAGEHLP_SYMBOL)symBuffer;
91
92 ZeroMemory(&sf, sizeof(sf));
93#ifdef _WIN64
94 sf.AddrPC.Offset = e->ContextRecord->Rip;
95 sf.AddrStack.Offset = e->ContextRecord->Rsp;
96 sf.AddrFrame.Offset = e->ContextRecord->Rbp;
97 machineType = IMAGE_FILE_MACHINE_AMD64;
98#else
99 sf.AddrPC.Offset = e->ContextRecord->Eip;
100 sf.AddrStack.Offset = e->ContextRecord->Esp;
101 sf.AddrFrame.Offset = e->ContextRecord->Ebp;
102 machineType = IMAGE_FILE_MACHINE_I386;
103#endif
104
105 sf.AddrPC.Mode = AddrModeFlat;
106 sf.AddrStack.Mode = AddrModeFlat;
107 sf.AddrFrame.Mode = AddrModeFlat;
108
109 process = GetCurrentProcess();
110 thread = GetCurrentThread();
111
112 while(1) {
113 more = StackWalk(machineType, process, thread, &sf, e->ContextRecord, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL);
114 if(!more || sf.AddrFrame.Offset == 0)
115 break;
116
117 dwModBase = SymGetModuleBase(process, sf.AddrPC.Offset);
118 if(dwModBase)
119 GetModuleFileNameA((HINSTANCE)dwModBase, modname, MAX_PATH);
120 else
121 strcpy(modname, "Unknown");
122
123 Disp = 0;
124 pSym->SizeOfStruct = sizeof(symBuffer);
125 pSym->MaxNameLength = 254;
126
127 if(SymGetSymFromAddr(process, sf.AddrPC.Offset, &Disp, pSym))
128 ss << stdext::format(" %d: %s(%s+%#0lx) [0x%016lX]\n", count, modname, pSym->Name, Disp, sf.AddrPC.Offset);
129 else
130 ss << stdext::format(" %d: %s [0x%016lX]\n", count, modname, sf.AddrPC.Offset);
131 ++count;
132 }
133 GlobalFree(pSym);
134}
135
136LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS e)
137{
138 // generate crash report
139 SymInitialize(GetCurrentProcess(), 0, TRUE);
140 std::stringstream ss;
141 ss << "== application crashed\n";
142 ss << stdext::format("app name: %s\n", g_app.getName());
143 ss << stdext::format("app version: %s\n", g_app.getVersion());
144 ss << stdext::format("build compiler: %s\n", BUILD_COMPILER);
145 ss << stdext::format("build date: %s\n", __DATE__);
146 ss << stdext::format("build type: %s\n", BUILD_TYPE);
147 ss << stdext::format("build revision: %s (%s)\n", BUILD_REVISION, BUILD_COMMIT);
148 ss << stdext::format("crash date: %s\n", stdext::date_time_string());
149 ss << stdext::format("exception: %s (0x%08lx)\n", getExceptionName(e->ExceptionRecord->ExceptionCode), e->ExceptionRecord->ExceptionCode);
150 ss << stdext::format("exception address: 0x%08lx\n", (size_t)e->ExceptionRecord->ExceptionAddress);
151 ss << stdext::format(" backtrace:\n");
152 Stacktrace(e, ss);
153 ss << "\n";
154 SymCleanup(GetCurrentProcess());
155
156 // print in stdout
157 g_logger.info(ss.str());
158
159 // write stacktrace to crashreport.log
160 auto dumpFilePath = std::filesystem::path(g_resources.getWriteDir());
161 dumpFilePath /= "crashreport.log";
162 std::ofstream fout(dumpFilePath, std::ios::out | std::ios::app);
163 if(fout.is_open() && fout.good()) {
164 fout << ss.str();
165 fout.close();
166 g_logger.info(stdext::format("Crash report saved to file %s", dumpFilePath.string()));
167 } else
168 g_logger.error("Failed to save crash report!");
169
170 // inform the user
171 std::string msg = std::string(
172 "The application has crashed.\n\n"
173 "A crash report has been written to:\n") + dumpFilePath.string();
174 if(!g_app.isStopping() && !quiet_crash) {
175 MessageBoxA(NULL, msg.c_str(), "Application crashed", 0);
176 }
177
178 // this seems to silently close the application
179 //return EXCEPTION_EXECUTE_HANDLER;
180
181 // this triggers the microsoft "application has crashed" error dialog
182 return EXCEPTION_CONTINUE_SEARCH;
183}
184
185
186#define TRACE_MAX_FUNCTION_NAME_LENGTH 1024
187#define TRACE_LOG_ERRORS FALSE
188#define TRACE_DUMP_NAME "exception.dmp"
189#define TRACE_DUMP_NAME_QUIET "exception2.dmp"
190#define TRACE_DUMP_NAME_FULL "exception_full.dmp"
191
192LONG WINAPI UnhandledExceptionFilter2(PEXCEPTION_POINTERS exception)
193{
194 CONTEXT context = *(exception->ContextRecord);
195 HANDLE thread = GetCurrentThread();
196 HANDLE process = GetCurrentProcess();
197 STACKFRAME frame;
198 memset(&frame, 0, sizeof(STACKFRAME));
199 DWORD image;
200
201#ifdef _M_IX86
202 image = IMAGE_FILE_MACHINE_I386;
203 frame.AddrPC.Offset = context.Eip;
204 frame.AddrPC.Mode = AddrModeFlat;
205 frame.AddrFrame.Offset = context.Ebp;
206 frame.AddrFrame.Mode = AddrModeFlat;
207 frame.AddrStack.Offset = context.Esp;
208 frame.AddrStack.Mode = AddrModeFlat;
209#elif _M_X64
210 image = IMAGE_FILE_MACHINE_AMD64;
211 frame.AddrPC.Offset = context.Rip;
212 frame.AddrPC.Mode = AddrModeFlat;
213 frame.AddrFrame.Offset = context.Rbp;
214 frame.AddrFrame.Mode = AddrModeFlat;
215 frame.AddrStack.Offset = context.Rsp;
216 frame.AddrStack.Mode = AddrModeFlat;
217#elif _M_IA64
218 image = IMAGE_FILE_MACHINE_IA64;
219 frame.AddrPC.Offset = context.StIIP;
220 frame.AddrPC.Mode = AddrModeFlat;
221 frame.AddrFrame.Offset = context.IntSp;
222 frame.AddrFrame.Mode = AddrModeFlat;
223 frame.AddrBStore.Offset = context.RsBSP;
224 frame.AddrBStore.Mode = AddrModeFlat;
225 frame.AddrStack.Offset = context.IntSp;
226 frame.AddrStack.Mode = AddrModeFlat;
227#else
228#error "This platform is not supported."
229#endif
230
231 auto dumpFilePath = std::filesystem::path(g_resources.getWriteDir());
232 if (quiet_crash) {
233 dumpFilePath /= TRACE_DUMP_NAME_QUIET;
234 } else {
235 dumpFilePath /= TRACE_DUMP_NAME;
236 }
237 {
238 HANDLE dumpFile = CreateFileA(dumpFilePath.string().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
239 MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
240 exceptionInformation.ThreadId = GetCurrentThreadId();
241 exceptionInformation.ExceptionPointers = exception;
242 exceptionInformation.ClientPointers = FALSE;
243 int flags = MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory;
244 MiniDumpWriteDump(process, GetProcessId(process), dumpFile, (MINIDUMP_TYPE)flags, exception ? &exceptionInformation : NULL, NULL, NULL);
245 }
246 {
247 dumpFilePath = std::filesystem::path(g_resources.getWriteDir());
248 dumpFilePath /= TRACE_DUMP_NAME_FULL;
249 HANDLE dumpFile = CreateFileA(dumpFilePath.string().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
250 MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
251 exceptionInformation.ThreadId = GetCurrentThreadId();
252 exceptionInformation.ExceptionPointers = exception;
253 exceptionInformation.ClientPointers = FALSE;
254 int flags = MiniDumpWithPrivateReadWriteMemory |
255 MiniDumpWithDataSegs |
256 MiniDumpWithHandleData |
257 MiniDumpWithFullMemoryInfo |
258 MiniDumpWithThreadInfo |
259 MiniDumpWithUnloadedModules;
260 MiniDumpWriteDump(process, GetProcessId(process), dumpFile, (MINIDUMP_TYPE)flags, exception ? &exceptionInformation : NULL, NULL, NULL);
261 }
262
263 if (quiet_crash) {
264#ifdef _MSC_VER
265 quick_exit(0);
266#else
267 exit(0);
268#endif
269 }
270
271 Sleep(1000);
272 ExceptionHandler(exception);
273
274 return EXCEPTION_CONTINUE_SEARCH;
275}
276
277void installCrashHandler()
278{
279 SetUnhandledExceptionFilter(UnhandledExceptionFilter2);
280}
281
282void uninstallCrashHandler()
283{
284 quiet_crash = true;
285}
286
287#endif
288