Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
Signed by: crueter
GPG key ID: A5A7629F109C8FD1
4081 changed files with 1185566 additions and 45 deletions

View file

@ -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.
=========================================================================

View file

@ -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

View file

@ -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__

View file

@ -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

View file

@ -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_

View file

@ -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

View file

@ -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__

View file

@ -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

View file

@ -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_