| // 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 "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 |