mirror of
https://git.suyu.dev/suyu/breakpad.git
synced 2026-03-04 11:56:31 +00:00
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:
@@ -34,6 +34,8 @@
|
||||
#include "client/windows/common/auto_critical_section.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
#include "client/windows/crash_generation/client_info.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Output buffer size.
|
||||
@@ -113,7 +115,7 @@ CrashGenerationServer::CrashGenerationServer(
|
||||
exit_context_(exit_context),
|
||||
generate_dumps_(generate_dumps),
|
||||
dump_generator_(NULL),
|
||||
server_state_(IPC_SERVER_STATE_INITIAL),
|
||||
server_state_(IPC_SERVER_STATE_UNINITIALIZED),
|
||||
shutting_down_(false),
|
||||
overlapped_(),
|
||||
client_info_(NULL),
|
||||
@@ -197,10 +199,18 @@ CrashGenerationServer::~CrashGenerationServer() {
|
||||
CloseHandle(server_alive_handle_);
|
||||
}
|
||||
|
||||
if (overlapped_.hEvent) {
|
||||
CloseHandle(overlapped_.hEvent);
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&clients_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);
|
||||
@@ -239,9 +249,12 @@ bool CrashGenerationServer::Start() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Signal the event to start a separate thread to handle
|
||||
// client connections.
|
||||
return SetEvent(overlapped_.hEvent) != FALSE;
|
||||
// Kick-start the state machine. This will initiate an asynchronous wait
|
||||
// for client connections.
|
||||
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
|
||||
@@ -283,33 +296,29 @@ void CrashGenerationServer::HandleInitialState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_INITIAL);
|
||||
|
||||
if (!ResetEvent(overlapped_.hEvent)) {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
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);
|
||||
|
||||
DWORD error_code = GetLastError();
|
||||
switch (error_code) {
|
||||
case ERROR_IO_PENDING:
|
||||
server_state_ = IPC_SERVER_STATE_CONNECTING;
|
||||
EnterStateWhenSignaled(IPC_SERVER_STATE_CONNECTING);
|
||||
break;
|
||||
|
||||
case ERROR_PIPE_CONNECTED:
|
||||
if (SetEvent(overlapped_.hEvent)) {
|
||||
server_state_ = IPC_SERVER_STATE_CONNECTED;
|
||||
} else {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
}
|
||||
EnterStateImmediately(IPC_SERVER_STATE_CONNECTED);
|
||||
break;
|
||||
|
||||
default:
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
EnterErrorState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -328,14 +337,14 @@ void CrashGenerationServer::HandleConnectingState() {
|
||||
&overlapped_,
|
||||
&bytes_count,
|
||||
FALSE) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
if (success) {
|
||||
server_state_ = IPC_SERVER_STATE_CONNECTED;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetLastError() != ERROR_IO_INCOMPLETE) {
|
||||
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
|
||||
EnterStateImmediately(IPC_SERVER_STATE_CONNECTED);
|
||||
} else if (error_code != ERROR_IO_INCOMPLETE) {
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
} else {
|
||||
// remain in CONNECTING state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,16 +362,17 @@ void CrashGenerationServer::HandleConnectedState() {
|
||||
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 || GetLastError() == ERROR_IO_PENDING) {
|
||||
server_state_ = IPC_SERVER_STATE_READING;
|
||||
if (success || error_code == ERROR_IO_PENDING) {
|
||||
EnterStateWhenSignaled(IPC_SERVER_STATE_READING);
|
||||
} else {
|
||||
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,21 +388,18 @@ void CrashGenerationServer::HandleReadingState() {
|
||||
&overlapped_,
|
||||
&bytes_count,
|
||||
FALSE) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
if (success && bytes_count == sizeof(ProtocolMessage)) {
|
||||
server_state_ = IPC_SERVER_STATE_READ_DONE;
|
||||
return;
|
||||
EnterStateImmediately(IPC_SERVER_STATE_READ_DONE);
|
||||
} 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,
|
||||
@@ -405,7 +412,7 @@ void CrashGenerationServer::HandleReadDoneState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_READ_DONE);
|
||||
|
||||
if (!IsClientRequestValid(msg_)) {
|
||||
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -419,22 +426,26 @@ void CrashGenerationServer::HandleReadDoneState() {
|
||||
msg_.custom_client_info));
|
||||
|
||||
if (!client_info->Initialize()) {
|
||||
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
|
||||
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())) {
|
||||
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
server_state_ = IPC_SERVER_STATE_WRITING;
|
||||
client_info_ = client_info.release();
|
||||
EnterStateWhenSignaled(IPC_SERVER_STATE_WRITING);
|
||||
}
|
||||
|
||||
// When the server thread serving the clients is in the WRITING state,
|
||||
@@ -449,21 +460,19 @@ void CrashGenerationServer::HandleWritingState() {
|
||||
&overlapped_,
|
||||
&bytes_count,
|
||||
FALSE) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
if (success) {
|
||||
server_state_ = IPC_SERVER_STATE_WRITE_DONE;
|
||||
EnterStateImmediately(IPC_SERVER_STATE_WRITE_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD error_code;
|
||||
error_code = GetLastError();
|
||||
|
||||
// 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
|
||||
// we do get INCOMPLETE, we have a bug in our code.
|
||||
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,
|
||||
@@ -473,23 +482,20 @@ void CrashGenerationServer::HandleWritingState() {
|
||||
void CrashGenerationServer::HandleWriteDoneState() {
|
||||
assert(server_state_ == IPC_SERVER_STATE_WRITE_DONE);
|
||||
|
||||
server_state_ = IPC_SERVER_STATE_READING_ACK;
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
bool success = ReadFile(pipe_,
|
||||
&msg_,
|
||||
sizeof(msg_),
|
||||
&bytes_count,
|
||||
&overlapped_) != FALSE;
|
||||
&msg_,
|
||||
sizeof(msg_),
|
||||
&bytes_count,
|
||||
&overlapped_) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD error_code = GetLastError();
|
||||
|
||||
if (error_code != ERROR_IO_PENDING) {
|
||||
server_state_ = IPC_SERVER_STATE_DISCONNECTING;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,6 +509,7 @@ void CrashGenerationServer::HandleReadingAckState() {
|
||||
&overlapped_,
|
||||
&bytes_count,
|
||||
FALSE) != FALSE;
|
||||
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
|
||||
|
||||
if (success) {
|
||||
// The connection handshake with the client is now complete; perform
|
||||
@@ -511,15 +518,13 @@ void CrashGenerationServer::HandleReadingAckState() {
|
||||
connect_callback_(connect_context_, client_info_);
|
||||
}
|
||||
} else {
|
||||
DWORD 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;
|
||||
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
|
||||
}
|
||||
|
||||
// When the server thread serving the client is in the DISCONNECTING state,
|
||||
@@ -539,12 +544,12 @@ void CrashGenerationServer::HandleDisconnectingState() {
|
||||
overlapped_.Pointer = NULL;
|
||||
|
||||
if (!ResetEvent(overlapped_.hEvent)) {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
EnterErrorState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DisconnectNamedPipe(pipe_)) {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
EnterErrorState();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -554,7 +559,21 @@ void CrashGenerationServer::HandleDisconnectingState() {
|
||||
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)) {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
}
|
||||
@@ -626,18 +645,25 @@ bool CrashGenerationServer::RespondToClient(ClientInfo* client_info) {
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD bytes_count = 0;
|
||||
bool success = WriteFile(pipe_,
|
||||
&reply,
|
||||
sizeof(reply),
|
||||
&bytes_count,
|
||||
&overlapped_) != FALSE;
|
||||
|
||||
return success || GetLastError() == ERROR_IO_PENDING;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The server thread servicing the clients runs this method. The method
|
||||
@@ -739,7 +765,7 @@ bool CrashGenerationServer::AddClient(ClientInfo* client_info) {
|
||||
|
||||
// static
|
||||
void CALLBACK CrashGenerationServer::OnPipeConnected(void* context, BOOLEAN) {
|
||||
assert (context);
|
||||
assert(context);
|
||||
|
||||
CrashGenerationServer* obj =
|
||||
reinterpret_cast<CrashGenerationServer*>(context);
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
#include "client/windows/crash_generation/client_info.h"
|
||||
#include "client/windows/crash_generation/minidump_generator.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
class ClientInfo;
|
||||
|
||||
// Abstraction for server side implementation of out-of-process crash
|
||||
// generation protocol for Windows platform only. It generates Windows
|
||||
@@ -91,7 +91,8 @@ class 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.
|
||||
bool Start();
|
||||
@@ -100,6 +101,9 @@ class CrashGenerationServer {
|
||||
// Various states the client can be in during the handshake with
|
||||
// the server.
|
||||
enum IPCServerState {
|
||||
// Server starts in this state.
|
||||
IPC_SERVER_STATE_UNINITIALIZED,
|
||||
|
||||
// Server is in error state and it cannot serve any clients.
|
||||
IPC_SERVER_STATE_ERROR,
|
||||
|
||||
@@ -192,6 +196,21 @@ class CrashGenerationServer {
|
||||
// Generates dump for the given client.
|
||||
bool GenerateDump(const ClientInfo& client, std::wstring* dump_path);
|
||||
|
||||
// Puts the server in a permanent error state and sets a signal such that
|
||||
// the state will be immediately entered after the current state transition
|
||||
// is complete.
|
||||
void EnterErrorState();
|
||||
|
||||
// Puts the server in the specified state and sets a signal such that the
|
||||
// state is immediately entered after the current state transition is
|
||||
// complete.
|
||||
void EnterStateImmediately(IPCServerState state);
|
||||
|
||||
// Puts the server in the specified state. No signal will be set, so the state
|
||||
// transition will only occur when signaled manually or by completion of an
|
||||
// asynchronous IO operation.
|
||||
void EnterStateWhenSignaled(IPCServerState state);
|
||||
|
||||
// Sync object for thread-safe access to the shared list of clients.
|
||||
CRITICAL_SECTION clients_sync_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user