mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-12 23:26:39 +00:00
@@ -0,0 +1,80 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
#define CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Automatically enters the critical section in the constructor and leaves
|
||||
// the critical section in the destructor.
|
||||
class AutoCriticalSection {
|
||||
public:
|
||||
// Creates a new instance with the given critical section object
|
||||
// and enters the critical section immediately.
|
||||
explicit AutoCriticalSection(CRITICAL_SECTION* cs) : cs_(cs), taken_(false) {
|
||||
assert(cs_);
|
||||
Acquire();
|
||||
}
|
||||
|
||||
// Destructor: leaves the critical section.
|
||||
~AutoCriticalSection() {
|
||||
if (taken_) {
|
||||
Release();
|
||||
}
|
||||
}
|
||||
|
||||
// Enters the critical section. Recursive Acquire() calls are not allowed.
|
||||
void Acquire() {
|
||||
assert(!taken_);
|
||||
EnterCriticalSection(cs_);
|
||||
taken_ = true;
|
||||
}
|
||||
|
||||
// Leaves the critical section. The caller should not call Release() unless
|
||||
// the critical seciton has been entered already.
|
||||
void Release() {
|
||||
assert(taken_);
|
||||
taken_ = false;
|
||||
LeaveCriticalSection(cs_);
|
||||
}
|
||||
|
||||
private:
|
||||
// Disable copy ctor and operator=.
|
||||
AutoCriticalSection(const AutoCriticalSection&);
|
||||
AutoCriticalSection& operator=(const AutoCriticalSection&);
|
||||
|
||||
CRITICAL_SECTION* cs_;
|
||||
bool taken_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
#define CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include "common/windows/string_utils-inl.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Name/value pair for custom client information.
|
||||
struct CustomInfoEntry {
|
||||
// Maximum length for name and value for client custom info.
|
||||
static const int kNameMaxLength = 64;
|
||||
static const int kValueMaxLength = 64;
|
||||
|
||||
CustomInfoEntry() {
|
||||
// Putting name and value in initializer list makes VC++ show warning 4351.
|
||||
set_name(NULL);
|
||||
set_value(NULL);
|
||||
}
|
||||
|
||||
CustomInfoEntry(const wchar_t* name_arg, const wchar_t* value_arg) {
|
||||
set_name(name_arg);
|
||||
set_value(value_arg);
|
||||
}
|
||||
|
||||
void set_name(const wchar_t* name_arg) {
|
||||
if (!name_arg) {
|
||||
name[0] = L'\0';
|
||||
return;
|
||||
}
|
||||
WindowsStringUtils::safe_wcscpy(name, kNameMaxLength, name_arg);
|
||||
}
|
||||
|
||||
void set_value(const wchar_t* value_arg) {
|
||||
if (!value_arg) {
|
||||
value[0] = L'\0';
|
||||
return;
|
||||
}
|
||||
|
||||
WindowsStringUtils::safe_wcscpy(value, kValueMaxLength, value_arg);
|
||||
}
|
||||
|
||||
void set(const wchar_t* name_arg, const wchar_t* value_arg) {
|
||||
set_name(name_arg);
|
||||
set_value(value_arg);
|
||||
}
|
||||
|
||||
wchar_t name[kNameMaxLength];
|
||||
wchar_t value[kValueMaxLength];
|
||||
};
|
||||
|
||||
// Constants for the protocol between client and the server.
|
||||
|
||||
// Tags sent with each message indicating the purpose of
|
||||
// the message.
|
||||
enum MessageTag {
|
||||
MESSAGE_TAG_NONE = 0,
|
||||
MESSAGE_TAG_REGISTRATION_REQUEST = 1,
|
||||
MESSAGE_TAG_REGISTRATION_RESPONSE = 2,
|
||||
MESSAGE_TAG_REGISTRATION_ACK = 3,
|
||||
MESSAGE_TAG_UPLOAD_REQUEST = 4
|
||||
};
|
||||
|
||||
struct CustomClientInfo {
|
||||
const CustomInfoEntry* entries;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
// Message structure for IPC between crash client and crash server.
|
||||
struct ProtocolMessage {
|
||||
ProtocolMessage()
|
||||
: tag(MESSAGE_TAG_NONE),
|
||||
id(0),
|
||||
dump_type(MiniDumpNormal),
|
||||
thread_id(0),
|
||||
exception_pointers(NULL),
|
||||
assert_info(NULL),
|
||||
custom_client_info(),
|
||||
dump_request_handle(NULL),
|
||||
dump_generated_handle(NULL),
|
||||
server_alive_handle(NULL) {
|
||||
}
|
||||
|
||||
// Creates an instance with the given parameters.
|
||||
ProtocolMessage(MessageTag arg_tag,
|
||||
DWORD arg_id,
|
||||
MINIDUMP_TYPE arg_dump_type,
|
||||
DWORD* arg_thread_id,
|
||||
EXCEPTION_POINTERS** arg_exception_pointers,
|
||||
MDRawAssertionInfo* arg_assert_info,
|
||||
const CustomClientInfo& custom_info,
|
||||
HANDLE arg_dump_request_handle,
|
||||
HANDLE arg_dump_generated_handle,
|
||||
HANDLE arg_server_alive)
|
||||
: tag(arg_tag),
|
||||
id(arg_id),
|
||||
dump_type(arg_dump_type),
|
||||
thread_id(arg_thread_id),
|
||||
exception_pointers(arg_exception_pointers),
|
||||
assert_info(arg_assert_info),
|
||||
custom_client_info(custom_info),
|
||||
dump_request_handle(arg_dump_request_handle),
|
||||
dump_generated_handle(arg_dump_generated_handle),
|
||||
server_alive_handle(arg_server_alive) {
|
||||
}
|
||||
|
||||
// Tag in the message.
|
||||
MessageTag tag;
|
||||
|
||||
// The id for this message. This may be either a process id or a crash id
|
||||
// depending on the type of message.
|
||||
DWORD id;
|
||||
|
||||
// Dump type requested.
|
||||
MINIDUMP_TYPE dump_type;
|
||||
|
||||
// Client thread id pointer.
|
||||
DWORD* thread_id;
|
||||
|
||||
// Exception information.
|
||||
EXCEPTION_POINTERS** exception_pointers;
|
||||
|
||||
// Assert information in case of an invalid parameter or
|
||||
// pure call failure.
|
||||
MDRawAssertionInfo* assert_info;
|
||||
|
||||
// Custom client information.
|
||||
CustomClientInfo custom_client_info;
|
||||
|
||||
// Handle to signal the crash event.
|
||||
HANDLE dump_request_handle;
|
||||
|
||||
// Handle to check if server is done generating crash.
|
||||
HANDLE dump_generated_handle;
|
||||
|
||||
// Handle to a mutex that becomes signaled (WAIT_ABANDONED)
|
||||
// if server process goes down.
|
||||
HANDLE server_alive_handle;
|
||||
|
||||
private:
|
||||
// Disable copy ctor and operator=.
|
||||
ProtocolMessage(const ProtocolMessage& msg);
|
||||
ProtocolMessage& operator=(const ProtocolMessage& msg);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
@@ -0,0 +1,58 @@
|
||||
=========================================================================
|
||||
State machine transitions for the Crash Generation Server
|
||||
=========================================================================
|
||||
|
||||
=========================================================================
|
||||
|
|
||||
STATE | ACTIONS
|
||||
|
|
||||
=========================================================================
|
||||
ERROR | Clean up resources used to serve clients.
|
||||
| Always remain in ERROR state.
|
||||
-------------------------------------------------------------------------
|
||||
INITIAL | Connect to the pipe asynchronously.
|
||||
| If connection is successfully queued up asynchronously,
|
||||
| go into CONNECTING state.
|
||||
| If connection is done synchronously, go into CONNECTED
|
||||
| state.
|
||||
| For any unexpected problems, go into ERROR state.
|
||||
-------------------------------------------------------------------------
|
||||
CONNECTING | Get the result of async connection request.
|
||||
| If I/O is still incomplete, remain in the CONNECTING
|
||||
| state.
|
||||
| If connection is complete, go into CONNECTED state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
CONNECTED | Read from the pipe asynchronously.
|
||||
| If read request is successfully queued up asynchronously,
|
||||
| go into READING state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READING | Get the result of async read request.
|
||||
| If read is done, go into READ_DONE state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READ_DONE | Register the client, prepare the reply and write the
|
||||
| reply to the pipe asynchronously.
|
||||
| If write request is successfully queued up asynchronously,
|
||||
| go into WRITING state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
WRITING | Get the result of the async write request.
|
||||
| If write is done, go into WRITE_DONE state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
WRITE_DONE | Read from the pipe asynchronously (for an ACK).
|
||||
| If read request is successfully queued up asynchonously,
|
||||
| go into READING_ACK state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READING_ACK | Get the result of the async read request.
|
||||
| If read is done, perform action for successful client
|
||||
| connection.
|
||||
| Go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
DISCONNECTING | Disconnect from the pipe, reset the event and go into
|
||||
| INITIAL state and signal the event again. If anything
|
||||
| fails, go into ERROR state.
|
||||
=========================================================================
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "client/windows/crash_generation/client_info.h"
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
|
||||
static const wchar_t kCustomInfoProcessUptimeName[] = L"ptime";
|
||||
static const size_t kMaxCustomInfoEntries = 4096;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
ClientInfo::ClientInfo(CrashGenerationServer* crash_server,
|
||||
DWORD pid,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
DWORD* thread_id,
|
||||
EXCEPTION_POINTERS** ex_info,
|
||||
MDRawAssertionInfo* assert_info,
|
||||
const CustomClientInfo& custom_client_info)
|
||||
: crash_server_(crash_server),
|
||||
pid_(pid),
|
||||
dump_type_(dump_type),
|
||||
ex_info_(ex_info),
|
||||
assert_info_(assert_info),
|
||||
custom_client_info_(custom_client_info),
|
||||
thread_id_(thread_id),
|
||||
process_handle_(NULL),
|
||||
dump_requested_handle_(NULL),
|
||||
dump_generated_handle_(NULL),
|
||||
dump_request_wait_handle_(NULL),
|
||||
process_exit_wait_handle_(NULL),
|
||||
crash_id_(NULL) {
|
||||
GetSystemTimeAsFileTime(&start_time_);
|
||||
}
|
||||
|
||||
bool ClientInfo::Initialize() {
|
||||
process_handle_ = OpenProcess(GENERIC_ALL, FALSE, pid_);
|
||||
if (!process_handle_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The crash_id will be the low order word of the process creation time.
|
||||
FILETIME creation_time, exit_time, kernel_time, user_time;
|
||||
if (GetProcessTimes(process_handle_, &creation_time, &exit_time,
|
||||
&kernel_time, &user_time)) {
|
||||
start_time_ = creation_time;
|
||||
}
|
||||
crash_id_ = start_time_.dwLowDateTime;
|
||||
|
||||
dump_requested_handle_ = CreateEvent(NULL, // Security attributes.
|
||||
TRUE, // Manual reset.
|
||||
FALSE, // Initial state.
|
||||
NULL); // Name.
|
||||
if (!dump_requested_handle_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dump_generated_handle_ = CreateEvent(NULL, // Security attributes.
|
||||
TRUE, // Manual reset.
|
||||
FALSE, // Initial state.
|
||||
NULL); // Name.
|
||||
return dump_generated_handle_ != NULL;
|
||||
}
|
||||
|
||||
void ClientInfo::UnregisterDumpRequestWaitAndBlockUntilNoPending() {
|
||||
if (dump_request_wait_handle_) {
|
||||
// Wait for callbacks that might already be running to finish.
|
||||
UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE);
|
||||
dump_request_wait_handle_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientInfo::UnregisterProcessExitWait(bool block_until_no_pending) {
|
||||
if (process_exit_wait_handle_) {
|
||||
if (block_until_no_pending) {
|
||||
// Wait for the callback that might already be running to finish.
|
||||
UnregisterWaitEx(process_exit_wait_handle_, INVALID_HANDLE_VALUE);
|
||||
} else {
|
||||
UnregisterWait(process_exit_wait_handle_);
|
||||
}
|
||||
process_exit_wait_handle_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ClientInfo::~ClientInfo() {
|
||||
// Waiting for the callback to finish here is safe because ClientInfo's are
|
||||
// never destroyed from the dump request handling callback.
|
||||
UnregisterDumpRequestWaitAndBlockUntilNoPending();
|
||||
|
||||
// This is a little tricky because ClientInfo's may be destroyed by the same
|
||||
// callback (OnClientEnd) and waiting for it to finish will cause a deadlock.
|
||||
// Regardless of this complication, wait for any running callbacks to finish
|
||||
// so that the common case is properly handled. In order to avoid deadlocks,
|
||||
// the OnClientEnd callback must call UnregisterProcessExitWait(false)
|
||||
// before deleting the ClientInfo.
|
||||
UnregisterProcessExitWait(true);
|
||||
|
||||
if (process_handle_) {
|
||||
CloseHandle(process_handle_);
|
||||
}
|
||||
|
||||
if (dump_requested_handle_) {
|
||||
CloseHandle(dump_requested_handle_);
|
||||
}
|
||||
|
||||
if (dump_generated_handle_) {
|
||||
CloseHandle(dump_generated_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientInfo::GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const {
|
||||
SIZE_T bytes_count = 0;
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
ex_info_,
|
||||
ex_info,
|
||||
sizeof(*ex_info),
|
||||
&bytes_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_count == sizeof(*ex_info);
|
||||
}
|
||||
|
||||
bool ClientInfo::GetClientThreadId(DWORD* thread_id) const {
|
||||
SIZE_T bytes_count = 0;
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
thread_id_,
|
||||
thread_id,
|
||||
sizeof(*thread_id),
|
||||
&bytes_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_count == sizeof(*thread_id);
|
||||
}
|
||||
|
||||
void ClientInfo::SetProcessUptime() {
|
||||
FILETIME now = {0};
|
||||
GetSystemTimeAsFileTime(&now);
|
||||
|
||||
ULARGE_INTEGER time_start;
|
||||
time_start.HighPart = start_time_.dwHighDateTime;
|
||||
time_start.LowPart = start_time_.dwLowDateTime;
|
||||
|
||||
ULARGE_INTEGER time_now;
|
||||
time_now.HighPart = now.dwHighDateTime;
|
||||
time_now.LowPart = now.dwLowDateTime;
|
||||
|
||||
// Calculate the delay and convert it from 100-nanoseconds to milliseconds.
|
||||
__int64 delay = (time_now.QuadPart - time_start.QuadPart) / 10 / 1000;
|
||||
|
||||
// Convert it to a string.
|
||||
wchar_t* value = custom_info_entries_.get()[custom_client_info_.count].value;
|
||||
_i64tow_s(delay, value, CustomInfoEntry::kValueMaxLength, 10);
|
||||
}
|
||||
|
||||
bool ClientInfo::PopulateCustomInfo() {
|
||||
if (custom_client_info_.count > kMaxCustomInfoEntries)
|
||||
return false;
|
||||
|
||||
SIZE_T bytes_count = 0;
|
||||
SIZE_T read_count = sizeof(CustomInfoEntry) * custom_client_info_.count;
|
||||
|
||||
// If the scoped array for custom info already has an array, it will be
|
||||
// the same size as what we need. This is because the number of custom info
|
||||
// entries is always the same. So allocate memory only if scoped array has
|
||||
// a NULL pointer.
|
||||
if (!custom_info_entries_.get()) {
|
||||
// Allocate an extra entry for reporting uptime for the client process.
|
||||
custom_info_entries_.reset(
|
||||
new CustomInfoEntry[custom_client_info_.count + 1]);
|
||||
// Use the last element in the array for uptime.
|
||||
custom_info_entries_.get()[custom_client_info_.count].set_name(
|
||||
kCustomInfoProcessUptimeName);
|
||||
}
|
||||
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
custom_client_info_.entries,
|
||||
custom_info_entries_.get(),
|
||||
read_count,
|
||||
&bytes_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetProcessUptime();
|
||||
return (bytes_count == read_count);
|
||||
}
|
||||
|
||||
CustomClientInfo ClientInfo::GetCustomInfo() const {
|
||||
CustomClientInfo custom_info;
|
||||
custom_info.entries = custom_info_entries_.get();
|
||||
// Add 1 to the count from the client process to account for extra entry for
|
||||
// process uptime.
|
||||
custom_info.count = custom_client_info_.count + 1;
|
||||
return custom_info;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__
|
||||
#define CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
#include "common/scoped_ptr.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class CrashGenerationServer;
|
||||
|
||||
// Abstraction for a crash client process.
|
||||
class ClientInfo {
|
||||
public:
|
||||
// Creates an instance with the given values. Gets the process
|
||||
// handle for the given process id and creates necessary event
|
||||
// objects.
|
||||
ClientInfo(CrashGenerationServer* crash_server,
|
||||
DWORD pid,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
DWORD* thread_id,
|
||||
EXCEPTION_POINTERS** ex_info,
|
||||
MDRawAssertionInfo* assert_info,
|
||||
const CustomClientInfo& custom_client_info);
|
||||
|
||||
~ClientInfo();
|
||||
|
||||
CrashGenerationServer* crash_server() const { return crash_server_; }
|
||||
DWORD pid() const { return pid_; }
|
||||
MINIDUMP_TYPE dump_type() const { return dump_type_; }
|
||||
EXCEPTION_POINTERS** ex_info() const { return ex_info_; }
|
||||
MDRawAssertionInfo* assert_info() const { return assert_info_; }
|
||||
DWORD* thread_id() const { return thread_id_; }
|
||||
HANDLE process_handle() const { return process_handle_; }
|
||||
HANDLE dump_requested_handle() const { return dump_requested_handle_; }
|
||||
HANDLE dump_generated_handle() const { return dump_generated_handle_; }
|
||||
DWORD crash_id() const { return crash_id_; }
|
||||
const CustomClientInfo& custom_client_info() const {
|
||||
return custom_client_info_;
|
||||
}
|
||||
|
||||
void set_dump_request_wait_handle(HANDLE value) {
|
||||
dump_request_wait_handle_ = value;
|
||||
}
|
||||
|
||||
void set_process_exit_wait_handle(HANDLE value) {
|
||||
process_exit_wait_handle_ = value;
|
||||
}
|
||||
|
||||
// Unregister the dump request wait operation and wait for all callbacks
|
||||
// that might already be running to complete before returning.
|
||||
void UnregisterDumpRequestWaitAndBlockUntilNoPending();
|
||||
|
||||
// Unregister the process exit wait operation. If block_until_no_pending is
|
||||
// true, wait for all callbacks that might already be running to complete
|
||||
// before returning.
|
||||
void UnregisterProcessExitWait(bool block_until_no_pending);
|
||||
|
||||
bool Initialize();
|
||||
bool GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const;
|
||||
bool GetClientThreadId(DWORD* thread_id) const;
|
||||
|
||||
// Reads the custom information from the client process address space.
|
||||
bool PopulateCustomInfo();
|
||||
|
||||
// Returns the client custom information.
|
||||
CustomClientInfo GetCustomInfo() const;
|
||||
|
||||
private:
|
||||
// Calcualtes the uptime for the client process, converts it to a string and
|
||||
// stores it in the last entry of client custom info.
|
||||
void SetProcessUptime();
|
||||
|
||||
// Crash generation server.
|
||||
CrashGenerationServer* crash_server_;
|
||||
|
||||
// Client process ID.
|
||||
DWORD pid_;
|
||||
|
||||
// Dump type requested by the client.
|
||||
MINIDUMP_TYPE dump_type_;
|
||||
|
||||
// Address of an EXCEPTION_POINTERS* variable in the client
|
||||
// process address space that will point to an instance of
|
||||
// EXCEPTION_POINTERS containing information about crash.
|
||||
//
|
||||
// WARNING: Do not dereference these pointers as they are pointers
|
||||
// in the address space of another process.
|
||||
EXCEPTION_POINTERS** ex_info_;
|
||||
|
||||
// Address of an instance of MDRawAssertionInfo in the client
|
||||
// process address space that will contain information about
|
||||
// non-exception related crashes like invalid parameter assertion
|
||||
// failures and pure calls.
|
||||
//
|
||||
// WARNING: Do not dereference these pointers as they are pointers
|
||||
// in the address space of another process.
|
||||
MDRawAssertionInfo* assert_info_;
|
||||
|
||||
// Custom information about the client.
|
||||
CustomClientInfo custom_client_info_;
|
||||
|
||||
// Contains the custom client info entries read from the client process
|
||||
// memory. This will be populated only if the method GetClientCustomInfo
|
||||
// is called.
|
||||
scoped_array<CustomInfoEntry> custom_info_entries_;
|
||||
|
||||
// Address of a variable in the client process address space that
|
||||
// will contain the thread id of the crashing client thread.
|
||||
//
|
||||
// WARNING: Do not dereference these pointers as they are pointers
|
||||
// in the address space of another process.
|
||||
DWORD* thread_id_;
|
||||
|
||||
// Client process handle.
|
||||
HANDLE process_handle_;
|
||||
|
||||
// Dump request event handle.
|
||||
HANDLE dump_requested_handle_;
|
||||
|
||||
// Dump generated event handle.
|
||||
HANDLE dump_generated_handle_;
|
||||
|
||||
// Wait handle for dump request event.
|
||||
HANDLE dump_request_wait_handle_;
|
||||
|
||||
// Wait handle for process exit event.
|
||||
HANDLE process_exit_wait_handle_;
|
||||
|
||||
// Time when the client process started. It is used to determine the uptime
|
||||
// for the client process when it signals a crash.
|
||||
FILETIME start_time_;
|
||||
|
||||
// The crash id which can be used to request an upload. This will be the
|
||||
// value of the low order dword of the process creation time for the process
|
||||
// being dumped.
|
||||
DWORD crash_id_;
|
||||
|
||||
// Disallow copy ctor and operator=.
|
||||
ClientInfo(const ClientInfo& client_info);
|
||||
ClientInfo& operator=(const ClientInfo& client_info);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "client/windows/crash_generation/crash_generation_client.h"
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
const int kPipeBusyWaitTimeoutMs = 2000;
|
||||
|
||||
#ifdef _DEBUG
|
||||
const DWORD kWaitForServerTimeoutMs = INFINITE;
|
||||
#else
|
||||
const DWORD kWaitForServerTimeoutMs = 15000;
|
||||
#endif
|
||||
|
||||
const int kPipeConnectMaxAttempts = 2;
|
||||
|
||||
const DWORD kPipeDesiredAccess = FILE_READ_DATA |
|
||||
FILE_WRITE_DATA |
|
||||
FILE_WRITE_ATTRIBUTES;
|
||||
|
||||
const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION |
|
||||
SECURITY_SQOS_PRESENT;
|
||||
|
||||
const DWORD kPipeMode = PIPE_READMODE_MESSAGE;
|
||||
|
||||
const size_t kWaitEventCount = 2;
|
||||
|
||||
// This function is orphan for production code. It can be used
|
||||
// for debugging to help repro some scenarios like the client
|
||||
// is slow in writing to the pipe after connecting, the client
|
||||
// is slow in reading from the pipe after writing, etc. The parameter
|
||||
// overlapped below is not used and it is present to match the signature
|
||||
// of this function to TransactNamedPipe Win32 API. Uncomment if needed
|
||||
// for debugging.
|
||||
/**
|
||||
static bool TransactNamedPipeDebugHelper(HANDLE pipe,
|
||||
const void* in_buffer,
|
||||
DWORD in_size,
|
||||
void* out_buffer,
|
||||
DWORD out_size,
|
||||
DWORD* bytes_count,
|
||||
LPOVERLAPPED) {
|
||||
// Uncomment the next sleep to create a gap before writing
|
||||
// to pipe.
|
||||
// Sleep(5000);
|
||||
|
||||
if (!WriteFile(pipe,
|
||||
in_buffer,
|
||||
in_size,
|
||||
bytes_count,
|
||||
NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Uncomment the next sleep to create a gap between write
|
||||
// and read.
|
||||
// Sleep(5000);
|
||||
|
||||
return ReadFile(pipe, out_buffer, out_size, bytes_count, NULL) != FALSE;
|
||||
}
|
||||
**/
|
||||
|
||||
CrashGenerationClient::CrashGenerationClient(
|
||||
const wchar_t* pipe_name,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
const CustomClientInfo* custom_info)
|
||||
: pipe_name_(pipe_name),
|
||||
pipe_handle_(NULL),
|
||||
custom_info_(),
|
||||
dump_type_(dump_type),
|
||||
crash_event_(NULL),
|
||||
crash_generated_(NULL),
|
||||
server_alive_(NULL),
|
||||
server_process_id_(0),
|
||||
thread_id_(0),
|
||||
exception_pointers_(NULL) {
|
||||
memset(&assert_info_, 0, sizeof(assert_info_));
|
||||
if (custom_info) {
|
||||
custom_info_ = *custom_info;
|
||||
}
|
||||
}
|
||||
|
||||
CrashGenerationClient::CrashGenerationClient(
|
||||
HANDLE pipe_handle,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
const CustomClientInfo* custom_info)
|
||||
: pipe_name_(),
|
||||
pipe_handle_(pipe_handle),
|
||||
custom_info_(),
|
||||
dump_type_(dump_type),
|
||||
crash_event_(NULL),
|
||||
crash_generated_(NULL),
|
||||
server_alive_(NULL),
|
||||
server_process_id_(0),
|
||||
thread_id_(0),
|
||||
exception_pointers_(NULL) {
|
||||
memset(&assert_info_, 0, sizeof(assert_info_));
|
||||
if (custom_info) {
|
||||
custom_info_ = *custom_info;
|
||||
}
|
||||
}
|
||||
|
||||
CrashGenerationClient::~CrashGenerationClient() {
|
||||
if (crash_event_) {
|
||||
CloseHandle(crash_event_);
|
||||
}
|
||||
|
||||
if (crash_generated_) {
|
||||
CloseHandle(crash_generated_);
|
||||
}
|
||||
|
||||
if (server_alive_) {
|
||||
CloseHandle(server_alive_);
|
||||
}
|
||||
}
|
||||
|
||||
// Performs the registration step with the server process.
|
||||
// The registration step involves communicating with the server
|
||||
// via a named pipe. The client sends the following pieces of
|
||||
// data to the server:
|
||||
//
|
||||
// * Message tag indicating the client is requesting registration.
|
||||
// * Process id of the client process.
|
||||
// * Address of a DWORD variable in the client address space
|
||||
// that will contain the thread id of the client thread that
|
||||
// caused the crash.
|
||||
// * Address of a EXCEPTION_POINTERS* variable in the client
|
||||
// address space that will point to an instance of EXCEPTION_POINTERS
|
||||
// when the crash happens.
|
||||
// * Address of an instance of MDRawAssertionInfo that will contain
|
||||
// relevant information in case of non-exception crashes like assertion
|
||||
// failures and pure calls.
|
||||
//
|
||||
// In return the client expects the following information from the server:
|
||||
//
|
||||
// * Message tag indicating successful registration.
|
||||
// * Server process id.
|
||||
// * Handle to an object that client can signal to request dump
|
||||
// generation from the server.
|
||||
// * Handle to an object that client can wait on after requesting
|
||||
// dump generation for the server to finish dump generation.
|
||||
// * Handle to a mutex object that client can wait on to make sure
|
||||
// server is still alive.
|
||||
//
|
||||
// If any step of the expected behavior mentioned above fails, the
|
||||
// registration step is not considered successful and hence out-of-process
|
||||
// dump generation service is not available.
|
||||
//
|
||||
// Returns true if the registration is successful; false otherwise.
|
||||
bool CrashGenerationClient::Register() {
|
||||
if (IsRegistered()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
HANDLE pipe = ConnectToServer();
|
||||
if (!pipe) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = RegisterClient(pipe);
|
||||
CloseHandle(pipe);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool CrashGenerationClient::RequestUpload(DWORD crash_id) {
|
||||
HANDLE pipe = ConnectToServer();
|
||||
if (!pipe) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CustomClientInfo custom_info = {NULL, 0};
|
||||
ProtocolMessage msg(MESSAGE_TAG_UPLOAD_REQUEST, crash_id,
|
||||
static_cast<MINIDUMP_TYPE>(NULL), NULL, NULL, NULL,
|
||||
custom_info, NULL, NULL, NULL);
|
||||
DWORD bytes_count = 0;
|
||||
bool success = WriteFile(pipe, &msg, sizeof(msg), &bytes_count, NULL) != 0;
|
||||
|
||||
CloseHandle(pipe);
|
||||
return success;
|
||||
}
|
||||
|
||||
HANDLE CrashGenerationClient::ConnectToServer() {
|
||||
HANDLE pipe = ConnectToPipe(pipe_name_.c_str(),
|
||||
kPipeDesiredAccess,
|
||||
kPipeFlagsAndAttributes);
|
||||
if (!pipe) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DWORD mode = kPipeMode;
|
||||
if (!SetNamedPipeHandleState(pipe, &mode, NULL, NULL)) {
|
||||
CloseHandle(pipe);
|
||||
pipe = NULL;
|
||||
}
|
||||
|
||||
return pipe;
|
||||
}
|
||||
|
||||
bool CrashGenerationClient::RegisterClient(HANDLE pipe) {
|
||||
ProtocolMessage msg(MESSAGE_TAG_REGISTRATION_REQUEST,
|
||||
GetCurrentProcessId(),
|
||||
dump_type_,
|
||||
&thread_id_,
|
||||
&exception_pointers_,
|
||||
&assert_info_,
|
||||
custom_info_,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
ProtocolMessage reply;
|
||||
DWORD bytes_count = 0;
|
||||
// The call to TransactNamedPipe below can be changed to a call
|
||||
// to TransactNamedPipeDebugHelper to help repro some scenarios.
|
||||
// For details see comments for TransactNamedPipeDebugHelper.
|
||||
if (!TransactNamedPipe(pipe,
|
||||
&msg,
|
||||
sizeof(msg),
|
||||
&reply,
|
||||
sizeof(ProtocolMessage),
|
||||
&bytes_count,
|
||||
NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateResponse(reply)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ProtocolMessage ack_msg;
|
||||
ack_msg.tag = MESSAGE_TAG_REGISTRATION_ACK;
|
||||
|
||||
if (!WriteFile(pipe, &ack_msg, sizeof(ack_msg), &bytes_count, NULL)) {
|
||||
return false;
|
||||
}
|
||||
crash_event_ = reply.dump_request_handle;
|
||||
crash_generated_ = reply.dump_generated_handle;
|
||||
server_alive_ = reply.server_alive_handle;
|
||||
server_process_id_ = reply.id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HANDLE CrashGenerationClient::ConnectToPipe(const wchar_t* pipe_name,
|
||||
DWORD pipe_access,
|
||||
DWORD flags_attrs) {
|
||||
if (pipe_handle_) {
|
||||
HANDLE t = pipe_handle_;
|
||||
pipe_handle_ = NULL;
|
||||
return t;
|
||||
}
|
||||
|
||||
for (int i = 0; i < kPipeConnectMaxAttempts; ++i) {
|
||||
HANDLE pipe = CreateFile(pipe_name,
|
||||
pipe_access,
|
||||
0,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
flags_attrs,
|
||||
NULL);
|
||||
if (pipe != INVALID_HANDLE_VALUE) {
|
||||
return pipe;
|
||||
}
|
||||
|
||||
// Cannot continue retrying if error is something other than
|
||||
// ERROR_PIPE_BUSY.
|
||||
if (GetLastError() != ERROR_PIPE_BUSY) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Cannot continue retrying if wait on pipe fails.
|
||||
if (!WaitNamedPipe(pipe_name, kPipeBusyWaitTimeoutMs)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool CrashGenerationClient::ValidateResponse(
|
||||
const ProtocolMessage& msg) const {
|
||||
return (msg.tag == MESSAGE_TAG_REGISTRATION_RESPONSE) &&
|
||||
(msg.id != 0) &&
|
||||
(msg.dump_request_handle != NULL) &&
|
||||
(msg.dump_generated_handle != NULL) &&
|
||||
(msg.server_alive_handle != NULL);
|
||||
}
|
||||
|
||||
bool CrashGenerationClient::IsRegistered() const {
|
||||
return crash_event_ != NULL;
|
||||
}
|
||||
|
||||
bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info,
|
||||
MDRawAssertionInfo* assert_info) {
|
||||
if (!IsRegistered()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
exception_pointers_ = ex_info;
|
||||
thread_id_ = GetCurrentThreadId();
|
||||
|
||||
if (assert_info) {
|
||||
memcpy(&assert_info_, assert_info, sizeof(assert_info_));
|
||||
} else {
|
||||
memset(&assert_info_, 0, sizeof(assert_info_));
|
||||
}
|
||||
|
||||
return SignalCrashEventAndWait();
|
||||
}
|
||||
|
||||
bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info) {
|
||||
return RequestDump(ex_info, NULL);
|
||||
}
|
||||
|
||||
bool CrashGenerationClient::RequestDump(MDRawAssertionInfo* assert_info) {
|
||||
return RequestDump(NULL, assert_info);
|
||||
}
|
||||
|
||||
bool CrashGenerationClient::SignalCrashEventAndWait() {
|
||||
assert(crash_event_);
|
||||
assert(crash_generated_);
|
||||
assert(server_alive_);
|
||||
|
||||
// Reset the dump generated event before signaling the crash
|
||||
// event so that the server can set the dump generated event
|
||||
// once it is done generating the event.
|
||||
if (!ResetEvent(crash_generated_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetEvent(crash_event_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE wait_handles[kWaitEventCount] = {crash_generated_, server_alive_};
|
||||
|
||||
DWORD result = WaitForMultipleObjects(kWaitEventCount,
|
||||
wait_handles,
|
||||
FALSE,
|
||||
kWaitForServerTimeoutMs);
|
||||
|
||||
// Crash dump was successfully generated only if the server
|
||||
// signaled the crash generated event.
|
||||
return result == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
HANDLE CrashGenerationClient::DuplicatePipeToClientProcess(const wchar_t* pipe_name,
|
||||
HANDLE hProcess) {
|
||||
for (int i = 0; i < kPipeConnectMaxAttempts; ++i) {
|
||||
HANDLE local_pipe = CreateFile(pipe_name, kPipeDesiredAccess,
|
||||
0, NULL, OPEN_EXISTING,
|
||||
kPipeFlagsAndAttributes, NULL);
|
||||
if (local_pipe != INVALID_HANDLE_VALUE) {
|
||||
HANDLE remotePipe = INVALID_HANDLE_VALUE;
|
||||
if (DuplicateHandle(GetCurrentProcess(), local_pipe,
|
||||
hProcess, &remotePipe, 0, FALSE,
|
||||
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
|
||||
return remotePipe;
|
||||
} else {
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot continue retrying if the error wasn't a busy pipe.
|
||||
if (GetLastError() != ERROR_PIPE_BUSY) {
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
if (!WaitNamedPipe(pipe_name, kPipeBusyWaitTimeoutMs)) {
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
||||
#define CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
#include "common/scoped_ptr.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
struct CustomClientInfo;
|
||||
|
||||
// Abstraction of client-side implementation of out of process
|
||||
// crash generation.
|
||||
//
|
||||
// The process that desires to have out-of-process crash dump
|
||||
// generation service can use this class in the following way:
|
||||
//
|
||||
// * Create an instance.
|
||||
// * Call Register method so that the client tries to register
|
||||
// with the server process and check the return value. If
|
||||
// registration is not successful, out-of-process crash dump
|
||||
// generation will not be available
|
||||
// * Request dump generation by calling either of the two
|
||||
// overloaded RequestDump methods - one in case of exceptions
|
||||
// and the other in case of assertion failures
|
||||
//
|
||||
// Note that it is the responsibility of the client code of
|
||||
// this class to set the unhandled exception filter with the
|
||||
// system by calling the SetUnhandledExceptionFilter function
|
||||
// and the client code should explicitly request dump generation.
|
||||
class CrashGenerationClient {
|
||||
public:
|
||||
CrashGenerationClient(const wchar_t* pipe_name,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
const CustomClientInfo* custom_info);
|
||||
|
||||
CrashGenerationClient(HANDLE pipe_handle,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
const CustomClientInfo* custom_info);
|
||||
|
||||
~CrashGenerationClient();
|
||||
|
||||
// Registers the client process with the crash server.
|
||||
//
|
||||
// Returns true if the registration is successful; false otherwise.
|
||||
bool Register();
|
||||
|
||||
// Requests the crash server to upload a previous dump with the
|
||||
// given crash id.
|
||||
bool RequestUpload(DWORD crash_id);
|
||||
|
||||
bool RequestDump(EXCEPTION_POINTERS* ex_info,
|
||||
MDRawAssertionInfo* assert_info);
|
||||
|
||||
// Requests the crash server to generate a dump with the given
|
||||
// exception information.
|
||||
//
|
||||
// Returns true if the dump was successful; false otherwise. Note that
|
||||
// if the registration step was not performed or it was not successful,
|
||||
// false will be returned.
|
||||
bool RequestDump(EXCEPTION_POINTERS* ex_info);
|
||||
|
||||
// Requests the crash server to generate a dump with the given
|
||||
// assertion information.
|
||||
//
|
||||
// Returns true if the dump was successful; false otherwise. Note that
|
||||
// if the registration step was not performed or it was not successful,
|
||||
// false will be returned.
|
||||
bool RequestDump(MDRawAssertionInfo* assert_info);
|
||||
|
||||
// If the crash generation client is running in a sandbox that prevents it
|
||||
// from opening the named pipe directly, the server process may open the
|
||||
// handle and duplicate it into the client process with this helper method.
|
||||
// Returns INVALID_HANDLE_VALUE on failure. The process must have been opened
|
||||
// with the PROCESS_DUP_HANDLE access right.
|
||||
static HANDLE DuplicatePipeToClientProcess(const wchar_t* pipe_name,
|
||||
HANDLE hProcess);
|
||||
|
||||
private:
|
||||
// Connects to the appropriate pipe and sets the pipe handle state.
|
||||
//
|
||||
// Returns the pipe handle if everything goes well; otherwise Returns NULL.
|
||||
HANDLE ConnectToServer();
|
||||
|
||||
// Performs a handshake with the server over the given pipe which should be
|
||||
// already connected to the server.
|
||||
//
|
||||
// Returns true if handshake with the server was successful; false otherwise.
|
||||
bool RegisterClient(HANDLE pipe);
|
||||
|
||||
// Validates the given server response.
|
||||
bool ValidateResponse(const ProtocolMessage& msg) const;
|
||||
|
||||
// Returns true if the registration step succeeded; false otherwise.
|
||||
bool IsRegistered() const;
|
||||
|
||||
// Connects to the given named pipe with given parameters.
|
||||
//
|
||||
// Returns true if the connection is successful; false otherwise.
|
||||
HANDLE ConnectToPipe(const wchar_t* pipe_name,
|
||||
DWORD pipe_access,
|
||||
DWORD flags_attrs);
|
||||
|
||||
// Signals the crash event and wait for the server to generate crash.
|
||||
bool SignalCrashEventAndWait();
|
||||
|
||||
// Pipe name to use to talk to server.
|
||||
std::wstring pipe_name_;
|
||||
|
||||
// Pipe handle duplicated from server process. Only valid before
|
||||
// Register is called.
|
||||
HANDLE pipe_handle_;
|
||||
|
||||
// Custom client information
|
||||
CustomClientInfo custom_info_;
|
||||
|
||||
// Type of dump to generate.
|
||||
MINIDUMP_TYPE dump_type_;
|
||||
|
||||
// Event to signal in case of a crash.
|
||||
HANDLE crash_event_;
|
||||
|
||||
// Handle to wait on after signaling a crash for the server
|
||||
// to finish generating crash dump.
|
||||
HANDLE crash_generated_;
|
||||
|
||||
// Handle to a mutex that will become signaled with WAIT_ABANDONED
|
||||
// if the server process goes down.
|
||||
HANDLE server_alive_;
|
||||
|
||||
// Server process id.
|
||||
DWORD server_process_id_;
|
||||
|
||||
// Id of the thread that caused the crash.
|
||||
DWORD thread_id_;
|
||||
|
||||
// Exception pointers for an exception crash.
|
||||
EXCEPTION_POINTERS* exception_pointers_;
|
||||
|
||||
// Assertion info for an invalid parameter or pure call crash.
|
||||
MDRawAssertionInfo assert_info_;
|
||||
|
||||
// Disable copy ctor and operator=.
|
||||
CrashGenerationClient(const CrashGenerationClient& crash_client);
|
||||
CrashGenerationClient& operator=(const CrashGenerationClient& crash_client);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
||||
+946
@@ -0,0 +1,946 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "client/windows/crash_generation/crash_generation_server.h"
|
||||
#include <windows.h>
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
#include "client/windows/common/auto_critical_section.h"
|
||||
#include "common/scoped_ptr.h"
|
||||
|
||||
#include "client/windows/crash_generation/client_info.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Output buffer size.
|
||||
static const size_t kOutBufferSize = 64;
|
||||
|
||||
// Input buffer size.
|
||||
static const size_t kInBufferSize = 64;
|
||||
|
||||
// Access flags for the client on the dump request event.
|
||||
static const DWORD kDumpRequestEventAccess = EVENT_MODIFY_STATE;
|
||||
|
||||
// Access flags for the client on the dump generated event.
|
||||
static const DWORD kDumpGeneratedEventAccess = EVENT_MODIFY_STATE |
|
||||
SYNCHRONIZE;
|
||||
|
||||
// Access flags for the client on the mutex.
|
||||
static const DWORD kMutexAccess = SYNCHRONIZE;
|
||||
|
||||
// Attribute flags for the pipe.
|
||||
static const DWORD kPipeAttr = FILE_FLAG_FIRST_PIPE_INSTANCE |
|
||||
PIPE_ACCESS_DUPLEX |
|
||||
FILE_FLAG_OVERLAPPED;
|
||||
|
||||
// Mode for the pipe.
|
||||
static const DWORD kPipeMode = PIPE_TYPE_MESSAGE |
|
||||
PIPE_READMODE_MESSAGE |
|
||||
PIPE_WAIT;
|
||||
|
||||
// For pipe I/O, execute the callback in the wait thread itself,
|
||||
// since the callback does very little work. The callback executes
|
||||
// the code for one of the states of the server state machine and
|
||||
// the code for all of the states perform async I/O and hence
|
||||
// finish very quickly.
|
||||
static const ULONG kPipeIOThreadFlags = WT_EXECUTEINWAITTHREAD;
|
||||
|
||||
// Dump request threads will, most likely, generate dumps. That may
|
||||
// take some time to finish, so specify WT_EXECUTELONGFUNCTION flag.
|
||||
static const ULONG kDumpRequestThreadFlags = WT_EXECUTEINWAITTHREAD |
|
||||
WT_EXECUTELONGFUNCTION;
|
||||
|
||||
static bool IsClientRequestValid(const ProtocolMessage& msg) {
|
||||
return msg.tag == MESSAGE_TAG_UPLOAD_REQUEST ||
|
||||
(msg.tag == MESSAGE_TAG_REGISTRATION_REQUEST &&
|
||||
msg.id != 0 &&
|
||||
msg.thread_id != NULL &&
|
||||
msg.exception_pointers != NULL &&
|
||||
msg.assert_info != NULL);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static bool CheckForIOIncomplete(bool success) {
|
||||
// We should never get an I/O incomplete since we should not execute this
|
||||
// unless the operation has finished and the overlapped event is signaled. If
|
||||
// we do get INCOMPLETE, we have a bug in our code.
|
||||
return success ? false : (GetLastError() == ERROR_IO_INCOMPLETE);
|
||||
}
|
||||
#endif
|
||||
|
||||
CrashGenerationServer::CrashGenerationServer(
|
||||
const std::wstring& pipe_name,
|
||||
SECURITY_ATTRIBUTES* pipe_sec_attrs,
|
||||
OnClientConnectedCallback connect_callback,
|
||||
void* connect_context,
|
||||
OnClientDumpRequestCallback dump_callback,
|
||||
void* dump_context,
|
||||
OnClientExitedCallback exit_callback,
|
||||
void* exit_context,
|
||||
OnClientUploadRequestCallback upload_request_callback,
|
||||
void* upload_context,
|
||||
bool generate_dumps,
|
||||
const std::wstring* dump_path)
|
||||
: pipe_name_(pipe_name),
|
||||
pipe_sec_attrs_(pipe_sec_attrs),
|
||||
pipe_(NULL),
|
||||
pipe_wait_handle_(NULL),
|
||||
server_alive_handle_(NULL),
|
||||
connect_callback_(connect_callback),
|
||||
connect_context_(connect_context),
|
||||
dump_callback_(dump_callback),
|
||||
dump_context_(dump_context),
|
||||
exit_callback_(exit_callback),
|
||||
exit_context_(exit_context),
|
||||
upload_request_callback_(upload_request_callback),
|
||||
upload_context_(upload_context),
|
||||
generate_dumps_(generate_dumps),
|
||||
pre_fetch_custom_info_(true),
|
||||
dump_path_(dump_path ? *dump_path : L""),
|
||||
server_state_(IPC_SERVER_STATE_UNINITIALIZED),
|
||||
shutting_down_(false),
|
||||
overlapped_(),
|
||||
client_info_(NULL) {
|
||||
InitializeCriticalSection(&sync_);
|
||||
}
|
||||
|
||||
// This should never be called from the OnPipeConnected callback.
|
||||
// Otherwise the UnregisterWaitEx call below will cause a deadlock.
|
||||
CrashGenerationServer::~CrashGenerationServer() {
|
||||
// New scope to release the lock automatically.
|
||||
{
|
||||
// Make sure no clients are added or removed beyond this point.
|
||||
// Before adding or removing any clients, the critical section
|
||||
// must be entered and the shutting_down_ flag checked. The
|
||||
// critical section is then exited only after the clients_ list
|
||||
// modifications are done and the list is in a consistent state.
|
||||
AutoCriticalSection lock(&sync_);
|
||||
|
||||
// Indicate to existing threads that server is shutting down.
|
||||
shutting_down_ = true;
|
||||
}
|
||||
// No one will modify the clients_ list beyond this point -
|
||||
// not even from another thread.
|
||||
|
||||
// Even if there are no current worker threads running, it is possible that
|
||||
// an I/O request is pending on the pipe right now but not yet done.
|
||||
// In fact, it's very likely this is the case unless we are in an ERROR
|
||||
// state. If we don't wait for the pending I/O to be done, then when the I/O
|
||||
// completes, it may write to invalid memory. AppVerifier will flag this
|
||||
// problem too. So we disconnect from the pipe and then wait for the server
|
||||
// to get into error state so that the pending I/O will fail and get
|
||||
// cleared.
|
||||
DisconnectNamedPipe(pipe_);
|
||||
int num_tries = 100;
|
||||
while (num_tries-- && server_state_ != IPC_SERVER_STATE_ERROR) {
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
// Unregister wait on the pipe.
|
||||
if (pipe_wait_handle_) {
|
||||
// Wait for already executing callbacks to finish.
|
||||
UnregisterWaitEx(pipe_wait_handle_, INVALID_HANDLE_VALUE);
|
||||
}
|
||||
|
||||
// Close the pipe to avoid further client connections.
|
||||
if (pipe_) {
|
||||
CloseHandle(pipe_);
|
||||
}
|
||||
|
||||
// Request all ClientInfo objects to unregister all waits.
|
||||
// No need to enter the critical section because no one is allowed to modify
|
||||
// the clients_ list once the shutting_down_ flag is set.
|
||||
std::list<ClientInfo*>::iterator iter;
|
||||
for (iter = clients_.begin(); iter != clients_.end(); ++iter) {
|
||||
ClientInfo* client_info = *iter;
|
||||
// Unregister waits. Wait for already executing callbacks to finish.
|
||||
// Unregister the client process exit wait first and only then unregister
|
||||
// the dump request wait. The reason is that the OnClientExit callback
|
||||
// also unregisters the dump request wait and such a race (doing the same
|
||||
// unregistration from two threads) is undesirable.
|
||||
client_info->UnregisterProcessExitWait(true);
|
||||
client_info->UnregisterDumpRequestWaitAndBlockUntilNoPending();
|
||||
|
||||
// Destroying the ClientInfo here is safe because all wait operations for
|
||||
// this ClientInfo were unregistered and no pending or running callbacks
|
||||
// for this ClientInfo can possible exist (block_until_no_pending option
|
||||
// was used).
|
||||
delete client_info;
|
||||
}
|
||||
|
||||
if (server_alive_handle_) {
|
||||
// Release the mutex before closing the handle so that clients requesting
|
||||
// dumps wait for a long time for the server to generate a dump.
|
||||
ReleaseMutex(server_alive_handle_);
|
||||
CloseHandle(server_alive_handle_);
|
||||
}
|
||||
|
||||
if (overlapped_.hEvent) {
|
||||
CloseHandle(overlapped_.hEvent);
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&sync_);
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::Start() {
|
||||
if (server_state_ != IPC_SERVER_STATE_UNINITIALIZED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
server_state_ = IPC_SERVER_STATE_INITIAL;
|
||||
|
||||
server_alive_handle_ = CreateMutex(NULL, TRUE, NULL);
|
||||
if (!server_alive_handle_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Event to signal the client connection and pipe reads and writes.
|
||||
overlapped_.hEvent = CreateEvent(NULL, // Security descriptor.
|
||||
TRUE, // Manual reset.
|
||||
FALSE, // Initially nonsignaled.
|
||||
NULL); // Name.
|
||||
if (!overlapped_.hEvent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register a callback with the thread pool for the client connection.
|
||||
if (!RegisterWaitForSingleObject(&pipe_wait_handle_,
|
||||
overlapped_.hEvent,
|
||||
OnPipeConnected,
|
||||
this,
|
||||
INFINITE,
|
||||
kPipeIOThreadFlags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pipe_ = CreateNamedPipe(pipe_name_.c_str(),
|
||||
kPipeAttr,
|
||||
kPipeMode,
|
||||
1,
|
||||
kOutBufferSize,
|
||||
kInBufferSize,
|
||||
0,
|
||||
pipe_sec_attrs_);
|
||||
if (pipe_ == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Kick-start the state machine. This will initiate an asynchronous wait
|
||||
// for client connections.
|
||||
if (!SetEvent(overlapped_.hEvent)) {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we are in error state, it's because we failed to start listening.
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the server thread serving clients ever gets into the
|
||||
// ERROR state, reset the event, close the pipe and remain
|
||||
// in the error state forever. Error state means something
|
||||
// that we didn't account for has happened, and it's dangerous
|
||||
// to do anything unknowingly.
|
||||
void CrashGenerationServer::HandleErrorState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_ERROR);
|
||||
|
||||
// If the server is shutting down anyway, don't clean up
|
||||
// here since shut down process will clean up.
|
||||
if (shutting_down_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pipe_wait_handle_) {
|
||||
UnregisterWait(pipe_wait_handle_);
|
||||
pipe_wait_handle_ = NULL;
|
||||
}
|
||||
|
||||
if (pipe_) {
|
||||
CloseHandle(pipe_);
|
||||
pipe_ = NULL;
|
||||
}
|
||||
|
||||
if (overlapped_.hEvent) {
|
||||
CloseHandle(overlapped_.hEvent);
|
||||
overlapped_.hEvent = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// When the server thread serving clients is in the INITIAL state,
|
||||
// try to connect to the pipe asynchronously. If the connection
|
||||
// finishes synchronously, directly go into the CONNECTED state;
|
||||
// otherwise go into the CONNECTING state. For any problems, go
|
||||
// into the ERROR state.
|
||||
void CrashGenerationServer::HandleInitialState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_INITIAL);
|
||||
|
||||
if (!ResetEvent(overlapped_.hEvent)) {
|
||||
EnterErrorState();
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = ConnectNamedPipe(pipe_, &overlapped_) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
// From MSDN, it is not clear that when ConnectNamedPipe is used
|
||||
// in an overlapped mode, will it ever return non-zero value, and
|
||||
// if so, in what cases.
|
||||
assert(!success);
|
||||
|
||||
switch (error_code) {
|
||||
case ERROR_IO_PENDING:
|
||||
EnterStateWhenSignaled(IPC_SERVER_STATE_CONNECTING);
|
||||
break;
|
||||
|
||||
case ERROR_PIPE_CONNECTED:
|
||||
EnterStateImmediately(IPC_SERVER_STATE_CONNECTED);
|
||||
break;
|
||||
|
||||
default:
|
||||
EnterErrorState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// When the server thread serving the clients is in the CONNECTING state,
|
||||
// try to get the result of the asynchronous connection request using
|
||||
// the OVERLAPPED object. If the result indicates the connection is done,
|
||||
// go into the CONNECTED state. If the result indicates I/O is still
|
||||
// INCOMPLETE, remain in the CONNECTING state. For any problems,
|
||||
// go into the DISCONNECTING state.
|
||||
void CrashGenerationServer::HandleConnectingState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_CONNECTING);
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
bool success = GetOverlappedResult(pipe_,
|
||||
&overlapped_,
|
||||
&bytes_count,
|
||||
FALSE) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
if (success) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_CONNECTED);
|
||||
} else if (error_code != ERROR_IO_INCOMPLETE) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
} else {
|
||||
// remain in CONNECTING state
|
||||
}
|
||||
}
|
||||
|
||||
// When the server thread serving the clients is in the CONNECTED state,
|
||||
// try to issue an asynchronous read from the pipe. If read completes
|
||||
// synchronously or if I/O is pending then go into the READING state.
|
||||
// For any problems, go into the DISCONNECTING state.
|
||||
void CrashGenerationServer::HandleConnectedState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_CONNECTED);
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
memset(&msg_, 0, sizeof(msg_));
|
||||
bool success = ReadFile(pipe_,
|
||||
&msg_,
|
||||
sizeof(msg_),
|
||||
&bytes_count,
|
||||
&overlapped_) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
// Note that the asynchronous read issued above can finish before the
|
||||
// code below executes. But, it is okay to change state after issuing
|
||||
// the asynchronous read. This is because even if the asynchronous read
|
||||
// is done, the callback for it would not be executed until the current
|
||||
// thread finishes its execution.
|
||||
if (success || error_code == ERROR_IO_PENDING) {
|
||||
EnterStateWhenSignaled(IPC_SERVER_STATE_READING);
|
||||
} else {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
// When the server thread serving the clients is in the READING state,
|
||||
// try to get the result of the async read. If async read is done,
|
||||
// go into the READ_DONE state. For any problems, go into the
|
||||
// DISCONNECTING state.
|
||||
void CrashGenerationServer::HandleReadingState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_READING);
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
bool success = GetOverlappedResult(pipe_,
|
||||
&overlapped_,
|
||||
&bytes_count,
|
||||
FALSE) != FALSE;
|
||||
if (success && bytes_count == sizeof(ProtocolMessage)) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_READ_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!CheckForIOIncomplete(success));
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
}
|
||||
|
||||
// When the server thread serving the client is in the READ_DONE state,
|
||||
// validate the client's request message, register the client by
|
||||
// creating appropriate objects and prepare the response. Then try to
|
||||
// write the response to the pipe asynchronously. If that succeeds,
|
||||
// go into the WRITING state. For any problems, go into the DISCONNECTING
|
||||
// state.
|
||||
void CrashGenerationServer::HandleReadDoneState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_READ_DONE);
|
||||
|
||||
if (!IsClientRequestValid(msg_)) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg_.tag == MESSAGE_TAG_UPLOAD_REQUEST) {
|
||||
if (upload_request_callback_)
|
||||
upload_request_callback_(upload_context_, msg_.id);
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
return;
|
||||
}
|
||||
|
||||
scoped_ptr<ClientInfo> client_info(
|
||||
new ClientInfo(this,
|
||||
msg_.id,
|
||||
msg_.dump_type,
|
||||
msg_.thread_id,
|
||||
msg_.exception_pointers,
|
||||
msg_.assert_info,
|
||||
msg_.custom_client_info));
|
||||
|
||||
if (!client_info->Initialize()) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
return;
|
||||
}
|
||||
|
||||
// Issues an asynchronous WriteFile call if successful.
|
||||
// Iff successful, assigns ownership of the client_info pointer to the server
|
||||
// instance, in which case we must be sure not to free it in this function.
|
||||
if (!RespondToClient(client_info.get())) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is only valid as long as it can be found in the clients_ list
|
||||
client_info_ = client_info.release();
|
||||
|
||||
// Note that the asynchronous write issued by RespondToClient function
|
||||
// can finish before the code below executes. But it is okay to change
|
||||
// state after issuing the asynchronous write. This is because even if
|
||||
// the asynchronous write is done, the callback for it would not be
|
||||
// executed until the current thread finishes its execution.
|
||||
EnterStateWhenSignaled(IPC_SERVER_STATE_WRITING);
|
||||
}
|
||||
|
||||
// When the server thread serving the clients is in the WRITING state,
|
||||
// try to get the result of the async write. If the async write is done,
|
||||
// go into the WRITE_DONE state. For any problems, go into the
|
||||
// DISONNECTING state.
|
||||
void CrashGenerationServer::HandleWritingState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_WRITING);
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
bool success = GetOverlappedResult(pipe_,
|
||||
&overlapped_,
|
||||
&bytes_count,
|
||||
FALSE) != FALSE;
|
||||
if (success) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_WRITE_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!CheckForIOIncomplete(success));
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
}
|
||||
|
||||
// When the server thread serving the clients is in the WRITE_DONE state,
|
||||
// try to issue an async read on the pipe. If the read completes synchronously
|
||||
// or if I/O is still pending then go into the READING_ACK state. For any
|
||||
// issues, go into the DISCONNECTING state.
|
||||
void CrashGenerationServer::HandleWriteDoneState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_WRITE_DONE);
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
bool success = ReadFile(pipe_,
|
||||
&msg_,
|
||||
sizeof(msg_),
|
||||
&bytes_count,
|
||||
&overlapped_) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
if (success) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_READING_ACK);
|
||||
} else if (error_code == ERROR_IO_PENDING) {
|
||||
EnterStateWhenSignaled(IPC_SERVER_STATE_READING_ACK);
|
||||
} else {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
// When the server thread serving the clients is in the READING_ACK state,
|
||||
// try to get result of async read. Go into the DISCONNECTING state.
|
||||
void CrashGenerationServer::HandleReadingAckState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_READING_ACK);
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
bool success = GetOverlappedResult(pipe_,
|
||||
&overlapped_,
|
||||
&bytes_count,
|
||||
FALSE) != FALSE;
|
||||
if (success) {
|
||||
// The connection handshake with the client is now complete; perform
|
||||
// the callback.
|
||||
if (connect_callback_) {
|
||||
// Note that there is only a single copy of the ClientInfo of the
|
||||
// currently connected client. However it is being referenced from
|
||||
// two different places:
|
||||
// - the client_info_ member
|
||||
// - the clients_ list
|
||||
// The lifetime of this ClientInfo depends on the lifetime of the
|
||||
// client process - basically it can go away at any time.
|
||||
// However, as long as it is referenced by the clients_ list it
|
||||
// is guaranteed to be valid. Enter the critical section and check
|
||||
// to see whether the client_info_ can be found in the list.
|
||||
// If found, execute the callback and only then leave the critical
|
||||
// section.
|
||||
AutoCriticalSection lock(&sync_);
|
||||
|
||||
bool client_is_still_alive = false;
|
||||
std::list<ClientInfo*>::iterator iter;
|
||||
for (iter = clients_.begin(); iter != clients_.end(); ++iter) {
|
||||
if (client_info_ == *iter) {
|
||||
client_is_still_alive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (client_is_still_alive) {
|
||||
connect_callback_(connect_context_, client_info_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(!CheckForIOIncomplete(success));
|
||||
}
|
||||
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
}
|
||||
|
||||
// When the server thread serving the client is in the DISCONNECTING state,
|
||||
// disconnect from the pipe and reset the event. If anything fails, go into
|
||||
// the ERROR state. If it goes well, go into the INITIAL state and set the
|
||||
// event to start all over again.
|
||||
void CrashGenerationServer::HandleDisconnectingState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_DISCONNECTING);
|
||||
|
||||
// Done serving the client.
|
||||
client_info_ = NULL;
|
||||
|
||||
overlapped_.Internal = NULL;
|
||||
overlapped_.InternalHigh = NULL;
|
||||
overlapped_.Offset = 0;
|
||||
overlapped_.OffsetHigh = 0;
|
||||
overlapped_.Pointer = NULL;
|
||||
|
||||
if (!ResetEvent(overlapped_.hEvent)) {
|
||||
EnterErrorState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DisconnectNamedPipe(pipe_)) {
|
||||
EnterErrorState();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the server is shutting down do not connect to the
|
||||
// next client.
|
||||
if (shutting_down_) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnterStateImmediately(IPC_SERVER_STATE_INITIAL);
|
||||
}
|
||||
|
||||
void CrashGenerationServer::EnterErrorState() {
|
||||
SetEvent(overlapped_.hEvent);
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
}
|
||||
|
||||
void CrashGenerationServer::EnterStateWhenSignaled(IPCServerState state) {
|
||||
server_state_ = state;
|
||||
}
|
||||
|
||||
void CrashGenerationServer::EnterStateImmediately(IPCServerState state) {
|
||||
server_state_ = state;
|
||||
|
||||
if (!SetEvent(overlapped_.hEvent)) {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::PrepareReply(const ClientInfo& client_info,
|
||||
ProtocolMessage* reply) const {
|
||||
reply->tag = MESSAGE_TAG_REGISTRATION_RESPONSE;
|
||||
reply->id = GetCurrentProcessId();
|
||||
|
||||
if (CreateClientHandles(client_info, reply)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Closing of remote handles (belonging to a different process) can
|
||||
// only be done through DuplicateHandle.
|
||||
if (reply->dump_request_handle) {
|
||||
DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle
|
||||
reply->dump_request_handle, // hSourceHandle
|
||||
NULL, // hTargetProcessHandle
|
||||
0, // lpTargetHandle
|
||||
0, // dwDesiredAccess
|
||||
FALSE, // bInheritHandle
|
||||
DUPLICATE_CLOSE_SOURCE); // dwOptions
|
||||
reply->dump_request_handle = NULL;
|
||||
}
|
||||
|
||||
if (reply->dump_generated_handle) {
|
||||
DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle
|
||||
reply->dump_generated_handle, // hSourceHandle
|
||||
NULL, // hTargetProcessHandle
|
||||
0, // lpTargetHandle
|
||||
0, // dwDesiredAccess
|
||||
FALSE, // bInheritHandle
|
||||
DUPLICATE_CLOSE_SOURCE); // dwOptions
|
||||
reply->dump_generated_handle = NULL;
|
||||
}
|
||||
|
||||
if (reply->server_alive_handle) {
|
||||
DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle
|
||||
reply->server_alive_handle, // hSourceHandle
|
||||
NULL, // hTargetProcessHandle
|
||||
0, // lpTargetHandle
|
||||
0, // dwDesiredAccess
|
||||
FALSE, // bInheritHandle
|
||||
DUPLICATE_CLOSE_SOURCE); // dwOptions
|
||||
reply->server_alive_handle = NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::CreateClientHandles(const ClientInfo& client_info,
|
||||
ProtocolMessage* reply) const {
|
||||
HANDLE current_process = GetCurrentProcess();
|
||||
if (!DuplicateHandle(current_process,
|
||||
client_info.dump_requested_handle(),
|
||||
client_info.process_handle(),
|
||||
&reply->dump_request_handle,
|
||||
kDumpRequestEventAccess,
|
||||
FALSE,
|
||||
0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DuplicateHandle(current_process,
|
||||
client_info.dump_generated_handle(),
|
||||
client_info.process_handle(),
|
||||
&reply->dump_generated_handle,
|
||||
kDumpGeneratedEventAccess,
|
||||
FALSE,
|
||||
0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DuplicateHandle(current_process,
|
||||
server_alive_handle_,
|
||||
client_info.process_handle(),
|
||||
&reply->server_alive_handle,
|
||||
kMutexAccess,
|
||||
FALSE,
|
||||
0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::RespondToClient(ClientInfo* client_info) {
|
||||
ProtocolMessage reply;
|
||||
if (!PrepareReply(*client_info, &reply)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
bool success = WriteFile(pipe_,
|
||||
&reply,
|
||||
sizeof(reply),
|
||||
&bytes_count,
|
||||
&overlapped_) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
if (!success && error_code != ERROR_IO_PENDING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Takes over ownership of client_info. We MUST return true if AddClient
|
||||
// succeeds.
|
||||
return AddClient(client_info);
|
||||
}
|
||||
|
||||
// The server thread servicing the clients runs this method. The method
|
||||
// implements the state machine described in ReadMe.txt along with the
|
||||
// helper methods HandleXXXState.
|
||||
void CrashGenerationServer::HandleConnectionRequest() {
|
||||
// If the server is shutting down, get into ERROR state, reset the event so
|
||||
// more workers don't run and return immediately.
|
||||
if (shutting_down_) {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
ResetEvent(overlapped_.hEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (server_state_) {
|
||||
case IPC_SERVER_STATE_ERROR:
|
||||
HandleErrorState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_INITIAL:
|
||||
HandleInitialState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_CONNECTING:
|
||||
HandleConnectingState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_CONNECTED:
|
||||
HandleConnectedState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_READING:
|
||||
HandleReadingState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_READ_DONE:
|
||||
HandleReadDoneState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_WRITING:
|
||||
HandleWritingState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_WRITE_DONE:
|
||||
HandleWriteDoneState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_READING_ACK:
|
||||
HandleReadingAckState();
|
||||
break;
|
||||
|
||||
case IPC_SERVER_STATE_DISCONNECTING:
|
||||
HandleDisconnectingState();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
// This indicates that we added one more state without
|
||||
// adding handling code.
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::AddClient(ClientInfo* client_info) {
|
||||
HANDLE request_wait_handle = NULL;
|
||||
if (!RegisterWaitForSingleObject(&request_wait_handle,
|
||||
client_info->dump_requested_handle(),
|
||||
OnDumpRequest,
|
||||
client_info,
|
||||
INFINITE,
|
||||
kDumpRequestThreadFlags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
client_info->set_dump_request_wait_handle(request_wait_handle);
|
||||
|
||||
// OnClientEnd will be called when the client process terminates.
|
||||
HANDLE process_wait_handle = NULL;
|
||||
if (!RegisterWaitForSingleObject(&process_wait_handle,
|
||||
client_info->process_handle(),
|
||||
OnClientEnd,
|
||||
client_info,
|
||||
INFINITE,
|
||||
WT_EXECUTEONLYONCE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
client_info->set_process_exit_wait_handle(process_wait_handle);
|
||||
|
||||
// New scope to hold the lock for the shortest time.
|
||||
{
|
||||
AutoCriticalSection lock(&sync_);
|
||||
if (shutting_down_) {
|
||||
// If server is shutting down, don't add new clients
|
||||
return false;
|
||||
}
|
||||
clients_.push_back(client_info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
void CALLBACK CrashGenerationServer::OnPipeConnected(void* context, BOOLEAN) {
|
||||
assert(context);
|
||||
|
||||
CrashGenerationServer* obj =
|
||||
reinterpret_cast<CrashGenerationServer*>(context);
|
||||
obj->HandleConnectionRequest();
|
||||
}
|
||||
|
||||
// static
|
||||
void CALLBACK CrashGenerationServer::OnDumpRequest(void* context, BOOLEAN) {
|
||||
assert(context);
|
||||
ClientInfo* client_info = reinterpret_cast<ClientInfo*>(context);
|
||||
|
||||
CrashGenerationServer* crash_server = client_info->crash_server();
|
||||
assert(crash_server);
|
||||
if (crash_server->pre_fetch_custom_info_) {
|
||||
client_info->PopulateCustomInfo();
|
||||
}
|
||||
crash_server->HandleDumpRequest(*client_info);
|
||||
|
||||
ResetEvent(client_info->dump_requested_handle());
|
||||
}
|
||||
|
||||
// static
|
||||
void CALLBACK CrashGenerationServer::OnClientEnd(void* context, BOOLEAN) {
|
||||
assert(context);
|
||||
ClientInfo* client_info = reinterpret_cast<ClientInfo*>(context);
|
||||
|
||||
CrashGenerationServer* crash_server = client_info->crash_server();
|
||||
assert(crash_server);
|
||||
|
||||
crash_server->HandleClientProcessExit(client_info);
|
||||
}
|
||||
|
||||
void CrashGenerationServer::HandleClientProcessExit(ClientInfo* client_info) {
|
||||
assert(client_info);
|
||||
|
||||
// Must unregister the dump request wait operation and wait for any
|
||||
// dump requests that might be pending to finish before proceeding
|
||||
// with the client_info cleanup.
|
||||
client_info->UnregisterDumpRequestWaitAndBlockUntilNoPending();
|
||||
|
||||
if (exit_callback_) {
|
||||
exit_callback_(exit_context_, client_info);
|
||||
}
|
||||
|
||||
// Start a new scope to release lock automatically.
|
||||
{
|
||||
AutoCriticalSection lock(&sync_);
|
||||
if (shutting_down_) {
|
||||
// The crash generation server is shutting down and as part of the
|
||||
// shutdown process it will delete all clients from the clients_ list.
|
||||
return;
|
||||
}
|
||||
clients_.remove(client_info);
|
||||
}
|
||||
|
||||
// Explicitly unregister the process exit wait using the non-blocking method.
|
||||
// Otherwise, the destructor will attempt to unregister it using the blocking
|
||||
// method which will lead to a deadlock because it is being called from the
|
||||
// callback of the same wait operation
|
||||
client_info->UnregisterProcessExitWait(false);
|
||||
|
||||
delete client_info;
|
||||
}
|
||||
|
||||
void CrashGenerationServer::HandleDumpRequest(const ClientInfo& client_info) {
|
||||
bool execute_callback = true;
|
||||
// Generate the dump only if it's explicitly requested by the
|
||||
// server application; otherwise the server might want to generate
|
||||
// dump in the callback.
|
||||
std::wstring dump_path;
|
||||
if (generate_dumps_) {
|
||||
if (!GenerateDump(client_info, &dump_path)) {
|
||||
// client proccess terminated or some other error
|
||||
execute_callback = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dump_callback_ && execute_callback) {
|
||||
std::wstring* ptr_dump_path = (dump_path == L"") ? NULL : &dump_path;
|
||||
dump_callback_(dump_context_, &client_info, ptr_dump_path);
|
||||
}
|
||||
|
||||
SetEvent(client_info.dump_generated_handle());
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::GenerateDump(const ClientInfo& client,
|
||||
std::wstring* dump_path) {
|
||||
assert(client.pid() != 0);
|
||||
assert(client.process_handle());
|
||||
|
||||
// We have to get the address of EXCEPTION_INFORMATION from
|
||||
// the client process address space.
|
||||
EXCEPTION_POINTERS* client_ex_info = NULL;
|
||||
if (!client.GetClientExceptionInfo(&client_ex_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD client_thread_id = 0;
|
||||
if (!client.GetClientThreadId(&client_thread_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MinidumpGenerator dump_generator(dump_path_,
|
||||
client.process_handle(),
|
||||
client.pid(),
|
||||
client_thread_id,
|
||||
GetCurrentThreadId(),
|
||||
client_ex_info,
|
||||
client.assert_info(),
|
||||
client.dump_type(),
|
||||
true);
|
||||
|
||||
if (!dump_generator.GenerateDumpFile(dump_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the client requests a full memory dump, we will write a normal mini
|
||||
// dump and a full memory dump. Both dump files use the same uuid as file
|
||||
// name prefix.
|
||||
if (client.dump_type() & MiniDumpWithFullMemory) {
|
||||
std::wstring full_dump_path;
|
||||
if (!dump_generator.GenerateFullDumpFile(&full_dump_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return dump_generator.WriteMinidump();
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__
|
||||
#define CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
#include "client/windows/crash_generation/minidump_generator.h"
|
||||
#include "common/scoped_ptr.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
class ClientInfo;
|
||||
|
||||
// Abstraction for server side implementation of out-of-process crash
|
||||
// generation protocol for Windows platform only. It generates Windows
|
||||
// minidump files for client processes that request dump generation. When
|
||||
// the server is requested to start listening for clients (by calling the
|
||||
// Start method), it creates a named pipe and waits for the clients to
|
||||
// register. In response, it hands them event handles that the client can
|
||||
// signal to request dump generation. When the clients request dump
|
||||
// generation in this way, the server generates Windows minidump files.
|
||||
class CrashGenerationServer {
|
||||
public:
|
||||
typedef void (*OnClientConnectedCallback)(void* context,
|
||||
const ClientInfo* client_info);
|
||||
|
||||
typedef void (*OnClientDumpRequestCallback)(void* context,
|
||||
const ClientInfo* client_info,
|
||||
const std::wstring* file_path);
|
||||
|
||||
typedef void (*OnClientExitedCallback)(void* context,
|
||||
const ClientInfo* client_info);
|
||||
|
||||
typedef void (*OnClientUploadRequestCallback)(void* context,
|
||||
const DWORD crash_id);
|
||||
|
||||
// Creates an instance with the given parameters.
|
||||
//
|
||||
// Parameter pipe_name: Name of the Windows named pipe
|
||||
// Parameter pipe_sec_attrs Security attributes to set on the pipe. Pass
|
||||
// NULL to use default security on the pipe. By default, the pipe created
|
||||
// allows Local System, Administrators and the Creator full control and
|
||||
// the Everyone group read access on the pipe.
|
||||
// Parameter connect_callback: Callback for a new client connection.
|
||||
// Parameter connect_context: Context for client connection callback.
|
||||
// Parameter crash_callback: Callback for a client crash dump request.
|
||||
// Parameter crash_context: Context for client crash dump request callback.
|
||||
// Parameter exit_callback: Callback for client process exit.
|
||||
// Parameter exit_context: Context for client exit callback.
|
||||
// Parameter generate_dumps: Whether to automatically generate dumps.
|
||||
// Client code of this class might want to generate dumps explicitly in the
|
||||
// crash dump request callback. In that case, false can be passed for this
|
||||
// parameter.
|
||||
// Parameter dump_path: Path for generating dumps; required only if true is
|
||||
// passed for generateDumps parameter; NULL can be passed otherwise.
|
||||
CrashGenerationServer(const std::wstring& pipe_name,
|
||||
SECURITY_ATTRIBUTES* pipe_sec_attrs,
|
||||
OnClientConnectedCallback connect_callback,
|
||||
void* connect_context,
|
||||
OnClientDumpRequestCallback dump_callback,
|
||||
void* dump_context,
|
||||
OnClientExitedCallback exit_callback,
|
||||
void* exit_context,
|
||||
OnClientUploadRequestCallback upload_request_callback,
|
||||
void* upload_context,
|
||||
bool generate_dumps,
|
||||
const std::wstring* dump_path);
|
||||
|
||||
~CrashGenerationServer();
|
||||
|
||||
// Performs initialization steps needed to start listening to clients. Upon
|
||||
// successful return clients may connect to this server's pipe.
|
||||
//
|
||||
// Returns true if initialization is successful; false otherwise.
|
||||
bool Start();
|
||||
|
||||
void pre_fetch_custom_info(bool do_pre_fetch) {
|
||||
pre_fetch_custom_info_ = do_pre_fetch;
|
||||
}
|
||||
|
||||
private:
|
||||
// Various states the client can be in during the handshake with
|
||||
// the server.
|
||||
enum IPCServerState {
|
||||
// Server starts in this state.
|
||||
IPC_SERVER_STATE_UNINITIALIZED,
|
||||
|
||||
// Server is in error state and it cannot serve any clients.
|
||||
IPC_SERVER_STATE_ERROR,
|
||||
|
||||
// Server starts in this state.
|
||||
IPC_SERVER_STATE_INITIAL,
|
||||
|
||||
// Server has issued an async connect to the pipe and it is waiting
|
||||
// for the connection to be established.
|
||||
IPC_SERVER_STATE_CONNECTING,
|
||||
|
||||
// Server is connected successfully.
|
||||
IPC_SERVER_STATE_CONNECTED,
|
||||
|
||||
// Server has issued an async read from the pipe and it is waiting for
|
||||
// the read to finish.
|
||||
IPC_SERVER_STATE_READING,
|
||||
|
||||
// Server is done reading from the pipe.
|
||||
IPC_SERVER_STATE_READ_DONE,
|
||||
|
||||
// Server has issued an async write to the pipe and it is waiting for
|
||||
// the write to finish.
|
||||
IPC_SERVER_STATE_WRITING,
|
||||
|
||||
// Server is done writing to the pipe.
|
||||
IPC_SERVER_STATE_WRITE_DONE,
|
||||
|
||||
// Server has issued an async read from the pipe for an ack and it
|
||||
// is waiting for the read to finish.
|
||||
IPC_SERVER_STATE_READING_ACK,
|
||||
|
||||
// Server is done writing to the pipe and it is now ready to disconnect
|
||||
// and reconnect.
|
||||
IPC_SERVER_STATE_DISCONNECTING
|
||||
};
|
||||
|
||||
//
|
||||
// Helper methods to handle various server IPC states.
|
||||
//
|
||||
void HandleErrorState();
|
||||
void HandleInitialState();
|
||||
void HandleConnectingState();
|
||||
void HandleConnectedState();
|
||||
void HandleReadingState();
|
||||
void HandleReadDoneState();
|
||||
void HandleWritingState();
|
||||
void HandleWriteDoneState();
|
||||
void HandleReadingAckState();
|
||||
void HandleDisconnectingState();
|
||||
|
||||
// Prepares reply for a client from the given parameters.
|
||||
bool PrepareReply(const ClientInfo& client_info,
|
||||
ProtocolMessage* reply) const;
|
||||
|
||||
// Duplicates various handles in the ClientInfo object for the client
|
||||
// process and stores them in the given ProtocolMessage instance. If
|
||||
// creating any handle fails, ProtocolMessage will contain the handles
|
||||
// already created successfully, which should be closed by the caller.
|
||||
bool CreateClientHandles(const ClientInfo& client_info,
|
||||
ProtocolMessage* reply) const;
|
||||
|
||||
// Response to the given client. Return true if all steps of
|
||||
// responding to the client succeed, false otherwise.
|
||||
bool RespondToClient(ClientInfo* client_info);
|
||||
|
||||
// Handles a connection request from the client.
|
||||
void HandleConnectionRequest();
|
||||
|
||||
// Handles a dump request from the client.
|
||||
void HandleDumpRequest(const ClientInfo& client_info);
|
||||
|
||||
// Callback for pipe connected event.
|
||||
static void CALLBACK OnPipeConnected(void* context, BOOLEAN timer_or_wait);
|
||||
|
||||
// Callback for a dump request.
|
||||
static void CALLBACK OnDumpRequest(void* context, BOOLEAN timer_or_wait);
|
||||
|
||||
// Callback for client process exit event.
|
||||
static void CALLBACK OnClientEnd(void* context, BOOLEAN timer_or_wait);
|
||||
|
||||
// Handles client process exit.
|
||||
void HandleClientProcessExit(ClientInfo* client_info);
|
||||
|
||||
// Adds the given client to the list of registered clients.
|
||||
bool AddClient(ClientInfo* client_info);
|
||||
|
||||
// Generates dump for the given client.
|
||||
bool GenerateDump(const ClientInfo& client, std::wstring* dump_path);
|
||||
|
||||
// Puts the server in a permanent error state and sets a signal such that
|
||||
// the state will be immediately entered after the current state transition
|
||||
// is complete.
|
||||
void EnterErrorState();
|
||||
|
||||
// Puts the server in the specified state and sets a signal such that the
|
||||
// state is immediately entered after the current state transition is
|
||||
// complete.
|
||||
void EnterStateImmediately(IPCServerState state);
|
||||
|
||||
// Puts the server in the specified state. No signal will be set, so the state
|
||||
// transition will only occur when signaled manually or by completion of an
|
||||
// asynchronous IO operation.
|
||||
void EnterStateWhenSignaled(IPCServerState state);
|
||||
|
||||
// Sync object for thread-safe access to the shared list of clients.
|
||||
CRITICAL_SECTION sync_;
|
||||
|
||||
// List of clients.
|
||||
std::list<ClientInfo*> clients_;
|
||||
|
||||
// Pipe name.
|
||||
std::wstring pipe_name_;
|
||||
|
||||
// Pipe security attributes
|
||||
SECURITY_ATTRIBUTES* pipe_sec_attrs_;
|
||||
|
||||
// Handle to the pipe used for handshake with clients.
|
||||
HANDLE pipe_;
|
||||
|
||||
// Pipe wait handle.
|
||||
HANDLE pipe_wait_handle_;
|
||||
|
||||
// Handle to server-alive mutex.
|
||||
HANDLE server_alive_handle_;
|
||||
|
||||
// Callback for a successful client connection.
|
||||
OnClientConnectedCallback connect_callback_;
|
||||
|
||||
// Context for client connected callback.
|
||||
void* connect_context_;
|
||||
|
||||
// Callback for a client dump request.
|
||||
OnClientDumpRequestCallback dump_callback_;
|
||||
|
||||
// Context for client dump request callback.
|
||||
void* dump_context_;
|
||||
|
||||
// Callback for client process exit.
|
||||
OnClientExitedCallback exit_callback_;
|
||||
|
||||
// Context for client process exit callback.
|
||||
void* exit_context_;
|
||||
|
||||
// Callback for upload request.
|
||||
OnClientUploadRequestCallback upload_request_callback_;
|
||||
|
||||
// Context for upload request callback.
|
||||
void* upload_context_;
|
||||
|
||||
// Whether to generate dumps.
|
||||
bool generate_dumps_;
|
||||
|
||||
// Wether to populate custom information up-front.
|
||||
bool pre_fetch_custom_info_;
|
||||
|
||||
// The dump path for the server.
|
||||
const std::wstring dump_path_;
|
||||
|
||||
// State of the server in performing the IPC with the client.
|
||||
// Note that since we restrict the pipe to one instance, we
|
||||
// only need to keep one state of the server. Otherwise, server
|
||||
// would have one state per client it is talking to.
|
||||
IPCServerState server_state_;
|
||||
|
||||
// Whether the server is shutting down.
|
||||
bool shutting_down_;
|
||||
|
||||
// Overlapped instance for async I/O on the pipe.
|
||||
OVERLAPPED overlapped_;
|
||||
|
||||
// Message object used in IPC with the client.
|
||||
ProtocolMessage msg_;
|
||||
|
||||
// Client Info for the client that's connecting to the server.
|
||||
ClientInfo* client_info_;
|
||||
|
||||
// Disable copy ctor and operator=.
|
||||
CrashGenerationServer(const CrashGenerationServer& crash_server);
|
||||
CrashGenerationServer& operator=(const CrashGenerationServer& crash_server);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__
|
||||
+586
@@ -0,0 +1,586 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "client/windows/crash_generation/minidump_generator.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <avrfsdk.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "client/windows/common/auto_critical_section.h"
|
||||
#include "common/scoped_ptr.h"
|
||||
#include "common/windows/guid_string.h"
|
||||
|
||||
using std::wstring;
|
||||
|
||||
namespace {
|
||||
|
||||
// A helper class used to collect handle operations data. Unlike
|
||||
// |MiniDumpWithHandleData| it records the operations for a single handle value
|
||||
// only, making it possible to include this information to a minidump.
|
||||
class HandleTraceData {
|
||||
public:
|
||||
HandleTraceData();
|
||||
~HandleTraceData();
|
||||
|
||||
// Collects the handle operations data and formats a user stream to be added
|
||||
// to the minidump.
|
||||
bool CollectHandleData(HANDLE process_handle,
|
||||
EXCEPTION_POINTERS* exception_pointers);
|
||||
|
||||
// Fills the user dump entry with a pointer to the collected handle operations
|
||||
// data. Returns |true| if the entry was initialized successfully, or |false|
|
||||
// if no trace data is available.
|
||||
bool GetUserStream(MINIDUMP_USER_STREAM* user_stream);
|
||||
|
||||
private:
|
||||
// Reads the exception code from the client process's address space.
|
||||
// This routine assumes that the client process's pointer width matches ours.
|
||||
static bool ReadExceptionCode(HANDLE process_handle,
|
||||
EXCEPTION_POINTERS* exception_pointers,
|
||||
DWORD* exception_code);
|
||||
|
||||
// Stores handle operations retrieved by VerifierEnumerateResource().
|
||||
static ULONG CALLBACK RecordHandleOperations(void* resource_description,
|
||||
void* enumeration_context,
|
||||
ULONG* enumeration_level);
|
||||
|
||||
// Function pointer type for VerifierEnumerateResource, which is looked up
|
||||
// dynamically.
|
||||
typedef BOOL (WINAPI* VerifierEnumerateResourceType)(
|
||||
HANDLE Process,
|
||||
ULONG Flags,
|
||||
ULONG ResourceType,
|
||||
AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback,
|
||||
PVOID EnumerationContext);
|
||||
|
||||
// Handle to dynamically loaded verifier.dll.
|
||||
HMODULE verifier_module_;
|
||||
|
||||
// Pointer to the VerifierEnumerateResource function.
|
||||
VerifierEnumerateResourceType enumerate_resource_;
|
||||
|
||||
// Handle value to look for.
|
||||
ULONG64 handle_;
|
||||
|
||||
// List of handle operations for |handle_|.
|
||||
std::list<AVRF_HANDLE_OPERATION> operations_;
|
||||
|
||||
// Minidump stream data.
|
||||
std::vector<char> stream_;
|
||||
};
|
||||
|
||||
HandleTraceData::HandleTraceData()
|
||||
: verifier_module_(NULL),
|
||||
enumerate_resource_(NULL),
|
||||
handle_(NULL) {
|
||||
}
|
||||
|
||||
HandleTraceData::~HandleTraceData() {
|
||||
if (verifier_module_) {
|
||||
FreeLibrary(verifier_module_);
|
||||
}
|
||||
}
|
||||
|
||||
bool HandleTraceData::CollectHandleData(
|
||||
HANDLE process_handle,
|
||||
EXCEPTION_POINTERS* exception_pointers) {
|
||||
DWORD exception_code;
|
||||
if (!ReadExceptionCode(process_handle, exception_pointers, &exception_code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify whether the execption is STATUS_INVALID_HANDLE. Do not record any
|
||||
// handle information if it is a different exception to keep the minidump
|
||||
// small.
|
||||
if (exception_code != STATUS_INVALID_HANDLE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load verifier!VerifierEnumerateResource() dynamically.
|
||||
verifier_module_ = LoadLibrary(TEXT("verifier.dll"));
|
||||
if (!verifier_module_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
enumerate_resource_ = reinterpret_cast<VerifierEnumerateResourceType>(
|
||||
GetProcAddress(verifier_module_, "VerifierEnumerateResource"));
|
||||
if (!enumerate_resource_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// STATUS_INVALID_HANDLE does not provide the offending handle value in
|
||||
// the exception parameters so we have to guess. At the moment we scan
|
||||
// the handle operations trace looking for the last invalid handle operation
|
||||
// and record only the operations for that handle value.
|
||||
if (enumerate_resource_(process_handle,
|
||||
0,
|
||||
AvrfResourceHandleTrace,
|
||||
&RecordHandleOperations,
|
||||
this) != ERROR_SUCCESS) {
|
||||
// The handle tracing must have not been enabled.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Now that |handle_| is initialized, purge all irrelevant operations.
|
||||
std::list<AVRF_HANDLE_OPERATION>::iterator i = operations_.begin();
|
||||
std::list<AVRF_HANDLE_OPERATION>::iterator i_end = operations_.end();
|
||||
while (i != i_end) {
|
||||
if (i->Handle == handle_) {
|
||||
++i;
|
||||
} else {
|
||||
i = operations_.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the list of recorded operations to a minidump stream.
|
||||
stream_.resize(sizeof(MINIDUMP_HANDLE_OPERATION_LIST) +
|
||||
sizeof(AVRF_HANDLE_OPERATION) * operations_.size());
|
||||
|
||||
MINIDUMP_HANDLE_OPERATION_LIST* stream_data =
|
||||
reinterpret_cast<MINIDUMP_HANDLE_OPERATION_LIST*>(
|
||||
&stream_.front());
|
||||
stream_data->SizeOfHeader = sizeof(MINIDUMP_HANDLE_OPERATION_LIST);
|
||||
stream_data->SizeOfEntry = sizeof(AVRF_HANDLE_OPERATION);
|
||||
stream_data->NumberOfEntries = static_cast<ULONG32>(operations_.size());
|
||||
stream_data->Reserved = 0;
|
||||
std::copy(operations_.begin(),
|
||||
operations_.end(),
|
||||
#if defined(_MSC_VER) && !defined(_LIBCPP_STD_VER)
|
||||
stdext::checked_array_iterator<AVRF_HANDLE_OPERATION*>(
|
||||
reinterpret_cast<AVRF_HANDLE_OPERATION*>(stream_data + 1),
|
||||
operations_.size())
|
||||
#else
|
||||
reinterpret_cast<AVRF_HANDLE_OPERATION*>(stream_data + 1)
|
||||
#endif
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HandleTraceData::GetUserStream(MINIDUMP_USER_STREAM* user_stream) {
|
||||
if (stream_.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
user_stream->Type = HandleOperationListStream;
|
||||
user_stream->BufferSize = static_cast<ULONG>(stream_.size());
|
||||
user_stream->Buffer = &stream_.front();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool HandleTraceData::ReadExceptionCode(
|
||||
HANDLE process_handle,
|
||||
EXCEPTION_POINTERS* exception_pointers,
|
||||
DWORD* exception_code) {
|
||||
EXCEPTION_POINTERS pointers;
|
||||
if (!ReadProcessMemory(process_handle,
|
||||
exception_pointers,
|
||||
&pointers,
|
||||
sizeof(pointers),
|
||||
NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadProcessMemory(process_handle,
|
||||
pointers.ExceptionRecord,
|
||||
exception_code,
|
||||
sizeof(*exception_code),
|
||||
NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ULONG CALLBACK HandleTraceData::RecordHandleOperations(
|
||||
void* resource_description,
|
||||
void* enumeration_context,
|
||||
ULONG* enumeration_level) {
|
||||
AVRF_HANDLE_OPERATION* description =
|
||||
reinterpret_cast<AVRF_HANDLE_OPERATION*>(resource_description);
|
||||
HandleTraceData* self =
|
||||
reinterpret_cast<HandleTraceData*>(enumeration_context);
|
||||
|
||||
// Remember the last invalid handle operation.
|
||||
if (description->OperationType == OperationDbBADREF) {
|
||||
self->handle_ = description->Handle;
|
||||
}
|
||||
|
||||
// Record all handle operations.
|
||||
self->operations_.push_back(*description);
|
||||
|
||||
*enumeration_level = HeapEnumerationEverything;
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
MinidumpGenerator::MinidumpGenerator(
|
||||
const std::wstring& dump_path,
|
||||
const HANDLE process_handle,
|
||||
const DWORD process_id,
|
||||
const DWORD thread_id,
|
||||
const DWORD requesting_thread_id,
|
||||
EXCEPTION_POINTERS* exception_pointers,
|
||||
MDRawAssertionInfo* assert_info,
|
||||
const MINIDUMP_TYPE dump_type,
|
||||
const bool is_client_pointers)
|
||||
: dbghelp_module_(NULL),
|
||||
write_dump_(NULL),
|
||||
rpcrt4_module_(NULL),
|
||||
create_uuid_(NULL),
|
||||
process_handle_(process_handle),
|
||||
process_id_(process_id),
|
||||
thread_id_(thread_id),
|
||||
requesting_thread_id_(requesting_thread_id),
|
||||
exception_pointers_(exception_pointers),
|
||||
assert_info_(assert_info),
|
||||
dump_type_(dump_type),
|
||||
is_client_pointers_(is_client_pointers),
|
||||
dump_path_(dump_path),
|
||||
uuid_generated_(false),
|
||||
dump_file_(INVALID_HANDLE_VALUE),
|
||||
full_dump_file_(INVALID_HANDLE_VALUE),
|
||||
dump_file_is_internal_(false),
|
||||
full_dump_file_is_internal_(false),
|
||||
additional_streams_(NULL),
|
||||
callback_info_(NULL) {
|
||||
uuid_ = {0};
|
||||
InitializeCriticalSection(&module_load_sync_);
|
||||
InitializeCriticalSection(&get_proc_address_sync_);
|
||||
}
|
||||
|
||||
MinidumpGenerator::~MinidumpGenerator() {
|
||||
if (dump_file_is_internal_ && dump_file_ != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(dump_file_);
|
||||
}
|
||||
|
||||
if (full_dump_file_is_internal_ && full_dump_file_ != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(full_dump_file_);
|
||||
}
|
||||
|
||||
if (dbghelp_module_) {
|
||||
FreeLibrary(dbghelp_module_);
|
||||
}
|
||||
|
||||
if (rpcrt4_module_) {
|
||||
FreeLibrary(rpcrt4_module_);
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&get_proc_address_sync_);
|
||||
DeleteCriticalSection(&module_load_sync_);
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteMinidump() {
|
||||
bool full_memory_dump = (dump_type_ & MiniDumpWithFullMemory) != 0;
|
||||
if (dump_file_ == INVALID_HANDLE_VALUE ||
|
||||
(full_memory_dump && full_dump_file_ == INVALID_HANDLE_VALUE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MiniDumpWriteDumpType write_dump = GetWriteDump();
|
||||
if (!write_dump) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MINIDUMP_EXCEPTION_INFORMATION* dump_exception_pointers = NULL;
|
||||
MINIDUMP_EXCEPTION_INFORMATION dump_exception_info;
|
||||
|
||||
// Setup the exception information object only if it's a dump
|
||||
// due to an exception.
|
||||
if (exception_pointers_) {
|
||||
dump_exception_pointers = &dump_exception_info;
|
||||
dump_exception_info.ThreadId = thread_id_;
|
||||
dump_exception_info.ExceptionPointers = exception_pointers_;
|
||||
dump_exception_info.ClientPointers = is_client_pointers_;
|
||||
}
|
||||
|
||||
// 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 = {0};
|
||||
if (!is_client_pointers_) {
|
||||
// Set the dump thread id and requesting thread id only in case of
|
||||
// in-process dump generation.
|
||||
breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
|
||||
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
|
||||
breakpad_info.dump_thread_id = thread_id_;
|
||||
breakpad_info.requesting_thread_id = requesting_thread_id_;
|
||||
}
|
||||
|
||||
int additional_streams_count = additional_streams_ ?
|
||||
additional_streams_->UserStreamCount : 0;
|
||||
scoped_array<MINIDUMP_USER_STREAM> user_stream_array(
|
||||
new MINIDUMP_USER_STREAM[3 + additional_streams_count]);
|
||||
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.get();
|
||||
|
||||
MDRawAssertionInfo* actual_assert_info = assert_info_;
|
||||
MDRawAssertionInfo client_assert_info = {{0}};
|
||||
|
||||
if (assert_info_) {
|
||||
// If the assertion info object lives in the client process,
|
||||
// read the memory of the client process.
|
||||
if (is_client_pointers_) {
|
||||
SIZE_T bytes_read = 0;
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
assert_info_,
|
||||
&client_assert_info,
|
||||
sizeof(client_assert_info),
|
||||
&bytes_read)) {
|
||||
if (dump_file_is_internal_)
|
||||
CloseHandle(dump_file_);
|
||||
if (full_dump_file_is_internal_ &&
|
||||
full_dump_file_ != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(full_dump_file_);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes_read != sizeof(client_assert_info)) {
|
||||
if (dump_file_is_internal_)
|
||||
CloseHandle(dump_file_);
|
||||
if (full_dump_file_is_internal_ &&
|
||||
full_dump_file_ != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(full_dump_file_);
|
||||
return false;
|
||||
}
|
||||
|
||||
actual_assert_info = &client_assert_info;
|
||||
}
|
||||
|
||||
user_stream_array[1].Type = MD_ASSERTION_INFO_STREAM;
|
||||
user_stream_array[1].BufferSize = sizeof(MDRawAssertionInfo);
|
||||
user_stream_array[1].Buffer = actual_assert_info;
|
||||
++user_streams.UserStreamCount;
|
||||
}
|
||||
|
||||
if (additional_streams_) {
|
||||
for (size_t i = 0;
|
||||
i < additional_streams_->UserStreamCount;
|
||||
i++, user_streams.UserStreamCount++) {
|
||||
user_stream_array[user_streams.UserStreamCount].Type =
|
||||
additional_streams_->UserStreamArray[i].Type;
|
||||
user_stream_array[user_streams.UserStreamCount].BufferSize =
|
||||
additional_streams_->UserStreamArray[i].BufferSize;
|
||||
user_stream_array[user_streams.UserStreamCount].Buffer =
|
||||
additional_streams_->UserStreamArray[i].Buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// If the process is terminated by STATUS_INVALID_HANDLE exception store
|
||||
// the trace of operations for the offending handle value. Do nothing special
|
||||
// if the client already requested the handle trace to be stored in the dump.
|
||||
HandleTraceData handle_trace_data;
|
||||
if (exception_pointers_ && (dump_type_ & MiniDumpWithHandleData) == 0) {
|
||||
if (!handle_trace_data.CollectHandleData(process_handle_,
|
||||
exception_pointers_)) {
|
||||
if (dump_file_is_internal_)
|
||||
CloseHandle(dump_file_);
|
||||
if (full_dump_file_is_internal_ &&
|
||||
full_dump_file_ != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(full_dump_file_);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool result_full_memory = true;
|
||||
if (full_memory_dump) {
|
||||
result_full_memory = write_dump(
|
||||
process_handle_,
|
||||
process_id_,
|
||||
full_dump_file_,
|
||||
static_cast<MINIDUMP_TYPE>((dump_type_ & (~MiniDumpNormal))
|
||||
| MiniDumpWithHandleData),
|
||||
dump_exception_pointers,
|
||||
&user_streams,
|
||||
NULL) != FALSE;
|
||||
}
|
||||
|
||||
// Add handle operations trace stream to the minidump if it was collected.
|
||||
if (handle_trace_data.GetUserStream(
|
||||
&user_stream_array[user_streams.UserStreamCount])) {
|
||||
++user_streams.UserStreamCount;
|
||||
}
|
||||
|
||||
bool result_minidump = write_dump(
|
||||
process_handle_,
|
||||
process_id_,
|
||||
dump_file_,
|
||||
static_cast<MINIDUMP_TYPE>((dump_type_ & (~MiniDumpWithFullMemory))
|
||||
| MiniDumpNormal),
|
||||
dump_exception_pointers,
|
||||
&user_streams,
|
||||
callback_info_) != FALSE;
|
||||
|
||||
return result_minidump && result_full_memory;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::GenerateDumpFile(wstring* dump_path) {
|
||||
// The dump file was already set by handle or this function was previously
|
||||
// called.
|
||||
if (dump_file_ != INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wstring dump_file_path;
|
||||
if (!GenerateDumpFilePath(&dump_file_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dump_file_ = CreateFile(dump_file_path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
0,
|
||||
NULL,
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
if (dump_file_ == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dump_file_is_internal_ = true;
|
||||
*dump_path = dump_file_path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::GenerateFullDumpFile(wstring* full_dump_path) {
|
||||
// A full minidump was not requested.
|
||||
if ((dump_type_ & MiniDumpWithFullMemory) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The dump file was already set by handle or this function was previously
|
||||
// called.
|
||||
if (full_dump_file_ != INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wstring full_dump_file_path;
|
||||
if (!GenerateDumpFilePath(&full_dump_file_path)) {
|
||||
return false;
|
||||
}
|
||||
full_dump_file_path.resize(full_dump_file_path.size() - 4); // strip .dmp
|
||||
full_dump_file_path.append(TEXT("-full.dmp"));
|
||||
|
||||
full_dump_file_ = CreateFile(full_dump_file_path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
0,
|
||||
NULL,
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
if (full_dump_file_ == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
full_dump_file_is_internal_ = true;
|
||||
*full_dump_path = full_dump_file_path;
|
||||
return true;
|
||||
}
|
||||
|
||||
HMODULE MinidumpGenerator::GetDbghelpModule() {
|
||||
AutoCriticalSection lock(&module_load_sync_);
|
||||
if (!dbghelp_module_) {
|
||||
dbghelp_module_ = LoadLibrary(TEXT("dbghelp.dll"));
|
||||
}
|
||||
|
||||
return dbghelp_module_;
|
||||
}
|
||||
|
||||
MinidumpGenerator::MiniDumpWriteDumpType MinidumpGenerator::GetWriteDump() {
|
||||
AutoCriticalSection lock(&get_proc_address_sync_);
|
||||
if (!write_dump_) {
|
||||
HMODULE module = GetDbghelpModule();
|
||||
if (module) {
|
||||
FARPROC proc = GetProcAddress(module, "MiniDumpWriteDump");
|
||||
write_dump_ = reinterpret_cast<MiniDumpWriteDumpType>(proc);
|
||||
}
|
||||
}
|
||||
|
||||
return write_dump_;
|
||||
}
|
||||
|
||||
HMODULE MinidumpGenerator::GetRpcrt4Module() {
|
||||
AutoCriticalSection lock(&module_load_sync_);
|
||||
if (!rpcrt4_module_) {
|
||||
rpcrt4_module_ = LoadLibrary(TEXT("rpcrt4.dll"));
|
||||
}
|
||||
|
||||
return rpcrt4_module_;
|
||||
}
|
||||
|
||||
MinidumpGenerator::UuidCreateType MinidumpGenerator::GetCreateUuid() {
|
||||
AutoCriticalSection lock(&module_load_sync_);
|
||||
if (!create_uuid_) {
|
||||
HMODULE module = GetRpcrt4Module();
|
||||
if (module) {
|
||||
FARPROC proc = GetProcAddress(module, "UuidCreate");
|
||||
create_uuid_ = reinterpret_cast<UuidCreateType>(proc);
|
||||
}
|
||||
}
|
||||
|
||||
return create_uuid_;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::GenerateDumpFilePath(wstring* file_path) {
|
||||
if (!uuid_generated_) {
|
||||
UuidCreateType create_uuid = GetCreateUuid();
|
||||
if (!create_uuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
create_uuid(&uuid_);
|
||||
uuid_generated_ = true;
|
||||
}
|
||||
|
||||
wstring id_str = GUIDString::GUIDToWString(&uuid_);
|
||||
|
||||
*file_path = dump_path_ + TEXT("\\") + id_str + TEXT(".dmp");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_
|
||||
#define CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <rpc.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Abstraction for various objects and operations needed to generate
|
||||
// minidump on Windows. This abstraction is useful to hide all the gory
|
||||
// details for minidump generation and provide a clean interface to
|
||||
// the clients to generate minidumps.
|
||||
class MinidumpGenerator {
|
||||
public:
|
||||
// Creates an instance with the given parameters.
|
||||
// is_client_pointers specifies whether the exception_pointers and
|
||||
// assert_info point into the process that is being dumped.
|
||||
// Before calling WriteMinidump on the returned instance a dump file muct be
|
||||
// specified by a call to either SetDumpFile() or GenerateDumpFile().
|
||||
// If a full dump file will be requested via a subsequent call to either
|
||||
// SetFullDumpFile or GenerateFullDumpFile() dump_type must include
|
||||
// MiniDumpWithFullMemory.
|
||||
MinidumpGenerator(const std::wstring& dump_path,
|
||||
const HANDLE process_handle,
|
||||
const DWORD process_id,
|
||||
const DWORD thread_id,
|
||||
const DWORD requesting_thread_id,
|
||||
EXCEPTION_POINTERS* exception_pointers,
|
||||
MDRawAssertionInfo* assert_info,
|
||||
const MINIDUMP_TYPE dump_type,
|
||||
const bool is_client_pointers);
|
||||
|
||||
~MinidumpGenerator();
|
||||
|
||||
void SetDumpFile(const HANDLE dump_file) { dump_file_ = dump_file; }
|
||||
void SetFullDumpFile(const HANDLE full_dump_file) {
|
||||
full_dump_file_ = full_dump_file;
|
||||
}
|
||||
|
||||
// Generate the name for the dump file that will be written to once
|
||||
// WriteMinidump() is called. Can only be called once and cannot be called
|
||||
// if the dump file is set via SetDumpFile().
|
||||
bool GenerateDumpFile(std::wstring* dump_path);
|
||||
|
||||
// Generate the name for the full dump file that will be written to once
|
||||
// WriteMinidump() is called. Cannot be called unless the minidump type
|
||||
// includes MiniDumpWithFullMemory. Can only be called once and cannot be
|
||||
// called if the dump file is set via SetFullDumpFile().
|
||||
bool GenerateFullDumpFile(std::wstring* full_dump_path);
|
||||
|
||||
void SetAdditionalStreams(
|
||||
MINIDUMP_USER_STREAM_INFORMATION* additional_streams) {
|
||||
additional_streams_ = additional_streams;
|
||||
}
|
||||
|
||||
void SetCallback(MINIDUMP_CALLBACK_INFORMATION* callback_info) {
|
||||
callback_info_ = callback_info;
|
||||
}
|
||||
|
||||
// Writes the minidump with the given parameters. Stores the
|
||||
// dump file path in the dump_path parameter if dump generation
|
||||
// succeeds.
|
||||
bool WriteMinidump();
|
||||
|
||||
private:
|
||||
// Function pointer type for MiniDumpWriteDump, which is looked up
|
||||
// dynamically.
|
||||
typedef BOOL (WINAPI* MiniDumpWriteDumpType)(
|
||||
HANDLE hProcess,
|
||||
DWORD ProcessId,
|
||||
HANDLE hFile,
|
||||
MINIDUMP_TYPE DumpType,
|
||||
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
|
||||
CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
|
||||
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
|
||||
|
||||
// Function pointer type for UuidCreate, which is looked up dynamically.
|
||||
typedef RPC_STATUS (RPC_ENTRY* UuidCreateType)(UUID* Uuid);
|
||||
|
||||
// Loads the appropriate DLL lazily in a thread safe way.
|
||||
HMODULE GetDbghelpModule();
|
||||
|
||||
// Loads the appropriate DLL and gets a pointer to the MiniDumpWriteDump
|
||||
// function lazily and in a thread-safe manner.
|
||||
MiniDumpWriteDumpType GetWriteDump();
|
||||
|
||||
// Loads the appropriate DLL lazily in a thread safe way.
|
||||
HMODULE GetRpcrt4Module();
|
||||
|
||||
// Loads the appropriate DLL and gets a pointer to the UuidCreate
|
||||
// function lazily and in a thread-safe manner.
|
||||
UuidCreateType GetCreateUuid();
|
||||
|
||||
// Returns the path for the file to write dump to.
|
||||
bool GenerateDumpFilePath(std::wstring* file_path);
|
||||
|
||||
// Handle to dynamically loaded DbgHelp.dll.
|
||||
HMODULE dbghelp_module_;
|
||||
|
||||
// Pointer to the MiniDumpWriteDump function.
|
||||
MiniDumpWriteDumpType write_dump_;
|
||||
|
||||
// Handle to dynamically loaded rpcrt4.dll.
|
||||
HMODULE rpcrt4_module_;
|
||||
|
||||
// Pointer to the UuidCreate function.
|
||||
UuidCreateType create_uuid_;
|
||||
|
||||
// Handle for the process to dump.
|
||||
HANDLE process_handle_;
|
||||
|
||||
// Process ID for the process to dump.
|
||||
DWORD process_id_;
|
||||
|
||||
// The crashing thread ID.
|
||||
DWORD thread_id_;
|
||||
|
||||
// The thread ID which is requesting the dump.
|
||||
DWORD requesting_thread_id_;
|
||||
|
||||
// Pointer to the exception information for the crash. This may point to an
|
||||
// address in the crashing process so it should not be dereferenced.
|
||||
EXCEPTION_POINTERS* exception_pointers_;
|
||||
|
||||
// Assertion info for the report.
|
||||
MDRawAssertionInfo* assert_info_;
|
||||
|
||||
// Type of minidump to generate.
|
||||
MINIDUMP_TYPE dump_type_;
|
||||
|
||||
// Specifies whether the exception_pointers_ reference memory in the crashing
|
||||
// process.
|
||||
bool is_client_pointers_;
|
||||
|
||||
// Folder path to store dump files.
|
||||
std::wstring dump_path_;
|
||||
|
||||
// UUID used to make dump file names.
|
||||
UUID uuid_;
|
||||
bool uuid_generated_;
|
||||
|
||||
// The file where the dump will be written.
|
||||
HANDLE dump_file_;
|
||||
|
||||
// The file where the full dump will be written.
|
||||
HANDLE full_dump_file_;
|
||||
|
||||
// Tracks whether the dump file handle is managed externally.
|
||||
bool dump_file_is_internal_;
|
||||
|
||||
// Tracks whether the full dump file handle is managed externally.
|
||||
bool full_dump_file_is_internal_;
|
||||
|
||||
// Additional streams to be written to the dump.
|
||||
MINIDUMP_USER_STREAM_INFORMATION* additional_streams_;
|
||||
|
||||
// The user defined callback for the various stages of the dump process.
|
||||
MINIDUMP_CALLBACK_INFORMATION* callback_info_;
|
||||
|
||||
// Critical section to sychronize action of loading modules dynamically.
|
||||
CRITICAL_SECTION module_load_sync_;
|
||||
|
||||
// Critical section to synchronize action of dynamically getting function
|
||||
// addresses from modules.
|
||||
CRITICAL_SECTION get_proc_address_sync_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,523 @@
|
||||
// Copyright 2006 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// ExceptionHandler can write a minidump file when an exception occurs,
|
||||
// or when WriteMinidump() is called explicitly by your program.
|
||||
//
|
||||
// To have the exception handler write minidumps when an uncaught exception
|
||||
// (crash) occurs, you should create an instance early in the execution
|
||||
// of your program, and keep it around for the entire time you want to
|
||||
// have crash handling active (typically, until shutdown).
|
||||
//
|
||||
// If you want to write minidumps without installing the exception handler,
|
||||
// you can create an ExceptionHandler with install_handler set to false,
|
||||
// then call WriteMinidump. You can also use this technique if you want to
|
||||
// use different minidump callbacks for different call sites.
|
||||
//
|
||||
// In either case, a callback function is called when a minidump is written,
|
||||
// which receives the unqiue id of the minidump. The caller can use this
|
||||
// id to collect and write additional application state, and to launch an
|
||||
// external crash-reporting application.
|
||||
//
|
||||
// It is important that creation and destruction of ExceptionHandler objects
|
||||
// be nested cleanly, when using install_handler = true.
|
||||
// Avoid the following pattern:
|
||||
// ExceptionHandler *e = new ExceptionHandler(...);
|
||||
// ExceptionHandler *f = new ExceptionHandler(...);
|
||||
// delete e;
|
||||
// This will put the exception filter stack into an inconsistent state.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__
|
||||
#define CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <rpc.h>
|
||||
|
||||
#pragma warning(push)
|
||||
// Disable exception handler warnings.
|
||||
#pragma warning(disable:4530)
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
#include "client/windows/crash_generation/crash_generation_client.h"
|
||||
#include "common/scoped_ptr.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::vector;
|
||||
using std::wstring;
|
||||
|
||||
// These entries store a list of memory regions that the client wants included
|
||||
// in the minidump.
|
||||
struct AppMemory {
|
||||
ULONG64 ptr;
|
||||
ULONG length;
|
||||
|
||||
bool operator==(const struct AppMemory& other) const {
|
||||
return ptr == other.ptr;
|
||||
}
|
||||
|
||||
bool operator==(const void* other) const {
|
||||
return ptr == reinterpret_cast<ULONG64>(other);
|
||||
}
|
||||
};
|
||||
typedef std::list<AppMemory> AppMemoryList;
|
||||
|
||||
class ExceptionHandler {
|
||||
public:
|
||||
// A callback function to run before Breakpad performs any substantial
|
||||
// processing of an exception. A FilterCallback is called before writing
|
||||
// a minidump. context is the parameter supplied by the user as
|
||||
// callback_context when the handler was created. exinfo points to the
|
||||
// exception record, if any; assertion points to assertion information,
|
||||
// if any.
|
||||
//
|
||||
// If a FilterCallback returns true, Breakpad will continue processing,
|
||||
// attempting to write a minidump. If a FilterCallback returns false,
|
||||
// Breakpad will immediately report the exception as unhandled without
|
||||
// writing a minidump, allowing another handler the opportunity to handle it.
|
||||
typedef bool (*FilterCallback)(void* context, EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion);
|
||||
|
||||
// A callback function to run after the minidump has been written.
|
||||
// minidump_id is a unique id for the dump, so the minidump
|
||||
// file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied
|
||||
// by the user as callback_context when the handler was created. exinfo
|
||||
// points to the exception record, or NULL if no exception occurred.
|
||||
// succeeded indicates whether a minidump file was successfully written.
|
||||
// assertion points to information about an assertion if the handler was
|
||||
// invoked by an assertion.
|
||||
//
|
||||
// If an exception occurred and the callback returns true, Breakpad will treat
|
||||
// the exception as fully-handled, suppressing any other handlers from being
|
||||
// notified of the exception. If the callback returns false, Breakpad will
|
||||
// treat the exception as unhandled, and allow another handler to handle it.
|
||||
// If there are no other handlers, Breakpad will report the exception to the
|
||||
// system as unhandled, allowing a debugger or native crash dialog the
|
||||
// opportunity to handle the exception. Most callback implementations
|
||||
// should normally return the value of |succeeded|, or when they wish to
|
||||
// not report an exception of handled, false. Callbacks will rarely want to
|
||||
// return true directly (unless |succeeded| is true).
|
||||
//
|
||||
// For out-of-process dump generation, dump path and minidump ID will always
|
||||
// be NULL. In case of out-of-process dump generation, the dump path and
|
||||
// minidump id are controlled by the server process and are not communicated
|
||||
// back to the crashing process.
|
||||
typedef bool (*MinidumpCallback)(const wchar_t* dump_path,
|
||||
const wchar_t* minidump_id,
|
||||
void* context,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion,
|
||||
bool succeeded);
|
||||
|
||||
// HandlerType specifies which types of handlers should be installed, if
|
||||
// any. Use HANDLER_NONE for an ExceptionHandler that remains idle,
|
||||
// without catching any failures on its own. This type of handler may
|
||||
// still be triggered by calling WriteMinidump. Otherwise, use a
|
||||
// combination of the other HANDLER_ values, or HANDLER_ALL to install
|
||||
// all handlers.
|
||||
enum HandlerType {
|
||||
HANDLER_NONE = 0,
|
||||
HANDLER_EXCEPTION = 1 << 0, // SetUnhandledExceptionFilter
|
||||
HANDLER_INVALID_PARAMETER = 1 << 1, // _set_invalid_parameter_handler
|
||||
HANDLER_PURECALL = 1 << 2, // _set_purecall_handler
|
||||
HANDLER_ALL = HANDLER_EXCEPTION |
|
||||
HANDLER_INVALID_PARAMETER |
|
||||
HANDLER_PURECALL
|
||||
};
|
||||
|
||||
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
||||
// Before writing a minidump, the optional filter callback will be called.
|
||||
// Its return value determines whether or not Breakpad should write a
|
||||
// minidump. Minidump files will be written to dump_path, and the optional
|
||||
// callback is called after writing the dump file, as described above.
|
||||
// handler_types specifies the types of handlers that should be installed.
|
||||
ExceptionHandler(const wstring& dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void* callback_context,
|
||||
int handler_types);
|
||||
|
||||
// Creates a new ExceptionHandler instance that can attempt to perform
|
||||
// out-of-process dump generation if pipe_name is not NULL. If pipe_name is
|
||||
// NULL, or if out-of-process dump generation registration step fails,
|
||||
// in-process dump generation will be used. This also allows specifying
|
||||
// the dump type to generate.
|
||||
ExceptionHandler(const wstring& dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void* callback_context,
|
||||
int handler_types,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
const wchar_t* pipe_name,
|
||||
const CustomClientInfo* custom_info);
|
||||
|
||||
// As above, creates a new ExceptionHandler instance to perform
|
||||
// out-of-process dump generation if the given pipe_handle is not NULL.
|
||||
ExceptionHandler(const wstring& dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void* callback_context,
|
||||
int handler_types,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
HANDLE pipe_handle,
|
||||
const CustomClientInfo* custom_info);
|
||||
|
||||
// ExceptionHandler that ENSURES out-of-process dump generation. Expects a
|
||||
// crash generation client that is already registered with a crash generation
|
||||
// server. Takes ownership of the passed-in crash_generation_client.
|
||||
//
|
||||
// Usage example:
|
||||
// crash_generation_client = new CrashGenerationClient(..);
|
||||
// if (crash_generation_client->Register()) {
|
||||
// // Registration with the crash generation server succeeded.
|
||||
// // Out-of-process dump generation is guaranteed.
|
||||
// g_handler = new ExceptionHandler(.., crash_generation_client, ..);
|
||||
// return true;
|
||||
// }
|
||||
ExceptionHandler(const wstring& dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void* callback_context,
|
||||
int handler_types,
|
||||
CrashGenerationClient* crash_generation_client);
|
||||
|
||||
~ExceptionHandler();
|
||||
|
||||
// Get and set the minidump path.
|
||||
wstring dump_path() const { return dump_path_; }
|
||||
void set_dump_path(const wstring& dump_path) {
|
||||
dump_path_ = dump_path;
|
||||
dump_path_c_ = dump_path_.c_str();
|
||||
UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_.
|
||||
}
|
||||
|
||||
// Requests that a previously reported crash be uploaded.
|
||||
bool RequestUpload(DWORD crash_id);
|
||||
|
||||
// Writes a minidump immediately. This can be used to capture the
|
||||
// execution state independently of a crash. Returns true on success.
|
||||
bool WriteMinidump();
|
||||
|
||||
// Writes a minidump immediately, with the user-supplied exception
|
||||
// information.
|
||||
bool WriteMinidumpForException(EXCEPTION_POINTERS* exinfo);
|
||||
|
||||
// Convenience form of WriteMinidump which does not require an
|
||||
// ExceptionHandler instance.
|
||||
static bool WriteMinidump(const wstring& dump_path,
|
||||
MinidumpCallback callback, void* callback_context,
|
||||
MINIDUMP_TYPE dump_type = MiniDumpNormal);
|
||||
|
||||
// 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,
|
||||
MINIDUMP_TYPE dump_type = MiniDumpNormal);
|
||||
|
||||
// Get the thread ID of the thread requesting the dump (either the exception
|
||||
// thread or any other thread that called WriteMinidump directly). This
|
||||
// may be useful if you want to include additional thread state in your
|
||||
// dumps.
|
||||
DWORD get_requesting_thread_id() const { return requesting_thread_id_; }
|
||||
|
||||
// Controls behavior of EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP.
|
||||
bool get_handle_debug_exceptions() const { return handle_debug_exceptions_; }
|
||||
void set_handle_debug_exceptions(bool handle_debug_exceptions) {
|
||||
handle_debug_exceptions_ = handle_debug_exceptions;
|
||||
}
|
||||
|
||||
// Controls behavior of EXCEPTION_INVALID_HANDLE.
|
||||
bool get_consume_invalid_handle_exceptions() const {
|
||||
return consume_invalid_handle_exceptions_;
|
||||
}
|
||||
void set_consume_invalid_handle_exceptions(
|
||||
bool consume_invalid_handle_exceptions) {
|
||||
consume_invalid_handle_exceptions_ = consume_invalid_handle_exceptions;
|
||||
}
|
||||
|
||||
// Returns whether out-of-process dump generation is used or not.
|
||||
bool IsOutOfProcess() const { return crash_generation_client_.get() != NULL; }
|
||||
|
||||
// Calling RegisterAppMemory(p, len) causes len bytes starting
|
||||
// at address p to be copied to the minidump when a crash happens.
|
||||
void RegisterAppMemory(void* ptr, size_t length);
|
||||
void UnregisterAppMemory(void* ptr);
|
||||
|
||||
private:
|
||||
friend class AutoExceptionHandler;
|
||||
|
||||
// Initializes the instance with given values.
|
||||
void Initialize(const wstring& dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void* callback_context,
|
||||
int handler_types,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
const wchar_t* pipe_name,
|
||||
HANDLE pipe_handle,
|
||||
CrashGenerationClient* crash_generation_client,
|
||||
const CustomClientInfo* custom_info);
|
||||
|
||||
// Function pointer type for MiniDumpWriteDump, which is looked up
|
||||
// dynamically.
|
||||
typedef BOOL (WINAPI *MiniDumpWriteDump_type)(
|
||||
HANDLE hProcess,
|
||||
DWORD dwPid,
|
||||
HANDLE hFile,
|
||||
MINIDUMP_TYPE DumpType,
|
||||
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
|
||||
CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
|
||||
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
|
||||
|
||||
// Function pointer type for UuidCreate, which is looked up dynamically.
|
||||
typedef RPC_STATUS (RPC_ENTRY *UuidCreate_type)(UUID* Uuid);
|
||||
|
||||
// Runs the main loop for the exception handler thread.
|
||||
static DWORD WINAPI ExceptionHandlerThreadMain(void* lpParameter);
|
||||
|
||||
// Called on the exception thread when an unhandled exception occurs.
|
||||
// Signals the exception handler thread to handle the exception.
|
||||
static LONG WINAPI HandleException(EXCEPTION_POINTERS* exinfo);
|
||||
|
||||
#if _MSC_VER >= 1400 // MSVC 2005/8
|
||||
// This function will be called by some CRT functions when they detect
|
||||
// that they were passed an invalid parameter. Note that in _DEBUG builds,
|
||||
// the CRT may display an assertion dialog before calling this function,
|
||||
// and the function will not be called unless the assertion dialog is
|
||||
// dismissed by clicking "Ignore."
|
||||
static void HandleInvalidParameter(const wchar_t* expression,
|
||||
const wchar_t* function,
|
||||
const wchar_t* file,
|
||||
unsigned int line,
|
||||
uintptr_t reserved);
|
||||
#endif // _MSC_VER >= 1400
|
||||
|
||||
// This function will be called by the CRT when a pure virtual
|
||||
// function is called.
|
||||
static void HandlePureVirtualCall();
|
||||
|
||||
// This is called on the exception thread or on another thread that
|
||||
// the user wishes to produce a dump from. It calls
|
||||
// WriteMinidumpWithException on the handler thread, avoiding stack
|
||||
// overflows and inconsistent dumps due to writing the dump from
|
||||
// the exception thread. If the dump is requested as a result of an
|
||||
// exception, exinfo contains exception information, otherwise, it
|
||||
// is NULL. If the dump is requested as a result of an assertion
|
||||
// (such as an invalid parameter being passed to a CRT function),
|
||||
// assertion contains data about the assertion, otherwise, it is NULL.
|
||||
bool WriteMinidumpOnHandlerThread(EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion);
|
||||
|
||||
// This function is called on the handler thread. It calls into
|
||||
// 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
|
||||
// an exception, exinfo contains exception information, otherwise,
|
||||
// it is NULL.
|
||||
bool WriteMinidumpWithException(DWORD requesting_thread_id,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion);
|
||||
|
||||
// This function is used as a callback when calling MinidumpWriteDump,
|
||||
// in order to add additional memory regions to the dump.
|
||||
static BOOL CALLBACK MinidumpWriteDumpCallback(
|
||||
PVOID context,
|
||||
const PMINIDUMP_CALLBACK_INPUT callback_input,
|
||||
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
|
||||
// path of the next minidump to be written in next_minidump_path_.
|
||||
void UpdateNextID();
|
||||
|
||||
FilterCallback filter_;
|
||||
MinidumpCallback callback_;
|
||||
void* callback_context_;
|
||||
|
||||
scoped_ptr<CrashGenerationClient> crash_generation_client_;
|
||||
|
||||
// The directory in which a minidump will be written, set by the dump_path
|
||||
// argument to the constructor, or set_dump_path.
|
||||
wstring dump_path_;
|
||||
|
||||
// The basename of the next minidump to be written, without the extension.
|
||||
wstring next_minidump_id_;
|
||||
|
||||
// The full pathname of the next minidump to be written, including the file
|
||||
// extension.
|
||||
wstring next_minidump_path_;
|
||||
|
||||
// Pointers to C-string representations of the above. These are set when
|
||||
// the above wstring versions are set in order to avoid calling c_str during
|
||||
// an exception, as c_str may attempt to allocate heap memory. These
|
||||
// pointers are not owned by the ExceptionHandler object, but their lifetimes
|
||||
// should be equivalent to the lifetimes of the associated wstring, provided
|
||||
// that the wstrings are not altered.
|
||||
const wchar_t* dump_path_c_;
|
||||
const wchar_t* next_minidump_id_c_;
|
||||
const wchar_t* next_minidump_path_c_;
|
||||
|
||||
HMODULE dbghelp_module_;
|
||||
MiniDumpWriteDump_type minidump_write_dump_;
|
||||
MINIDUMP_TYPE dump_type_;
|
||||
|
||||
HMODULE rpcrt4_module_;
|
||||
UuidCreate_type uuid_create_;
|
||||
|
||||
// Tracks the handler types that were installed according to the
|
||||
// handler_types constructor argument.
|
||||
int handler_types_;
|
||||
|
||||
// When installed_handler_ is true, previous_filter_ is the unhandled
|
||||
// exception filter that was set prior to installing ExceptionHandler as
|
||||
// the unhandled exception filter and pointing it to |this|. NULL indicates
|
||||
// that there is no previous unhandled exception filter.
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER previous_filter_;
|
||||
|
||||
#if _MSC_VER >= 1400 // MSVC 2005/8
|
||||
// Beginning in VC 8, the CRT provides an invalid parameter handler that will
|
||||
// be called when some CRT functions are passed invalid parameters. In
|
||||
// earlier CRTs, the same conditions would cause unexpected behavior or
|
||||
// crashes.
|
||||
_invalid_parameter_handler previous_iph_;
|
||||
#endif // _MSC_VER >= 1400
|
||||
|
||||
// The CRT allows you to override the default handler for pure
|
||||
// virtual function calls.
|
||||
_purecall_handler previous_pch_;
|
||||
|
||||
// The exception handler thread.
|
||||
HANDLE handler_thread_;
|
||||
|
||||
// True if the exception handler is being destroyed.
|
||||
// Starting with MSVC 2005, Visual C has stronger guarantees on volatile vars.
|
||||
// It has release semantics on write and acquire semantics on reads.
|
||||
// See the msdn documentation.
|
||||
volatile bool is_shutdown_;
|
||||
|
||||
// The critical section enforcing the requirement that only one exception be
|
||||
// handled by a handler at a time.
|
||||
CRITICAL_SECTION handler_critical_section_;
|
||||
|
||||
// Semaphores used to move exception handling between the exception thread
|
||||
// and the handler thread. handler_start_semaphore_ is signalled by the
|
||||
// exception thread to wake up the handler thread when an exception occurs.
|
||||
// handler_finish_semaphore_ is signalled by the handler thread to wake up
|
||||
// the exception thread when handling is complete.
|
||||
HANDLE handler_start_semaphore_;
|
||||
HANDLE handler_finish_semaphore_;
|
||||
|
||||
// The next 2 fields contain data passed from the requesting thread to
|
||||
// the handler thread.
|
||||
|
||||
// The thread ID of the thread requesting the dump (either the exception
|
||||
// thread or any other thread that called WriteMinidump directly).
|
||||
DWORD requesting_thread_id_;
|
||||
|
||||
// The exception info passed to the exception handler on the exception
|
||||
// thread, if an exception occurred. NULL for user-requested dumps.
|
||||
EXCEPTION_POINTERS* exception_info_;
|
||||
|
||||
// If the handler is invoked due to an assertion, this will contain a
|
||||
// pointer to the assertion information. It is NULL at other times.
|
||||
MDRawAssertionInfo* assertion_;
|
||||
|
||||
// The return value of the handler, passed from the handler thread back to
|
||||
// the requesting thread.
|
||||
bool handler_return_value_;
|
||||
|
||||
// If true, the handler will intercept EXCEPTION_BREAKPOINT and
|
||||
// EXCEPTION_SINGLE_STEP exceptions. Leave this false (the default)
|
||||
// to not interfere with debuggers.
|
||||
bool handle_debug_exceptions_;
|
||||
|
||||
// If true, the handler will consume any EXCEPTION_INVALID_HANDLE exceptions.
|
||||
// Leave this false (the default) to handle these exceptions as normal.
|
||||
bool consume_invalid_handle_exceptions_;
|
||||
|
||||
// Callers can request additional memory regions to be included in
|
||||
// the dump.
|
||||
AppMemoryList app_memory_info_;
|
||||
|
||||
// A stack of ExceptionHandler objects that have installed unhandled
|
||||
// exception filters. This vector is used by HandleException to determine
|
||||
// which ExceptionHandler object to route an exception to. When an
|
||||
// ExceptionHandler is created with install_handler true, it will append
|
||||
// itself to this list.
|
||||
static vector<ExceptionHandler*>* handler_stack_;
|
||||
|
||||
// The index of the ExceptionHandler in handler_stack_ that will handle the
|
||||
// next exception. Note that 0 means the last entry in handler_stack_, 1
|
||||
// means the next-to-last entry, and so on. This is used by HandleException
|
||||
// to support multiple stacked Breakpad handlers.
|
||||
static LONG handler_stack_index_;
|
||||
|
||||
// handler_stack_critical_section_ guards operations on handler_stack_ and
|
||||
// handler_stack_index_. The critical section is initialized by the
|
||||
// first instance of the class and destroyed by the last instance of it.
|
||||
static CRITICAL_SECTION handler_stack_critical_section_;
|
||||
|
||||
// The number of instances of this class.
|
||||
static volatile LONG instance_count_;
|
||||
|
||||
// disallow copy ctor and operator=
|
||||
explicit ExceptionHandler(const ExceptionHandler&);
|
||||
void operator=(const ExceptionHandler&);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
#endif // CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2006 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Disable exception handler warnings.
|
||||
#pragma warning( disable : 4530 )
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "client/windows/sender/crash_report_sender.h"
|
||||
#include "common/windows/http_upload.h"
|
||||
|
||||
#if _MSC_VER < 1400 // MSVC 2005/8
|
||||
// Older MSVC doesn't have fscanf_s, but they are compatible as long as
|
||||
// we don't use the string conversions (%s/%c/%S/%C).
|
||||
#define fscanf_s fscanf
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
static const char kCheckpointSignature[] = "GBP1\n";
|
||||
|
||||
CrashReportSender::CrashReportSender(const wstring& checkpoint_file)
|
||||
: checkpoint_file_(checkpoint_file),
|
||||
max_reports_per_day_(-1),
|
||||
last_sent_date_(-1),
|
||||
reports_sent_(0) {
|
||||
FILE* fd;
|
||||
if (OpenCheckpointFile(L"r", &fd) == 0) {
|
||||
ReadCheckpoint(fd);
|
||||
fclose(fd);
|
||||
}
|
||||
}
|
||||
|
||||
ReportResult CrashReportSender::SendCrashReport(
|
||||
const wstring& url, const map<wstring, wstring>& parameters,
|
||||
const map<wstring, wstring>& files, wstring* report_code) {
|
||||
int today = GetCurrentDate();
|
||||
if (today == last_sent_date_ &&
|
||||
max_reports_per_day_ != -1 &&
|
||||
reports_sent_ >= max_reports_per_day_) {
|
||||
return RESULT_THROTTLED;
|
||||
}
|
||||
|
||||
int http_response = 0;
|
||||
bool result = HTTPUpload::SendMultipartPostRequest(
|
||||
url, parameters, files, NULL, report_code,
|
||||
&http_response);
|
||||
|
||||
if (result) {
|
||||
ReportSent(today);
|
||||
return RESULT_SUCCEEDED;
|
||||
} else if (http_response >= 400 && http_response < 500) {
|
||||
return RESULT_REJECTED;
|
||||
} else {
|
||||
return RESULT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void CrashReportSender::ReadCheckpoint(FILE* fd) {
|
||||
char buf[128];
|
||||
if (!fgets(buf, sizeof(buf), fd) ||
|
||||
strcmp(buf, kCheckpointSignature) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fscanf_s(fd, "%d\n", &last_sent_date_) != 1) {
|
||||
last_sent_date_ = -1;
|
||||
return;
|
||||
}
|
||||
if (fscanf_s(fd, "%d\n", &reports_sent_) != 1) {
|
||||
reports_sent_ = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CrashReportSender::ReportSent(int today) {
|
||||
// Update the report stats
|
||||
if (today != last_sent_date_) {
|
||||
last_sent_date_ = today;
|
||||
reports_sent_ = 0;
|
||||
}
|
||||
++reports_sent_;
|
||||
|
||||
// Update the checkpoint file
|
||||
FILE* fd;
|
||||
if (OpenCheckpointFile(L"w", &fd) == 0) {
|
||||
fputs(kCheckpointSignature, fd);
|
||||
fprintf(fd, "%d\n", last_sent_date_);
|
||||
fprintf(fd, "%d\n", reports_sent_);
|
||||
fclose(fd);
|
||||
}
|
||||
}
|
||||
|
||||
int CrashReportSender::GetCurrentDate() const {
|
||||
SYSTEMTIME system_time;
|
||||
GetSystemTime(&system_time);
|
||||
return (system_time.wYear * 10000) + (system_time.wMonth * 100) +
|
||||
system_time.wDay;
|
||||
}
|
||||
|
||||
int CrashReportSender::OpenCheckpointFile(const wchar_t* mode, FILE** fd) {
|
||||
if (checkpoint_file_.empty()) {
|
||||
return ENOENT;
|
||||
}
|
||||
#if _MSC_VER >= 1400 // MSVC 2005/8
|
||||
return _wfopen_s(fd, checkpoint_file_.c_str(), mode);
|
||||
#else
|
||||
*fd = _wfopen(checkpoint_file_.c_str(), mode);
|
||||
if (*fd == NULL) {
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2006 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_SENDER_CRASH_REPORT_SENDER_H__
|
||||
#define CLIENT_WINDOWS_SENDER_CRASH_REPORT_SENDER_H__
|
||||
|
||||
// CrashReportSender is a "static" class which provides an API to upload
|
||||
// crash reports via HTTP(S). A crash report is formatted as a multipart POST
|
||||
// request, which contains a set of caller-supplied string key/value pairs,
|
||||
// and a minidump file to upload.
|
||||
//
|
||||
// To use this library in your project, you will need to link against
|
||||
// wininet.lib.
|
||||
|
||||
#pragma warning( push )
|
||||
// Disable exception handler warnings.
|
||||
#pragma warning( disable : 4530 )
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::wstring;
|
||||
using std::map;
|
||||
|
||||
typedef enum {
|
||||
RESULT_FAILED = 0, // Failed to communicate with the server; try later.
|
||||
RESULT_REJECTED, // Successfully sent the crash report, but the
|
||||
// server rejected it; don't resend this report.
|
||||
RESULT_SUCCEEDED, // The server accepted the crash report.
|
||||
RESULT_THROTTLED // No attempt was made to send the crash report, because
|
||||
// we exceeded the maximum reports per day.
|
||||
} ReportResult;
|
||||
|
||||
class CrashReportSender {
|
||||
public:
|
||||
// Initializes a CrashReportSender instance.
|
||||
// If checkpoint_file is non-empty, breakpad will persist crash report
|
||||
// state to this file. A checkpoint file is required for
|
||||
// set_max_reports_per_day() to function properly.
|
||||
explicit CrashReportSender(const wstring& checkpoint_file);
|
||||
~CrashReportSender() {}
|
||||
|
||||
// Sets the maximum number of crash reports that will be sent in a 24-hour
|
||||
// period. This uses the state persisted to the checkpoint file.
|
||||
// The default value of -1 means that there is no limit on reports sent.
|
||||
void set_max_reports_per_day(int reports) {
|
||||
max_reports_per_day_ = reports;
|
||||
}
|
||||
|
||||
int max_reports_per_day() const { return max_reports_per_day_; }
|
||||
|
||||
// Sends the specified files, along with the map of
|
||||
// name value pairs, as a multipart POST request to the given URL.
|
||||
// Parameter names must contain only printable ASCII characters,
|
||||
// and may not contain a quote (") character.
|
||||
// Only HTTP(S) URLs are currently supported. The return value indicates
|
||||
// the result of the operation (see above for possible results).
|
||||
// If report_code is non-NULL and the report is sent successfully (that is,
|
||||
// the return value is RESULT_SUCCEEDED), a code uniquely identifying the
|
||||
// report will be returned in report_code.
|
||||
// (Otherwise, report_code will be unchanged.)
|
||||
ReportResult SendCrashReport(const wstring& url,
|
||||
const map<wstring, wstring>& parameters,
|
||||
const map<wstring, wstring>& files,
|
||||
wstring* report_code);
|
||||
|
||||
private:
|
||||
// Reads persistent state from a checkpoint file.
|
||||
void ReadCheckpoint(FILE* fd);
|
||||
|
||||
// Called when a new report has been sent, to update the checkpoint state.
|
||||
void ReportSent(int today);
|
||||
|
||||
// Returns today's date (UTC) formatted as YYYYMMDD.
|
||||
int GetCurrentDate() const;
|
||||
|
||||
// Opens the checkpoint file with the specified mode.
|
||||
// Returns zero on success, or an error code on failure.
|
||||
int OpenCheckpointFile(const wchar_t* mode, FILE** fd);
|
||||
|
||||
wstring checkpoint_file_;
|
||||
int max_reports_per_day_;
|
||||
// The last date on which we sent a report, expressed as YYYYMMDD.
|
||||
int last_sent_date_;
|
||||
// Number of reports sent on last_sent_date_
|
||||
int reports_sent_;
|
||||
|
||||
// Disallow copy constructor and operator=
|
||||
explicit CrashReportSender(const CrashReportSender&);
|
||||
void operator=(const CrashReportSender&);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#pragma warning( pop )
|
||||
|
||||
#endif // CLIENT_WINDOWS_SENDER_CRASH_REPORT_SENDER_H__
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "client/windows/tests/crash_generation_app/abstract_class.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
Base::Base(Derived* derived)
|
||||
: derived_(derived) {
|
||||
}
|
||||
|
||||
Base::~Base() {
|
||||
derived_->DoSomething();
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4355)
|
||||
// Disable warning C4355: 'this' : used in base member initializer list.
|
||||
Derived::Derived()
|
||||
: Base(this) { // C4355
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
void Derived::DoSomething() {
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_ABSTRACT_CLASS_H__
|
||||
#define CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_ABSTRACT_CLASS_H__
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Dummy classes to help generate a pure call violation.
|
||||
|
||||
class Derived;
|
||||
|
||||
class Base {
|
||||
public:
|
||||
Base(Derived* derived);
|
||||
virtual ~Base();
|
||||
virtual void DoSomething() = 0;
|
||||
|
||||
private:
|
||||
Derived* derived_;
|
||||
};
|
||||
|
||||
class Derived : public Base {
|
||||
public:
|
||||
Derived();
|
||||
virtual void DoSomething();
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_CRASH_GENERATION_APP_H__
|
||||
+525
@@ -0,0 +1,525 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// crash_generation_app.cpp : Defines the entry point for the application.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "client/windows/tests/crash_generation_app/crash_generation_app.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
|
||||
#include "client/windows/crash_generation/client_info.h"
|
||||
#include "client/windows/crash_generation/crash_generation_server.h"
|
||||
#include "client/windows/handler/exception_handler.h"
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
|
||||
#include "client/windows/tests/crash_generation_app/abstract_class.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
const int kMaxLoadString = 100;
|
||||
const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashServices\\TestServer";
|
||||
|
||||
const DWORD kEditBoxStyles = WS_CHILD |
|
||||
WS_VISIBLE |
|
||||
WS_VSCROLL |
|
||||
ES_LEFT |
|
||||
ES_MULTILINE |
|
||||
ES_AUTOVSCROLL |
|
||||
ES_READONLY;
|
||||
|
||||
// Maximum length of a line in the edit box.
|
||||
const size_t kMaximumLineLength = 256;
|
||||
|
||||
// CS to access edit control in a thread safe way.
|
||||
static CRITICAL_SECTION* cs_edit = NULL;
|
||||
|
||||
// Edit control.
|
||||
static HWND client_status_edit_box;
|
||||
|
||||
HINSTANCE current_instance; // Current instance.
|
||||
TCHAR title[kMaxLoadString]; // Title bar text.
|
||||
TCHAR window_class[kMaxLoadString]; // Main window class name.
|
||||
|
||||
ATOM MyRegisterClass(HINSTANCE instance);
|
||||
BOOL InitInstance(HINSTANCE, int);
|
||||
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
|
||||
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
|
||||
|
||||
static size_t kCustomInfoCount = 2;
|
||||
static CustomInfoEntry kCustomInfoEntries[] = {
|
||||
CustomInfoEntry(L"prod", L"CrashTestApp"),
|
||||
CustomInfoEntry(L"ver", L"1.0"),
|
||||
};
|
||||
|
||||
static ExceptionHandler* handler = NULL;
|
||||
static CrashGenerationServer* crash_server = NULL;
|
||||
|
||||
// Registers the window class.
|
||||
//
|
||||
// This function and its usage are only necessary if you want this code
|
||||
// to be compatible with Win32 systems prior to the 'RegisterClassEx'
|
||||
// function that was added to Windows 95. It is important to call this
|
||||
// function so that the application will get 'well formed' small icons
|
||||
// associated with it.
|
||||
ATOM MyRegisterClass(HINSTANCE instance) {
|
||||
WNDCLASSEX wcex;
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wcex.lpfnWndProc = WndProc;
|
||||
wcex.cbClsExtra = 0;
|
||||
wcex.cbWndExtra = 0;
|
||||
wcex.hInstance = instance;
|
||||
wcex.hIcon = LoadIcon(instance,
|
||||
MAKEINTRESOURCE(IDI_CRASHGENERATIONAPP));
|
||||
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
|
||||
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP);
|
||||
wcex.lpszClassName = window_class;
|
||||
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
|
||||
|
||||
return RegisterClassEx(&wcex);
|
||||
}
|
||||
|
||||
// Saves instance handle and creates main window
|
||||
//
|
||||
// In this function, we save the instance handle in a global variable and
|
||||
// create and display the main program window.
|
||||
BOOL InitInstance(HINSTANCE instance, int command_show) {
|
||||
current_instance = instance;
|
||||
HWND wnd = CreateWindow(window_class,
|
||||
title,
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT,
|
||||
0,
|
||||
CW_USEDEFAULT,
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
instance,
|
||||
NULL);
|
||||
|
||||
if (!wnd) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ShowWindow(wnd, command_show);
|
||||
UpdateWindow(wnd);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void AppendTextToEditBox(TCHAR* text) {
|
||||
EnterCriticalSection(cs_edit);
|
||||
SYSTEMTIME current_time;
|
||||
GetLocalTime(¤t_time);
|
||||
TCHAR line[kMaximumLineLength];
|
||||
int result = swprintf_s(line,
|
||||
kMaximumLineLength,
|
||||
L"[%.2d-%.2d-%.4d %.2d:%.2d:%.2d] %s",
|
||||
current_time.wMonth,
|
||||
current_time.wDay,
|
||||
current_time.wYear,
|
||||
current_time.wHour,
|
||||
current_time.wMinute,
|
||||
current_time.wSecond,
|
||||
text);
|
||||
|
||||
if (result == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int length = GetWindowTextLength(client_status_edit_box);
|
||||
SendMessage(client_status_edit_box,
|
||||
EM_SETSEL,
|
||||
(WPARAM)length,
|
||||
(LPARAM)length);
|
||||
SendMessage(client_status_edit_box,
|
||||
EM_REPLACESEL,
|
||||
(WPARAM)FALSE,
|
||||
(LPARAM)line);
|
||||
LeaveCriticalSection(cs_edit);
|
||||
}
|
||||
|
||||
static DWORD WINAPI AppendTextWorker(void* context) {
|
||||
TCHAR* text = reinterpret_cast<TCHAR*>(context);
|
||||
|
||||
AppendTextToEditBox(text);
|
||||
delete[] text;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ShowDumpResults(const wchar_t* dump_path,
|
||||
const wchar_t* minidump_id,
|
||||
void* context,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion,
|
||||
bool succeeded) {
|
||||
TCHAR* text = new TCHAR[kMaximumLineLength];
|
||||
text[0] = _T('\0');
|
||||
int result = swprintf_s(text,
|
||||
kMaximumLineLength,
|
||||
TEXT("Dump generation request %s\r\n"),
|
||||
succeeded ? TEXT("succeeded") : TEXT("failed"));
|
||||
if (result == -1) {
|
||||
delete [] text;
|
||||
}
|
||||
|
||||
QueueUserWorkItem(AppendTextWorker, text, WT_EXECUTEDEFAULT);
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
static void ShowClientConnected(void* context,
|
||||
const ClientInfo* client_info) {
|
||||
TCHAR* line = new TCHAR[kMaximumLineLength];
|
||||
line[0] = _T('\0');
|
||||
int result = swprintf_s(line,
|
||||
kMaximumLineLength,
|
||||
L"Client connected:\t\t%d\r\n",
|
||||
client_info->pid());
|
||||
|
||||
if (result == -1) {
|
||||
delete[] line;
|
||||
return;
|
||||
}
|
||||
|
||||
QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
|
||||
}
|
||||
|
||||
static void ShowClientCrashed(void* context,
|
||||
const ClientInfo* client_info,
|
||||
const wstring* dump_path) {
|
||||
TCHAR* line = new TCHAR[kMaximumLineLength];
|
||||
line[0] = _T('\0');
|
||||
int result = swprintf_s(line,
|
||||
kMaximumLineLength,
|
||||
TEXT("Client requested dump:\t%d\r\n"),
|
||||
client_info->pid());
|
||||
|
||||
if (result == -1) {
|
||||
delete[] line;
|
||||
return;
|
||||
}
|
||||
|
||||
QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
|
||||
|
||||
CustomClientInfo custom_info = client_info->GetCustomInfo();
|
||||
if (custom_info.count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
wstring str_line;
|
||||
for (size_t i = 0; i < custom_info.count; ++i) {
|
||||
if (i > 0) {
|
||||
str_line += L", ";
|
||||
}
|
||||
str_line += custom_info.entries[i].name;
|
||||
str_line += L": ";
|
||||
str_line += custom_info.entries[i].value;
|
||||
}
|
||||
|
||||
line = new TCHAR[kMaximumLineLength];
|
||||
line[0] = _T('\0');
|
||||
result = swprintf_s(line,
|
||||
kMaximumLineLength,
|
||||
L"%s\n",
|
||||
str_line.c_str());
|
||||
if (result == -1) {
|
||||
delete[] line;
|
||||
return;
|
||||
}
|
||||
QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
|
||||
}
|
||||
|
||||
static void ShowClientExited(void* context,
|
||||
const ClientInfo* client_info) {
|
||||
TCHAR* line = new TCHAR[kMaximumLineLength];
|
||||
line[0] = _T('\0');
|
||||
int result = swprintf_s(line,
|
||||
kMaximumLineLength,
|
||||
TEXT("Client exited:\t\t%d\r\n"),
|
||||
client_info->pid());
|
||||
|
||||
if (result == -1) {
|
||||
delete[] line;
|
||||
return;
|
||||
}
|
||||
|
||||
QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
|
||||
}
|
||||
|
||||
void CrashServerStart() {
|
||||
// Do not create another instance of the server.
|
||||
if (crash_server) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring dump_path = L"C:\\Dumps\\";
|
||||
|
||||
if (_wmkdir(dump_path.c_str()) && (errno != EEXIST)) {
|
||||
MessageBoxW(NULL, L"Unable to create dump directory", L"Dumper", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
crash_server = new CrashGenerationServer(kPipeName,
|
||||
NULL,
|
||||
ShowClientConnected,
|
||||
NULL,
|
||||
ShowClientCrashed,
|
||||
NULL,
|
||||
ShowClientExited,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
true,
|
||||
&dump_path);
|
||||
|
||||
if (!crash_server->Start()) {
|
||||
MessageBoxW(NULL, L"Unable to start server", L"Dumper", MB_OK);
|
||||
delete crash_server;
|
||||
crash_server = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CrashServerStop() {
|
||||
delete crash_server;
|
||||
crash_server = NULL;
|
||||
}
|
||||
|
||||
void DerefZeroCrash() {
|
||||
int* x = 0;
|
||||
*x = 1;
|
||||
}
|
||||
|
||||
void InvalidParamCrash() {
|
||||
printf(NULL);
|
||||
}
|
||||
|
||||
void PureCallCrash() {
|
||||
Derived derived;
|
||||
}
|
||||
|
||||
void RequestDump() {
|
||||
if (!handler->WriteMinidump()) {
|
||||
MessageBoxW(NULL, L"Dump request failed", L"Dumper", MB_OK);
|
||||
}
|
||||
kCustomInfoEntries[1].set_value(L"1.1");
|
||||
}
|
||||
|
||||
void CleanUp() {
|
||||
if (cs_edit) {
|
||||
DeleteCriticalSection(cs_edit);
|
||||
delete cs_edit;
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
delete handler;
|
||||
}
|
||||
|
||||
if (crash_server) {
|
||||
delete crash_server;
|
||||
}
|
||||
}
|
||||
|
||||
// Processes messages for the main window.
|
||||
//
|
||||
// WM_COMMAND - process the application menu.
|
||||
// WM_PAINT - Paint the main window.
|
||||
// WM_DESTROY - post a quit message and return.
|
||||
LRESULT CALLBACK WndProc(HWND wnd,
|
||||
UINT message,
|
||||
WPARAM w_param,
|
||||
LPARAM l_param) {
|
||||
int message_id;
|
||||
int message_event;
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc;
|
||||
|
||||
HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(wnd, GWLP_HINSTANCE);
|
||||
|
||||
switch (message) {
|
||||
case WM_COMMAND:
|
||||
// Parse the menu selections.
|
||||
message_id = LOWORD(w_param);
|
||||
message_event = HIWORD(w_param);
|
||||
switch (message_id) {
|
||||
case IDM_ABOUT:
|
||||
DialogBox(current_instance,
|
||||
MAKEINTRESOURCE(IDD_ABOUTBOX),
|
||||
wnd,
|
||||
About);
|
||||
break;
|
||||
case IDM_EXIT:
|
||||
DestroyWindow(wnd);
|
||||
break;
|
||||
case ID_SERVER_START:
|
||||
CrashServerStart();
|
||||
break;
|
||||
case ID_SERVER_STOP:
|
||||
CrashServerStop();
|
||||
break;
|
||||
case ID_CLIENT_DEREFZERO:
|
||||
DerefZeroCrash();
|
||||
break;
|
||||
case ID_CLIENT_INVALIDPARAM:
|
||||
InvalidParamCrash();
|
||||
break;
|
||||
case ID_CLIENT_PURECALL:
|
||||
PureCallCrash();
|
||||
break;
|
||||
case ID_CLIENT_REQUESTEXPLICITDUMP:
|
||||
RequestDump();
|
||||
break;
|
||||
default:
|
||||
return DefWindowProc(wnd, message, w_param, l_param);
|
||||
}
|
||||
break;
|
||||
case WM_CREATE:
|
||||
client_status_edit_box = CreateWindow(TEXT("EDIT"),
|
||||
NULL,
|
||||
kEditBoxStyles,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
wnd,
|
||||
NULL,
|
||||
instance,
|
||||
NULL);
|
||||
break;
|
||||
case WM_SIZE:
|
||||
// Make the edit control the size of the window's client area.
|
||||
MoveWindow(client_status_edit_box,
|
||||
0,
|
||||
0,
|
||||
LOWORD(l_param), // width of client area.
|
||||
HIWORD(l_param), // height of client area.
|
||||
TRUE); // repaint window.
|
||||
break;
|
||||
case WM_SETFOCUS:
|
||||
SetFocus(client_status_edit_box);
|
||||
break;
|
||||
case WM_PAINT:
|
||||
hdc = BeginPaint(wnd, &ps);
|
||||
EndPaint(wnd, &ps);
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
CleanUp();
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
default:
|
||||
return DefWindowProc(wnd, message, w_param, l_param);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Message handler for about box.
|
||||
INT_PTR CALLBACK About(HWND dlg,
|
||||
UINT message,
|
||||
WPARAM w_param,
|
||||
LPARAM l_param) {
|
||||
UNREFERENCED_PARAMETER(l_param);
|
||||
switch (message) {
|
||||
case WM_INITDIALOG:
|
||||
return (INT_PTR)TRUE;
|
||||
|
||||
case WM_COMMAND:
|
||||
if (LOWORD(w_param) == IDOK || LOWORD(w_param) == IDCANCEL) {
|
||||
EndDialog(dlg, LOWORD(w_param));
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return (INT_PTR)FALSE;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
int APIENTRY _tWinMain(HINSTANCE instance,
|
||||
HINSTANCE previous_instance,
|
||||
LPTSTR command_line,
|
||||
int command_show) {
|
||||
using namespace google_breakpad;
|
||||
|
||||
UNREFERENCED_PARAMETER(previous_instance);
|
||||
UNREFERENCED_PARAMETER(command_line);
|
||||
|
||||
cs_edit = new CRITICAL_SECTION();
|
||||
InitializeCriticalSection(cs_edit);
|
||||
|
||||
CustomClientInfo custom_info = {kCustomInfoEntries, kCustomInfoCount};
|
||||
|
||||
CrashServerStart();
|
||||
// This is needed for CRT to not show dialog for invalid param
|
||||
// failures and instead let the code handle it.
|
||||
_CrtSetReportMode(_CRT_ASSERT, 0);
|
||||
handler = new ExceptionHandler(L"C:\\dumps\\",
|
||||
NULL,
|
||||
google_breakpad::ShowDumpResults,
|
||||
NULL,
|
||||
ExceptionHandler::HANDLER_ALL,
|
||||
MiniDumpNormal,
|
||||
kPipeName,
|
||||
&custom_info);
|
||||
|
||||
// Initialize global strings.
|
||||
LoadString(instance, IDS_APP_TITLE, title, kMaxLoadString);
|
||||
LoadString(instance,
|
||||
IDC_CRASHGENERATIONAPP,
|
||||
window_class,
|
||||
kMaxLoadString);
|
||||
MyRegisterClass(instance);
|
||||
|
||||
// Perform application initialization.
|
||||
if (!InitInstance(instance, command_show)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HACCEL accel_table = LoadAccelerators(
|
||||
instance,
|
||||
MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP));
|
||||
|
||||
// Main message loop.
|
||||
MSG msg;
|
||||
while (GetMessage(&msg, NULL, 0, 0)) {
|
||||
if (!TranslateAccelerator(msg.hwnd, accel_table, &msg)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<int>(msg.wParam);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_CRASH_GENERATION_APP_H__
|
||||
#define CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_CRASH_GENERATION_APP_H__
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
#endif // CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_CRASH_GENERATION_APP_H__
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
+144
@@ -0,0 +1,144 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#define APSTUDIO_HIDDEN_SYMBOLS
|
||||
#include "windows.h"
|
||||
#undef APSTUDIO_HIDDEN_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (U.S.) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
#ifdef _WIN32
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
#pragma code_page(1252)
|
||||
#endif //_WIN32
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_CRASHGENERATIONAPP ICON "crash_generation_app.ico"
|
||||
IDI_SMALL ICON "small.ico"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Menu
|
||||
//
|
||||
|
||||
IDC_CRASHGENERATIONAPP MENU
|
||||
BEGIN
|
||||
POPUP "&File"
|
||||
BEGIN
|
||||
MENUITEM "E&xit", IDM_EXIT
|
||||
END
|
||||
POPUP "&Server"
|
||||
BEGIN
|
||||
MENUITEM "&Start", ID_SERVER_START
|
||||
MENUITEM "S&top", ID_SERVER_STOP
|
||||
END
|
||||
POPUP "&Client"
|
||||
BEGIN
|
||||
MENUITEM "&Deref Zero", ID_CLIENT_DEREFZERO
|
||||
MENUITEM "&Invalid Param", ID_CLIENT_INVALIDPARAM
|
||||
MENUITEM "&Pure Call", ID_CLIENT_PURECALL
|
||||
MENUITEM "&Request Dump", ID_CLIENT_REQUESTEXPLICITDUMP
|
||||
END
|
||||
END
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Accelerator
|
||||
//
|
||||
|
||||
IDC_CRASHGENERATIONAPP ACCELERATORS
|
||||
BEGIN
|
||||
"?", IDM_ABOUT, ASCII, ALT
|
||||
"/", IDM_ABOUT, ASCII, ALT
|
||||
END
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Dialog
|
||||
//
|
||||
|
||||
IDD_ABOUTBOX DIALOG 22, 17, 230, 75
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION "About"
|
||||
FONT 8, "System"
|
||||
BEGIN
|
||||
ICON IDI_CRASHGENERATIONAPP,IDC_MYICON,14,9,16,16
|
||||
LTEXT "CrashGenerationApp Version 1.0",IDC_STATIC,49,10,119,8,SS_NOPREFIX
|
||||
LTEXT "Copyright (C) 2008",IDC_STATIC,49,20,119,8
|
||||
DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
|
||||
END
|
||||
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
|
||||
"#include ""windows.h""\r\n"
|
||||
"#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// String Table
|
||||
//
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_APP_TITLE "CrashGenerationApp"
|
||||
IDC_CRASHGENERATIONAPP "CRASHGENERATIONAPP"
|
||||
END
|
||||
|
||||
#endif // English (U.S.) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2008 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// PreCompile.h : include file for standard system include files,
|
||||
// or project specific include files that are used frequently, but
|
||||
// are changed infrequently
|
||||
//
|
||||
|
||||
#ifndef CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_RESOURCE_H__
|
||||
#define CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_RESOURCE_H__
|
||||
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by crash_generation_app.rc
|
||||
//
|
||||
#define IDC_MYICON 2
|
||||
#define IDD_CRASHGENERATIONAPP_DIALOG 102
|
||||
#define IDS_APP_TITLE 103
|
||||
#define IDD_ABOUTBOX 103
|
||||
#define IDM_ABOUT 104
|
||||
#define IDM_EXIT 105
|
||||
#define IDI_CRASHGENERATIONAPP 107
|
||||
#define IDI_SMALL 108
|
||||
#define IDC_CRASHGENERATIONAPP 109
|
||||
#define IDR_MAINFRAME 128
|
||||
#define ID_SERVER_START 32771
|
||||
#define ID_SERVER_STOP 32772
|
||||
#define ID_CLIENT_INVALIDPARAM 32773
|
||||
#define ID_CLIENT_ASSERTFAILURE 32774
|
||||
#define ID_CLIENT_DEREFZERO 32775
|
||||
#define ID_CLIENT_PURECALL 32777
|
||||
#define ID_CLIENT_REQUESTEXPLICITDUMP 32778
|
||||
#define IDC_STATIC -1
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NO_MFC 1
|
||||
#define _APS_NEXT_RESOURCE_VALUE 129
|
||||
#define _APS_NEXT_COMMAND_VALUE 32780
|
||||
#define _APS_NEXT_CONTROL_VALUE 1000
|
||||
#define _APS_NEXT_SYMED_VALUE 110
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_RESOURCE_H__
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
+306
@@ -0,0 +1,306 @@
|
||||
// Copyright 2010 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/windows/crash_generation/crash_generation_server.h"
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
|
||||
using testing::_;
|
||||
|
||||
namespace {
|
||||
|
||||
const wchar_t kPipeName[] =
|
||||
L"\\\\.\\pipe\\CrashGenerationServerTest\\TestCaseServer";
|
||||
|
||||
const DWORD kPipeDesiredAccess = FILE_READ_DATA |
|
||||
FILE_WRITE_DATA |
|
||||
FILE_WRITE_ATTRIBUTES;
|
||||
|
||||
const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION |
|
||||
SECURITY_SQOS_PRESENT;
|
||||
|
||||
const DWORD kPipeMode = PIPE_READMODE_MESSAGE;
|
||||
|
||||
#define arraysize(f) (sizeof(f) / sizeof(*f))
|
||||
const google_breakpad::CustomInfoEntry kCustomInfoEntries[] = {
|
||||
google_breakpad::CustomInfoEntry(L"prod", L"CrashGenerationServerTest"),
|
||||
google_breakpad::CustomInfoEntry(L"ver", L"1.0"),
|
||||
};
|
||||
|
||||
class CrashGenerationServerTest : public ::testing::Test {
|
||||
public:
|
||||
CrashGenerationServerTest()
|
||||
: crash_generation_server_(kPipeName,
|
||||
NULL,
|
||||
CallOnClientConnected, &mock_callbacks_,
|
||||
CallOnClientDumpRequested, &mock_callbacks_,
|
||||
CallOnClientExited, &mock_callbacks_,
|
||||
CallOnClientUploadRequested, &mock_callbacks_,
|
||||
false,
|
||||
NULL),
|
||||
thread_id_(0),
|
||||
exception_pointers_(NULL) {
|
||||
memset(&assert_info_, 0, sizeof(assert_info_));
|
||||
}
|
||||
|
||||
protected:
|
||||
class MockCrashGenerationServerCallbacks {
|
||||
public:
|
||||
MOCK_METHOD1(OnClientConnected,
|
||||
void(const google_breakpad::ClientInfo* client_info));
|
||||
MOCK_METHOD2(OnClientDumpRequested,
|
||||
void(const google_breakpad::ClientInfo* client_info,
|
||||
const std::wstring* file_path));
|
||||
MOCK_METHOD1(OnClientExited,
|
||||
void(const google_breakpad::ClientInfo* client_info));
|
||||
MOCK_METHOD1(OnClientUploadRequested,
|
||||
void(const DWORD crash_id));
|
||||
};
|
||||
|
||||
enum ClientFault {
|
||||
NO_FAULT,
|
||||
CLOSE_AFTER_CONNECT,
|
||||
SEND_INVALID_REGISTRATION,
|
||||
TRUNCATE_REGISTRATION,
|
||||
CLOSE_AFTER_REGISTRATION,
|
||||
RESPONSE_BUFFER_TOO_SMALL,
|
||||
CLOSE_AFTER_RESPONSE,
|
||||
SEND_INVALID_ACK
|
||||
};
|
||||
|
||||
void SetUp() {
|
||||
ASSERT_TRUE(crash_generation_server_.Start());
|
||||
}
|
||||
|
||||
void FaultyClient(ClientFault fault_type) {
|
||||
HANDLE pipe = CreateFile(kPipeName,
|
||||
kPipeDesiredAccess,
|
||||
0,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
kPipeFlagsAndAttributes,
|
||||
NULL);
|
||||
|
||||
if (pipe == INVALID_HANDLE_VALUE) {
|
||||
ASSERT_EQ(ERROR_PIPE_BUSY, GetLastError());
|
||||
|
||||
// Cannot continue retrying if wait on pipe fails.
|
||||
ASSERT_TRUE(WaitNamedPipe(kPipeName, 500));
|
||||
|
||||
pipe = CreateFile(kPipeName,
|
||||
kPipeDesiredAccess,
|
||||
0,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
kPipeFlagsAndAttributes,
|
||||
NULL);
|
||||
}
|
||||
|
||||
ASSERT_NE(pipe, INVALID_HANDLE_VALUE);
|
||||
|
||||
DWORD mode = kPipeMode;
|
||||
ASSERT_TRUE(SetNamedPipeHandleState(pipe, &mode, NULL, NULL));
|
||||
|
||||
DoFaultyClient(fault_type, pipe);
|
||||
|
||||
CloseHandle(pipe);
|
||||
}
|
||||
|
||||
void DoTestFault(ClientFault fault) {
|
||||
EXPECT_CALL(mock_callbacks_, OnClientConnected(_)).Times(0);
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
|
||||
|
||||
EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT));
|
||||
|
||||
// Slight hack. The OnClientConnected is only invoked after the ack is
|
||||
// received by the server. At that point, the FaultyClient call has already
|
||||
// returned. The best way to wait until the server is done handling that is
|
||||
// to send one more ping, whose processing will be blocked by delivery of
|
||||
// the OnClientConnected message.
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
|
||||
}
|
||||
|
||||
MockCrashGenerationServerCallbacks mock_callbacks_;
|
||||
|
||||
private:
|
||||
// Depends on the caller to successfully open the pipe before invocation and
|
||||
// to close it immediately afterwards.
|
||||
void DoFaultyClient(ClientFault fault_type, HANDLE pipe) {
|
||||
if (fault_type == CLOSE_AFTER_CONNECT) {
|
||||
return;
|
||||
}
|
||||
|
||||
google_breakpad::CustomClientInfo custom_info = {kCustomInfoEntries,
|
||||
arraysize(kCustomInfoEntries)};
|
||||
|
||||
google_breakpad::ProtocolMessage msg(
|
||||
fault_type == SEND_INVALID_REGISTRATION ?
|
||||
google_breakpad::MESSAGE_TAG_NONE :
|
||||
google_breakpad::MESSAGE_TAG_REGISTRATION_REQUEST,
|
||||
GetCurrentProcessId(),
|
||||
MiniDumpNormal,
|
||||
&thread_id_,
|
||||
&exception_pointers_,
|
||||
&assert_info_,
|
||||
custom_info,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
|
||||
ASSERT_TRUE(WriteFile(pipe,
|
||||
&msg,
|
||||
fault_type == TRUNCATE_REGISTRATION ?
|
||||
sizeof(msg) / 2 : sizeof(msg),
|
||||
&bytes_count,
|
||||
NULL));
|
||||
|
||||
if (fault_type == CLOSE_AFTER_REGISTRATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
google_breakpad::ProtocolMessage reply;
|
||||
|
||||
if (!ReadFile(pipe,
|
||||
&reply,
|
||||
fault_type == RESPONSE_BUFFER_TOO_SMALL ?
|
||||
sizeof(google_breakpad::ProtocolMessage) / 2 :
|
||||
sizeof(google_breakpad::ProtocolMessage),
|
||||
&bytes_count,
|
||||
NULL)) {
|
||||
switch (fault_type) {
|
||||
case TRUNCATE_REGISTRATION:
|
||||
case RESPONSE_BUFFER_TOO_SMALL:
|
||||
case SEND_INVALID_REGISTRATION:
|
||||
return;
|
||||
|
||||
default:
|
||||
FAIL() << "Unexpectedly failed to register.";
|
||||
}
|
||||
}
|
||||
|
||||
if (fault_type == CLOSE_AFTER_RESPONSE) {
|
||||
return;
|
||||
}
|
||||
|
||||
google_breakpad::ProtocolMessage ack_msg;
|
||||
ack_msg.tag = google_breakpad::MESSAGE_TAG_REGISTRATION_ACK;
|
||||
|
||||
ASSERT_TRUE(WriteFile(pipe,
|
||||
&ack_msg,
|
||||
SEND_INVALID_ACK ?
|
||||
sizeof(ack_msg) : sizeof(ack_msg) / 2,
|
||||
&bytes_count,
|
||||
NULL));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void CallOnClientConnected(
|
||||
void* context, const google_breakpad::ClientInfo* client_info) {
|
||||
static_cast<MockCrashGenerationServerCallbacks*>(context)->
|
||||
OnClientConnected(client_info);
|
||||
}
|
||||
|
||||
static void CallOnClientDumpRequested(
|
||||
void* context,
|
||||
const google_breakpad::ClientInfo* client_info,
|
||||
const std::wstring* file_path) {
|
||||
static_cast<MockCrashGenerationServerCallbacks*>(context)->
|
||||
OnClientDumpRequested(client_info, file_path);
|
||||
}
|
||||
|
||||
static void CallOnClientExited(
|
||||
void* context, const google_breakpad::ClientInfo* client_info) {
|
||||
static_cast<MockCrashGenerationServerCallbacks*>(context)->
|
||||
OnClientExited(client_info);
|
||||
}
|
||||
|
||||
static void CallOnClientUploadRequested(void* context, const DWORD crash_id) {
|
||||
static_cast<MockCrashGenerationServerCallbacks*>(context)->
|
||||
OnClientUploadRequested(crash_id);
|
||||
}
|
||||
|
||||
DWORD thread_id_;
|
||||
EXCEPTION_POINTERS* exception_pointers_;
|
||||
MDRawAssertionInfo assert_info_;
|
||||
|
||||
google_breakpad::CrashGenerationServer crash_generation_server_;
|
||||
};
|
||||
|
||||
TEST_F(CrashGenerationServerTest, PingServerTest) {
|
||||
DoTestFault(CLOSE_AFTER_CONNECT);
|
||||
}
|
||||
|
||||
TEST_F(CrashGenerationServerTest, InvalidRegistration) {
|
||||
DoTestFault(SEND_INVALID_REGISTRATION);
|
||||
}
|
||||
|
||||
TEST_F(CrashGenerationServerTest, TruncateRegistration) {
|
||||
DoTestFault(TRUNCATE_REGISTRATION);
|
||||
}
|
||||
|
||||
TEST_F(CrashGenerationServerTest, CloseAfterRegistration) {
|
||||
DoTestFault(CLOSE_AFTER_REGISTRATION);
|
||||
}
|
||||
|
||||
TEST_F(CrashGenerationServerTest, ResponseBufferTooSmall) {
|
||||
DoTestFault(RESPONSE_BUFFER_TOO_SMALL);
|
||||
}
|
||||
|
||||
TEST_F(CrashGenerationServerTest, CloseAfterResponse) {
|
||||
DoTestFault(CLOSE_AFTER_RESPONSE);
|
||||
}
|
||||
|
||||
// It turns out that, as long as you send one byte, the ACK is accepted and
|
||||
// registration succeeds.
|
||||
TEST_F(CrashGenerationServerTest, SendInvalidAck) {
|
||||
EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(SEND_INVALID_ACK));
|
||||
|
||||
// See DoTestFault for an explanation of this line
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
|
||||
|
||||
EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT));
|
||||
|
||||
// See DoTestFault for an explanation of this line
|
||||
ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2010 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/windows/unittests/dump_analysis.h" // NOLINT
|
||||
|
||||
DumpAnalysis::~DumpAnalysis() {
|
||||
if (dump_file_view_ != NULL) {
|
||||
EXPECT_TRUE(::UnmapViewOfFile(dump_file_view_));
|
||||
::CloseHandle(dump_file_mapping_);
|
||||
dump_file_mapping_ = NULL;
|
||||
}
|
||||
|
||||
if (dump_file_handle_ != NULL) {
|
||||
::CloseHandle(dump_file_handle_);
|
||||
dump_file_handle_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void DumpAnalysis::EnsureDumpMapped() {
|
||||
if (dump_file_view_ == NULL) {
|
||||
dump_file_handle_ = ::CreateFile(dump_file_.c_str(),
|
||||
GENERIC_READ,
|
||||
0,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
NULL);
|
||||
ASSERT_TRUE(dump_file_handle_ != NULL);
|
||||
ASSERT_TRUE(dump_file_mapping_ == NULL);
|
||||
|
||||
dump_file_mapping_ = ::CreateFileMapping(dump_file_handle_,
|
||||
NULL,
|
||||
PAGE_READONLY,
|
||||
0,
|
||||
0,
|
||||
NULL);
|
||||
ASSERT_TRUE(dump_file_mapping_ != NULL);
|
||||
|
||||
dump_file_view_ = ::MapViewOfFile(dump_file_mapping_,
|
||||
FILE_MAP_READ,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
ASSERT_TRUE(dump_file_view_ != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool DumpAnalysis::HasTebs() const {
|
||||
MINIDUMP_THREAD_LIST* thread_list = NULL;
|
||||
size_t thread_list_size = GetStream(ThreadListStream, &thread_list);
|
||||
|
||||
if (thread_list_size > 0 && thread_list != NULL) {
|
||||
for (ULONG i = 0; i < thread_list->NumberOfThreads; ++i) {
|
||||
if (!HasMemory(thread_list->Threads[i].Teb))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// No thread list, no TEB info.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DumpAnalysis::HasPeb() const {
|
||||
MINIDUMP_THREAD_LIST* thread_list = NULL;
|
||||
size_t thread_list_size = GetStream(ThreadListStream, &thread_list);
|
||||
|
||||
if (thread_list_size > 0 && thread_list != NULL &&
|
||||
thread_list->NumberOfThreads > 0) {
|
||||
FakeTEB* teb = NULL;
|
||||
if (!HasMemory(thread_list->Threads[0].Teb, &teb))
|
||||
return false;
|
||||
|
||||
return HasMemory(teb->peb);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DumpAnalysis::HasStream(ULONG stream_number) const {
|
||||
void* stream = NULL;
|
||||
size_t stream_size = GetStreamImpl(stream_number, &stream);
|
||||
return stream_size > 0 && stream != NULL;
|
||||
}
|
||||
|
||||
size_t DumpAnalysis::GetStreamImpl(ULONG stream_number, void** stream) const {
|
||||
MINIDUMP_DIRECTORY* directory = NULL;
|
||||
ULONG memory_list_size = 0;
|
||||
BOOL ret = ::MiniDumpReadDumpStream(dump_file_view_,
|
||||
stream_number,
|
||||
&directory,
|
||||
stream,
|
||||
&memory_list_size);
|
||||
|
||||
return ret ? memory_list_size : 0;
|
||||
}
|
||||
|
||||
bool DumpAnalysis::HasMemoryImpl(const void* addr_in, size_t structuresize,
|
||||
void** structure) const {
|
||||
uintptr_t address = reinterpret_cast<uintptr_t>(addr_in);
|
||||
MINIDUMP_MEMORY_LIST* memory_list = NULL;
|
||||
size_t memory_list_size = GetStream(MemoryListStream, &memory_list);
|
||||
if (memory_list_size > 0 && memory_list != NULL) {
|
||||
for (ULONG i = 0; i < memory_list->NumberOfMemoryRanges; ++i) {
|
||||
MINIDUMP_MEMORY_DESCRIPTOR& descr = memory_list->MemoryRanges[i];
|
||||
const uintptr_t range_start =
|
||||
static_cast<uintptr_t>(descr.StartOfMemoryRange);
|
||||
uintptr_t range_end = range_start + descr.Memory.DataSize;
|
||||
|
||||
if (address >= range_start &&
|
||||
address + structuresize < range_end) {
|
||||
// The start address falls in the range, and the end address is
|
||||
// in bounds, return a pointer to the structure if requested.
|
||||
if (structure != NULL)
|
||||
*structure = RVA_TO_ADDR(dump_file_view_, descr.Memory.Rva);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find the range in a MINIDUMP_MEMORY_LIST, so maybe this
|
||||
// is a full dump using MINIDUMP_MEMORY64_LIST with all the memory at the
|
||||
// end of the dump file.
|
||||
MINIDUMP_MEMORY64_LIST* memory64_list = NULL;
|
||||
memory_list_size = GetStream(Memory64ListStream, &memory64_list);
|
||||
if (memory_list_size > 0 && memory64_list != NULL) {
|
||||
// Keep track of where the current descriptor maps to.
|
||||
RVA64 curr_rva = memory64_list->BaseRva;
|
||||
for (ULONG i = 0; i < memory64_list->NumberOfMemoryRanges; ++i) {
|
||||
MINIDUMP_MEMORY_DESCRIPTOR64& descr = memory64_list->MemoryRanges[i];
|
||||
uintptr_t range_start =
|
||||
static_cast<uintptr_t>(descr.StartOfMemoryRange);
|
||||
uintptr_t range_end = range_start + static_cast<size_t>(descr.DataSize);
|
||||
|
||||
if (address >= range_start &&
|
||||
address + structuresize < range_end) {
|
||||
// The start address falls in the range, and the end address is
|
||||
// in bounds, return a pointer to the structure if requested.
|
||||
if (structure != NULL)
|
||||
*structure = RVA_TO_ADDR(dump_file_view_, curr_rva);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Advance the current RVA.
|
||||
curr_rva += descr.DataSize;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2010 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_
|
||||
#define CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_
|
||||
|
||||
#include "client/windows/crash_generation/minidump_generator.h"
|
||||
|
||||
// Convenience to get to the PEB pointer in a TEB.
|
||||
struct FakeTEB {
|
||||
char dummy[0x30];
|
||||
void* peb;
|
||||
};
|
||||
|
||||
class DumpAnalysis {
|
||||
public:
|
||||
explicit DumpAnalysis(const std::wstring& file_path)
|
||||
: dump_file_(file_path), dump_file_view_(NULL), dump_file_mapping_(NULL),
|
||||
dump_file_handle_(NULL) {
|
||||
EnsureDumpMapped();
|
||||
}
|
||||
~DumpAnalysis();
|
||||
|
||||
bool HasStream(ULONG stream_number) const;
|
||||
|
||||
// This is template to keep type safety in the front, but we end up casting
|
||||
// to void** inside the implementation to pass the pointer to Win32. So
|
||||
// casting here is considered safe.
|
||||
template <class StreamType>
|
||||
size_t GetStream(ULONG stream_number, StreamType** stream) const {
|
||||
return GetStreamImpl(stream_number, reinterpret_cast<void**>(stream));
|
||||
}
|
||||
|
||||
bool HasTebs() const;
|
||||
bool HasPeb() const;
|
||||
bool HasMemory(ULONG64 address) const {
|
||||
return HasMemory<BYTE>(address, NULL);
|
||||
}
|
||||
|
||||
bool HasMemory(const void* address) const {
|
||||
return HasMemory<BYTE>(address, NULL);
|
||||
}
|
||||
|
||||
template <class StructureType>
|
||||
bool HasMemory(ULONG64 address, StructureType** structure = NULL) const {
|
||||
// We can't cope with 64 bit addresses for now.
|
||||
if (address > 0xFFFFFFFFUL)
|
||||
return false;
|
||||
|
||||
return HasMemory(reinterpret_cast<void*>(address), structure);
|
||||
}
|
||||
|
||||
template <class StructureType>
|
||||
bool HasMemory(const void* addr_in, StructureType** structure = NULL) const {
|
||||
return HasMemoryImpl(addr_in, sizeof(StructureType),
|
||||
reinterpret_cast<void**>(structure));
|
||||
}
|
||||
|
||||
protected:
|
||||
void EnsureDumpMapped();
|
||||
|
||||
HANDLE dump_file_mapping_;
|
||||
HANDLE dump_file_handle_;
|
||||
void* dump_file_view_;
|
||||
std::wstring dump_file_;
|
||||
|
||||
private:
|
||||
// This is the implementation of GetStream<>.
|
||||
size_t GetStreamImpl(ULONG stream_number, void** stream) const;
|
||||
|
||||
// This is the implementation of HasMemory<>.
|
||||
bool HasMemoryImpl(const void* addr_in, size_t pointersize,
|
||||
void** structure) const;
|
||||
};
|
||||
|
||||
#endif // CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_
|
||||
+590
@@ -0,0 +1,590 @@
|
||||
// Copyright 2009 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <strsafe.h>
|
||||
#include <objbase.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/windows/crash_generation/crash_generation_server.h"
|
||||
#include "client/windows/handler/exception_handler.h"
|
||||
#include "client/windows/unittests/exception_handler_test.h"
|
||||
#include "common/windows/string_utils-inl.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using std::wstring;
|
||||
using namespace google_breakpad;
|
||||
|
||||
const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer";
|
||||
const char kSuccessIndicator[] = "success";
|
||||
const char kFailureIndicator[] = "failure";
|
||||
|
||||
// Utility function to test for a path's existence.
|
||||
BOOL DoesPathExist(const TCHAR* path_name);
|
||||
|
||||
enum OutOfProcGuarantee {
|
||||
OUT_OF_PROC_GUARANTEED,
|
||||
OUT_OF_PROC_BEST_EFFORT,
|
||||
};
|
||||
|
||||
class ExceptionHandlerDeathTest : public ::testing::Test {
|
||||
protected:
|
||||
// Member variable for each test that they can use
|
||||
// for temporary storage.
|
||||
TCHAR temp_path_[MAX_PATH];
|
||||
// Actually constructs a temp path name.
|
||||
virtual void SetUp();
|
||||
// A helper method that tests can use to crash.
|
||||
void DoCrashAccessViolation(const OutOfProcGuarantee out_of_proc_guarantee);
|
||||
void DoCrashPureVirtualCall();
|
||||
};
|
||||
|
||||
void ExceptionHandlerDeathTest::SetUp() {
|
||||
const ::testing::TestInfo* const test_info =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
TCHAR temp_path[MAX_PATH] = { '\0' };
|
||||
TCHAR test_name_wide[MAX_PATH] = { '\0' };
|
||||
// We want the temporary directory to be what the OS returns
|
||||
// to us, + the test case name.
|
||||
GetTempPath(MAX_PATH, temp_path);
|
||||
// The test case name is exposed as a c-style string,
|
||||
// convert it to a wchar_t string.
|
||||
int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(),
|
||||
static_cast<int>(strlen(test_info->name())),
|
||||
test_name_wide,
|
||||
MAX_PATH);
|
||||
if (!dwRet) {
|
||||
assert(false);
|
||||
}
|
||||
StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide);
|
||||
CreateDirectory(temp_path_, NULL);
|
||||
}
|
||||
|
||||
BOOL DoesPathExist(const TCHAR* path_name) {
|
||||
DWORD flags = GetFileAttributes(path_name);
|
||||
if (flags == INVALID_FILE_ATTRIBUTES) {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool MinidumpWrittenCallback(const wchar_t* dump_path,
|
||||
const wchar_t* minidump_id,
|
||||
void* context,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion,
|
||||
bool succeeded) {
|
||||
if (succeeded && DoesPathExist(dump_path)) {
|
||||
fprintf(stderr, kSuccessIndicator);
|
||||
} else {
|
||||
fprintf(stderr, kFailureIndicator);
|
||||
}
|
||||
// If we don't flush, the output doesn't get sent before
|
||||
// this process dies.
|
||||
fflush(stderr);
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerDeathTest, InProcTest) {
|
||||
// For the in-proc test, we just need to instantiate an exception
|
||||
// handler in in-proc mode, and crash. Since the entire test is
|
||||
// reexecuted in the child process, we don't have to worry about
|
||||
// the semantics of the exception handler being inherited/not
|
||||
// inherited across CreateProcess().
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
scoped_ptr<google_breakpad::ExceptionHandler> exc(
|
||||
new google_breakpad::ExceptionHandler(
|
||||
temp_path_,
|
||||
NULL,
|
||||
&MinidumpWrittenCallback,
|
||||
NULL,
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL));
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
int* i = NULL;
|
||||
ASSERT_DEATH((*i)++, kSuccessIndicator);
|
||||
}
|
||||
|
||||
static bool gDumpCallbackCalled = false;
|
||||
|
||||
void clientDumpCallback(void* dump_context,
|
||||
const google_breakpad::ClientInfo* client_info,
|
||||
const std::wstring* dump_path) {
|
||||
gDumpCallbackCalled = true;
|
||||
}
|
||||
|
||||
void ExceptionHandlerDeathTest::DoCrashAccessViolation(
|
||||
const OutOfProcGuarantee out_of_proc_guarantee) {
|
||||
scoped_ptr<google_breakpad::ExceptionHandler> exc;
|
||||
|
||||
if (out_of_proc_guarantee == OUT_OF_PROC_GUARANTEED) {
|
||||
google_breakpad::CrashGenerationClient* client =
|
||||
new google_breakpad::CrashGenerationClient(kPipeName,
|
||||
MiniDumpNormal,
|
||||
NULL); // custom_info
|
||||
ASSERT_TRUE(client->Register());
|
||||
exc.reset(new google_breakpad::ExceptionHandler(
|
||||
temp_path_,
|
||||
NULL, // filter
|
||||
NULL, // callback
|
||||
NULL, // callback_context
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL,
|
||||
client));
|
||||
} else {
|
||||
ASSERT_TRUE(out_of_proc_guarantee == OUT_OF_PROC_BEST_EFFORT);
|
||||
exc.reset(new google_breakpad::ExceptionHandler(
|
||||
temp_path_,
|
||||
NULL, // filter
|
||||
NULL, // callback
|
||||
NULL, // callback_context
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL,
|
||||
MiniDumpNormal,
|
||||
kPipeName,
|
||||
NULL)); // custom_info
|
||||
}
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
// Although this is executing in the child process of the death test,
|
||||
// if it's not true we'll still get an error rather than the crash
|
||||
// being expected.
|
||||
ASSERT_TRUE(exc->IsOutOfProcess());
|
||||
int* i = NULL;
|
||||
printf("%d\n", (*i)++);
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerDeathTest, OutOfProcTest) {
|
||||
// We can take advantage of a detail of google test here to save some
|
||||
// complexity in testing: when you do a death test, it actually forks.
|
||||
// So we can make the main test harness the crash generation server,
|
||||
// and call ASSERT_DEATH on a NULL dereference, it to expecting test
|
||||
// the out of process scenario, since it's happening in a different
|
||||
// process! This is different from the above because, above, we pass
|
||||
// a NULL pipe name, and we also don't start a crash generation server.
|
||||
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
std::wstring dump_path(temp_path_);
|
||||
google_breakpad::CrashGenerationServer server(
|
||||
kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL,
|
||||
NULL, true, &dump_path);
|
||||
|
||||
// This HAS to be EXPECT_, because when this test case is executed in the
|
||||
// child process, the server registration will fail due to the named pipe
|
||||
// being the same.
|
||||
EXPECT_TRUE(server.Start());
|
||||
gDumpCallbackCalled = false;
|
||||
ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_BEST_EFFORT), "");
|
||||
EXPECT_TRUE(gDumpCallbackCalled);
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerDeathTest, OutOfProcGuaranteedTest) {
|
||||
// This is similar to the previous test (OutOfProcTest). The only difference
|
||||
// is that in this test, the crash generation client is created and registered
|
||||
// with the crash generation server outside of the ExceptionHandler
|
||||
// constructor which allows breakpad users to opt out of the default
|
||||
// in-process dump generation when the registration with the crash generation
|
||||
// server fails.
|
||||
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
std::wstring dump_path(temp_path_);
|
||||
google_breakpad::CrashGenerationServer server(
|
||||
kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL,
|
||||
NULL, true, &dump_path);
|
||||
|
||||
// This HAS to be EXPECT_, because when this test case is executed in the
|
||||
// child process, the server registration will fail due to the named pipe
|
||||
// being the same.
|
||||
EXPECT_TRUE(server.Start());
|
||||
gDumpCallbackCalled = false;
|
||||
ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_GUARANTEED), "");
|
||||
EXPECT_TRUE(gDumpCallbackCalled);
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerDeathTest, InvalidParameterTest) {
|
||||
using google_breakpad::ExceptionHandler;
|
||||
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
ExceptionHandler handler(temp_path_, NULL, NULL, NULL,
|
||||
ExceptionHandler::HANDLER_INVALID_PARAMETER);
|
||||
|
||||
// Disable the message box for assertions
|
||||
_CrtSetReportMode(_CRT_ASSERT, 0);
|
||||
|
||||
// Call with a bad argument. The invalid parameter will be swallowed
|
||||
// and a dump will be generated, the process will exit(0).
|
||||
ASSERT_EXIT(printf(NULL), ::testing::ExitedWithCode(0), "");
|
||||
}
|
||||
|
||||
|
||||
struct PureVirtualCallBase {
|
||||
PureVirtualCallBase() {
|
||||
// We have to reinterpret so the linker doesn't get confused because the
|
||||
// method isn't defined.
|
||||
reinterpret_cast<PureVirtualCallBase*>(this)->PureFunction();
|
||||
}
|
||||
virtual ~PureVirtualCallBase() {}
|
||||
virtual void PureFunction() const = 0;
|
||||
};
|
||||
struct PureVirtualCall : public PureVirtualCallBase {
|
||||
PureVirtualCall() { PureFunction(); }
|
||||
virtual void PureFunction() const {}
|
||||
};
|
||||
|
||||
void ExceptionHandlerDeathTest::DoCrashPureVirtualCall() {
|
||||
PureVirtualCall instance;
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerDeathTest, PureVirtualCallTest) {
|
||||
using google_breakpad::ExceptionHandler;
|
||||
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
ExceptionHandler handler(temp_path_, NULL, NULL, NULL,
|
||||
ExceptionHandler::HANDLER_PURECALL);
|
||||
|
||||
// Disable the message box for assertions
|
||||
_CrtSetReportMode(_CRT_ASSERT, 0);
|
||||
|
||||
// Calls a pure virtual function.
|
||||
EXPECT_EXIT(DoCrashPureVirtualCall(), ::testing::ExitedWithCode(0), "");
|
||||
}
|
||||
|
||||
wstring find_minidump_in_directory(const wstring& directory) {
|
||||
wstring search_path = directory + L"\\*";
|
||||
WIN32_FIND_DATA find_data;
|
||||
HANDLE find_handle = FindFirstFileW(search_path.c_str(), &find_data);
|
||||
if (find_handle == INVALID_HANDLE_VALUE)
|
||||
return wstring();
|
||||
|
||||
wstring filename;
|
||||
do {
|
||||
const wchar_t extension[] = L".dmp";
|
||||
const size_t extension_length = sizeof(extension) / sizeof(extension[0]) - 1;
|
||||
const size_t filename_length = wcslen(find_data.cFileName);
|
||||
if (filename_length > extension_length &&
|
||||
wcsncmp(extension,
|
||||
find_data.cFileName + filename_length - extension_length,
|
||||
extension_length) == 0) {
|
||||
filename = directory + L"\\" + find_data.cFileName;
|
||||
break;
|
||||
}
|
||||
} while (FindNextFile(find_handle, &find_data));
|
||||
FindClose(find_handle);
|
||||
return filename;
|
||||
}
|
||||
|
||||
#ifndef ADDRESS_SANITIZER
|
||||
|
||||
TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemory) {
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
scoped_ptr<google_breakpad::ExceptionHandler> exc(
|
||||
new google_breakpad::ExceptionHandler(
|
||||
temp_path_,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL));
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
// Get some executable memory.
|
||||
const uint32_t kMemorySize = 256; // bytes
|
||||
const int kOffset = kMemorySize / 2;
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
char* memory = reinterpret_cast<char*>(VirtualAlloc(NULL,
|
||||
kMemorySize,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE));
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
// Write some instructions that will crash. Put them
|
||||
// in the middle of the block of memory, because the
|
||||
// minidump should contain 128 bytes on either side of the
|
||||
// instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
ASSERT_DEATH(memory_function(), "");
|
||||
|
||||
// free the memory.
|
||||
VirtualFree(memory, 0, MEM_RELEASE);
|
||||
|
||||
// Verify that the resulting minidump contains the memory around the IP
|
||||
wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
|
||||
ASSERT_FALSE(minidump_filename_wide.empty());
|
||||
string minidump_filename;
|
||||
ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
|
||||
&minidump_filename));
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers at least 128 bytes on either
|
||||
// side of the instruction pointer from the exception record.
|
||||
{
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_LT((unsigned)0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
uint64_t instruction_pointer;
|
||||
ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
EXPECT_LE(kMemorySize, region->GetSize());
|
||||
const uint8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
uint64_t ip_offset = instruction_pointer - region->GetBase();
|
||||
EXPECT_GE(region->GetSize() - kOffset, ip_offset);
|
||||
EXPECT_LE(kOffset, ip_offset);
|
||||
|
||||
uint8_t prefix_bytes[kOffset];
|
||||
uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
|
||||
memset(prefix_bytes, 0, sizeof(prefix_bytes));
|
||||
memset(suffix_bytes, 0, sizeof(suffix_bytes));
|
||||
EXPECT_EQ(0, memcmp(bytes + ip_offset - kOffset, prefix_bytes,
|
||||
sizeof(prefix_bytes)));
|
||||
EXPECT_EQ(0, memcmp(bytes + ip_offset, instructions, sizeof(instructions)));
|
||||
EXPECT_EQ(0, memcmp(bytes + ip_offset + sizeof(instructions), suffix_bytes,
|
||||
sizeof(suffix_bytes)));
|
||||
}
|
||||
|
||||
DeleteFileW(minidump_filename_wide.c_str());
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMinBound) {
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
scoped_ptr<google_breakpad::ExceptionHandler> exc(
|
||||
new google_breakpad::ExceptionHandler(
|
||||
temp_path_,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL));
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
SYSTEM_INFO sSysInfo; // Useful information about the system
|
||||
GetSystemInfo(&sSysInfo); // Initialize the structure.
|
||||
|
||||
const uint32_t kMemorySize = 256; // bytes
|
||||
const DWORD kPageSize = sSysInfo.dwPageSize;
|
||||
const int kOffset = 0;
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
// Get some executable memory. Specifically, reserve two pages,
|
||||
// but only commit the second.
|
||||
char* all_memory = reinterpret_cast<char*>(VirtualAlloc(NULL,
|
||||
kPageSize * 2,
|
||||
MEM_RESERVE,
|
||||
PAGE_NOACCESS));
|
||||
ASSERT_TRUE(all_memory);
|
||||
char* memory = all_memory + kPageSize;
|
||||
ASSERT_TRUE(VirtualAlloc(memory, kPageSize,
|
||||
MEM_COMMIT, PAGE_EXECUTE_READWRITE));
|
||||
|
||||
// Write some instructions that will crash. Put them
|
||||
// in the middle of the block of memory, because the
|
||||
// minidump should contain 128 bytes on either side of the
|
||||
// instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
ASSERT_DEATH(memory_function(), "");
|
||||
|
||||
// free the memory.
|
||||
VirtualFree(memory, 0, MEM_RELEASE);
|
||||
|
||||
// Verify that the resulting minidump contains the memory around the IP
|
||||
wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
|
||||
ASSERT_FALSE(minidump_filename_wide.empty());
|
||||
string minidump_filename;
|
||||
ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
|
||||
&minidump_filename));
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
{
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_LT((unsigned)0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
uint64_t instruction_pointer;
|
||||
ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
EXPECT_EQ(kMemorySize / 2, region->GetSize());
|
||||
const uint8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
uint8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
|
||||
memset(suffix_bytes, 0, sizeof(suffix_bytes));
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset,
|
||||
instructions, sizeof(instructions)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
|
||||
suffix_bytes, sizeof(suffix_bytes)) == 0);
|
||||
}
|
||||
|
||||
DeleteFileW(minidump_filename_wide.c_str());
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMaxBound) {
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
scoped_ptr<google_breakpad::ExceptionHandler> exc(
|
||||
new google_breakpad::ExceptionHandler(
|
||||
temp_path_,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL));
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
SYSTEM_INFO sSysInfo; // Useful information about the system
|
||||
GetSystemInfo(&sSysInfo); // Initialize the structure.
|
||||
|
||||
const DWORD kPageSize = sSysInfo.dwPageSize;
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
const int kOffset = kPageSize - sizeof(instructions);
|
||||
// Get some executable memory. Specifically, reserve two pages,
|
||||
// but only commit the first.
|
||||
char* memory = reinterpret_cast<char*>(VirtualAlloc(NULL,
|
||||
kPageSize * 2,
|
||||
MEM_RESERVE,
|
||||
PAGE_NOACCESS));
|
||||
ASSERT_TRUE(memory);
|
||||
ASSERT_TRUE(VirtualAlloc(memory, kPageSize,
|
||||
MEM_COMMIT, PAGE_EXECUTE_READWRITE));
|
||||
|
||||
// Write some instructions that will crash.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
ASSERT_DEATH(memory_function(), "");
|
||||
|
||||
// free the memory.
|
||||
VirtualFree(memory, 0, MEM_RELEASE);
|
||||
|
||||
// Verify that the resulting minidump contains the memory around the IP
|
||||
wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
|
||||
ASSERT_FALSE(minidump_filename_wide.empty());
|
||||
string minidump_filename;
|
||||
ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
|
||||
&minidump_filename));
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
{
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_LT((unsigned)0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
uint64_t instruction_pointer;
|
||||
ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
const size_t kPrefixSize = 128; // bytes
|
||||
EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
|
||||
const uint8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
uint8_t prefix_bytes[kPrefixSize];
|
||||
memset(prefix_bytes, 0, sizeof(prefix_bytes));
|
||||
EXPECT_EQ(0, memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)));
|
||||
EXPECT_EQ(0, memcmp(bytes + kPrefixSize,
|
||||
instructions, sizeof(instructions)));
|
||||
}
|
||||
|
||||
DeleteFileW(minidump_filename_wide.c_str());
|
||||
}
|
||||
|
||||
#endif // !ADDRESS_SANITIZER
|
||||
|
||||
} // namespace
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/windows/handler/exception_handler.h"
|
||||
#include "client/windows/unittests/exception_handler_test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char kFoo[] = "foo";
|
||||
const char kBar[] = "bar";
|
||||
|
||||
const char kStartOfLine[] = "^";
|
||||
const char kEndOfLine[] = "$";
|
||||
|
||||
const char kFilterReturnsTrue[] = "filter_returns_true";
|
||||
const char kFilterReturnsFalse[] = "filter_returns_false";
|
||||
|
||||
const char kCallbackReturnsTrue[] = "callback_returns_true";
|
||||
const char kCallbackReturnsFalse[] = "callback_returns_false";
|
||||
|
||||
bool DoesPathExist(const wchar_t* path_name) {
|
||||
DWORD flags = GetFileAttributes(path_name);
|
||||
if (flags == INVALID_FILE_ATTRIBUTES) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// A callback function to run before Breakpad performs any substantial
|
||||
// processing of an exception. A FilterCallback is called before writing
|
||||
// a minidump. context is the parameter supplied by the user as
|
||||
// callback_context when the handler was created. exinfo points to the
|
||||
// exception record, if any; assertion points to assertion information,
|
||||
// if any.
|
||||
//
|
||||
// If a FilterCallback returns true, Breakpad will continue processing,
|
||||
// attempting to write a minidump. If a FilterCallback returns false,
|
||||
// Breakpad will immediately report the exception as unhandled without
|
||||
// writing a minidump, allowing another handler the opportunity to handle it.
|
||||
template <bool filter_return_value>
|
||||
bool CrashHandlerFilter(void* context,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion) {
|
||||
if (filter_return_value) {
|
||||
fprintf(stderr, kFilterReturnsTrue);
|
||||
} else {
|
||||
fprintf(stderr, kFilterReturnsFalse);
|
||||
}
|
||||
fflush(stderr);
|
||||
|
||||
return filter_return_value;
|
||||
}
|
||||
|
||||
// A callback function to run after the minidump has been written.
|
||||
// minidump_id is a unique id for the dump, so the minidump
|
||||
// file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied
|
||||
// by the user as callback_context when the handler was created. exinfo
|
||||
// points to the exception record, or NULL if no exception occurred.
|
||||
// succeeded indicates whether a minidump file was successfully written.
|
||||
// assertion points to information about an assertion if the handler was
|
||||
// invoked by an assertion.
|
||||
//
|
||||
// If an exception occurred and the callback returns true, Breakpad will treat
|
||||
// the exception as fully-handled, suppressing any other handlers from being
|
||||
// notified of the exception. If the callback returns false, Breakpad will
|
||||
// treat the exception as unhandled, and allow another handler to handle it.
|
||||
// If there are no other handlers, Breakpad will report the exception to the
|
||||
// system as unhandled, allowing a debugger or native crash dialog the
|
||||
// opportunity to handle the exception. Most callback implementations
|
||||
// should normally return the value of |succeeded|, or when they wish to
|
||||
// not report an exception of handled, false. Callbacks will rarely want to
|
||||
// return true directly (unless |succeeded| is true).
|
||||
//
|
||||
// For out-of-process dump generation, dump path and minidump ID will always
|
||||
// be NULL. In case of out-of-process dump generation, the dump path and
|
||||
// minidump id are controlled by the server process and are not communicated
|
||||
// back to the crashing process.
|
||||
template <bool callback_return_value>
|
||||
bool MinidumpWrittenCallback(const wchar_t* dump_path,
|
||||
const wchar_t* minidump_id,
|
||||
void* context,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion,
|
||||
bool succeeded) {
|
||||
bool rv = false;
|
||||
if (callback_return_value &&
|
||||
succeeded &&
|
||||
DoesPathExist(dump_path)) {
|
||||
rv = true;
|
||||
fprintf(stderr, kCallbackReturnsTrue);
|
||||
} else {
|
||||
fprintf(stderr, kCallbackReturnsFalse);
|
||||
}
|
||||
fflush(stderr);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
void DoCrash(const char* message) {
|
||||
if (message) {
|
||||
fprintf(stderr, "%s", message);
|
||||
fflush(stderr);
|
||||
}
|
||||
int* i = NULL;
|
||||
(*i)++;
|
||||
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
|
||||
void InstallExceptionHandlerAndCrash(bool install_filter,
|
||||
bool filter_return_value,
|
||||
bool install_callback,
|
||||
bool callback_return_value) {
|
||||
wchar_t temp_path[MAX_PATH] = { '\0' };
|
||||
GetTempPath(MAX_PATH, temp_path);
|
||||
|
||||
ASSERT_TRUE(DoesPathExist(temp_path));
|
||||
google_breakpad::ExceptionHandler exc(
|
||||
temp_path,
|
||||
install_filter ?
|
||||
(filter_return_value ?
|
||||
&CrashHandlerFilter<true> :
|
||||
&CrashHandlerFilter<false>) :
|
||||
NULL,
|
||||
install_callback ?
|
||||
(callback_return_value ?
|
||||
&MinidumpWrittenCallback<true> :
|
||||
&MinidumpWrittenCallback<false>) :
|
||||
NULL,
|
||||
NULL, // callback_context
|
||||
google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
DoCrash(NULL);
|
||||
}
|
||||
|
||||
TEST(AssertDeathSanity, Simple) {
|
||||
ASSERT_DEATH(DoCrash(NULL), "");
|
||||
}
|
||||
|
||||
TEST(AssertDeathSanity, Regex) {
|
||||
ASSERT_DEATH(DoCrash(kFoo),
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kFoo) +
|
||||
std::string(kEndOfLine));
|
||||
|
||||
ASSERT_DEATH(DoCrash(kBar),
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kBar) +
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerCallbacks, FilterTrue_No_Callback) {
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(true, // install_filter
|
||||
true, // filter_return_value
|
||||
false, // install_callback
|
||||
false), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kFilterReturnsTrue) +
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerCallbacks, FilterTrue_Callback) {
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(true, // install_filter
|
||||
true, // filter_return_value
|
||||
true, // install_callback
|
||||
false), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kFilterReturnsTrue) +
|
||||
std::string(kCallbackReturnsFalse) +
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerCallbacks, FilterFalse_No_Callback) {
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(true, // install_filter
|
||||
false, // filter_return_value
|
||||
false, // install_callback
|
||||
false), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kFilterReturnsFalse) +
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
// Callback shouldn't be executed when filter returns false
|
||||
TEST(ExceptionHandlerCallbacks, FilterFalse_Callback) {
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(true, // install_filter
|
||||
false, // filter_return_value
|
||||
true, // install_callback
|
||||
false), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kFilterReturnsFalse) +
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerCallbacks, No_Filter_No_Callback) {
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(false, // install_filter
|
||||
true, // filter_return_value
|
||||
false, // install_callback
|
||||
false), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerCallbacks, No_Filter_Callback) {
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(false, // install_filter
|
||||
true, // filter_return_value
|
||||
true, // install_callback
|
||||
false), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kCallbackReturnsFalse) +
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
|
||||
TEST(ExceptionHandlerNesting, Skip_From_Inner_Filter) {
|
||||
wchar_t temp_path[MAX_PATH] = { '\0' };
|
||||
GetTempPath(MAX_PATH, temp_path);
|
||||
|
||||
ASSERT_TRUE(DoesPathExist(temp_path));
|
||||
google_breakpad::ExceptionHandler exc(
|
||||
temp_path,
|
||||
&CrashHandlerFilter<true>,
|
||||
&MinidumpWrittenCallback<false>,
|
||||
NULL, // callback_context
|
||||
google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
|
||||
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(true, // install_filter
|
||||
false, // filter_return_value
|
||||
true, // install_callback
|
||||
true), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kFilterReturnsFalse) + // inner filter
|
||||
std::string(kFilterReturnsTrue) + // outer filter
|
||||
std::string(kCallbackReturnsFalse) + // outer callback
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerNesting, Skip_From_Inner_Callback) {
|
||||
wchar_t temp_path[MAX_PATH] = { '\0' };
|
||||
GetTempPath(MAX_PATH, temp_path);
|
||||
|
||||
ASSERT_TRUE(DoesPathExist(temp_path));
|
||||
google_breakpad::ExceptionHandler exc(
|
||||
temp_path,
|
||||
&CrashHandlerFilter<true>,
|
||||
&MinidumpWrittenCallback<false>,
|
||||
NULL, // callback_context
|
||||
google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
|
||||
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(true, // install_filter
|
||||
true, // filter_return_value
|
||||
true, // install_callback
|
||||
false), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kFilterReturnsTrue) + // inner filter
|
||||
std::string(kCallbackReturnsFalse) + // inner callback
|
||||
std::string(kFilterReturnsTrue) + // outer filter
|
||||
std::string(kCallbackReturnsFalse) + // outer callback
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerNesting, Handled_By_Inner_Handler) {
|
||||
wchar_t temp_path[MAX_PATH] = { '\0' };
|
||||
GetTempPath(MAX_PATH, temp_path);
|
||||
|
||||
ASSERT_TRUE(DoesPathExist(temp_path));
|
||||
google_breakpad::ExceptionHandler exc(
|
||||
temp_path,
|
||||
&CrashHandlerFilter<true>,
|
||||
&MinidumpWrittenCallback<true>,
|
||||
NULL, // callback_context
|
||||
google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
|
||||
|
||||
ASSERT_DEATH(
|
||||
InstallExceptionHandlerAndCrash(true, // install_filter
|
||||
true, // filter_return_value
|
||||
true, // install_callback
|
||||
true), // callback_return_value
|
||||
std::string(kStartOfLine) +
|
||||
std::string(kFilterReturnsTrue) + // inner filter
|
||||
std::string(kCallbackReturnsTrue) + // inner callback
|
||||
std::string(kEndOfLine));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,506 @@
|
||||
// Copyright 2009 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "client/windows/unittests/exception_handler_test.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <strsafe.h>
|
||||
#include <objbase.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/windows/crash_generation/crash_generation_server.h"
|
||||
#include "client/windows/handler/exception_handler.h"
|
||||
#include "client/windows/unittests/dump_analysis.h" // NOLINT
|
||||
#include "common/windows/string_utils-inl.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
namespace testing {
|
||||
|
||||
DisableExceptionHandlerInScope::DisableExceptionHandlerInScope() {
|
||||
catch_exceptions_ = GTEST_FLAG(catch_exceptions);
|
||||
GTEST_FLAG(catch_exceptions) = false;
|
||||
}
|
||||
|
||||
DisableExceptionHandlerInScope::~DisableExceptionHandlerInScope() {
|
||||
GTEST_FLAG(catch_exceptions) = catch_exceptions_;
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
|
||||
namespace {
|
||||
|
||||
using std::wstring;
|
||||
using namespace google_breakpad;
|
||||
|
||||
const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer";
|
||||
const char kSuccessIndicator[] = "success";
|
||||
const char kFailureIndicator[] = "failure";
|
||||
|
||||
const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>(
|
||||
MiniDumpWithFullMemory | // Full memory from process.
|
||||
MiniDumpWithProcessThreadData | // Get PEB and TEB.
|
||||
MiniDumpWithHandleData); // Get all handle information.
|
||||
|
||||
class ExceptionHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
// Member variable for each test that they can use
|
||||
// for temporary storage.
|
||||
TCHAR temp_path_[MAX_PATH];
|
||||
|
||||
// Actually constructs a temp path name.
|
||||
virtual void SetUp();
|
||||
|
||||
// Deletes temporary files.
|
||||
virtual void TearDown();
|
||||
|
||||
void DoCrashInvalidParameter();
|
||||
void DoCrashPureVirtualCall();
|
||||
|
||||
// Utility function to test for a path's existence.
|
||||
static BOOL DoesPathExist(const TCHAR* path_name);
|
||||
|
||||
// Client callback.
|
||||
static void ClientDumpCallback(
|
||||
void* dump_context,
|
||||
const google_breakpad::ClientInfo* client_info,
|
||||
const std::wstring* dump_path);
|
||||
|
||||
static bool DumpCallback(const wchar_t* dump_path,
|
||||
const wchar_t* minidump_id,
|
||||
void* context,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion,
|
||||
bool succeeded);
|
||||
|
||||
static std::wstring dump_file;
|
||||
static std::wstring full_dump_file;
|
||||
};
|
||||
|
||||
std::wstring ExceptionHandlerTest::dump_file;
|
||||
std::wstring ExceptionHandlerTest::full_dump_file;
|
||||
|
||||
void ExceptionHandlerTest::SetUp() {
|
||||
const ::testing::TestInfo* const test_info =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
TCHAR temp_path[MAX_PATH] = { '\0' };
|
||||
TCHAR test_name_wide[MAX_PATH] = { '\0' };
|
||||
// We want the temporary directory to be what the OS returns
|
||||
// to us, + the test case name.
|
||||
GetTempPath(MAX_PATH, temp_path);
|
||||
// THe test case name is exposed to use as a c-style string,
|
||||
// But we might be working in UNICODE here on Windows.
|
||||
int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(),
|
||||
static_cast<int>(strlen(test_info->name())),
|
||||
test_name_wide,
|
||||
MAX_PATH);
|
||||
if (!dwRet) {
|
||||
assert(false);
|
||||
}
|
||||
StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide);
|
||||
CreateDirectory(temp_path_, NULL);
|
||||
}
|
||||
|
||||
void ExceptionHandlerTest::TearDown() {
|
||||
if (!dump_file.empty()) {
|
||||
::DeleteFile(dump_file.c_str());
|
||||
dump_file = L"";
|
||||
}
|
||||
if (!full_dump_file.empty()) {
|
||||
::DeleteFile(full_dump_file.c_str());
|
||||
full_dump_file = L"";
|
||||
}
|
||||
}
|
||||
|
||||
BOOL ExceptionHandlerTest::DoesPathExist(const TCHAR* path_name) {
|
||||
DWORD flags = GetFileAttributes(path_name);
|
||||
if (flags == INVALID_FILE_ATTRIBUTES) {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// static
|
||||
void ExceptionHandlerTest::ClientDumpCallback(
|
||||
void* dump_context,
|
||||
const google_breakpad::ClientInfo* client_info,
|
||||
const wstring* dump_path) {
|
||||
dump_file = *dump_path;
|
||||
// Create the full dump file name from the dump path.
|
||||
full_dump_file = dump_file.substr(0, dump_file.length() - 4) + L"-full.dmp";
|
||||
}
|
||||
|
||||
// static
|
||||
bool ExceptionHandlerTest::DumpCallback(const wchar_t* dump_path,
|
||||
const wchar_t* minidump_id,
|
||||
void* context,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion,
|
||||
bool succeeded) {
|
||||
dump_file = dump_path;
|
||||
dump_file += L"\\";
|
||||
dump_file += minidump_id;
|
||||
dump_file += L".dmp";
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
void ExceptionHandlerTest::DoCrashInvalidParameter() {
|
||||
google_breakpad::ExceptionHandler* exc =
|
||||
new google_breakpad::ExceptionHandler(
|
||||
temp_path_, NULL, NULL, NULL,
|
||||
google_breakpad::ExceptionHandler::HANDLER_INVALID_PARAMETER,
|
||||
kFullDumpType, kPipeName, NULL);
|
||||
|
||||
// Disable the message box for assertions
|
||||
_CrtSetReportMode(_CRT_ASSERT, 0);
|
||||
|
||||
// Although this is executing in the child process of the death test,
|
||||
// if it's not true we'll still get an error rather than the crash
|
||||
// being expected.
|
||||
ASSERT_TRUE(exc->IsOutOfProcess());
|
||||
printf(NULL);
|
||||
}
|
||||
|
||||
|
||||
struct PureVirtualCallBase {
|
||||
PureVirtualCallBase() {
|
||||
// We have to reinterpret so the linker doesn't get confused because the
|
||||
// method isn't defined.
|
||||
reinterpret_cast<PureVirtualCallBase*>(this)->PureFunction();
|
||||
}
|
||||
virtual ~PureVirtualCallBase() {}
|
||||
virtual void PureFunction() const = 0;
|
||||
};
|
||||
struct PureVirtualCall : public PureVirtualCallBase {
|
||||
PureVirtualCall() { PureFunction(); }
|
||||
virtual void PureFunction() const {}
|
||||
};
|
||||
|
||||
void ExceptionHandlerTest::DoCrashPureVirtualCall() {
|
||||
google_breakpad::ExceptionHandler* exc =
|
||||
new google_breakpad::ExceptionHandler(
|
||||
temp_path_, NULL, NULL, NULL,
|
||||
google_breakpad::ExceptionHandler::HANDLER_PURECALL,
|
||||
kFullDumpType, kPipeName, NULL);
|
||||
|
||||
// Disable the message box for assertions
|
||||
_CrtSetReportMode(_CRT_ASSERT, 0);
|
||||
|
||||
// Although this is executing in the child process of the death test,
|
||||
// if it's not true we'll still get an error rather than the crash
|
||||
// being expected.
|
||||
ASSERT_TRUE(exc->IsOutOfProcess());
|
||||
|
||||
// Create a new frame to ensure PureVirtualCall is not optimized to some
|
||||
// other line in this function.
|
||||
{
|
||||
PureVirtualCall instance;
|
||||
}
|
||||
}
|
||||
|
||||
// This test validates that the minidump is written correctly.
|
||||
TEST_F(ExceptionHandlerTest, InvalidParameterMiniDumpTest) {
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
|
||||
// Call with a bad argument
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
wstring dump_path(temp_path_);
|
||||
google_breakpad::CrashGenerationServer server(
|
||||
kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, NULL,
|
||||
NULL, true, &dump_path);
|
||||
|
||||
ASSERT_TRUE(dump_file.empty() && full_dump_file.empty());
|
||||
|
||||
// This HAS to be EXPECT_, because when this test case is executed in the
|
||||
// child process, the server registration will fail due to the named pipe
|
||||
// being the same.
|
||||
EXPECT_TRUE(server.Start());
|
||||
EXPECT_EXIT(DoCrashInvalidParameter(), ::testing::ExitedWithCode(0), "");
|
||||
ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty());
|
||||
ASSERT_TRUE(DoesPathExist(dump_file.c_str()));
|
||||
ASSERT_TRUE(DoesPathExist(full_dump_file.c_str()));
|
||||
|
||||
// Verify the dump for infos.
|
||||
DumpAnalysis mini(dump_file);
|
||||
DumpAnalysis full(full_dump_file);
|
||||
|
||||
// The dump should have all of these streams.
|
||||
EXPECT_TRUE(mini.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(full.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(full.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(full.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(mini.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(full.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(MiscInfoStream));
|
||||
EXPECT_TRUE(full.HasStream(MiscInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(HandleDataStream));
|
||||
EXPECT_TRUE(full.HasStream(HandleDataStream));
|
||||
|
||||
// We expect PEB and TEBs in this dump.
|
||||
EXPECT_TRUE(mini.HasTebs() || full.HasTebs());
|
||||
EXPECT_TRUE(mini.HasPeb() || full.HasPeb());
|
||||
|
||||
// Minidump should have a memory listing, but no 64-bit memory.
|
||||
EXPECT_TRUE(mini.HasStream(MemoryListStream));
|
||||
EXPECT_FALSE(mini.HasStream(Memory64ListStream));
|
||||
|
||||
EXPECT_FALSE(full.HasStream(MemoryListStream));
|
||||
EXPECT_TRUE(full.HasStream(Memory64ListStream));
|
||||
|
||||
// This is the only place we don't use OR because we want both not
|
||||
// to have the streams.
|
||||
EXPECT_FALSE(mini.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(full.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(full.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(full.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(mini.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(full.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(full.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(full.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(full.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(mini.HasStream(TokenStream));
|
||||
EXPECT_FALSE(full.HasStream(TokenStream));
|
||||
}
|
||||
|
||||
|
||||
// This test validates that the minidump is written correctly.
|
||||
TEST_F(ExceptionHandlerTest, PureVirtualCallMiniDumpTest) {
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
|
||||
// Call with a bad argument
|
||||
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||
wstring dump_path(temp_path_);
|
||||
google_breakpad::CrashGenerationServer server(
|
||||
kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, NULL,
|
||||
NULL, true, &dump_path);
|
||||
|
||||
ASSERT_TRUE(dump_file.empty() && full_dump_file.empty());
|
||||
|
||||
// This HAS to be EXPECT_, because when this test case is executed in the
|
||||
// child process, the server registration will fail due to the named pipe
|
||||
// being the same.
|
||||
EXPECT_TRUE(server.Start());
|
||||
EXPECT_EXIT(DoCrashPureVirtualCall(), ::testing::ExitedWithCode(0), "");
|
||||
ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty());
|
||||
ASSERT_TRUE(DoesPathExist(dump_file.c_str()));
|
||||
ASSERT_TRUE(DoesPathExist(full_dump_file.c_str()));
|
||||
|
||||
// Verify the dump for infos.
|
||||
DumpAnalysis mini(dump_file);
|
||||
DumpAnalysis full(full_dump_file);
|
||||
|
||||
// The dump should have all of these streams.
|
||||
EXPECT_TRUE(mini.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(full.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(full.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(full.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(mini.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(full.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(MiscInfoStream));
|
||||
EXPECT_TRUE(full.HasStream(MiscInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(HandleDataStream));
|
||||
EXPECT_TRUE(full.HasStream(HandleDataStream));
|
||||
|
||||
// We expect PEB and TEBs in this dump.
|
||||
EXPECT_TRUE(mini.HasTebs() || full.HasTebs());
|
||||
EXPECT_TRUE(mini.HasPeb() || full.HasPeb());
|
||||
|
||||
// Minidump should have a memory listing, but no 64-bit memory.
|
||||
EXPECT_TRUE(mini.HasStream(MemoryListStream));
|
||||
EXPECT_FALSE(mini.HasStream(Memory64ListStream));
|
||||
|
||||
EXPECT_FALSE(full.HasStream(MemoryListStream));
|
||||
EXPECT_TRUE(full.HasStream(Memory64ListStream));
|
||||
|
||||
// This is the only place we don't use OR because we want both not
|
||||
// to have the streams.
|
||||
EXPECT_FALSE(mini.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(full.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(full.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(full.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(mini.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(full.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(full.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(full.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(full.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(mini.HasStream(TokenStream));
|
||||
EXPECT_FALSE(full.HasStream(TokenStream));
|
||||
}
|
||||
|
||||
// Test that writing a minidump produces a valid minidump containing
|
||||
// some expected structures.
|
||||
TEST_F(ExceptionHandlerTest, WriteMinidumpTest) {
|
||||
ExceptionHandler handler(temp_path_,
|
||||
NULL,
|
||||
DumpCallback,
|
||||
NULL,
|
||||
ExceptionHandler::HANDLER_ALL);
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
ASSERT_TRUE(handler.WriteMinidump());
|
||||
ASSERT_FALSE(dump_file.empty());
|
||||
|
||||
string minidump_filename;
|
||||
ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
|
||||
&minidump_filename));
|
||||
|
||||
// Read the minidump and verify some info.
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
// TODO(ted): more comprehensive tests...
|
||||
}
|
||||
|
||||
// Test that an additional memory region can be included in the minidump.
|
||||
TEST_F(ExceptionHandlerTest, AdditionalMemory) {
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
const uint32_t kMemorySize = si.dwPageSize;
|
||||
|
||||
// Get some heap memory.
|
||||
uint8_t* memory = new uint8_t[kMemorySize];
|
||||
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
// Stick some data into the memory so the contents can be verified.
|
||||
for (uint32_t i = 0; i < kMemorySize; ++i) {
|
||||
memory[i] = i % 255;
|
||||
}
|
||||
|
||||
ExceptionHandler handler(temp_path_,
|
||||
NULL,
|
||||
DumpCallback,
|
||||
NULL,
|
||||
ExceptionHandler::HANDLER_ALL);
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
// Add the memory region to the list of memory to be included.
|
||||
handler.RegisterAppMemory(memory, kMemorySize);
|
||||
ASSERT_TRUE(handler.WriteMinidump());
|
||||
ASSERT_FALSE(dump_file.empty());
|
||||
|
||||
string minidump_filename;
|
||||
ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
|
||||
&minidump_filename));
|
||||
|
||||
// Read the minidump. Ensure that the memory region is present
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(dump_memory_list);
|
||||
const MinidumpMemoryRegion* region =
|
||||
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
EXPECT_EQ(kMemoryAddress, region->GetBase());
|
||||
EXPECT_EQ(kMemorySize, region->GetSize());
|
||||
|
||||
// Verify memory contents.
|
||||
EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
|
||||
|
||||
delete[] memory;
|
||||
}
|
||||
|
||||
// Test that a memory region that was previously registered
|
||||
// can be unregistered.
|
||||
TEST_F(ExceptionHandlerTest, AdditionalMemoryRemove) {
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
const uint32_t kMemorySize = si.dwPageSize;
|
||||
|
||||
// Get some heap memory.
|
||||
uint8_t* memory = new uint8_t[kMemorySize];
|
||||
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
// Stick some data into the memory so the contents can be verified.
|
||||
for (uint32_t i = 0; i < kMemorySize; ++i) {
|
||||
memory[i] = i % 255;
|
||||
}
|
||||
|
||||
ExceptionHandler handler(temp_path_,
|
||||
NULL,
|
||||
DumpCallback,
|
||||
NULL,
|
||||
ExceptionHandler::HANDLER_ALL);
|
||||
|
||||
// Disable GTest SEH handler
|
||||
testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
|
||||
// Add the memory region to the list of memory to be included.
|
||||
handler.RegisterAppMemory(memory, kMemorySize);
|
||||
|
||||
// ...and then remove it
|
||||
handler.UnregisterAppMemory(memory);
|
||||
|
||||
ASSERT_TRUE(handler.WriteMinidump());
|
||||
ASSERT_FALSE(dump_file.empty());
|
||||
|
||||
string minidump_filename;
|
||||
ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
|
||||
&minidump_filename));
|
||||
|
||||
// Read the minidump. Ensure that the memory region is not present.
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(dump_memory_list);
|
||||
const MinidumpMemoryRegion* region =
|
||||
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
|
||||
EXPECT_FALSE(region);
|
||||
|
||||
delete[] memory;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_
|
||||
#define CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_
|
||||
|
||||
namespace testing {
|
||||
|
||||
// By default, GTest (on Windows) installs a SEH filter (and a handler) before
|
||||
// starting to run all the tests in order to avoid test interruptions is some
|
||||
// of the tests are crashing. Unfortunately, this functionality prevents the
|
||||
// execution to reach the UnhandledExceptionFilter installed by Google-Breakpad
|
||||
// ExceptionHandler so in order to test the Google-Breakpad exception handling
|
||||
// code the exception handling done by GTest must be disabled.
|
||||
// Usage:
|
||||
//
|
||||
// google_breakpad::ExceptionHandler exc(...);
|
||||
//
|
||||
// // Disable GTest SEH handler
|
||||
// testing::DisableExceptionHandlerInScope disable_exception_handler;
|
||||
// ...
|
||||
// ASSERT_DEATH( ... some crash ...);
|
||||
//
|
||||
class DisableExceptionHandlerInScope {
|
||||
public:
|
||||
DisableExceptionHandlerInScope();
|
||||
~DisableExceptionHandlerInScope();
|
||||
|
||||
private:
|
||||
bool catch_exceptions_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_
|
||||
@@ -0,0 +1,335 @@
|
||||
// Copyright 2010 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/windows/crash_generation/minidump_generator.h"
|
||||
#include "client/windows/unittests/dump_analysis.h" // NOLINT
|
||||
|
||||
namespace {
|
||||
|
||||
// Minidump with stacks, PEB, TEB, and unloaded module list.
|
||||
const MINIDUMP_TYPE kSmallDumpType = static_cast<MINIDUMP_TYPE>(
|
||||
MiniDumpWithProcessThreadData | // Get PEB and TEB.
|
||||
MiniDumpWithUnloadedModules); // Get unloaded modules when available.
|
||||
|
||||
// Minidump with all of the above, plus memory referenced from stack.
|
||||
const MINIDUMP_TYPE kLargerDumpType = static_cast<MINIDUMP_TYPE>(
|
||||
MiniDumpWithProcessThreadData | // Get PEB and TEB.
|
||||
MiniDumpWithUnloadedModules | // Get unloaded modules when available.
|
||||
MiniDumpWithIndirectlyReferencedMemory); // Get memory referenced by stack.
|
||||
|
||||
// Large dump with all process memory.
|
||||
const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>(
|
||||
MiniDumpWithFullMemory | // Full memory from process.
|
||||
MiniDumpWithProcessThreadData | // Get PEB and TEB.
|
||||
MiniDumpWithHandleData | // Get all handle information.
|
||||
MiniDumpWithUnloadedModules); // Get unloaded modules when available.
|
||||
|
||||
class MinidumpTest: public testing::Test {
|
||||
public:
|
||||
MinidumpTest() {
|
||||
wchar_t temp_dir_path[ MAX_PATH ] = {0};
|
||||
::GetTempPath(MAX_PATH, temp_dir_path);
|
||||
dump_path_ = temp_dir_path;
|
||||
}
|
||||
|
||||
virtual void SetUp() {
|
||||
// Make sure URLMon isn't loaded into our process.
|
||||
ASSERT_EQ(NULL, ::GetModuleHandle(L"urlmon.dll"));
|
||||
|
||||
// Then load and unload it to ensure we have something to
|
||||
// stock the unloaded module list with.
|
||||
HMODULE urlmon = ::LoadLibrary(L"urlmon.dll");
|
||||
ASSERT_TRUE(urlmon != NULL);
|
||||
ASSERT_TRUE(::FreeLibrary(urlmon));
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
if (!dump_file_.empty()) {
|
||||
::DeleteFile(dump_file_.c_str());
|
||||
dump_file_ = L"";
|
||||
}
|
||||
if (!full_dump_file_.empty()) {
|
||||
::DeleteFile(full_dump_file_.c_str());
|
||||
full_dump_file_ = L"";
|
||||
}
|
||||
}
|
||||
|
||||
bool WriteDump(ULONG flags) {
|
||||
using google_breakpad::MinidumpGenerator;
|
||||
|
||||
// Fake exception is access violation on write to this.
|
||||
EXCEPTION_RECORD ex_record = {
|
||||
STATUS_ACCESS_VIOLATION, // ExceptionCode
|
||||
0, // ExceptionFlags
|
||||
NULL, // ExceptionRecord;
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(0xCAFEBABE)), // ExceptionAddress;
|
||||
2, // NumberParameters;
|
||||
{ EXCEPTION_WRITE_FAULT, reinterpret_cast<ULONG_PTR>(this) }
|
||||
};
|
||||
CONTEXT ctx_record = {};
|
||||
EXCEPTION_POINTERS ex_ptrs = {
|
||||
&ex_record,
|
||||
&ctx_record,
|
||||
};
|
||||
|
||||
MinidumpGenerator generator(dump_path_,
|
||||
::GetCurrentProcess(),
|
||||
::GetCurrentProcessId(),
|
||||
::GetCurrentThreadId(),
|
||||
::GetCurrentThreadId(),
|
||||
&ex_ptrs,
|
||||
NULL,
|
||||
static_cast<MINIDUMP_TYPE>(flags),
|
||||
TRUE);
|
||||
generator.GenerateDumpFile(&dump_file_);
|
||||
generator.GenerateFullDumpFile(&full_dump_file_);
|
||||
// And write a dump
|
||||
bool result = generator.WriteMinidump();
|
||||
return result == TRUE;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::wstring dump_file_;
|
||||
std::wstring full_dump_file_;
|
||||
|
||||
std::wstring dump_path_;
|
||||
};
|
||||
|
||||
// We need to be able to get file information from Windows
|
||||
bool HasFileInfo(const std::wstring& file_path) {
|
||||
DWORD dummy;
|
||||
const wchar_t* path = file_path.c_str();
|
||||
DWORD length = ::GetFileVersionInfoSize(path, &dummy);
|
||||
if (length == 0)
|
||||
return NULL;
|
||||
|
||||
void* data = calloc(length, 1);
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
if (!::GetFileVersionInfo(path, dummy, length, data)) {
|
||||
free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* translate = NULL;
|
||||
UINT page_count;
|
||||
BOOL query_result = VerQueryValue(
|
||||
data,
|
||||
L"\\VarFileInfo\\Translation",
|
||||
static_cast<void**>(&translate),
|
||||
&page_count);
|
||||
|
||||
free(data);
|
||||
if (query_result && translate) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MinidumpTest, Version) {
|
||||
// Loads DbgHelp.dll in process
|
||||
ImagehlpApiVersion();
|
||||
|
||||
HMODULE dbg_help = ::GetModuleHandle(L"dbghelp.dll");
|
||||
ASSERT_TRUE(dbg_help != NULL);
|
||||
|
||||
wchar_t dbg_help_file[1024] = {};
|
||||
ASSERT_TRUE(::GetModuleFileName(dbg_help,
|
||||
dbg_help_file,
|
||||
sizeof(dbg_help_file) /
|
||||
sizeof(*dbg_help_file)));
|
||||
ASSERT_TRUE(HasFileInfo(std::wstring(dbg_help_file)) != NULL);
|
||||
|
||||
// LOG(INFO) << "DbgHelp.dll version: " << file_info->file_version();
|
||||
}
|
||||
|
||||
TEST_F(MinidumpTest, Normal) {
|
||||
EXPECT_TRUE(WriteDump(MiniDumpNormal));
|
||||
DumpAnalysis mini(dump_file_);
|
||||
|
||||
// We expect threads, modules and some memory.
|
||||
EXPECT_TRUE(mini.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(MemoryListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(mini.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(MiscInfoStream));
|
||||
|
||||
EXPECT_FALSE(mini.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(mini.HasStream(Memory64ListStream));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(mini.HasStream(HandleDataStream));
|
||||
EXPECT_FALSE(mini.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(mini.HasStream(UnloadedModuleListStream));
|
||||
EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(mini.HasStream(TokenStream));
|
||||
|
||||
// We expect no PEB nor TEBs in this dump.
|
||||
EXPECT_FALSE(mini.HasTebs());
|
||||
EXPECT_FALSE(mini.HasPeb());
|
||||
|
||||
// We expect no off-stack memory in this dump.
|
||||
EXPECT_FALSE(mini.HasMemory(this));
|
||||
}
|
||||
|
||||
TEST_F(MinidumpTest, SmallDump) {
|
||||
ASSERT_TRUE(WriteDump(kSmallDumpType));
|
||||
DumpAnalysis mini(dump_file_);
|
||||
|
||||
EXPECT_TRUE(mini.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(MemoryListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(mini.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(MiscInfoStream));
|
||||
|
||||
// We expect PEB and TEBs in this dump.
|
||||
EXPECT_TRUE(mini.HasTebs());
|
||||
EXPECT_TRUE(mini.HasPeb());
|
||||
|
||||
EXPECT_FALSE(mini.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(mini.HasStream(Memory64ListStream));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(mini.HasStream(HandleDataStream));
|
||||
EXPECT_FALSE(mini.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(mini.HasStream(TokenStream));
|
||||
|
||||
// We expect no off-stack memory in this dump.
|
||||
EXPECT_FALSE(mini.HasMemory(this));
|
||||
}
|
||||
|
||||
TEST_F(MinidumpTest, LargerDump) {
|
||||
ASSERT_TRUE(WriteDump(kLargerDumpType));
|
||||
DumpAnalysis mini(dump_file_);
|
||||
|
||||
// The dump should have all of these streams.
|
||||
EXPECT_TRUE(mini.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(MemoryListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(mini.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(MiscInfoStream));
|
||||
|
||||
// We expect memory referenced by stack in this dump.
|
||||
EXPECT_TRUE(mini.HasMemory(this));
|
||||
|
||||
// We expect PEB and TEBs in this dump.
|
||||
EXPECT_TRUE(mini.HasTebs());
|
||||
EXPECT_TRUE(mini.HasPeb());
|
||||
|
||||
EXPECT_FALSE(mini.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(mini.HasStream(Memory64ListStream));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(mini.HasStream(HandleDataStream));
|
||||
EXPECT_FALSE(mini.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(mini.HasStream(TokenStream));
|
||||
}
|
||||
|
||||
TEST_F(MinidumpTest, FullDump) {
|
||||
ASSERT_TRUE(WriteDump(kFullDumpType));
|
||||
ASSERT_TRUE(dump_file_ != L"");
|
||||
ASSERT_TRUE(full_dump_file_ != L"");
|
||||
DumpAnalysis mini(dump_file_);
|
||||
DumpAnalysis full(full_dump_file_);
|
||||
|
||||
// Either dumps can contain part of the information.
|
||||
|
||||
// The dump should have all of these streams.
|
||||
EXPECT_TRUE(mini.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(full.HasStream(ThreadListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(full.HasStream(ModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(full.HasStream(ExceptionStream));
|
||||
EXPECT_TRUE(mini.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(full.HasStream(SystemInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream));
|
||||
EXPECT_TRUE(full.HasStream(UnloadedModuleListStream));
|
||||
EXPECT_TRUE(mini.HasStream(MiscInfoStream));
|
||||
EXPECT_TRUE(full.HasStream(MiscInfoStream));
|
||||
EXPECT_TRUE(mini.HasStream(HandleDataStream));
|
||||
EXPECT_TRUE(full.HasStream(HandleDataStream));
|
||||
|
||||
// We expect memory referenced by stack in this dump.
|
||||
EXPECT_FALSE(mini.HasMemory(this));
|
||||
EXPECT_TRUE(full.HasMemory(this));
|
||||
|
||||
// We expect PEB and TEBs in this dump.
|
||||
EXPECT_TRUE(mini.HasTebs() || full.HasTebs());
|
||||
EXPECT_TRUE(mini.HasPeb() || full.HasPeb());
|
||||
|
||||
EXPECT_TRUE(mini.HasStream(MemoryListStream));
|
||||
EXPECT_TRUE(full.HasStream(Memory64ListStream));
|
||||
EXPECT_FALSE(mini.HasStream(Memory64ListStream));
|
||||
EXPECT_FALSE(full.HasStream(MemoryListStream));
|
||||
|
||||
// This is the only place we don't use OR because we want both not
|
||||
// to have the streams.
|
||||
EXPECT_FALSE(mini.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(full.HasStream(ThreadExListStream));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(full.HasStream(CommentStreamA));
|
||||
EXPECT_FALSE(mini.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(full.HasStream(CommentStreamW));
|
||||
EXPECT_FALSE(mini.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(full.HasStream(FunctionTableStream));
|
||||
EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(full.HasStream(MemoryInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(full.HasStream(ThreadInfoListStream));
|
||||
EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(full.HasStream(HandleOperationListStream));
|
||||
EXPECT_FALSE(mini.HasStream(TokenStream));
|
||||
EXPECT_FALSE(full.HasStream(TokenStream));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user