mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2025-09-13 19:37:11 +00:00
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:
parent
cae59b4ae4
commit
0d9bd40775
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue