From 0d9bd40775211c223c75543890d862135f677d67 Mon Sep 17 00:00:00 2001 From: "ted.mielczarek@gmail.com" Date: Wed, 15 Dec 2010 16:28:28 +0000 Subject: Allow writing on-request minidumps with an exception stream R=mark at http://breakpad.appspot.com/172001/show git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@745 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/client/mac/handler/exception_handler.cc | 75 +++++++++++++++++--------- src/client/mac/handler/exception_handler.h | 37 ++++++++++--- src/client/mac/tests/exception_handler_test.cc | 47 ++++++++++++++-- 3 files changed, 123 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/client/mac/handler/exception_handler.cc b/src/client/mac/handler/exception_handler.cc index b9f84b48..28d6ad64 100644 --- a/src/client/mac/handler/exception_handler.cc +++ b/src/client/mac/handler/exception_handler.cc @@ -53,9 +53,6 @@ namespace google_breakpad { using std::map; -// Message ID telling the handler thread to quit. -static const mach_msg_id_t kQuitMessage = 1; - // These structures and techniques are illustrated in // Mac OS X Internals, Amit Singh, ch 9.7 struct ExceptionMessage { @@ -272,7 +269,7 @@ ExceptionHandler::~ExceptionHandler() { Teardown(); } -bool ExceptionHandler::WriteMinidump() { +bool ExceptionHandler::WriteMinidump(bool write_exception_stream) { // If we're currently writing, just return if (use_minidump_write_mutex_) return false; @@ -284,7 +281,9 @@ bool ExceptionHandler::WriteMinidump() { if (pthread_mutex_lock(&minidump_write_mutex_) == 0) { // Send an empty message to the handle port so that a minidump will // be written - SendEmptyMachMessage(false); + SendMessageToHandlerThread(write_exception_stream ? + kWriteDumpWithExceptionMessage : + kWriteDumpMessage); // Wait for the minidump writer to complete its writing. It will unlock // the mutex when completed @@ -298,11 +297,12 @@ bool ExceptionHandler::WriteMinidump() { // static bool ExceptionHandler::WriteMinidump(const string &dump_path, + bool write_exception_stream, MinidumpCallback callback, void *callback_context) { ExceptionHandler handler(dump_path, NULL, callback, callback_context, false, NULL); - return handler.WriteMinidump(); + return handler.WriteMinidump(write_exception_stream); } // static @@ -323,7 +323,7 @@ bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child, #elif defined (__ppc__) || defined (__ppc64__) EXC_PPC_BREAKPOINT, #else - #error architecture not supported +#error architecture not supported #endif 0, child_blamed_thread); @@ -339,7 +339,8 @@ bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child, bool ExceptionHandler::WriteMinidumpWithException(int exception_type, int exception_code, int exception_subcode, - mach_port_t thread_name) { + mach_port_t thread_name, + bool exit_after_write) { bool result = false; if (directCallback_) { @@ -348,7 +349,7 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type, exception_code, exception_subcode, thread_name) ) { - if (exception_type && exception_code) + if (exit_after_write) _exit(exception_type); } } else if (IsOutOfProcess()) { @@ -390,7 +391,7 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type, // forwarding the exception to the next handler. if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_, result)) { - if (exception_type && exception_code) + if (exit_after_write) _exit(exception_type); } } @@ -527,7 +528,7 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) { if (!receive.exception) { // Don't touch self, since this message could have been sent // from its destructor. - if (receive.header.msgh_id == kQuitMessage) + if (receive.header.msgh_id == kShutdownMessage) return NULL; self->SuspendThreads(); @@ -537,11 +538,26 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) { gBreakpadAllocator->Unprotect(); #endif + mach_port_t thread = MACH_PORT_NULL; + int exception_type = 0; + int exception_code = 0; + if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) { + thread = receive.thread.name; + exception_type = EXC_BREAKPOINT; +#if defined (__i386__) || defined(__x86_64__) + exception_code = EXC_I386_BPT; +#elif defined (__ppc__) || defined (__ppc64__) + exception_code = EXC_PPC_BREAKPOINT; +#else +#error architecture not supported +#endif + } + // Write out the dump and save the result for later retrieval self->last_minidump_write_result_ = - self->WriteMinidumpWithException(0, 0, 0, 0); - - self->UninstallHandler(false); + self->WriteMinidumpWithException(exception_type, exception_code, + 0, thread, + false); #if USE_PROTECTED_ALLOCATIONS if(gBreakpadAllocator) @@ -575,7 +591,7 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) { // Generate the minidump with the exception data. self->WriteMinidumpWithException(receive.exception, receive.code[0], - subcode, receive.thread.name); + subcode, receive.thread.name, true); self->UninstallHandler(true); @@ -710,7 +726,7 @@ bool ExceptionHandler::Teardown() { return false; // Send an empty message so that the handler_thread exits - if (SendEmptyMachMessage(true)) { + if (SendMessageToHandlerThread(kShutdownMessage)) { mach_port_t current_task = mach_task_self(); result = mach_port_deallocate(current_task, handler_port_); if (result != KERN_SUCCESS) @@ -726,18 +742,25 @@ bool ExceptionHandler::Teardown() { return result == KERN_SUCCESS; } -bool ExceptionHandler::SendEmptyMachMessage(bool quit) { - ExceptionMessage empty; - memset(&empty, 0, sizeof(empty)); - if (quit) - empty.header.msgh_id = kQuitMessage; - empty.header.msgh_size = sizeof(empty) - sizeof(empty.padding); - empty.header.msgh_remote_port = handler_port_; - empty.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, +bool ExceptionHandler::SendMessageToHandlerThread( + HandlerThreadMessage message_id) { + ExceptionMessage msg; + memset(&msg, 0, sizeof(msg)); + msg.header.msgh_id = message_id; + if (message_id == kWriteDumpMessage || + message_id == kWriteDumpWithExceptionMessage) { + // Include this thread's port. + msg.thread.name = mach_thread_self(); + msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND; + msg.thread.type = MACH_MSG_PORT_DESCRIPTOR; + } + msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding); + msg.header.msgh_remote_port = handler_port_; + msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); - kern_return_t result = mach_msg(&(empty.header), + kern_return_t result = mach_msg(&(msg.header), MACH_SEND_MSG | MACH_SEND_TIMEOUT, - empty.header.msgh_size, 0, 0, + msg.header.msgh_size, 0, 0, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); return result == KERN_SUCCESS; diff --git a/src/client/mac/handler/exception_handler.h b/src/client/mac/handler/exception_handler.h index 182faa9d..172dc358 100644 --- a/src/client/mac/handler/exception_handler.h +++ b/src/client/mac/handler/exception_handler.h @@ -49,6 +49,16 @@ using std::string; struct ExceptionParameters; +enum HandlerThreadMessage { + // Message ID telling the handler thread to write a dump. + kWriteDumpMessage = 0, + // Message ID telling the handler thread to write a dump and include + // an exception stream. + kWriteDumpWithExceptionMessage = 1, + // Message ID telling the handler thread to quit. + kShutdownMessage = 2 +}; + class ExceptionHandler { public: // A callback function to run before Breakpad performs any substantial @@ -114,11 +124,22 @@ class ExceptionHandler { // Writes a minidump immediately. This can be used to capture the // execution state independently of a crash. Returns true on success. - bool WriteMinidump(); + bool WriteMinidump() { + return WriteMinidump(false); + } + + bool WriteMinidump(bool write_exception_stream); // Convenience form of WriteMinidump which does not require an // ExceptionHandler instance. static bool WriteMinidump(const string &dump_path, MinidumpCallback callback, + void *callback_context) { + return WriteMinidump(dump_path, false, callback, callback_context); + } + + static bool WriteMinidump(const string &dump_path, + bool write_exception_stream, + MinidumpCallback callback, void *callback_context); // Write a minidump of child immediately. This can be used to capture @@ -149,14 +170,16 @@ class ExceptionHandler { // thread bool Teardown(); - // Send an "empty" mach message to the exception handler. Return true on - // success, false otherwise. If quit is true, the handler thread should - // simply quit. - bool SendEmptyMachMessage(bool quit); + // Send a mach message to the exception handler. Return true on + // success, false otherwise. + bool SendMessageToHandlerThread(HandlerThreadMessage message_id); // All minidump writing goes through this one routine - bool WriteMinidumpWithException(int exception_type, int exception_code, - int exception_subcode, mach_port_t thread_name); + bool WriteMinidumpWithException(int exception_type, + int exception_code, + int exception_subcode, + mach_port_t thread_name, + bool exit_after_write); // When installed, this static function will be call from a newly created // pthread with |this| as the argument diff --git a/src/client/mac/tests/exception_handler_test.cc b/src/client/mac/tests/exception_handler_test.cc index ae2e0513..b1b74ef1 100644 --- a/src/client/mac/tests/exception_handler_test.cc +++ b/src/client/mac/tests/exception_handler_test.cc @@ -128,8 +128,8 @@ TEST_F(ExceptionHandlerTest, InProcess) { EXPECT_EQ(0, WEXITSTATUS(ret)); } -static bool ChildMDCallback(const char *dump_dir, const char *file_name, - void *context, bool success) { +static bool DumpNameMDCallback(const char *dump_dir, const char *file_name, + void *context, bool success) { ExceptionHandlerTest *self = reinterpret_cast(context); if (dump_dir && file_name) { self->lastDumpName = dump_dir; @@ -140,6 +140,47 @@ static bool ChildMDCallback(const char *dump_dir, const char *file_name, return true; } +TEST_F(ExceptionHandlerTest, WriteMinidump) { + ExceptionHandler eh(tempDir.path, NULL, DumpNameMDCallback, this, true, NULL); + ASSERT_TRUE(eh.WriteMinidump()); + + // Ensure that minidump file exists and is > 0 bytes. + ASSERT_FALSE(lastDumpName.empty()); + struct stat st; + ASSERT_EQ(0, stat(lastDumpName.c_str(), &st)); + ASSERT_LT(0, st.st_size); + + // The minidump should not contain an exception stream. + Minidump minidump(lastDumpName); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + EXPECT_FALSE(exception); +} + +TEST_F(ExceptionHandlerTest, WriteMinidumpWithException) { + ExceptionHandler eh(tempDir.path, NULL, DumpNameMDCallback, this, true, NULL); + ASSERT_TRUE(eh.WriteMinidump(true)); + + // Ensure that minidump file exists and is > 0 bytes. + ASSERT_FALSE(lastDumpName.empty()); + struct stat st; + ASSERT_EQ(0, stat(lastDumpName.c_str(), &st)); + ASSERT_LT(0, st.st_size); + + // The minidump should contain an exception stream. + Minidump minidump(lastDumpName); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + const MDRawExceptionStream* raw_exception = exception->exception(); + ASSERT_TRUE(raw_exception); + + EXPECT_EQ(MD_EXCEPTION_MAC_BREAKPOINT, + raw_exception->exception_record.exception_code); +} + TEST_F(ExceptionHandlerTest, DumpChildProcess) { const int kTimeoutMs = 2000; // Create a mach port to receive the child task on. @@ -188,7 +229,7 @@ TEST_F(ExceptionHandlerTest, DumpChildProcess) { bool result = ExceptionHandler::WriteMinidumpForChild(child_task, child_thread, tempDir.path, - ChildMDCallback, + DumpNameMDCallback, this); ASSERT_EQ(true, result); -- cgit v1.2.1