Source code
Revision control
Copy as Markdown
Other Tools
// Copyright (c) 2008, Google Inc.
// All rights reserved.
//
// 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 Inc. 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.
#include "windows/crash_generation/crash_generation_server.h"
#include <windows.h>
#include <cassert>
#include <list>
#include "windows/common/auto_critical_section.h"
#include "common/scoped_ptr.h"
#include "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,
OnClientDumpWrittenCallback written_callback,
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),
written_callback_(written_callback),
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),
include_context_heap_(false) {
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);
// New scope to hold the lock for the shortest time.
{
AutoCriticalSection lock(&sync_);
// 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);
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);
}
AutoCriticalSection lock(&sync_);
// 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) {
dump_callback_(dump_context_, client_info, dump_path);
}
SetEvent(client_info.dump_generated_handle());
if (written_callback_ && execute_callback) {
written_callback_(dump_context_, client_info);
}
}
void CrashGenerationServer::set_include_context_heap(bool enabled) {
include_context_heap_ = enabled;
}
bool CrashGenerationServer::GenerateDump(const ClientInfo& client,
std::wstring* dump_path) {
assert(client.pid() != 0);
assert(client.process_handle());
DWORD client_thread_id = 0;
if (!client.GetClientThreadId(&client_thread_id)) {
return false;
}
// We have to get the address of EXCEPTION_INFORMATION from
// the client process address space.
EXCEPTION_POINTERS* client_ex_info = NULL;
// Only needs to read the value of a remote pointer in client_ex_info.
if (!client.GetClientExceptionInfo(&client_ex_info)) {
return false;
}
if (include_context_heap_) {
CONTEXT context_content;
// Needs to read the content of CONTEXT from the client.
if (!client.PopulateClientExceptionContext(client_ex_info,
&context_content)) {
include_context_heap_ = false;
}
// Allocate AppMemory instances for exception context.
for (size_t i = 0; i < kExceptionAppMemoryRegions; i++) {
AppMemory app_memory;
app_memory.ptr = reinterpret_cast<ULONG64>(nullptr);
app_memory.length = 0;
app_memory.preallocated = true;
app_memory_info_.push_back(app_memory);
}
IncludeAppMemoryFromExceptionContext(client.process_handle(),
client_thread_id,
app_memory_info_,
&context_content,
!include_context_heap_);
}
MinidumpGenerator dump_generator(dump_path_,
client.process_handle(),
client.pid(),
client_thread_id,
GetCurrentThreadId(),
client_ex_info,
client.assert_info(),
client.dump_type(),
true);
MinidumpCallbackContext callback_context;
MINIDUMP_CALLBACK_INFORMATION callback;
if (include_context_heap_) {
// Set memory callback to include heap regions.
callback_context.iter = app_memory_info_.cbegin();
callback_context.end = app_memory_info_.cend();
callback.CallbackRoutine = MinidumpWriteDumpCallback;
callback.CallbackParam = &callback_context;
dump_generator.SetCallback(&callback);
}
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