Fix CrashGenerationServer to recover from protocol errors and a test for same.

R=siggi at http://breakpad.appspot.com/196001/show


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@695 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
erikwright@chromium.org 2010-09-20 21:35:24 +00:00
parent 378e28e301
commit b6ee7dcb22
6 changed files with 442 additions and 77 deletions

View File

@ -40,7 +40,7 @@
'./handler/exception_handler.gyp:*', './handler/exception_handler.gyp:*',
'./sender/crash_report_sender.gyp:*', './sender/crash_report_sender.gyp:*',
'./unittests/client_tests.gyp:*', './unittests/client_tests.gyp:*',
'./unittests/gtest.gyp:*', './unittests/testing.gyp:*',
] ]
}, },
{ {

View File

@ -34,6 +34,8 @@
#include "client/windows/common/auto_critical_section.h" #include "client/windows/common/auto_critical_section.h"
#include "processor/scoped_ptr.h" #include "processor/scoped_ptr.h"
#include "client/windows/crash_generation/client_info.h"
namespace google_breakpad { namespace google_breakpad {
// Output buffer size. // Output buffer size.
@ -113,7 +115,7 @@ CrashGenerationServer::CrashGenerationServer(
exit_context_(exit_context), exit_context_(exit_context),
generate_dumps_(generate_dumps), generate_dumps_(generate_dumps),
dump_generator_(NULL), dump_generator_(NULL),
server_state_(IPC_SERVER_STATE_INITIAL), server_state_(IPC_SERVER_STATE_UNINITIALIZED),
shutting_down_(false), shutting_down_(false),
overlapped_(), overlapped_(),
client_info_(NULL), client_info_(NULL),
@ -197,10 +199,18 @@ CrashGenerationServer::~CrashGenerationServer() {
CloseHandle(server_alive_handle_); CloseHandle(server_alive_handle_);
} }
if (overlapped_.hEvent) {
CloseHandle(overlapped_.hEvent);
}
DeleteCriticalSection(&clients_sync_); DeleteCriticalSection(&clients_sync_);
} }
bool CrashGenerationServer::Start() { bool CrashGenerationServer::Start() {
if (server_state_ != IPC_SERVER_STATE_UNINITIALIZED) {
return false;
}
server_state_ = IPC_SERVER_STATE_INITIAL; server_state_ = IPC_SERVER_STATE_INITIAL;
server_alive_handle_ = CreateMutex(NULL, TRUE, NULL); server_alive_handle_ = CreateMutex(NULL, TRUE, NULL);
@ -239,9 +249,12 @@ bool CrashGenerationServer::Start() {
return false; return false;
} }
// Signal the event to start a separate thread to handle // Kick-start the state machine. This will initiate an asynchronous wait
// client connections. // for client connections.
return SetEvent(overlapped_.hEvent) != FALSE; HandleInitialState();
// If we are in error state, it's because we failed to start listening.
return server_state_ != IPC_SERVER_STATE_ERROR;
} }
// If the server thread serving clients ever gets into the // If the server thread serving clients ever gets into the
@ -283,33 +296,29 @@ void CrashGenerationServer::HandleInitialState() {
assert(server_state_ == IPC_SERVER_STATE_INITIAL); assert(server_state_ == IPC_SERVER_STATE_INITIAL);
if (!ResetEvent(overlapped_.hEvent)) { if (!ResetEvent(overlapped_.hEvent)) {
server_state_ = IPC_SERVER_STATE_ERROR; EnterErrorState();
return; return;
} }
bool success = ConnectNamedPipe(pipe_, &overlapped_) != FALSE; bool success = ConnectNamedPipe(pipe_, &overlapped_) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
// From MSDN, it is not clear that when ConnectNamedPipe is used // From MSDN, it is not clear that when ConnectNamedPipe is used
// in an overlapped mode, will it ever return non-zero value, and // in an overlapped mode, will it ever return non-zero value, and
// if so, in what cases. // if so, in what cases.
assert(!success); assert(!success);
DWORD error_code = GetLastError();
switch (error_code) { switch (error_code) {
case ERROR_IO_PENDING: case ERROR_IO_PENDING:
server_state_ = IPC_SERVER_STATE_CONNECTING; EnterStateWhenSignaled(IPC_SERVER_STATE_CONNECTING);
break; break;
case ERROR_PIPE_CONNECTED: case ERROR_PIPE_CONNECTED:
if (SetEvent(overlapped_.hEvent)) { EnterStateImmediately(IPC_SERVER_STATE_CONNECTED);
server_state_ = IPC_SERVER_STATE_CONNECTED;
} else {
server_state_ = IPC_SERVER_STATE_ERROR;
}
break; break;
default: default:
server_state_ = IPC_SERVER_STATE_ERROR; EnterErrorState();
break; break;
} }
} }
@ -328,14 +337,14 @@ void CrashGenerationServer::HandleConnectingState() {
&overlapped_, &overlapped_,
&bytes_count, &bytes_count,
FALSE) != FALSE; FALSE) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
if (success) { if (success) {
server_state_ = IPC_SERVER_STATE_CONNECTED; EnterStateImmediately(IPC_SERVER_STATE_CONNECTED);
return; } else if (error_code != ERROR_IO_INCOMPLETE) {
} EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
} else {
if (GetLastError() != ERROR_IO_INCOMPLETE) { // remain in CONNECTING state
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
} }
} }
@ -353,16 +362,17 @@ void CrashGenerationServer::HandleConnectedState() {
sizeof(msg_), sizeof(msg_),
&bytes_count, &bytes_count,
&overlapped_) != FALSE; &overlapped_) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
// Note that the asynchronous read issued above can finish before the // Note that the asynchronous read issued above can finish before the
// code below executes. But, it is okay to change state after issuing // code below executes. But, it is okay to change state after issuing
// the asynchronous read. This is because even if the asynchronous read // the asynchronous read. This is because even if the asynchronous read
// is done, the callback for it would not be executed until the current // is done, the callback for it would not be executed until the current
// thread finishes its execution. // thread finishes its execution.
if (success || GetLastError() == ERROR_IO_PENDING) { if (success || error_code == ERROR_IO_PENDING) {
server_state_ = IPC_SERVER_STATE_READING; EnterStateWhenSignaled(IPC_SERVER_STATE_READING);
} else { } else {
server_state_ = IPC_SERVER_STATE_DISCONNECTING; EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
} }
} }
@ -378,21 +388,18 @@ void CrashGenerationServer::HandleReadingState() {
&overlapped_, &overlapped_,
&bytes_count, &bytes_count,
FALSE) != FALSE; FALSE) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
if (success && bytes_count == sizeof(ProtocolMessage)) { if (success && bytes_count == sizeof(ProtocolMessage)) {
server_state_ = IPC_SERVER_STATE_READ_DONE; EnterStateImmediately(IPC_SERVER_STATE_READ_DONE);
return; } else {
// We should never get an I/O incomplete since we should not execute this
// unless the Read has finished and the overlapped event is signaled. If
// we do get INCOMPLETE, we have a bug in our code.
assert(error_code != ERROR_IO_INCOMPLETE);
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
} }
DWORD error_code;
error_code = GetLastError();
// We should never get an I/O incomplete since we should not execute this
// unless the Read has finished and the overlapped event is signaled. If
// we do get INCOMPLETE, we have a bug in our code.
assert(error_code != ERROR_IO_INCOMPLETE);
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
} }
// When the server thread serving the client is in the READ_DONE state, // When the server thread serving the client is in the READ_DONE state,
@ -405,7 +412,7 @@ void CrashGenerationServer::HandleReadDoneState() {
assert(server_state_ == IPC_SERVER_STATE_READ_DONE); assert(server_state_ == IPC_SERVER_STATE_READ_DONE);
if (!IsClientRequestValid(msg_)) { if (!IsClientRequestValid(msg_)) {
server_state_ = IPC_SERVER_STATE_DISCONNECTING; EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
return; return;
} }
@ -419,22 +426,26 @@ void CrashGenerationServer::HandleReadDoneState() {
msg_.custom_client_info)); msg_.custom_client_info));
if (!client_info->Initialize()) { if (!client_info->Initialize()) {
server_state_ = IPC_SERVER_STATE_DISCONNECTING; EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
return; 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())) { if (!RespondToClient(client_info.get())) {
server_state_ = IPC_SERVER_STATE_DISCONNECTING; EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
return; return;
} }
client_info_ = client_info.release();
// Note that the asynchronous write issued by RespondToClient function // Note that the asynchronous write issued by RespondToClient function
// can finish before the code below executes. But it is okay to change // can finish before the code below executes. But it is okay to change
// state after issuing the asynchronous write. This is because even if // state after issuing the asynchronous write. This is because even if
// the asynchronous write is done, the callback for it would not be // the asynchronous write is done, the callback for it would not be
// executed until the current thread finishes its execution. // executed until the current thread finishes its execution.
server_state_ = IPC_SERVER_STATE_WRITING; EnterStateWhenSignaled(IPC_SERVER_STATE_WRITING);
client_info_ = client_info.release();
} }
// When the server thread serving the clients is in the WRITING state, // When the server thread serving the clients is in the WRITING state,
@ -449,21 +460,19 @@ void CrashGenerationServer::HandleWritingState() {
&overlapped_, &overlapped_,
&bytes_count, &bytes_count,
FALSE) != FALSE; FALSE) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
if (success) { if (success) {
server_state_ = IPC_SERVER_STATE_WRITE_DONE; EnterStateImmediately(IPC_SERVER_STATE_WRITE_DONE);
return; return;
} }
DWORD error_code;
error_code = GetLastError();
// We should never get an I/O incomplete since we should not execute this // We should never get an I/O incomplete since we should not execute this
// unless the Write has finished and the overlapped event is signaled. If // unless the Write has finished and the overlapped event is signaled. If
// we do get INCOMPLETE, we have a bug in our code. // we do get INCOMPLETE, we have a bug in our code.
assert(error_code != ERROR_IO_INCOMPLETE); assert(error_code != ERROR_IO_INCOMPLETE);
server_state_ = IPC_SERVER_STATE_DISCONNECTING; EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
} }
// When the server thread serving the clients is in the WRITE_DONE state, // When the server thread serving the clients is in the WRITE_DONE state,
@ -473,23 +482,20 @@ void CrashGenerationServer::HandleWritingState() {
void CrashGenerationServer::HandleWriteDoneState() { void CrashGenerationServer::HandleWriteDoneState() {
assert(server_state_ == IPC_SERVER_STATE_WRITE_DONE); assert(server_state_ == IPC_SERVER_STATE_WRITE_DONE);
server_state_ = IPC_SERVER_STATE_READING_ACK;
DWORD bytes_count = 0; DWORD bytes_count = 0;
bool success = ReadFile(pipe_, bool success = ReadFile(pipe_,
&msg_, &msg_,
sizeof(msg_), sizeof(msg_),
&bytes_count, &bytes_count,
&overlapped_) != FALSE; &overlapped_) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
if (success) { if (success) {
return; EnterStateImmediately(IPC_SERVER_STATE_READING_ACK);
} } else if (error_code == ERROR_IO_PENDING) {
EnterStateWhenSignaled(IPC_SERVER_STATE_READING_ACK);
DWORD error_code = GetLastError(); } else {
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
if (error_code != ERROR_IO_PENDING) {
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
} }
} }
@ -503,6 +509,7 @@ void CrashGenerationServer::HandleReadingAckState() {
&overlapped_, &overlapped_,
&bytes_count, &bytes_count,
FALSE) != FALSE; FALSE) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
if (success) { if (success) {
// The connection handshake with the client is now complete; perform // The connection handshake with the client is now complete; perform
@ -511,15 +518,13 @@ void CrashGenerationServer::HandleReadingAckState() {
connect_callback_(connect_context_, client_info_); connect_callback_(connect_context_, client_info_);
} }
} else { } else {
DWORD error_code = GetLastError();
// We should never get an I/O incomplete since we should not execute this // We should never get an I/O incomplete since we should not execute this
// unless the Read has finished and the overlapped event is signaled. If // unless the Read has finished and the overlapped event is signaled. If
// we do get INCOMPLETE, we have a bug in our code. // we do get INCOMPLETE, we have a bug in our code.
assert(error_code != ERROR_IO_INCOMPLETE); assert(error_code != ERROR_IO_INCOMPLETE);
} }
server_state_ = IPC_SERVER_STATE_DISCONNECTING; EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
} }
// When the server thread serving the client is in the DISCONNECTING state, // When the server thread serving the client is in the DISCONNECTING state,
@ -539,12 +544,12 @@ void CrashGenerationServer::HandleDisconnectingState() {
overlapped_.Pointer = NULL; overlapped_.Pointer = NULL;
if (!ResetEvent(overlapped_.hEvent)) { if (!ResetEvent(overlapped_.hEvent)) {
server_state_ = IPC_SERVER_STATE_ERROR; EnterErrorState();
return; return;
} }
if (!DisconnectNamedPipe(pipe_)) { if (!DisconnectNamedPipe(pipe_)) {
server_state_ = IPC_SERVER_STATE_ERROR; EnterErrorState();
return; return;
} }
@ -554,7 +559,21 @@ void CrashGenerationServer::HandleDisconnectingState() {
return; return;
} }
server_state_ = IPC_SERVER_STATE_INITIAL; 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)) { if (!SetEvent(overlapped_.hEvent)) {
server_state_ = IPC_SERVER_STATE_ERROR; server_state_ = IPC_SERVER_STATE_ERROR;
} }
@ -626,18 +645,25 @@ bool CrashGenerationServer::RespondToClient(ClientInfo* client_info) {
return false; 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.
if (!AddClient(client_info)) { if (!AddClient(client_info)) {
return false; return false;
} }
DWORD bytes_count = 0; return true;
bool success = WriteFile(pipe_,
&reply,
sizeof(reply),
&bytes_count,
&overlapped_) != FALSE;
return success || GetLastError() == ERROR_IO_PENDING;
} }
// The server thread servicing the clients runs this method. The method // The server thread servicing the clients runs this method. The method
@ -739,7 +765,7 @@ bool CrashGenerationServer::AddClient(ClientInfo* client_info) {
// static // static
void CALLBACK CrashGenerationServer::OnPipeConnected(void* context, BOOLEAN) { void CALLBACK CrashGenerationServer::OnPipeConnected(void* context, BOOLEAN) {
assert (context); assert(context);
CrashGenerationServer* obj = CrashGenerationServer* obj =
reinterpret_cast<CrashGenerationServer*>(context); reinterpret_cast<CrashGenerationServer*>(context);

View File

@ -33,11 +33,11 @@
#include <list> #include <list>
#include <string> #include <string>
#include "client/windows/common/ipc_protocol.h" #include "client/windows/common/ipc_protocol.h"
#include "client/windows/crash_generation/client_info.h"
#include "client/windows/crash_generation/minidump_generator.h" #include "client/windows/crash_generation/minidump_generator.h"
#include "processor/scoped_ptr.h" #include "processor/scoped_ptr.h"
namespace google_breakpad { namespace google_breakpad {
class ClientInfo;
// Abstraction for server side implementation of out-of-process crash // Abstraction for server side implementation of out-of-process crash
// generation protocol for Windows platform only. It generates Windows // generation protocol for Windows platform only. It generates Windows
@ -91,7 +91,8 @@ class CrashGenerationServer {
~CrashGenerationServer(); ~CrashGenerationServer();
// Performs initialization steps needed to start listening to clients. // 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. // Returns true if initialization is successful; false otherwise.
bool Start(); bool Start();
@ -100,6 +101,9 @@ class CrashGenerationServer {
// Various states the client can be in during the handshake with // Various states the client can be in during the handshake with
// the server. // the server.
enum IPCServerState { enum IPCServerState {
// Server starts in this state.
IPC_SERVER_STATE_UNINITIALIZED,
// Server is in error state and it cannot serve any clients. // Server is in error state and it cannot serve any clients.
IPC_SERVER_STATE_ERROR, IPC_SERVER_STATE_ERROR,
@ -192,6 +196,21 @@ class CrashGenerationServer {
// Generates dump for the given client. // Generates dump for the given client.
bool GenerateDump(const ClientInfo& client, std::wstring* dump_path); 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. // Sync object for thread-safe access to the shared list of clients.
CRITICAL_SECTION clients_sync_; CRITICAL_SECTION clients_sync_;

View File

@ -41,9 +41,11 @@
'minidump_test.cc', 'minidump_test.cc',
'dump_analysis.cc', 'dump_analysis.cc',
'dump_analysis.h', 'dump_analysis.h',
'crash_generation_server_test.cc'
], ],
'dependencies': [ 'dependencies': [
'gtest.gyp:gtest', 'testing.gyp:gtest',
'testing.gyp:gmock',
'../breakpad_client.gyp:common', '../breakpad_client.gyp:common',
'../crash_generation/crash_generation.gyp:crash_generation_server', '../crash_generation/crash_generation.gyp:crash_generation_server',
'../crash_generation/crash_generation.gyp:crash_generation_client', '../crash_generation/crash_generation.gyp:crash_generation_client',

View File

@ -0,0 +1,298 @@
// Copyright 2010, 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 "testing/gtest/include/gtest/gtest.h"
#include "testing/include/gmock/gmock.h"
#include "client/windows/crash_generation/crash_generation_server.h"
#include "client/windows/common/ipc_protocol.h"
using testing::_;
namespace {
const wchar_t kPipeName[] =
L"\\\\.\\pipe\\CrashGenerationServerTest\\TestCaseServer";
const DWORD kPipeDesiredAccess = FILE_READ_DATA |
FILE_WRITE_DATA |
FILE_WRITE_ATTRIBUTES;
const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION |
SECURITY_SQOS_PRESENT;
const DWORD kPipeMode = PIPE_READMODE_MESSAGE;
int kCustomInfoCount = 2;
google_breakpad::CustomInfoEntry kCustomInfoEntries[] = {
google_breakpad::CustomInfoEntry(L"prod", L"CrashGenerationServerTest"),
google_breakpad::CustomInfoEntry(L"ver", L"1.0"),
};
class CrashGenerationServerTest : public ::testing::Test {
public:
CrashGenerationServerTest()
: crash_generation_server_(kPipeName,
NULL,
CallOnClientConnected, &mock_callbacks_,
CallOnClientDumpRequested, &mock_callbacks_,
CallOnClientExited, &mock_callbacks_,
false,
NULL),
thread_id_(0),
exception_pointers_(NULL) {
memset(&assert_info_, 0, sizeof(assert_info_));
}
protected:
class MockCrashGenerationServerCallbacks {
public:
MOCK_METHOD1(OnClientConnected,
void(const google_breakpad::ClientInfo* client_info));
MOCK_METHOD2(OnClientDumpRequested,
void(const google_breakpad::ClientInfo* client_info,
const std::wstring* file_path));
MOCK_METHOD1(OnClientExited,
void(const google_breakpad::ClientInfo* client_info));
};
enum ClientFault {
NO_FAULT,
CLOSE_AFTER_CONNECT,
SEND_INVALID_REGISTRATION,
TRUNCATE_REGISTRATION,
CLOSE_AFTER_REGISTRATION,
RESPONSE_BUFFER_TOO_SMALL,
CLOSE_AFTER_RESPONSE,
SEND_INVALID_ACK
};
void SetUp() {
ASSERT_TRUE(crash_generation_server_.Start());
}
void FaultyClient(ClientFault fault_type) {
HANDLE pipe = CreateFile(kPipeName,
kPipeDesiredAccess,
0,
NULL,
OPEN_EXISTING,
kPipeFlagsAndAttributes,
NULL);
if (pipe == INVALID_HANDLE_VALUE) {
ASSERT_EQ(ERROR_PIPE_BUSY, GetLastError());
// Cannot continue retrying if wait on pipe fails.
ASSERT_TRUE(WaitNamedPipe(kPipeName, 500));
pipe = CreateFile(kPipeName,
kPipeDesiredAccess,
0,
NULL,
OPEN_EXISTING,
kPipeFlagsAndAttributes,
NULL);
}
ASSERT_NE(pipe, INVALID_HANDLE_VALUE);
DWORD mode = kPipeMode;
ASSERT_TRUE(SetNamedPipeHandleState(pipe, &mode, NULL, NULL));
DoFaultyClient(fault_type, pipe);
CloseHandle(pipe);
}
void DoTestFault(ClientFault fault) {
EXPECT_CALL(mock_callbacks_, OnClientConnected(_)).Times(0);
ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT));
// Slight hack. The OnClientConnected is only invoked after the ack is
// received by the server. At that point, the FaultyClient call has already
// returned. The best way to wait until the server is done handling that is
// to send one more ping, whose processing will be blocked by delivery of
// the OnClientConnected message.
ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
}
MockCrashGenerationServerCallbacks mock_callbacks_;
private:
// Depends on the caller to successfully open the pipe before invocation and
// to close it immediately afterwards.
void DoFaultyClient(ClientFault fault_type, HANDLE pipe) {
if (fault_type == CLOSE_AFTER_CONNECT) {
return;
}
google_breakpad::CustomClientInfo custom_info = {kCustomInfoEntries,
kCustomInfoCount};
google_breakpad::ProtocolMessage msg(
fault_type == SEND_INVALID_REGISTRATION ?
google_breakpad::MESSAGE_TAG_NONE :
google_breakpad::MESSAGE_TAG_REGISTRATION_REQUEST,
GetCurrentProcessId(),
MiniDumpNormal,
&thread_id_,
&exception_pointers_,
&assert_info_,
custom_info,
NULL,
NULL,
NULL);
DWORD bytes_count = 0;
ASSERT_TRUE(WriteFile(pipe,
&msg,
fault_type == TRUNCATE_REGISTRATION ?
sizeof(msg) / 2 : sizeof(msg),
&bytes_count,
NULL));
if (fault_type == CLOSE_AFTER_REGISTRATION) {
return;
}
google_breakpad::ProtocolMessage reply;
if (!ReadFile(pipe,
&reply,
fault_type == RESPONSE_BUFFER_TOO_SMALL ?
sizeof(google_breakpad::ProtocolMessage) / 2 :
sizeof(google_breakpad::ProtocolMessage),
&bytes_count,
NULL)) {
switch (fault_type) {
case TRUNCATE_REGISTRATION:
case RESPONSE_BUFFER_TOO_SMALL:
case SEND_INVALID_REGISTRATION:
return;
default:
FAIL() << "Unexpectedly failed to register.";
}
}
if (fault_type == CLOSE_AFTER_RESPONSE) {
return;
}
google_breakpad::ProtocolMessage ack_msg;
ack_msg.tag = google_breakpad::MESSAGE_TAG_REGISTRATION_ACK;
ASSERT_TRUE(WriteFile(pipe,
&ack_msg,
SEND_INVALID_ACK ?
sizeof(ack_msg) : sizeof(ack_msg) / 2,
&bytes_count,
NULL));
return;
}
static void CallOnClientConnected(
void* context, const google_breakpad::ClientInfo* client_info) {
static_cast<MockCrashGenerationServerCallbacks*>(context)->
OnClientConnected(client_info);
}
static void CallOnClientDumpRequested(
void* context,
const google_breakpad::ClientInfo* client_info,
const std::wstring* file_path) {
static_cast<MockCrashGenerationServerCallbacks*>(context)->
OnClientDumpRequested(client_info, file_path);
}
static void CallOnClientExited(
void* context, const google_breakpad::ClientInfo* client_info) {
static_cast<MockCrashGenerationServerCallbacks*>(context)->
OnClientExited(client_info);
}
DWORD thread_id_;
EXCEPTION_POINTERS* exception_pointers_;
MDRawAssertionInfo assert_info_;
google_breakpad::CrashGenerationServer crash_generation_server_;
};
TEST_F(CrashGenerationServerTest, PingServerTest) {
DoTestFault(CLOSE_AFTER_CONNECT);
}
TEST_F(CrashGenerationServerTest, InvalidRegistration) {
DoTestFault(SEND_INVALID_REGISTRATION);
}
TEST_F(CrashGenerationServerTest, TruncateRegistration) {
DoTestFault(TRUNCATE_REGISTRATION);
}
TEST_F(CrashGenerationServerTest, CloseAfterRegistration) {
DoTestFault(CLOSE_AFTER_REGISTRATION);
}
TEST_F(CrashGenerationServerTest, ResponseBufferTooSmall) {
DoTestFault(RESPONSE_BUFFER_TOO_SMALL);
}
TEST_F(CrashGenerationServerTest, CloseAfterResponse) {
DoTestFault(CLOSE_AFTER_RESPONSE);
}
// It turns out that, as long as you send one byte, the ACK is accepted and
// registration succeeds.
TEST_F(CrashGenerationServerTest, SendInvalidAck) {
EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
ASSERT_NO_FATAL_FAILURE(FaultyClient(SEND_INVALID_ACK));
// See DoTestFault for an explanation of this line
ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT));
// See DoTestFault for an explanation of this line
ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
}
} // anonymous namespace

View File

@ -44,7 +44,6 @@
], ],
'sources': [ 'sources': [
'<(DEPTH)/testing/gtest/src/gtest-all.cc', '<(DEPTH)/testing/gtest/src/gtest-all.cc',
'<(DEPTH)/testing/gtest/src/gtest_main.cc',
], ],
'direct_dependent_settings': { 'direct_dependent_settings': {
'include_dirs': [ 'include_dirs': [
@ -53,5 +52,26 @@
] ]
}, },
}, },
{
'target_name': 'gmock',
'type': '<(library)',
'include_dirs': [
'<(DEPTH)/testing/include',
'<(DEPTH)/testing/',
'<(DEPTH)/testing/gtest',
'<(DEPTH)/testing/gtest/include',
],
'sources': [
'<(DEPTH)/testing/src/gmock-all.cc',
'<(DEPTH)/testing/src/gmock_main.cc',
],
'direct_dependent_settings': {
'include_dirs': [
'<(DEPTH)/testing/include',
'<(DEPTH)/testing/gtest/include',
]
},
},
], ],
} }