mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2024-11-28 04:14:21 +01:00
Allow dumping live processes on OS X
R=mark at http://breakpad.appspot.com/148001/show git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@647 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
315fd78199
commit
144938cf22
@ -33,6 +33,7 @@
|
|||||||
#include "client/mac/handler/exception_handler.h"
|
#include "client/mac/handler/exception_handler.h"
|
||||||
#include "client/mac/handler/minidump_generator.h"
|
#include "client/mac/handler/minidump_generator.h"
|
||||||
#include "common/mac/macho_utilities.h"
|
#include "common/mac/macho_utilities.h"
|
||||||
|
#include "common/mac/scoped_task_suspend-inl.h"
|
||||||
|
|
||||||
#ifndef USE_PROTECTED_ALLOCATIONS
|
#ifndef USE_PROTECTED_ALLOCATIONS
|
||||||
#define USE_PROTECTED_ALLOCATIONS 0
|
#define USE_PROTECTED_ALLOCATIONS 0
|
||||||
@ -301,6 +302,37 @@ bool ExceptionHandler::WriteMinidump(const string &dump_path,
|
|||||||
return handler.WriteMinidump();
|
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,
|
bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||||
int exception_code,
|
int exception_code,
|
||||||
int exception_subcode,
|
int exception_subcode,
|
||||||
|
@ -121,6 +121,14 @@ class ExceptionHandler {
|
|||||||
static bool WriteMinidump(const string &dump_path, MinidumpCallback callback,
|
static bool WriteMinidump(const string &dump_path, MinidumpCallback callback,
|
||||||
void *callback_context);
|
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.
|
// Returns whether out-of-process dump generation is used or not.
|
||||||
bool IsOutOfProcess() const {
|
bool IsOutOfProcess() const {
|
||||||
return crash_generation_client_.get() != NULL;
|
return crash_generation_client_.get() != NULL;
|
||||||
|
@ -536,7 +536,10 @@ bool MinidumpGenerator::WriteThreadListStream(
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Don't include the generator thread
|
// Don't include the generator thread
|
||||||
|
if (handler_thread_ != MACH_PORT_NULL)
|
||||||
non_generator_thread_count = thread_count - 1;
|
non_generator_thread_count = thread_count - 1;
|
||||||
|
else
|
||||||
|
non_generator_thread_count = thread_count;
|
||||||
if (!list.AllocateObjectAndArray(non_generator_thread_count,
|
if (!list.AllocateObjectAndArray(non_generator_thread_count,
|
||||||
sizeof(MDRawThread)))
|
sizeof(MDRawThread)))
|
||||||
return false;
|
return false;
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
#include "breakpad_googletest_includes.h"
|
#include "breakpad_googletest_includes.h"
|
||||||
#include "client/mac/handler/exception_handler.h"
|
#include "client/mac/handler/exception_handler.h"
|
||||||
#include "client/mac/tests/auto_tempdir.h"
|
#include "client/mac/tests/auto_tempdir.h"
|
||||||
|
#include "common/mac/MachIPC.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using std::string;
|
using std::string;
|
||||||
@ -42,6 +43,12 @@ using google_breakpad::AutoTempDir;
|
|||||||
using google_breakpad::ExceptionHandler;
|
using google_breakpad::ExceptionHandler;
|
||||||
using testing::Test;
|
using testing::Test;
|
||||||
|
|
||||||
|
class ExceptionHandlerTest : public Test {
|
||||||
|
public:
|
||||||
|
AutoTempDir tempDir;
|
||||||
|
string lastDumpName;
|
||||||
|
};
|
||||||
|
|
||||||
static void Crasher() {
|
static void Crasher() {
|
||||||
int *a = (int*)0x42;
|
int *a = (int*)0x42;
|
||||||
|
|
||||||
@ -68,7 +75,7 @@ static bool MDCallback(const char *dump_dir, const char *file_name,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ExceptionHandler, InProcess) {
|
TEST_F(ExceptionHandlerTest, InProcess) {
|
||||||
AutoTempDir tempDir;
|
AutoTempDir tempDir;
|
||||||
// Give the child process a pipe to report back on.
|
// Give the child process a pipe to report back on.
|
||||||
int fds[2];
|
int fds[2];
|
||||||
@ -76,6 +83,7 @@ TEST(ExceptionHandler, InProcess) {
|
|||||||
// Fork off a child process so it can crash.
|
// Fork off a child process so it can crash.
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
|
// In the child process.
|
||||||
close(fds[0]);
|
close(fds[0]);
|
||||||
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
|
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
|
||||||
// crash
|
// crash
|
||||||
@ -83,6 +91,7 @@ TEST(ExceptionHandler, InProcess) {
|
|||||||
// not reached
|
// not reached
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
// In the parent process.
|
||||||
ASSERT_NE(-1, pid);
|
ASSERT_NE(-1, pid);
|
||||||
// Wait for the background process to return the minidump file.
|
// Wait for the background process to return the minidump file.
|
||||||
close(fds[1]);
|
close(fds[1]);
|
||||||
@ -101,4 +110,85 @@ TEST(ExceptionHandler, InProcess) {
|
|||||||
EXPECT_EQ(0, WEXITSTATUS(ret));
|
EXPECT_EQ(0, WEXITSTATUS(ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool ChildMDCallback(const char *dump_dir, const char *file_name,
|
||||||
|
void *context, bool success) {
|
||||||
|
ExceptionHandlerTest *self = reinterpret_cast<ExceptionHandlerTest*>(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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user