diff --git a/src/client/mac/handler/exception_handler.cc b/src/client/mac/handler/exception_handler.cc index ccf1ff99..89cc4e90 100644 --- a/src/client/mac/handler/exception_handler.cc +++ b/src/client/mac/handler/exception_handler.cc @@ -33,6 +33,7 @@ #include "client/mac/handler/exception_handler.h" #include "client/mac/handler/minidump_generator.h" #include "common/mac/macho_utilities.h" +#include "common/mac/scoped_task_suspend-inl.h" #ifndef USE_PROTECTED_ALLOCATIONS #define USE_PROTECTED_ALLOCATIONS 0 @@ -301,6 +302,37 @@ bool ExceptionHandler::WriteMinidump(const string &dump_path, return handler.WriteMinidump(); } +// static +bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child, + mach_port_t child_blamed_thread, + const string &dump_path, + MinidumpCallback callback, + void *callback_context) { + ScopedTaskSuspend suspend(child); + + MinidumpGenerator generator(child, MACH_PORT_NULL); + string dump_id; + string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id); + + generator.SetExceptionInformation(EXC_BREAKPOINT, +#if defined (__i386__) || defined(__x86_64__) + EXC_I386_BPT, +#elif defined (__ppc__) || defined (__ppc64__) + EXC_PPC_BREAKPOINT, +#else + #error architecture not supported +#endif + 0, + child_blamed_thread); + bool result = generator.Write(dump_filename.c_str()); + + if (callback) { + return callback(dump_path.c_str(), dump_id.c_str(), + callback_context, result); + } + return result; +} + bool ExceptionHandler::WriteMinidumpWithException(int exception_type, int exception_code, int exception_subcode, diff --git a/src/client/mac/handler/exception_handler.h b/src/client/mac/handler/exception_handler.h index ee3e2e1d..76353255 100644 --- a/src/client/mac/handler/exception_handler.h +++ b/src/client/mac/handler/exception_handler.h @@ -121,6 +121,14 @@ class ExceptionHandler { static bool WriteMinidump(const string &dump_path, MinidumpCallback callback, void *callback_context); + // Write a minidump of child immediately. This can be used to capture + // the execution state of a child process independently of a crash. + static bool WriteMinidumpForChild(mach_port_t child, + mach_port_t child_blamed_thread, + const std::string &dump_path, + MinidumpCallback callback, + void *callback_context); + // Returns whether out-of-process dump generation is used or not. bool IsOutOfProcess() const { return crash_generation_client_.get() != NULL; diff --git a/src/client/mac/handler/minidump_generator.cc b/src/client/mac/handler/minidump_generator.cc index c5886be0..0c54cf42 100644 --- a/src/client/mac/handler/minidump_generator.cc +++ b/src/client/mac/handler/minidump_generator.cc @@ -536,7 +536,10 @@ bool MinidumpGenerator::WriteThreadListStream( return false; // Don't include the generator thread - non_generator_thread_count = thread_count - 1; + if (handler_thread_ != MACH_PORT_NULL) + non_generator_thread_count = thread_count - 1; + else + non_generator_thread_count = thread_count; if (!list.AllocateObjectAndArray(non_generator_thread_count, sizeof(MDRawThread))) return false; diff --git a/src/client/mac/tests/exception_handler_test.cc b/src/client/mac/tests/exception_handler_test.cc index 6ba244dd..f949a8b1 100644 --- a/src/client/mac/tests/exception_handler_test.cc +++ b/src/client/mac/tests/exception_handler_test.cc @@ -35,6 +35,7 @@ #include "breakpad_googletest_includes.h" #include "client/mac/handler/exception_handler.h" #include "client/mac/tests/auto_tempdir.h" +#include "common/mac/MachIPC.h" namespace { using std::string; @@ -42,6 +43,12 @@ using google_breakpad::AutoTempDir; using google_breakpad::ExceptionHandler; using testing::Test; +class ExceptionHandlerTest : public Test { + public: + AutoTempDir tempDir; + string lastDumpName; +}; + static void Crasher() { int *a = (int*)0x42; @@ -68,7 +75,7 @@ static bool MDCallback(const char *dump_dir, const char *file_name, return true; } -TEST(ExceptionHandler, InProcess) { +TEST_F(ExceptionHandlerTest, InProcess) { AutoTempDir tempDir; // Give the child process a pipe to report back on. int fds[2]; @@ -76,6 +83,7 @@ TEST(ExceptionHandler, InProcess) { // Fork off a child process so it can crash. pid_t pid = fork(); if (pid == 0) { + // In the child process. close(fds[0]); ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL); // crash @@ -83,6 +91,7 @@ TEST(ExceptionHandler, InProcess) { // not reached exit(1); } + // In the parent process. ASSERT_NE(-1, pid); // Wait for the background process to return the minidump file. close(fds[1]); @@ -101,4 +110,85 @@ TEST(ExceptionHandler, InProcess) { EXPECT_EQ(0, WEXITSTATUS(ret)); } +static bool ChildMDCallback(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; + self->lastDumpName += "/"; + self->lastDumpName += file_name; + self->lastDumpName += ".dmp"; + } + return true; +} + +TEST_F(ExceptionHandlerTest, DumpChildProcess) { + const int kTimeoutMs = 2000; + // Create a mach port to receive the child task on. + char machPortName[128]; + sprintf(machPortName, "ExceptionHandlerTest.%d", getpid()); + ReceivePort parent_recv_port(machPortName); + + // Give the child process a pipe to block on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // Fork off a child process to dump. + pid_t pid = fork(); + if (pid == 0) { + // In the child process + close(fds[0]); + + // Send parent process the task and thread ports. + MachSendMessage child_message(0); + child_message.AddDescriptor(mach_task_self()); + child_message.AddDescriptor(mach_thread_self()); + + MachPortSender child_sender(machPortName); + if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) + exit(1); + + // Wait for the parent process. + uint8_t data; + read(fds[1], &data, 1); + exit(0); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Read the child's task and thread ports. + MachReceiveMessage child_message; + ASSERT_EQ(KERN_SUCCESS, + parent_recv_port.WaitForMessage(&child_message, kTimeoutMs)); + mach_port_t child_task = child_message.GetTranslatedPort(0); + mach_port_t child_thread = child_message.GetTranslatedPort(1); + ASSERT_NE(MACH_PORT_NULL, child_task); + ASSERT_NE(MACH_PORT_NULL, child_thread); + + // Write a minidump of the child process. + bool result = ExceptionHandler::WriteMinidumpForChild(child_task, + child_thread, + tempDir.path, + ChildMDCallback, + this); + ASSERT_EQ(true, result); + + // 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); + + // Unblock child process + uint8_t data = 1; + (void)write(fds[0], &data, 1); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); +} + }