aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/windows/handler/exception_handler.cc234
-rw-r--r--src/client/windows/handler/exception_handler.h118
2 files changed, 270 insertions, 82 deletions
diff --git a/src/client/windows/handler/exception_handler.cc b/src/client/windows/handler/exception_handler.cc
index 4df0cef3..10d5ec08 100644
--- a/src/client/windows/handler/exception_handler.cc
+++ b/src/client/windows/handler/exception_handler.cc
@@ -29,6 +29,7 @@
#include <ObjBase.h>
+#include <cassert>
#include <cstdio>
#include "common/windows/string_utils-inl.h"
@@ -39,49 +40,83 @@
namespace google_airbag {
-ExceptionHandler *ExceptionHandler::current_handler_ = NULL;
-HANDLE ExceptionHandler::handler_thread_ = NULL;
-CRITICAL_SECTION ExceptionHandler::handler_critical_section_;
-HANDLE ExceptionHandler::handler_start_semaphore_ = NULL;
-HANDLE ExceptionHandler::handler_finish_semaphore_ = NULL;
-ExceptionHandler *ExceptionHandler::requesting_handler_ = NULL;
-DWORD ExceptionHandler::requesting_thread_id_ = 0;
-EXCEPTION_POINTERS *ExceptionHandler::exception_info_ = NULL;
-bool ExceptionHandler::handler_return_value_ = false;
+static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024;
+
+vector<ExceptionHandler *> *ExceptionHandler::handler_stack_ = NULL;
+LONG ExceptionHandler::handler_stack_index_ = 0;
+CRITICAL_SECTION ExceptionHandler::handler_stack_critical_section_;
+bool ExceptionHandler::handler_stack_critical_section_initialized_ = false;
ExceptionHandler::ExceptionHandler(const wstring &dump_path,
+ FilterCallback filter,
MinidumpCallback callback,
void *callback_context,
bool install_handler)
- : callback_(callback), callback_context_(callback_context),
- dump_path_(dump_path), dbghelp_module_(NULL),
- minidump_write_dump_(NULL), previous_handler_(current_handler_),
- previous_filter_(NULL) {
- if (!handler_thread_) {
- // The first time an ExceptionHandler is created, set up the handler
- // thread and the synchronization primitives.
- InitializeCriticalSection(&handler_critical_section_);
- handler_start_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL);
- handler_finish_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL);
-
- DWORD thread_id;
- handler_thread_ = CreateThread(NULL, // lpThreadAttributes
- 64 * 1024, // dwStackSize
- ExceptionHandlerThreadMain,
- NULL, // lpParameter
- 0, // dwCreationFlags
- &thread_id);
- }
+ : filter_(filter),
+ callback_(callback),
+ callback_context_(callback_context),
+ dump_path_(),
+ next_minidump_id_(),
+ next_minidump_path_(),
+ dump_path_c_(),
+ next_minidump_id_c_(NULL),
+ next_minidump_path_c_(NULL),
+ dbghelp_module_(NULL),
+ minidump_write_dump_(NULL),
+ installed_handler_(install_handler),
+ previous_filter_(NULL),
+ handler_thread_(0),
+ handler_critical_section_(),
+ handler_start_semaphore_(NULL),
+ handler_finish_semaphore_(NULL),
+ requesting_thread_id_(0),
+ exception_info_(NULL),
+ handler_return_value_(false) {
+ // set_dump_path calls UpdateNextID. This sets up all of the path and id
+ // strings, and their equivalent c_str pointers.
+ set_dump_path(dump_path);
+
+ // Set synchronization primitives and the handler thread. Each
+ // ExceptionHandler object gets its own handler thread, even if
+ // install_handler is false, because that's the only way to reliably
+ // guarantee sufficient stack space in an exception, and the only way to
+ // get a snapshot of the requesting thread's context outside of an
+ // exception.
+ InitializeCriticalSection(&handler_critical_section_);
+ handler_start_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL);
+ handler_finish_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL);
+
+ DWORD thread_id;
+ handler_thread_ = CreateThread(NULL, // lpThreadAttributes
+ kExceptionHandlerThreadInitialStackSize,
+ ExceptionHandlerThreadMain,
+ this, // lpParameter
+ 0, // dwCreationFlags
+ &thread_id);
- UpdateNextID();
dbghelp_module_ = LoadLibrary(L"dbghelp.dll");
if (dbghelp_module_) {
minidump_write_dump_ = reinterpret_cast<MiniDumpWriteDump_type>(
GetProcAddress(dbghelp_module_, "MiniDumpWriteDump"));
}
+
if (install_handler) {
+ if (!handler_stack_critical_section_initialized_) {
+ InitializeCriticalSection(&handler_stack_critical_section_);
+ handler_stack_critical_section_initialized_ = true;
+ }
+
+ EnterCriticalSection(&handler_stack_critical_section_);
+
+ // The first time an ExceptionHandler that installs a handler is
+ // created, set up the handler stack.
+ if (!handler_stack_) {
+ handler_stack_ = new vector<ExceptionHandler *>();
+ }
+ handler_stack_->push_back(this);
previous_filter_ = SetUnhandledExceptionFilter(HandleException);
- current_handler_ = this;
+
+ LeaveCriticalSection(&handler_stack_critical_section_);
}
}
@@ -89,35 +124,58 @@ ExceptionHandler::~ExceptionHandler() {
if (dbghelp_module_) {
FreeLibrary(dbghelp_module_);
}
- if (current_handler_ == this) {
+
+ if (installed_handler_) {
+ EnterCriticalSection(&handler_stack_critical_section_);
+
SetUnhandledExceptionFilter(previous_filter_);
- current_handler_ = previous_handler_;
- }
+ if (handler_stack_->back() == this) {
+ handler_stack_->pop_back();
+ } else {
+ // TODO(mmentovai): use advapi32!ReportEvent to log the warning to the
+ // system's application event log.
+ fprintf(stderr, "warning: removing Airbag handler out of order\n");
+ for (vector<ExceptionHandler *>::iterator iterator =
+ handler_stack_->begin();
+ iterator != handler_stack_->end();
+ ++iterator) {
+ if (*iterator == this) {
+ handler_stack_->erase(iterator);
+ }
+ }
+ }
+
+ if (handler_stack_->empty()) {
+ // When destroying the last ExceptionHandler that installed a handler,
+ // clean up the handler stack.
+ delete handler_stack_;
+ handler_stack_ = NULL;
+ }
- if (previous_handler_ == NULL) {
- // When destroying the last ExceptionHandler, clean up the handler thread
- // and synchronization primitives.
- TerminateThread(handler_thread_, 1);
- handler_thread_ = NULL;
- DeleteCriticalSection(&handler_critical_section_);
- CloseHandle(handler_start_semaphore_);
- handler_start_semaphore_ = NULL;
- CloseHandle(handler_finish_semaphore_);
- handler_finish_semaphore_ = NULL;
+ LeaveCriticalSection(&handler_stack_critical_section_);
}
+
+ // Clean up the handler thread and synchronization primitives.
+ TerminateThread(handler_thread_, 1);
+ DeleteCriticalSection(&handler_critical_section_);
+ CloseHandle(handler_start_semaphore_);
+ CloseHandle(handler_finish_semaphore_);
}
// static
DWORD ExceptionHandler::ExceptionHandlerThreadMain(void *lpParameter) {
+ ExceptionHandler *self = reinterpret_cast<ExceptionHandler *>(lpParameter);
+ assert(self);
+
while (true) {
- if (WaitForSingleObject(handler_start_semaphore_, INFINITE) ==
+ if (WaitForSingleObject(self->handler_start_semaphore_, INFINITE) ==
WAIT_OBJECT_0) {
// Perform the requested action.
- handler_return_value_ = requesting_handler_->WriteMinidumpWithException(
- requesting_thread_id_, exception_info_);
+ self->handler_return_value_ = self->WriteMinidumpWithException(
+ self->requesting_thread_id_, self->exception_info_);
// Allow the requesting thread to proceed.
- ReleaseSemaphore(handler_finish_semaphore_, 1, NULL);
+ ReleaseSemaphore(self->handler_finish_semaphore_, 1, NULL);
}
}
@@ -128,15 +186,66 @@ DWORD ExceptionHandler::ExceptionHandlerThreadMain(void *lpParameter) {
// static
LONG ExceptionHandler::HandleException(EXCEPTION_POINTERS *exinfo) {
- return current_handler_->WriteMinidumpOnHandlerThread(exinfo) ?
- EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
+ // Increment handler_stack_index_ so that if another Airbag handler is
+ // registered using this same HandleException function, and it needs to be
+ // called while this handler is running (either becaause this handler
+ // declines to handle the exception, or an exception occurs during
+ // handling), HandleException will find the appropriate ExceptionHandler
+ // object in handler_stack_ to deliver the exception to.
+ //
+ // Because handler_stack_ is addressed in reverse (as |size - index|),
+ // preincrementing handler_stack_index_ avoids needing to subtract 1 from
+ // the argument to |at|.
+ //
+ // The index is maintained instead of popping elements off of the handler
+ // stack and pushing them at the end of this method. This avoids ruining
+ // the order of elements in the stack in the event that some other thread
+ // decides to manipulate the handler stack (such as creating a new
+ // ExceptionHandler object) while an exception is being handled.
+ EnterCriticalSection(&handler_stack_critical_section_);
+ ExceptionHandler *current_handler =
+ handler_stack_->at(handler_stack_->size() - ++handler_stack_index_);
+ LeaveCriticalSection(&handler_stack_critical_section_);
+
+ // In case another exception occurs while this handler is doing its thing,
+ // it should be delivered to the previous filter.
+ LPTOP_LEVEL_EXCEPTION_FILTER previous = current_handler->previous_filter_;
+ LPTOP_LEVEL_EXCEPTION_FILTER restore = SetUnhandledExceptionFilter(previous);
+
+ LONG action;
+ if (current_handler->WriteMinidumpOnHandlerThread(exinfo)) {
+ // The handler fully handled the exception. Returning
+ // EXCEPTION_EXECUTE_HANDLER indicates this to the system, and usually
+ // results in the applicaiton being terminated.
+ //
+ // Note: If the application was launched from within the Cygwin
+ // environment, returning EXCEPTION_EXECUTE_HANDLER seems to cause the
+ // application to be restarted.
+ action = EXCEPTION_EXECUTE_HANDLER;
+ } else {
+ // There was an exception, but the handler decided not to handle it.
+ // This could be because the filter callback didn't want it, because
+ // minidump writing failed for some reason, or because the post-minidump
+ // callback function indicated failure. Give the previous handler a
+ // chance to do something with the exception. If there is no previous
+ // handler, return EXCEPTION_CONTINUE_SEARCH, which will allow a debugger
+ // or native "crashed" dialog to handle the exception.
+ action = previous ? previous(exinfo) : EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ // Put things back the way they were before entering this handler.
+ SetUnhandledExceptionFilter(restore);
+ EnterCriticalSection(&handler_stack_critical_section_);
+ --handler_stack_index_;
+ LeaveCriticalSection(&handler_stack_critical_section_);
+
+ return action;
}
bool ExceptionHandler::WriteMinidumpOnHandlerThread(EXCEPTION_POINTERS *exinfo) {
EnterCriticalSection(&handler_critical_section_);
// Set up data to be passed in to the handler thread.
- requesting_handler_ = this;
requesting_thread_id_ = GetCurrentThreadId();
exception_info_ = exinfo;
@@ -148,7 +257,6 @@ bool ExceptionHandler::WriteMinidumpOnHandlerThread(EXCEPTION_POINTERS *exinfo)
bool status = handler_return_value_;
// Clean up.
- requesting_handler_ = NULL;
requesting_thread_id_ = 0;
exception_info_ = NULL;
@@ -167,15 +275,25 @@ bool ExceptionHandler::WriteMinidump() {
bool ExceptionHandler::WriteMinidump(const wstring &dump_path,
MinidumpCallback callback,
void *callback_context) {
- ExceptionHandler handler(dump_path, callback, callback_context, false);
+ ExceptionHandler handler(dump_path, NULL, callback, callback_context, false);
return handler.WriteMinidump();
}
bool ExceptionHandler::WriteMinidumpWithException(DWORD requesting_thread_id,
EXCEPTION_POINTERS *exinfo) {
+ // Give user code a chance to approve or prevent writing a minidump. If the
+ // filter returns false, don't handle the exception at all. If this method
+ // was called as a result of an exception, returning false will cause
+ // HandleException to call any previous handler or return
+ // EXCEPTION_CONTINUE_SEARCH on the exception thread, allowing it to appear
+ // as though this handler were not present at all.
+ if (filter_&& !filter_(callback_context_)) {
+ return false;
+ }
+
bool success = false;
if (minidump_write_dump_) {
- HANDLE dump_file = CreateFile(next_minidump_path_.c_str(),
+ HANDLE dump_file = CreateFile(next_minidump_path_c_,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
@@ -223,9 +341,9 @@ bool ExceptionHandler::WriteMinidumpWithException(DWORD requesting_thread_id,
}
if (callback_) {
- callback_(next_minidump_id_, callback_context_, success);
+ success = callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
+ success);
}
- // TODO(bryner): log an error on failure
return success;
}
@@ -234,12 +352,14 @@ void ExceptionHandler::UpdateNextID() {
GUID id;
CoCreateGuid(&id);
next_minidump_id_ = GUIDString::GUIDToWString(&id);
+ next_minidump_id_c_ = next_minidump_id_.c_str();
wchar_t minidump_path[MAX_PATH];
WindowsStringUtils::safe_swprintf(minidump_path, MAX_PATH, L"%s\\%s.dmp",
- dump_path_.c_str(),
- next_minidump_id_.c_str());
+ dump_path_c_,
+ next_minidump_id_c_);
next_minidump_path_ = minidump_path;
+ next_minidump_path_c_ = next_minidump_path_.c_str();
}
} // namespace google_airbag
diff --git a/src/client/windows/handler/exception_handler.h b/src/client/windows/handler/exception_handler.h
index b24a37b7..f5ca31b9 100644
--- a/src/client/windows/handler/exception_handler.h
+++ b/src/client/windows/handler/exception_handler.h
@@ -67,27 +67,57 @@
#pragma warning( disable : 4530 )
#include <string>
+#include <vector>
namespace google_airbag {
+using std::vector;
using std::wstring;
class ExceptionHandler {
public:
+ // A callback function to run before Airbag performs any substantial
+ // processing of an exception. A FilterCallback is called before writing
+ // a minidump. context is the parameter supplied by the user as
+ // callback_context when the handler was created.
+ //
+ // If a FilterCallback returns true, Airbag will continue processing,
+ // attempting to write a minidump. If a FilterCallback returns false, Airbag
+ // will immediately report the exception as unhandled without writing a
+ // minidump, allowing another handler the opportunity to handle it.
+ typedef bool (*FilterCallback)(void *context);
+
// A callback function to run after the minidump has been written.
// minidump_id is a unique id for the dump, so the minidump
- // file is <dump_path>\<minidump_id>.dmp. succeeded indicates whether
- // a minidump file was successfully written.
- typedef void (*MinidumpCallback)(const wstring &minidump_id,
- void *context, bool succeeded);
+ // file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied
+ // by the user as callback_context when the handler was created. succeeded
+ // indicates whether a minidump file was successfully written.
+ //
+ // If an exception occurred and the callback returns true, Airbag will treat
+ // the exception as fully-handled, suppressing any other handlers from being
+ // notified of the exception. If the callback returns false, Airbag will
+ // treat the exception as unhandled, and allow another handler to handle it.
+ // If there are no other handlers, Airbag will report the exception to the
+ // system as unhandled, allowing a debugger or native crash dialog the
+ // opportunity to handle the exception. Most callback implementations
+ // should normally return the value of |succeeded|, or when they wish to
+ // not report an exception of handled, false. Callbacks will rarely want to
+ // return true directly (unless |succeeded| is true).
+ typedef bool (*MinidumpCallback)(const wchar_t *dump_path,
+ const wchar_t *minidump_id,
+ void *context,
+ bool succeeded);
// Creates a new ExceptionHandler instance to handle writing minidumps.
+ // Before writing a minidump, the optional filter callback will be called.
+ // Its return value determines whether or not Airbag should write a minidump.
// Minidump files will be written to dump_path, and the optional callback
// is called after writing the dump file, as described above.
// If install_handler is true, then a minidump will be written whenever
// an unhandled exception occurs. If it is false, minidumps will only
// be written when WriteMinidump is called.
- ExceptionHandler(const wstring &dump_path, MinidumpCallback callback,
+ ExceptionHandler(const wstring &dump_path,
+ FilterCallback filter, MinidumpCallback callback,
void *callback_context, bool install_handler);
~ExceptionHandler();
@@ -95,6 +125,7 @@ class ExceptionHandler {
wstring dump_path() const { return dump_path_; }
void set_dump_path(const wstring &dump_path) {
dump_path_ = dump_path;
+ dump_path_c_ = dump_path_.c_str();
UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_.
}
@@ -147,56 +178,93 @@ class ExceptionHandler {
// path of the next minidump to be written in next_minidump_path_.
void UpdateNextID();
+ FilterCallback filter_;
MinidumpCallback callback_;
void *callback_context_;
+ // The directory in which a minidump will be written, set by the dump_path
+ // argument to the constructor, or set_dump_path.
wstring dump_path_;
+
+ // The basename of the next minidump to be written, without the extension.
wstring next_minidump_id_;
+
+ // The full pathname of the next minidump to be written, including the file
+ // extension.
wstring next_minidump_path_;
+ // Pointers to C-string representations of the above. These are set when
+ // the above wstring versions are set in order to avoid calling c_str during
+ // an exception, as c_str may attempt to allocate heap memory. These
+ // pointers are not owned by the ExceptionHandler object, but their lifetimes
+ // should be equivalent to the lifetimes of the associated wstring, provided
+ // that the wstrings are not altered.
+ const wchar_t *dump_path_c_;
+ const wchar_t *next_minidump_id_c_;
+ const wchar_t *next_minidump_path_c_;
+
HMODULE dbghelp_module_;
MiniDumpWriteDump_type minidump_write_dump_;
- ExceptionHandler *previous_handler_; // current_handler_ before us
+ // True if the ExceptionHandler installed an unhandled exception filter
+ // when created (with an install_handler parameter set to true).
+ bool installed_handler_;
+
+ // When installed_handler_ is true, previous_filter_ is the unhandled
+ // exception filter that was set prior to installing ExceptionHandler as
+ // the unhandled exception filter and pointing it to |this|. NULL indicates
+ // that there is no previous unhandled exception filter.
LPTOP_LEVEL_EXCEPTION_FILTER previous_filter_;
-
- // the currently-installed ExceptionHandler, of which there can be only 1
- static ExceptionHandler *current_handler_;
-
- // The exception handler thread, if one has been created.
- static HANDLE handler_thread_;
+
+ // The exception handler thread.
+ HANDLE handler_thread_;
// The critical section enforcing the requirement that only one exception be
- // handled at a time.
- static CRITICAL_SECTION handler_critical_section_;
+ // handled by a handler at a time.
+ CRITICAL_SECTION handler_critical_section_;
// Semaphores used to move exception handling between the exception thread
// and the handler thread. handler_start_semaphore_ is signalled by the
// exception thread to wake up the handler thread when an exception occurs.
// handler_finish_semaphore_ is signalled by the handler thread to wake up
// the exception thread when handling is complete.
- static HANDLE handler_start_semaphore_;
- static HANDLE handler_finish_semaphore_;
+ HANDLE handler_start_semaphore_;
+ HANDLE handler_finish_semaphore_;
- // The next 3 fields are static data passed from the requesting thread to
+ // The next 2 fields contain data passed from the requesting thread to
// the handler thread.
- // The ExceptionHandler through which a request to write a dump was routed.
- // This will be the same as current_handler_ for exceptions, but
- // user-requested dumps may be routed through any live ExceptionHandler.
- static ExceptionHandler *requesting_handler_;
-
// The thread ID of the thread requesting the dump (either the exception
// thread or any other thread that called WriteMinidump directly).
- static DWORD requesting_thread_id_;
+ DWORD requesting_thread_id_;
// The exception info passed to the exception handler on the exception
// thread, if an exception occurred. NULL for user-requested dumps.
- static EXCEPTION_POINTERS *exception_info_;
+ EXCEPTION_POINTERS *exception_info_;
// The return value of the handler, passed from the handler thread back to
// the requesting thread.
- static bool handler_return_value_;
+ bool handler_return_value_;
+
+ // A stack of ExceptionHandler objects that have installed unhandled
+ // exception filters. This vector is used by HandleException to determine
+ // which ExceptionHandler object to route an exception to. When an
+ // ExceptionHandler is created with install_handler true, it will append
+ // itself to this list.
+ static vector<ExceptionHandler *> *handler_stack_;
+
+ // The index of the ExceptionHandler in handler_stack_ that will handle the
+ // next exception. Note that 0 means the last entry in handler_stack_, 1
+ // means the next-to-last entry, and so on. This is used by HandleException
+ // to support multiple stacked Airbag handlers.
+ static LONG handler_stack_index_;
+
+ // handler_stack_critical_section_ guards operations on handler_stack_ and
+ // handler_stack_index_.
+ static CRITICAL_SECTION handler_stack_critical_section_;
+
+ // True when handler_stack_critical_section_ has been initialized.
+ static bool handler_stack_critical_section_initialized_;
// disallow copy ctor and operator=
explicit ExceptionHandler(const ExceptionHandler &);