Library of Assembled Shared Sources
crash_dump_win32.inl
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2024 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43/* @file
44 * @internal
45 * http://www.debuginfo.com/examples/debugfilterexamples.html
46 */
47
48#ifndef LASS_GUARDIAN_OF_INCLUSION_UTIL_IMPL_CRASH_DUMP_WIN32_INL
49#define LASS_GUARDIAN_OF_INCLUSION_UTIL_IMPL_CRASH_DUMP_WIN32_INL
50
51#include "../util_common.h"
52#include "lass_errno.h"
53#include "../thread_fun.h"
54
55#include <windows.h>
56#include <objbase.h>
57#include <tchar.h>
58#include <stdlib.h>
59
60#pragma warning(push)
61#pragma warning(disable: 4996) // This function or variable may be unsafe.
62#pragma warning(disable: 4091) // 'typedef ': ignored on left of '' when no variable is declared (in C:\Program Files (x86)\Windows Kits\8.1\Include\um\dbghelp.h)
63
64#include <dbghelp.h>
65
66#define LASS_EXPERIMENTAL_CATCH_AND_REPORT(expression)\
67 try\
68 {\
69 expression;\
70 }\
71 catch (std::exception& error)\
72 {\
73 std::cerr << "[LASS RUN MSG] UNDEFINED BEHAVIOUR WARNING: '" LASS_STRINGIFY(expression)\
74 "' failed in '" LASS_HERE "' with std::exception: " << error.what() << std::endl << std::flush;\
75 }\
76 catch (...)\
77 {\
78 std::cerr << "[LASS RUN MSG] UNDEFINED BEHAVIOUR WARNING: '" LASS_STRINGIFY(expression)\
79 "' failed in '" LASS_HERE "' with unknown exception." << std::endl << std::flush;\
80 }\
81 /**/
82
83namespace lass
84{
85namespace util
86{
87namespace impl
88{
89
90/**
91 *
92 */
93class CrashDumpImpl
94{
95public:
96
97 typedef void(*TCallback)(const char* /* filename */, void* /* closure */);
98
99 CrashDumpImpl(const std::string& name, TCallback callback, void* callbackClosure):
100 callback_(callback),
101 callbackClosure_(callbackClosure),
102 mutex_(0),
103 oldFilter_(0),
104 isExiting_(false),
105 isHandlingException_(false)
106 {
107 if (instance_)
108 {
109 LASS_THROW("You can install only one crash dumper at the same time");
110 }
111
112 ::GUID guid;
113 LASS_ENFORCE_COM(::CoCreateGuid(&guid));
114 const size_t guidSize = 39;
115 char guidString[guidSize];
116 _snprintf(guidString, guidSize, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
117 int(guid.Data1), int(guid.Data2), int(guid.Data3), int(guid.Data4[0]), int(guid.Data4[1]),
118 int(guid.Data4[2]), int(guid.Data4[3]), int(guid.Data4[4]), int(guid.Data4[5]),
119 int(guid.Data4[6]), int(guid.Data4[7]));
120 guidString[guidSize - 1] = '\0';
121 const size_t guidLength = strlen(guidString);
122
123 const size_t n = name.length();
124 const char* c_str = name.c_str();
125 std::copy(c_str, c_str + n, std::back_inserter(dumpFilename_));
126 dumpFilename_.push_back('-');
127 std::copy(guidString + 1, guidString + guidLength - 1, std::back_inserter(dumpFilename_));
128 const size_t extensionSize = 5;
129 char extension[extensionSize] = {'.', 'd', 'm', 'p', '\0'};
130 std::copy(extension, extension + extensionSize, std::back_inserter(dumpFilename_));
131
132 if (!dumpFilename_.empty())
133 {
134#ifdef _UNICODE
135 const char* filename = &dumpFilename_[0];
136 const int bufferLength = LASS_ENFORCE_WINAPI(
137 ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, 0, 0));
138 dumpFilenameT_.resize(bufferLength);
139 LASS_ENFORCE_WINAPI(::MultiByteToWideChar(CP_UTF8, 0, filename, -1, &dumpFilenameT_[0], bufferLength));
140#else
141 dumpFilenameT_ = dumpFilename_;
142#endif
143 }
144
145 instance_ = this;
146 }
147
148 void init()
149 {
150 if (instance_ != this || mutex_)
151 {
152 LASS_THROW("you should have called this!");
153 }
154 mutex_ = new TMutex;
155
156 dbghelp_ = loadLibrary(_T("dbghelp.dll"));
157 miniDumpWriteDump_ = reinterpret_cast<TMiniDumpWriteDump>(LASS_ENFORCE_WINAPI(
158 ::GetProcAddress(dbghelp_, "MiniDumpWriteDump")));
159
160 isExiting_ = false;
161 handlerThread_.reset(util::threadMemFun(this, &CrashDumpImpl::handlerThread, util::threadJoinable));
162 handlerThread_->run();
163
164 oldFilter_ = ::SetUnhandledExceptionFilter(unhandledExceptionFilter);
165 patchSetUnhandledExceptionFilter(true);
166
167 instance_ = this;
168 }
169
170 ~CrashDumpImpl()
171 {
172 instance_ = 0;
173
174 LASS_EXPERIMENTAL_CATCH_AND_REPORT(patchSetUnhandledExceptionFilter(false));
175 ::SetUnhandledExceptionFilter(oldFilter_);
176
177 isExiting_ = true;
178 handleCondition_.signal();
179 handlerThread_->join();
180
181 LASS_WARN_WINAPI(::FreeLibrary(dbghelp_));
182
183 delete mutex_;
184 mutex_ = 0;
185 }
186
187public:
188
189 enum
190 {
191 bufferSize_ = _MAX_PATH,
192#if LASS_ADDRESS_SIZE == 64
193 numPatchBytes_ = 3,
194#else
195 numPatchBytes_ = 5,
196#endif
197 };
198
199 typedef BOOL (WINAPI *TMiniDumpWriteDump)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType,
200 CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
201 CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
202
203 typedef util::CriticalSection TMutex;
204
205 HMODULE loadLibrary(const TCHAR* libraryName) const
206 {
207 HMODULE library = 0;
208
209 // try to load from exe directory
210 library = loadSideLibrary(libraryName, 0);
211
212#if LASS_BUILD_DLL
213 if (!library && dll::getLassInstance())
214 {
215 // try to load from LASS DLL directory
216 library = loadSideLibrary(libraryName, dll::getLassInstance());
217 }
218#endif
219
220 if (!library)
221 {
222 // try to load from default location
223 library = ::LoadLibrary(libraryName);
224 }
225
226 if (!library)
227 {
228 LASS_THROW("Failed to install CrashDump: failed to load " << libraryName);
229 }
230
231 return library;
232 }
233
234 HMODULE loadSideLibrary(const TCHAR* libraryName, HMODULE me) const
235 {
236 TCHAR modulePath[bufferSize_];
237 const DWORD pathLength = ::GetModuleFileName(me, modulePath, bufferSize_);
238 if (pathLength != 0 && pathLength < bufferSize_)
239 {
240 TCHAR* const lastBackSlash = ::_tcsrchr(modulePath, _T('\\'));
241 if (lastBackSlash)
242 {
243 TCHAR* const filename = lastBackSlash + 1;
244 ::_tcsncpy(filename, libraryName, (bufferSize_ - 1) - (filename - modulePath));
245 modulePath[bufferSize_ - 1] = _T('\0');
246 if (::_tcscmp(filename, libraryName) == 0)
247 {
248 return ::LoadLibrary(modulePath);
249 }
250 }
251 }
252 return 0;
253 }
254
255 /** @internal
256 * Patch or restore ::SetUnhandledExceptionFilter to keep our filter in charge.
257 *
258 * We need to sabotage ::SetUnhandledExceptionFilter in order to prevent anyone else from
259 * calling it to override our filter. This is a very real issue since even the runtime
260 * library is tempted to do so.
261 *
262 * @param patch [in] true to patch it, false to restore it.
263 */
264 void patchSetUnhandledExceptionFilter(bool patch)
265 {
266 HMODULE kernel32 = LASS_ENFORCE_WINAPI(::GetModuleHandle(_T("kernel32.dll")));
267 void* fun = reinterpret_cast<void*>(LASS_ENFORCE_WINAPI(::GetProcAddress(kernel32, "SetUnhandledExceptionFilter")));
268 if (patch)
269 {
270 LASS_ENFORCE(!::IsBadReadPtr(fun, numPatchBytes_));
271 ::memcpy(oldBytes_, fun, numPatchBytes_);
272 writeMemory(fun, patchBytes_, numPatchBytes_);
273 }
274 else
275 {
276 writeMemory(fun, oldBytes_, numPatchBytes_);
277 }
278 }
279
280 static void writeMemory(void* dest, const void* source, size_t size)
281 {
282 DWORD oldProtect, dummy;
283 LASS_ENFORCE_WINAPI(::VirtualProtect(dest, size, PAGE_EXECUTE_READWRITE, &oldProtect));
284 ::memcpy(dest, source, size);
285 ::VirtualProtect(dest, size, oldProtect, &dummy);
286 }
287
288 void handlerThread()
289 {
290 while (true)
291 {
292 while (!(isHandlingException_ || isExiting_))
293 {
294 handleCondition_.wait(2000);
295 }
296 if (isExiting_)
297 {
298 return;
299 }
300
301 doHandleException();
302
303 const char* filename = &dumpFilename_[0];
304 if (callback_)
305 {
306 callback_(filename, callbackClosure_);
307 }
308 else
309 {
310 std::cerr << "[LASS RUN MSG] dumped minidump at \"" << filename << "\"."
311 << std::endl << std::flush;
312 }
313 isHandlingException_ = false;
314 resultCondition_.signal();
315 }
316 }
317
318 void doHandleException()
319 {
320 handlerResult_ = EXCEPTION_CONTINUE_SEARCH;
321 const TCHAR* filename = &dumpFilenameT_[0];
322 HANDLE file = ::CreateFile(filename, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
323 if (file == INVALID_HANDLE_VALUE)
324 {
325 return;
326 }
327
328 HANDLE process = ::GetCurrentProcess();
329 DWORD processId = ::GetCurrentProcessId();
330 MINIDUMP_EXCEPTION_INFORMATION exceptionParam;
331 exceptionParam.ThreadId = threadId_;
332 exceptionParam.ExceptionPointers = exceptionInfo_;
333 exceptionParam.ClientPointers = 0;
334
335 if (miniDumpWriteDump_(process, processId, file, ::MiniDumpNormal, &exceptionParam, 0, 0))
336 {
337 handlerResult_ = EXCEPTION_EXECUTE_HANDLER;
338 }
339
340 ::CloseHandle(file);
341 }
342
343 static LONG WINAPI unhandledExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo)
344 {
345 const DWORD code = exceptionInfo->ExceptionRecord->ExceptionCode;
346 if (code == EXCEPTION_BREAKPOINT || code == EXCEPTION_SINGLE_STEP)
347 {
348 return EXCEPTION_CONTINUE_SEARCH;
349 };
350 LASS_LOCK(*instance_->mutex_)
351 {
352 instance_->exceptionInfo_ = exceptionInfo;
353 instance_->threadId_ = ::GetCurrentThreadId();
354 instance_->isHandlingException_ = true;
355 instance_->handleCondition_.signal();
356 while (instance_->isHandlingException_)
357 {
358 instance_->resultCondition_.wait(500);
359 }
360 return instance_->handlerResult_;
361 }
362 return EXCEPTION_CONTINUE_SEARCH; // unreachable though
363 }
364
365 std::vector<char> dumpFilename_;
366 std::vector<TCHAR> dumpFilenameT_;
367 TCallback callback_;
368 void* callbackClosure_;
369 HMODULE dbghelp_;
370 TMiniDumpWriteDump miniDumpWriteDump_;
371 std::unique_ptr<Thread> handlerThread_;
372 util::Condition handleCondition_;
373 util::Condition resultCondition_;
374 TMutex* mutex_;
375 LPTOP_LEVEL_EXCEPTION_FILTER oldFilter_;
376 unsigned char oldBytes_[numPatchBytes_];
377
378 _EXCEPTION_POINTERS* exceptionInfo_;
379 DWORD threadId_;
380 LONG handlerResult_;
381
382 std::atomic<bool> isExiting_;
383 std::atomic<bool> isHandlingException_;
384
385 static CrashDumpImpl* instance_;
386 static unsigned char patchBytes_[numPatchBytes_];
387};
388
389CrashDumpImpl* CrashDumpImpl::instance_ = 0;
390#if LASS_ADDRESS_SIZE == 64
391unsigned char CrashDumpImpl::patchBytes_[numPatchBytes_] = { 0x33, 0xc0, 0xc3 };
392#else
393unsigned char CrashDumpImpl::patchBytes_[numPatchBytes_] = { 0x33, 0xc0, 0xc2, 0x04, 0x00 };
394#endif
395
396}
397}
398}
399
400#pragma warning(pop)
401
402#endif
403
404// EOF
#define LASS_LOCK(iLock)
Locks a iLock and starts a scope block in which it remains locked.
Definition thread.h:619
@ threadJoinable
joinable thread, can be waited for
Definition thread.h:111
general utility, debug facilities, ...
Library for Assembled Shared Sources.
Definition config.h:53