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
This commit is contained in:
ted.mielczarek@gmail.com 2010-12-15 16:28:28 +00:00
parent cae59b4ae4
commit 0d9bd40775
3 changed files with 123 additions and 36 deletions

View file

@ -53,9 +53,6 @@ namespace google_breakpad {
using std::map; 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 // These structures and techniques are illustrated in
// Mac OS X Internals, Amit Singh, ch 9.7 // Mac OS X Internals, Amit Singh, ch 9.7
struct ExceptionMessage { struct ExceptionMessage {
@ -272,7 +269,7 @@ ExceptionHandler::~ExceptionHandler() {
Teardown(); Teardown();
} }
bool ExceptionHandler::WriteMinidump() { bool ExceptionHandler::WriteMinidump(bool write_exception_stream) {
// If we're currently writing, just return // If we're currently writing, just return
if (use_minidump_write_mutex_) if (use_minidump_write_mutex_)
return false; return false;
@ -284,7 +281,9 @@ bool ExceptionHandler::WriteMinidump() {
if (pthread_mutex_lock(&minidump_write_mutex_) == 0) { if (pthread_mutex_lock(&minidump_write_mutex_) == 0) {
// Send an empty message to the handle port so that a minidump will // Send an empty message to the handle port so that a minidump will
// be written // be written
SendEmptyMachMessage(false); SendMessageToHandlerThread(write_exception_stream ?
kWriteDumpWithExceptionMessage :
kWriteDumpMessage);
// Wait for the minidump writer to complete its writing. It will unlock // Wait for the minidump writer to complete its writing. It will unlock
// the mutex when completed // the mutex when completed
@ -298,11 +297,12 @@ bool ExceptionHandler::WriteMinidump() {
// static // static
bool ExceptionHandler::WriteMinidump(const string &dump_path, bool ExceptionHandler::WriteMinidump(const string &dump_path,
bool write_exception_stream,
MinidumpCallback callback, MinidumpCallback callback,
void *callback_context) { void *callback_context) {
ExceptionHandler handler(dump_path, NULL, callback, callback_context, false, ExceptionHandler handler(dump_path, NULL, callback, callback_context, false,
NULL); NULL);
return handler.WriteMinidump(); return handler.WriteMinidump(write_exception_stream);
} }
// static // static
@ -323,7 +323,7 @@ bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
#elif defined (__ppc__) || defined (__ppc64__) #elif defined (__ppc__) || defined (__ppc64__)
EXC_PPC_BREAKPOINT, EXC_PPC_BREAKPOINT,
#else #else
#error architecture not supported #error architecture not supported
#endif #endif
0, 0,
child_blamed_thread); child_blamed_thread);
@ -339,7 +339,8 @@ bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
bool ExceptionHandler::WriteMinidumpWithException(int exception_type, bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
int exception_code, int exception_code,
int exception_subcode, int exception_subcode,
mach_port_t thread_name) { mach_port_t thread_name,
bool exit_after_write) {
bool result = false; bool result = false;
if (directCallback_) { if (directCallback_) {
@ -348,7 +349,7 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
exception_code, exception_code,
exception_subcode, exception_subcode,
thread_name) ) { thread_name) ) {
if (exception_type && exception_code) if (exit_after_write)
_exit(exception_type); _exit(exception_type);
} }
} else if (IsOutOfProcess()) { } else if (IsOutOfProcess()) {
@ -390,7 +391,7 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
// forwarding the exception to the next handler. // forwarding the exception to the next handler.
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_, if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
result)) { result)) {
if (exception_type && exception_code) if (exit_after_write)
_exit(exception_type); _exit(exception_type);
} }
} }
@ -527,7 +528,7 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
if (!receive.exception) { if (!receive.exception) {
// Don't touch self, since this message could have been sent // Don't touch self, since this message could have been sent
// from its destructor. // from its destructor.
if (receive.header.msgh_id == kQuitMessage) if (receive.header.msgh_id == kShutdownMessage)
return NULL; return NULL;
self->SuspendThreads(); self->SuspendThreads();
@ -537,11 +538,26 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
gBreakpadAllocator->Unprotect(); gBreakpadAllocator->Unprotect();
#endif #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 // Write out the dump and save the result for later retrieval
self->last_minidump_write_result_ = self->last_minidump_write_result_ =
self->WriteMinidumpWithException(0, 0, 0, 0); self->WriteMinidumpWithException(exception_type, exception_code,
0, thread,
self->UninstallHandler(false); false);
#if USE_PROTECTED_ALLOCATIONS #if USE_PROTECTED_ALLOCATIONS
if(gBreakpadAllocator) if(gBreakpadAllocator)
@ -575,7 +591,7 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
// Generate the minidump with the exception data. // Generate the minidump with the exception data.
self->WriteMinidumpWithException(receive.exception, receive.code[0], self->WriteMinidumpWithException(receive.exception, receive.code[0],
subcode, receive.thread.name); subcode, receive.thread.name, true);
self->UninstallHandler(true); self->UninstallHandler(true);
@ -710,7 +726,7 @@ bool ExceptionHandler::Teardown() {
return false; return false;
// Send an empty message so that the handler_thread exits // Send an empty message so that the handler_thread exits
if (SendEmptyMachMessage(true)) { if (SendMessageToHandlerThread(kShutdownMessage)) {
mach_port_t current_task = mach_task_self(); mach_port_t current_task = mach_task_self();
result = mach_port_deallocate(current_task, handler_port_); result = mach_port_deallocate(current_task, handler_port_);
if (result != KERN_SUCCESS) if (result != KERN_SUCCESS)
@ -726,18 +742,25 @@ bool ExceptionHandler::Teardown() {
return result == KERN_SUCCESS; return result == KERN_SUCCESS;
} }
bool ExceptionHandler::SendEmptyMachMessage(bool quit) { bool ExceptionHandler::SendMessageToHandlerThread(
ExceptionMessage empty; HandlerThreadMessage message_id) {
memset(&empty, 0, sizeof(empty)); ExceptionMessage msg;
if (quit) memset(&msg, 0, sizeof(msg));
empty.header.msgh_id = kQuitMessage; msg.header.msgh_id = message_id;
empty.header.msgh_size = sizeof(empty) - sizeof(empty.padding); if (message_id == kWriteDumpMessage ||
empty.header.msgh_remote_port = handler_port_; message_id == kWriteDumpWithExceptionMessage) {
empty.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, // 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); 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, 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); MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
return result == KERN_SUCCESS; return result == KERN_SUCCESS;

View file

@ -49,6 +49,16 @@ using std::string;
struct ExceptionParameters; 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 { class ExceptionHandler {
public: public:
// A callback function to run before Breakpad performs any substantial // 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 // Writes a minidump immediately. This can be used to capture the
// execution state independently of a crash. Returns true on success. // 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 // Convenience form of WriteMinidump which does not require an
// ExceptionHandler instance. // ExceptionHandler instance.
static bool WriteMinidump(const string &dump_path, MinidumpCallback callback, 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); void *callback_context);
// Write a minidump of child immediately. This can be used to capture // Write a minidump of child immediately. This can be used to capture
@ -149,14 +170,16 @@ class ExceptionHandler {
// thread // thread
bool Teardown(); bool Teardown();
// Send an "empty" mach message to the exception handler. Return true on // Send a mach message to the exception handler. Return true on
// success, false otherwise. If quit is true, the handler thread should // success, false otherwise.
// simply quit. bool SendMessageToHandlerThread(HandlerThreadMessage message_id);
bool SendEmptyMachMessage(bool quit);
// All minidump writing goes through this one routine // All minidump writing goes through this one routine
bool WriteMinidumpWithException(int exception_type, int exception_code, bool WriteMinidumpWithException(int exception_type,
int exception_subcode, mach_port_t thread_name); 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 // When installed, this static function will be call from a newly created
// pthread with |this| as the argument // pthread with |this| as the argument

View file

@ -128,8 +128,8 @@ TEST_F(ExceptionHandlerTest, InProcess) {
EXPECT_EQ(0, WEXITSTATUS(ret)); EXPECT_EQ(0, WEXITSTATUS(ret));
} }
static bool ChildMDCallback(const char *dump_dir, const char *file_name, static bool DumpNameMDCallback(const char *dump_dir, const char *file_name,
void *context, bool success) { void *context, bool success) {
ExceptionHandlerTest *self = reinterpret_cast<ExceptionHandlerTest*>(context); ExceptionHandlerTest *self = reinterpret_cast<ExceptionHandlerTest*>(context);
if (dump_dir && file_name) { if (dump_dir && file_name) {
self->lastDumpName = dump_dir; self->lastDumpName = dump_dir;
@ -140,6 +140,47 @@ static bool ChildMDCallback(const char *dump_dir, const char *file_name,
return true; 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) { TEST_F(ExceptionHandlerTest, DumpChildProcess) {
const int kTimeoutMs = 2000; const int kTimeoutMs = 2000;
// Create a mach port to receive the child task on. // Create a mach port to receive the child task on.
@ -188,7 +229,7 @@ TEST_F(ExceptionHandlerTest, DumpChildProcess) {
bool result = ExceptionHandler::WriteMinidumpForChild(child_task, bool result = ExceptionHandler::WriteMinidumpForChild(child_task,
child_thread, child_thread,
tempDir.path, tempDir.path,
ChildMDCallback, DumpNameMDCallback,
this); this);
ASSERT_EQ(true, result); ASSERT_EQ(true, result);