// 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_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), dump_type_(dump_type), thread_id_(0), server_process_id_(0), crash_event_(NULL), crash_generated_(NULL), server_alive_(NULL), exception_pointers_(NULL), custom_info_() { 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), dump_type_(dump_type), thread_id_(0), server_process_id_(0), crash_event_(NULL), crash_generated_(NULL), server_alive_(NULL), exception_pointers_(NULL), custom_info_() { 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