mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2025-01-09 14:45:34 +00:00
Allow generating minidumps from live processes on Windows.
R=mark at https://breakpad.appspot.com/115002/ git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1042 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
61d9b9ff96
commit
6a5ab68d56
|
@ -738,6 +738,62 @@ bool ExceptionHandler::WriteMinidump(const wstring &dump_path,
|
||||||
return handler.WriteMinidump();
|
return handler.WriteMinidump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool ExceptionHandler::WriteMinidumpForChild(HANDLE child,
|
||||||
|
DWORD child_blamed_thread,
|
||||||
|
const wstring& dump_path,
|
||||||
|
MinidumpCallback callback,
|
||||||
|
void* callback_context) {
|
||||||
|
EXCEPTION_RECORD ex;
|
||||||
|
CONTEXT ctx;
|
||||||
|
EXCEPTION_POINTERS exinfo = { NULL, NULL };
|
||||||
|
DWORD last_suspend_count = -1;
|
||||||
|
HANDLE child_thread_handle = OpenThread(THREAD_GET_CONTEXT |
|
||||||
|
THREAD_QUERY_INFORMATION |
|
||||||
|
THREAD_SUSPEND_RESUME,
|
||||||
|
FALSE,
|
||||||
|
child_blamed_thread);
|
||||||
|
// This thread may have died already, so not opening the handle is a
|
||||||
|
// non-fatal error.
|
||||||
|
if (child_thread_handle != NULL) {
|
||||||
|
last_suspend_count = SuspendThread(child_thread_handle);
|
||||||
|
if (last_suspend_count >= 0) {
|
||||||
|
ctx.ContextFlags = CONTEXT_ALL;
|
||||||
|
if (GetThreadContext(child_thread_handle, &ctx)) {
|
||||||
|
memset(&ex, 0, sizeof(ex));
|
||||||
|
ex.ExceptionCode = EXCEPTION_BREAKPOINT;
|
||||||
|
#if defined(_M_IX86)
|
||||||
|
ex.ExceptionAddress = reinterpret_cast<PVOID>(ctx.Eip);
|
||||||
|
#elif defined(_M_X64)
|
||||||
|
ex.ExceptionAddress = reinterpret_cast<PVOID>(ctx.Rip);
|
||||||
|
#endif
|
||||||
|
exinfo.ExceptionRecord = &ex;
|
||||||
|
exinfo.ContextRecord = &ctx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExceptionHandler handler(dump_path, NULL, callback, callback_context,
|
||||||
|
HANDLER_NONE);
|
||||||
|
bool success = handler.WriteMinidumpWithExceptionForProcess(
|
||||||
|
child_blamed_thread,
|
||||||
|
exinfo.ExceptionRecord ? &exinfo : NULL,
|
||||||
|
NULL, child, false);
|
||||||
|
|
||||||
|
if (last_suspend_count >= 0) {
|
||||||
|
ResumeThread(child_thread_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(child_thread_handle);
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
success = callback(handler.dump_path_c_, handler.next_minidump_id_c_,
|
||||||
|
callback_context, NULL, NULL, success);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
bool ExceptionHandler::WriteMinidumpWithException(
|
bool ExceptionHandler::WriteMinidumpWithException(
|
||||||
DWORD requesting_thread_id,
|
DWORD requesting_thread_id,
|
||||||
EXCEPTION_POINTERS* exinfo,
|
EXCEPTION_POINTERS* exinfo,
|
||||||
|
@ -756,114 +812,11 @@ bool ExceptionHandler::WriteMinidumpWithException(
|
||||||
if (IsOutOfProcess()) {
|
if (IsOutOfProcess()) {
|
||||||
success = crash_generation_client_->RequestDump(exinfo, assertion);
|
success = crash_generation_client_->RequestDump(exinfo, assertion);
|
||||||
} else {
|
} else {
|
||||||
if (minidump_write_dump_) {
|
success = WriteMinidumpWithExceptionForProcess(requesting_thread_id,
|
||||||
HANDLE dump_file = CreateFile(next_minidump_path_c_,
|
exinfo,
|
||||||
GENERIC_WRITE,
|
assertion,
|
||||||
0, // no sharing
|
GetCurrentProcess(),
|
||||||
NULL,
|
true);
|
||||||
CREATE_NEW, // fail if exists
|
|
||||||
FILE_ATTRIBUTE_NORMAL,
|
|
||||||
NULL);
|
|
||||||
if (dump_file != INVALID_HANDLE_VALUE) {
|
|
||||||
MINIDUMP_EXCEPTION_INFORMATION except_info;
|
|
||||||
except_info.ThreadId = requesting_thread_id;
|
|
||||||
except_info.ExceptionPointers = exinfo;
|
|
||||||
except_info.ClientPointers = FALSE;
|
|
||||||
|
|
||||||
// Add an MDRawBreakpadInfo stream to the minidump, to provide
|
|
||||||
// additional information about the exception handler to the Breakpad
|
|
||||||
// processor. The information will help the processor determine which
|
|
||||||
// threads are relevant. The Breakpad processor does not require this
|
|
||||||
// information but can function better with Breakpad-generated dumps
|
|
||||||
// when it is present. The native debugger is not harmed by the
|
|
||||||
// presence of this information.
|
|
||||||
MDRawBreakpadInfo breakpad_info;
|
|
||||||
breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
|
|
||||||
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
|
|
||||||
breakpad_info.dump_thread_id = GetCurrentThreadId();
|
|
||||||
breakpad_info.requesting_thread_id = requesting_thread_id;
|
|
||||||
|
|
||||||
// Leave room in user_stream_array for a possible assertion info stream.
|
|
||||||
MINIDUMP_USER_STREAM user_stream_array[2];
|
|
||||||
user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM;
|
|
||||||
user_stream_array[0].BufferSize = sizeof(breakpad_info);
|
|
||||||
user_stream_array[0].Buffer = &breakpad_info;
|
|
||||||
|
|
||||||
MINIDUMP_USER_STREAM_INFORMATION user_streams;
|
|
||||||
user_streams.UserStreamCount = 1;
|
|
||||||
user_streams.UserStreamArray = user_stream_array;
|
|
||||||
|
|
||||||
if (assertion) {
|
|
||||||
user_stream_array[1].Type = MD_ASSERTION_INFO_STREAM;
|
|
||||||
user_stream_array[1].BufferSize = sizeof(MDRawAssertionInfo);
|
|
||||||
user_stream_array[1].Buffer = assertion;
|
|
||||||
++user_streams.UserStreamCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Older versions of DbgHelp.dll don't correctly put the memory around
|
|
||||||
// the faulting instruction pointer into the minidump. This
|
|
||||||
// callback will ensure that it gets included.
|
|
||||||
if (exinfo) {
|
|
||||||
// Find a memory region of 256 bytes centered on the
|
|
||||||
// faulting instruction pointer.
|
|
||||||
const ULONG64 instruction_pointer =
|
|
||||||
#if defined(_M_IX86)
|
|
||||||
exinfo->ContextRecord->Eip;
|
|
||||||
#elif defined(_M_AMD64)
|
|
||||||
exinfo->ContextRecord->Rip;
|
|
||||||
#else
|
|
||||||
#error Unsupported platform
|
|
||||||
#endif
|
|
||||||
|
|
||||||
MEMORY_BASIC_INFORMATION info;
|
|
||||||
if (VirtualQuery(reinterpret_cast<LPCVOID>(instruction_pointer),
|
|
||||||
&info,
|
|
||||||
sizeof(MEMORY_BASIC_INFORMATION)) != 0 &&
|
|
||||||
info.State == MEM_COMMIT) {
|
|
||||||
// Attempt to get 128 bytes before and after the instruction
|
|
||||||
// pointer, but settle for whatever's available up to the
|
|
||||||
// boundaries of the memory region.
|
|
||||||
const ULONG64 kIPMemorySize = 256;
|
|
||||||
ULONG64 base =
|
|
||||||
(std::max)(reinterpret_cast<ULONG64>(info.BaseAddress),
|
|
||||||
instruction_pointer - (kIPMemorySize / 2));
|
|
||||||
ULONG64 end_of_range =
|
|
||||||
(std::min)(instruction_pointer + (kIPMemorySize / 2),
|
|
||||||
reinterpret_cast<ULONG64>(info.BaseAddress)
|
|
||||||
+ info.RegionSize);
|
|
||||||
ULONG size = static_cast<ULONG>(end_of_range - base);
|
|
||||||
|
|
||||||
AppMemory& elt = app_memory_info_.front();
|
|
||||||
elt.ptr = base;
|
|
||||||
elt.length = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MinidumpCallbackContext context;
|
|
||||||
context.iter = app_memory_info_.begin();
|
|
||||||
context.end = app_memory_info_.end();
|
|
||||||
|
|
||||||
// Skip the reserved element if there was no instruction memory
|
|
||||||
if (context.iter->ptr == 0) {
|
|
||||||
context.iter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
MINIDUMP_CALLBACK_INFORMATION callback;
|
|
||||||
callback.CallbackRoutine = MinidumpWriteDumpCallback;
|
|
||||||
callback.CallbackParam = reinterpret_cast<void*>(&context);
|
|
||||||
|
|
||||||
// The explicit comparison to TRUE avoids a warning (C4800).
|
|
||||||
success = (minidump_write_dump_(GetCurrentProcess(),
|
|
||||||
GetCurrentProcessId(),
|
|
||||||
dump_file,
|
|
||||||
dump_type_,
|
|
||||||
exinfo ? &except_info : NULL,
|
|
||||||
&user_streams,
|
|
||||||
&callback) == TRUE);
|
|
||||||
|
|
||||||
CloseHandle(dump_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback_) {
|
if (callback_) {
|
||||||
|
@ -917,6 +870,132 @@ BOOL CALLBACK ExceptionHandler::MinidumpWriteDumpCallback(
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ExceptionHandler::WriteMinidumpWithExceptionForProcess(
|
||||||
|
DWORD requesting_thread_id,
|
||||||
|
EXCEPTION_POINTERS* exinfo,
|
||||||
|
MDRawAssertionInfo* assertion,
|
||||||
|
HANDLE process,
|
||||||
|
bool write_requester_stream) {
|
||||||
|
bool success = false;
|
||||||
|
if (minidump_write_dump_) {
|
||||||
|
HANDLE dump_file = CreateFile(next_minidump_path_c_,
|
||||||
|
GENERIC_WRITE,
|
||||||
|
0, // no sharing
|
||||||
|
NULL,
|
||||||
|
CREATE_NEW, // fail if exists
|
||||||
|
FILE_ATTRIBUTE_NORMAL,
|
||||||
|
NULL);
|
||||||
|
if (dump_file != INVALID_HANDLE_VALUE) {
|
||||||
|
MINIDUMP_EXCEPTION_INFORMATION except_info;
|
||||||
|
except_info.ThreadId = requesting_thread_id;
|
||||||
|
except_info.ExceptionPointers = exinfo;
|
||||||
|
except_info.ClientPointers = FALSE;
|
||||||
|
|
||||||
|
// Leave room in user_stream_array for possible breakpad and
|
||||||
|
// assertion info streams.
|
||||||
|
MINIDUMP_USER_STREAM user_stream_array[2];
|
||||||
|
MINIDUMP_USER_STREAM_INFORMATION user_streams;
|
||||||
|
user_streams.UserStreamCount = 0;
|
||||||
|
user_streams.UserStreamArray = user_stream_array;
|
||||||
|
|
||||||
|
if (write_requester_stream) {
|
||||||
|
// Add an MDRawBreakpadInfo stream to the minidump, to provide
|
||||||
|
// additional information about the exception handler to the Breakpad
|
||||||
|
// processor. The information will help the processor determine which
|
||||||
|
// threads are relevant. The Breakpad processor does not require this
|
||||||
|
// information but can function better with Breakpad-generated dumps
|
||||||
|
// when it is present. The native debugger is not harmed by the
|
||||||
|
// presence of this information.
|
||||||
|
MDRawBreakpadInfo breakpad_info;
|
||||||
|
breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
|
||||||
|
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
|
||||||
|
breakpad_info.dump_thread_id = GetCurrentThreadId();
|
||||||
|
breakpad_info.requesting_thread_id = requesting_thread_id;
|
||||||
|
|
||||||
|
int index = user_streams.UserStreamCount;
|
||||||
|
user_stream_array[index].Type = MD_BREAKPAD_INFO_STREAM;
|
||||||
|
user_stream_array[index].BufferSize = sizeof(breakpad_info);
|
||||||
|
user_stream_array[index].Buffer = &breakpad_info;
|
||||||
|
++user_streams.UserStreamCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assertion) {
|
||||||
|
int index = user_streams.UserStreamCount;
|
||||||
|
user_stream_array[index].Type = MD_ASSERTION_INFO_STREAM;
|
||||||
|
user_stream_array[index].BufferSize = sizeof(MDRawAssertionInfo);
|
||||||
|
user_stream_array[index].Buffer = assertion;
|
||||||
|
++user_streams.UserStreamCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older versions of DbgHelp.dll don't correctly put the memory around
|
||||||
|
// the faulting instruction pointer into the minidump. This
|
||||||
|
// callback will ensure that it gets included.
|
||||||
|
if (exinfo) {
|
||||||
|
// Find a memory region of 256 bytes centered on the
|
||||||
|
// faulting instruction pointer.
|
||||||
|
const ULONG64 instruction_pointer =
|
||||||
|
#if defined(_M_IX86)
|
||||||
|
exinfo->ContextRecord->Eip;
|
||||||
|
#elif defined(_M_AMD64)
|
||||||
|
exinfo->ContextRecord->Rip;
|
||||||
|
#else
|
||||||
|
#error Unsupported platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MEMORY_BASIC_INFORMATION info;
|
||||||
|
if (VirtualQueryEx(process,
|
||||||
|
reinterpret_cast<LPCVOID>(instruction_pointer),
|
||||||
|
&info,
|
||||||
|
sizeof(MEMORY_BASIC_INFORMATION)) != 0 &&
|
||||||
|
info.State == MEM_COMMIT) {
|
||||||
|
// Attempt to get 128 bytes before and after the instruction
|
||||||
|
// pointer, but settle for whatever's available up to the
|
||||||
|
// boundaries of the memory region.
|
||||||
|
const ULONG64 kIPMemorySize = 256;
|
||||||
|
ULONG64 base =
|
||||||
|
(std::max)(reinterpret_cast<ULONG64>(info.BaseAddress),
|
||||||
|
instruction_pointer - (kIPMemorySize / 2));
|
||||||
|
ULONG64 end_of_range =
|
||||||
|
(std::min)(instruction_pointer + (kIPMemorySize / 2),
|
||||||
|
reinterpret_cast<ULONG64>(info.BaseAddress)
|
||||||
|
+ info.RegionSize);
|
||||||
|
ULONG size = static_cast<ULONG>(end_of_range - base);
|
||||||
|
|
||||||
|
AppMemory& elt = app_memory_info_.front();
|
||||||
|
elt.ptr = base;
|
||||||
|
elt.length = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MinidumpCallbackContext context;
|
||||||
|
context.iter = app_memory_info_.begin();
|
||||||
|
context.end = app_memory_info_.end();
|
||||||
|
|
||||||
|
// Skip the reserved element if there was no instruction memory
|
||||||
|
if (context.iter->ptr == 0) {
|
||||||
|
context.iter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
MINIDUMP_CALLBACK_INFORMATION callback;
|
||||||
|
callback.CallbackRoutine = MinidumpWriteDumpCallback;
|
||||||
|
callback.CallbackParam = reinterpret_cast<void*>(&context);
|
||||||
|
|
||||||
|
// The explicit comparison to TRUE avoids a warning (C4800).
|
||||||
|
success = (minidump_write_dump_(process,
|
||||||
|
GetCurrentProcessId(),
|
||||||
|
dump_file,
|
||||||
|
dump_type_,
|
||||||
|
exinfo ? &except_info : NULL,
|
||||||
|
&user_streams,
|
||||||
|
&callback) == TRUE);
|
||||||
|
|
||||||
|
CloseHandle(dump_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
void ExceptionHandler::UpdateNextID() {
|
void ExceptionHandler::UpdateNextID() {
|
||||||
assert(uuid_create_);
|
assert(uuid_create_);
|
||||||
UUID id = {0};
|
UUID id = {0};
|
||||||
|
|
|
@ -221,6 +221,17 @@ class ExceptionHandler {
|
||||||
static bool WriteMinidump(const wstring &dump_path,
|
static bool WriteMinidump(const wstring &dump_path,
|
||||||
MinidumpCallback callback, void* callback_context);
|
MinidumpCallback callback, void* callback_context);
|
||||||
|
|
||||||
|
// Write a minidump of |child| immediately. This can be used to
|
||||||
|
// capture the execution state of |child| independently of a crash.
|
||||||
|
// Pass a meaningful |child_blamed_thread| to make that thread in
|
||||||
|
// the child process the one from which a crash signature is
|
||||||
|
// extracted.
|
||||||
|
static bool WriteMinidumpForChild(HANDLE child,
|
||||||
|
DWORD child_blamed_thread,
|
||||||
|
const wstring& dump_path,
|
||||||
|
MinidumpCallback callback,
|
||||||
|
void* callback_context);
|
||||||
|
|
||||||
// Get the thread ID of the thread requesting the dump (either the exception
|
// Get the thread ID of the thread requesting the dump (either the exception
|
||||||
// thread or any other thread that called WriteMinidump directly). This
|
// thread or any other thread that called WriteMinidump directly). This
|
||||||
// may be useful if you want to include additional thread state in your
|
// may be useful if you want to include additional thread state in your
|
||||||
|
@ -305,8 +316,9 @@ class ExceptionHandler {
|
||||||
bool WriteMinidumpOnHandlerThread(EXCEPTION_POINTERS* exinfo,
|
bool WriteMinidumpOnHandlerThread(EXCEPTION_POINTERS* exinfo,
|
||||||
MDRawAssertionInfo* assertion);
|
MDRawAssertionInfo* assertion);
|
||||||
|
|
||||||
// This function does the actual writing of a minidump. It is called
|
// This function is called on the handler thread. It calls into
|
||||||
// on the handler thread. requesting_thread_id is the ID of the thread
|
// WriteMinidumpWithExceptionForProcess() with a handle to the
|
||||||
|
// current process. requesting_thread_id is the ID of the thread
|
||||||
// that requested the dump. If the dump is requested as a result of
|
// that requested the dump. If the dump is requested as a result of
|
||||||
// an exception, exinfo contains exception information, otherwise,
|
// an exception, exinfo contains exception information, otherwise,
|
||||||
// it is NULL.
|
// it is NULL.
|
||||||
|
@ -321,6 +333,20 @@ class ExceptionHandler {
|
||||||
const PMINIDUMP_CALLBACK_INPUT callback_input,
|
const PMINIDUMP_CALLBACK_INPUT callback_input,
|
||||||
PMINIDUMP_CALLBACK_OUTPUT callback_output);
|
PMINIDUMP_CALLBACK_OUTPUT callback_output);
|
||||||
|
|
||||||
|
// This function does the actual writing of a minidump. It is
|
||||||
|
// called on the handler thread. requesting_thread_id is the ID of
|
||||||
|
// the thread that requested the dump, if that information is
|
||||||
|
// meaningful. If the dump is requested as a result of an
|
||||||
|
// exception, exinfo contains exception information, otherwise, it
|
||||||
|
// is NULL. process is the one that will be dumped. If
|
||||||
|
// requesting_thread_id is meaningful and should be added to the
|
||||||
|
// minidump, write_requester_stream is |true|.
|
||||||
|
bool WriteMinidumpWithExceptionForProcess(DWORD requesting_thread_id,
|
||||||
|
EXCEPTION_POINTERS* exinfo,
|
||||||
|
MDRawAssertionInfo* assertion,
|
||||||
|
HANDLE process,
|
||||||
|
bool write_requester_stream);
|
||||||
|
|
||||||
// Generates a new ID and stores it in next_minidump_id_, and stores the
|
// Generates a new ID and stores it in next_minidump_id_, and stores the
|
||||||
// path of the next minidump to be written in next_minidump_path_.
|
// path of the next minidump to be written in next_minidump_path_.
|
||||||
void UpdateNextID();
|
void UpdateNextID();
|
||||||
|
|
Loading…
Reference in a new issue