Introduce RegistrationServer, which implements a Crashpad client registration protocol for Windows.

BUG=
R=cpu@chromium.org, scottmg@chromium.org

Review URL: https://codereview.chromium.org/1126783004
This commit is contained in:
Erik Wright 2015-05-26 14:31:04 -04:00
parent 6d121a1b88
commit 9ff3d9335f
14 changed files with 1562 additions and 0 deletions

View File

@ -15,6 +15,7 @@
# limitations under the License. # limitations under the License.
import os import os
import platform
import subprocess import subprocess
import sys import sys
@ -50,6 +51,10 @@ def main(args):
'crashpad_test_test', 'crashpad_test_test',
'crashpad_util_test', 'crashpad_util_test',
] ]
if platform.system() == 'Windows':
tests += [
'crashpad_handler_test',
]
for test in tests: for test in tests:
print '-' * 80 print '-' * 80
print test print test

View File

@ -40,6 +40,7 @@
'crashpad_client_win.cc', 'crashpad_client_win.cc',
'crashpad_info.cc', 'crashpad_info.cc',
'crashpad_info.h', 'crashpad_info.h',
'registration_protocol_win.h',
'settings.cc', 'settings.cc',
'settings.h', 'settings.h',
'simple_string_dictionary.cc', 'simple_string_dictionary.cc',

View File

@ -0,0 +1,55 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_CLIENT_REGISTRATION_PROTOCOL_WIN_H_
#define CRASHPAD_CLIENT_REGISTRATION_PROTOCOL_WIN_H_
#include <windows.h>
#include <stdint.h>
#include "util/win/address_types.h"
namespace crashpad {
#pragma pack(push, 1)
//! \brief A client registration request.
struct RegistrationRequest {
//! \brief The PID of the client process.
DWORD client_process_id;
//! \brief The address, in the client process address space, of a CrashpadInfo
//! structure.
crashpad::WinVMAddress crashpad_info_address;
};
#pragma pack(pop)
#pragma pack(push, 1)
//! \brief A client registration response. See
//! https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203 for
//! details on communicating handle values between 32-bit and 64-bit
//! processes.
struct RegistrationResponse {
//! \brief An event `HANDLE`, valid in the client process, that should be
//! signaled to request a crash report. 64-bit clients should convert the
//! value to a `HANDLE` using sign-extension.
uint32_t request_report_event;
//! \brief An event `HANDLE`, valid in the client process, that will be
//! signaled when the requested crash report is complete. 64-bit clients
//! should convert the value to a `HANDLE` using sign-extension.
uint32_t report_complete_event;
};
#pragma pack(pop)
} // namespace crashpad
#endif // CRASHPAD_CLIENT_REGISTRATION_PROTOCOL_WIN_H_

View File

@ -23,6 +23,7 @@
'client/client_test.gyp:*', 'client/client_test.gyp:*',
'compat/compat.gyp:*', 'compat/compat.gyp:*',
'handler/handler.gyp:*', 'handler/handler.gyp:*',
'handler/handler_test.gyp:*',
'minidump/minidump.gyp:*', 'minidump/minidump.gyp:*',
'minidump/minidump_test.gyp:*', 'minidump/minidump_test.gyp:*',
'snapshot/snapshot.gyp:*', 'snapshot/snapshot.gyp:*',

View File

@ -68,6 +68,28 @@
], ],
}, },
], ],
},],
['OS=="win"', {
'targets': [
{
'target_name': 'crashpad_handler',
'type': 'static_library',
'dependencies': [
'../client/client.gyp:crashpad_client',
'../third_party/mini_chromium/mini_chromium.gyp:base',
'../util/util.gyp:crashpad_util',
],
'include_dirs': [
'..',
],
'sources': [
'win/registration_pipe_state.cc',
'win/registration_pipe_state.h',
'win/registration_server.cc',
'win/registration_server.h',
],
},
],
}, { }, {
'targets': [], 'targets': [],
}], }],

48
handler/handler_test.gyp Normal file
View File

@ -0,0 +1,48 @@
# Copyright 2015 The Crashpad Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{
'includes': [
'../build/crashpad.gypi',
],
'targets': [],
'conditions': [
['OS=="win"', {
'targets': [
{
'target_name': 'crashpad_handler_test',
'type': 'executable',
'dependencies': [
'handler.gyp:crashpad_handler',
'../compat/compat.gyp:crashpad_compat',
'../test/test.gyp:crashpad_test',
'../third_party/gtest/gtest.gyp:gtest',
'../third_party/gtest/gtest.gyp:gtest_main',
'../third_party/mini_chromium/mini_chromium.gyp:base',
'../util/util.gyp:crashpad_util',
],
'include_dirs': [
'..',
],
'sources': [
'win/registration_pipe_state_test.cc',
'win/registration_server_test.cc',
'win/registration_test_base.cc',
'win/registration_test_base.h',
],
},
],
}],
],
}

View File

@ -0,0 +1,289 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/win/registration_pipe_state.h"
#include <string.h>
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "util/stdlib/pointer_container.h"
namespace crashpad {
RegistrationPipeState::RegistrationPipeState(
ScopedFileHANDLE pipe,
RegistrationServer::Delegate* delegate)
: request_(),
response_(),
completion_handler_(nullptr),
overlapped_(),
event_(),
pipe_(pipe.Pass()),
waiting_for_close_(false),
delegate_(delegate),
get_named_pipe_client_process_id_proc_(nullptr) {
HMODULE kernel_dll = GetModuleHandle(L"kernel32.dll");
if (kernel_dll) {
get_named_pipe_client_process_id_proc_ =
reinterpret_cast<decltype(GetNamedPipeClientProcessId)*>(
GetProcAddress(kernel_dll, "GetNamedPipeClientProcessId"));
}
}
RegistrationPipeState::~RegistrationPipeState() {
}
bool RegistrationPipeState::Initialize() {
DCHECK(!event_.is_valid());
DCHECK(pipe_.is_valid());
event_.reset(CreateEvent(nullptr, true, false, nullptr));
if (!event_.is_valid()) {
PLOG(ERROR) << "CreateEvent";
} else {
overlapped_.hEvent = event_.get();
if (IssueConnect())
return true;
}
overlapped_.hEvent = nullptr;
event_.reset();
pipe_.reset();
completion_handler_ = nullptr;
return false;
}
void RegistrationPipeState::Stop() {
DCHECK(pipe_.is_valid());
if (!CancelIo(pipe_.get()))
PLOG(FATAL) << "CancelIo";
}
bool RegistrationPipeState::OnCompletion() {
AsyncCompletionHandler completion_handler = completion_handler_;
completion_handler_ = nullptr;
DWORD bytes_transferred = 0;
BOOL success = GetOverlappedResult(pipe_.get(),
&overlapped_,
&bytes_transferred,
false); // Do not wait.
if (!success) {
// ERROR_BROKEN_PIPE is expected when we are waiting for the client to close
// the pipe (signaling that they are done reading the response).
if (!waiting_for_close_ || GetLastError() != ERROR_BROKEN_PIPE)
PLOG(ERROR) << "GetOverlappedResult";
}
bool still_running = false;
if (!ResetEvent(event_.get())) {
PLOG(ERROR) << "ResetEvent";
} else if (!completion_handler) {
NOTREACHED();
still_running = ResetConnection();
} else if (!success) {
still_running = ResetConnection();
} else {
still_running = (this->*completion_handler)(bytes_transferred);
}
if (!still_running) {
overlapped_.hEvent = nullptr;
event_.reset();
pipe_.reset();
completion_handler_ = nullptr;
} else {
DCHECK(completion_handler_);
}
return still_running;
}
bool RegistrationPipeState::OnConnectComplete(DWORD /* bytes_transferred */) {
return IssueRead();
}
bool RegistrationPipeState::OnReadComplete(DWORD bytes_transferred) {
if (bytes_transferred != sizeof(request_)) {
LOG(ERROR) << "Invalid message size: " << bytes_transferred;
return ResetConnection();
} else {
return HandleRequest();
}
}
bool RegistrationPipeState::OnWriteComplete(DWORD bytes_transferred) {
if (bytes_transferred != sizeof(response_)) {
LOG(ERROR) << "Incomplete write operation. Bytes written: "
<< bytes_transferred;
}
return IssueWaitForClientClose();
}
bool RegistrationPipeState::OnWaitForClientCloseComplete(
DWORD bytes_transferred) {
LOG(ERROR) << "Unexpected extra data (" << bytes_transferred
<< " bytes) received from client.";
return ResetConnection();
}
bool RegistrationPipeState::IssueConnect() {
if (ConnectNamedPipe(pipe_.get(), &overlapped_)) {
return OnConnectComplete(0); // bytes_transferred (ignored)
} else {
DWORD result = GetLastError();
if (result == ERROR_PIPE_CONNECTED) {
return OnConnectComplete(0); // bytes_transferred (ignored)
} else if (result == ERROR_IO_PENDING) {
completion_handler_ = &RegistrationPipeState::OnConnectComplete;
return true;
} else {
PLOG(ERROR) << "ConnectNamedPipe";
return false;
}
}
}
bool RegistrationPipeState::IssueRead() {
DWORD bytes_read = 0;
if (ReadFile(pipe_.get(),
&request_,
sizeof(request_),
&bytes_read,
&overlapped_)) {
return OnReadComplete(bytes_read);
} else if (GetLastError() == ERROR_IO_PENDING) {
completion_handler_ = &RegistrationPipeState::OnReadComplete;
return true;
} else {
PLOG(ERROR) << "ReadFile";
return ResetConnection();
}
}
bool RegistrationPipeState::IssueWrite() {
DWORD bytes_written = 0;
if (WriteFile(pipe_.get(),
&response_,
sizeof(response_),
&bytes_written,
&overlapped_)) {
return OnWriteComplete(bytes_written);
} else if (GetLastError() == ERROR_IO_PENDING) {
completion_handler_ = &RegistrationPipeState::OnWriteComplete;
return true;
} else {
PLOG(ERROR) << "WriteFile";
return ResetConnection();
}
}
bool RegistrationPipeState::IssueWaitForClientClose() {
// If we invoke DisconnectNamedPipe before the client has read the response
// the response will never be delivered. Therefore we issue an extra ReadFile
// operation after writing the response. No data is expected - the operation
// will be 'completed' when the client closes the pipe.
waiting_for_close_ = true;
DWORD bytes_read = 0;
if (ReadFile(pipe_.get(),
&request_,
sizeof(request_),
&bytes_read,
&overlapped_)) {
return OnWaitForClientCloseComplete(bytes_read);
} else if (GetLastError() == ERROR_IO_PENDING) {
completion_handler_ = &RegistrationPipeState::OnWaitForClientCloseComplete;
return true;
} else {
PLOG(ERROR) << "ReadFile";
return ResetConnection();
}
}
bool RegistrationPipeState::HandleRequest() {
if (get_named_pipe_client_process_id_proc_) {
// On Vista+ we can verify that the client is who they claim to be, thus
// preventing arbitrary processes from having us duplicate handles into
// other processes.
DWORD real_client_process_id = 0;
if (!get_named_pipe_client_process_id_proc_(pipe_.get(),
&real_client_process_id)) {
PLOG(ERROR) << "GetNamedPipeClientProcessId";
} else if (real_client_process_id != request_.client_process_id) {
LOG(ERROR) << "Client process ID from request ("
<< request_.client_process_id
<< ") does not match pipe client process ID ("
<< real_client_process_id << ").";
return ResetConnection();
}
}
ScopedKernelHANDLE client_process(
OpenProcess(PROCESS_ALL_ACCESS, false, request_.client_process_id));
if (!client_process.is_valid()) {
if (ImpersonateNamedPipeClient(pipe_.get())) {
client_process.reset(
OpenProcess(PROCESS_ALL_ACCESS, false, request_.client_process_id));
RevertToSelf();
}
}
if (!client_process.is_valid()) {
LOG(ERROR) << "Failed to open client process.";
return ResetConnection();
}
memset(&response_, 0, sizeof(response_));
HANDLE request_report_event = nullptr;
HANDLE report_complete_event = nullptr;
if (!delegate_->RegisterClient(client_process.Pass(),
request_.crashpad_info_address,
&request_report_event,
&report_complete_event)) {
return ResetConnection();
}
// A handle has at most 32 significant bits, though its type is void*. Thus we
// truncate it here. An interesting exception is INVALID_HANDLE_VALUE, which
// is '-1'. It is still safe to truncate it from 0xFFFFFFFFFFFFFFFF to
// 0xFFFFFFFF, but a 64-bit client receiving that value must correctly sign
// extend it.
response_.request_report_event =
reinterpret_cast<uint32_t>(request_report_event);
response_.report_complete_event =
reinterpret_cast<uint32_t>(report_complete_event);
return IssueWrite();
}
bool RegistrationPipeState::ResetConnection() {
memset(&request_, 0, sizeof(request_));
waiting_for_close_ = false;
if (!DisconnectNamedPipe(pipe_.get())) {
PLOG(ERROR) << "DisconnectNamedPipe";
return false;
} else {
return IssueConnect();
}
}
} // namespace crashpad

View File

@ -0,0 +1,112 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_HANDLER_WIN_REGISTRATION_PIPE_STATE_H_
#define CRASHPAD_HANDLER_WIN_REGISTRATION_PIPE_STATE_H_
#include <windows.h>
#include "base/basictypes.h"
#include "client/registration_protocol_win.h"
#include "handler/win/registration_server.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
//! \brief Implements the state and state transitions of a single named pipe for
//! the Crashpad client registration protocol for Windows. Each pipe
//! instance may handle a single client connection at a time. After each
//! connection completes, whether successfully or otherwise, the instance
//! will attempt to return to a listening state, ready for a new client
//! connection.
class RegistrationPipeState {
public:
//! \brief Initializes the state for a single pipe instance. The client must
//! call Initialize() before clients may connect to this pipe.
//! \param[in] pipe The named pipe to listen on.
//! \param[in] delegate The delegate that will be used to handle requests.
RegistrationPipeState(ScopedFileHANDLE pipe,
RegistrationServer::Delegate* delegate);
~RegistrationPipeState();
//! \brief Places the pipe in the running state, ready to receive and process
//! client connections. Before destroying a running RegistrationPipeState
//! instance you must invoke Stop() and then wait for completion_event()
//! to be signaled one last time.
//! \return Returns true if successful, in which case the client must observe
//! completion_event() and invoke OnCompletion() whenever it is signaled.
bool Initialize();
//! \brief Cancels any pending asynchronous operations. After invoking this
//! method you must wait for completion_event() to be signaled before
//! destroying the instance.
void Stop();
//! \brief Returns an event handle that will be signaled whenever an
//! asynchronous operation associated with this instance completes.
HANDLE completion_event() { return event_.get(); }
//! \brief Must be called by the client whenever completion_event() is
//! signaled.
//! \return Returns true if the pipe is still in the running state. Otherwise,
//! a permanent failure has occurred and the instance may be immediately
//! destroyed.
bool OnCompletion();
private:
using AsyncCompletionHandler =
bool (RegistrationPipeState::*)(DWORD bytes_transferred);
// State transition handlers. Return true if the pipe is still valid.
bool OnConnectComplete(DWORD /* bytes_transferred */);
bool OnReadComplete(DWORD bytes_transferred);
bool OnWriteComplete(DWORD bytes_transferred);
bool OnWaitForClientCloseComplete(DWORD bytes_transferred);
// Pipe operations. Return true if the pipe is still valid.
// Prepares the pipe to accept a new client connecion.
bool IssueConnect();
// Reads into |request_|.
bool IssueRead();
// Writes from |response_|.
bool IssueWrite();
// Issues a final ReadFile() that is expected to be terminated when the client
// closes the pipe.
bool IssueWaitForClientClose();
// Processes |request_| using |delegate_| and stores the result in
// |response_|.
bool HandleRequest();
// Closes the active connection and invokes IssueConnect().
bool ResetConnection();
RegistrationRequest request_;
RegistrationResponse response_;
// The state transition handler to be invoked when the active asynchronous
// operation completes.
AsyncCompletionHandler completion_handler_;
OVERLAPPED overlapped_;
ScopedKernelHANDLE event_;
ScopedFileHANDLE pipe_;
bool waiting_for_close_;
RegistrationServer::Delegate* delegate_;
decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id_proc_;
DISALLOW_COPY_AND_ASSIGN(RegistrationPipeState);
};
} // namespace crashpad
#endif // CRASHPAD_HANDLER_WIN_REGISTRATION_PIPE_STATE_H_

View File

@ -0,0 +1,259 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/win/registration_pipe_state.h"
#include <windows.h>
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "client/crashpad_info.h"
#include "client/registration_protocol_win.h"
#include "gtest/gtest.h"
#include "handler/win/registration_test_base.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
namespace test {
namespace {
class RegistrationRegistrationPipeStateTest : public RegistrationTestBase {
public:
RegistrationRegistrationPipeStateTest() : pipe_state_() {}
void SetUp() override {
ScopedFileHANDLE pipe(
CreateNamedPipe(pipe_name().c_str(),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
1,
512, // nOutBufferSize
512, // nInBufferSize
20, // nDefaultTimeOut
nullptr)); // lpSecurityAttributes
ASSERT_TRUE(pipe.is_valid());
pipe_state_.reset(new RegistrationPipeState(pipe.Pass(), &delegate()));
}
~RegistrationRegistrationPipeStateTest() override {}
RegistrationPipeState& pipe_state() {
DCHECK(pipe_state_.get());
return *pipe_state_;
}
private:
scoped_ptr<RegistrationPipeState> pipe_state_;
DISALLOW_COPY_AND_ASSIGN(RegistrationRegistrationPipeStateTest);
};
TEST_F(RegistrationRegistrationPipeStateTest, CancelIoWhenConnectIsComplete) {
// -> Connecting
ASSERT_TRUE(pipe_state().Initialize());
ScopedFileHANDLE client(Connect());
ASSERT_TRUE(client.is_valid());
// Connect completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Connecting -> Stopping
pipe_state().Stop();
// Stop completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
}
TEST_F(RegistrationRegistrationPipeStateTest, CancelIoWhenReadIsComplete) {
// -> Connecting
ASSERT_TRUE(pipe_state().Initialize());
ScopedFileHANDLE client(Connect());
ASSERT_TRUE(client.is_valid());
// Connect completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Connecting -> Reading
ASSERT_TRUE(pipe_state().OnCompletion());
RegistrationRequest request = {0};
CrashpadInfo crashpad_info;
request.client_process_id = GetCurrentProcessId();
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
ASSERT_TRUE(WriteRequest(client.get(), &request, sizeof(request)));
// Read completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Reading -> Stopping
pipe_state().Stop();
// Stop completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
}
TEST_F(RegistrationRegistrationPipeStateTest, CancelIoWhenWriteIsComplete) {
// -> Connecting
ASSERT_TRUE(pipe_state().Initialize());
ScopedFileHANDLE client(Connect());
ASSERT_TRUE(client.is_valid());
// Connect completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Connecting -> Reading
ASSERT_TRUE(pipe_state().OnCompletion());
RegistrationRequest request = {0};
CrashpadInfo crashpad_info;
request.client_process_id = GetCurrentProcessId();
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
ASSERT_TRUE(WriteRequest(client.get(), &request, sizeof(request)));
// Read completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Reading -> Writing -> Waiting for Close
ASSERT_TRUE(pipe_state().OnCompletion());
RegistrationResponse response = {0};
ASSERT_TRUE(ReadResponse(client.get(), &response));
// Waiting for Close -> Stopping
pipe_state().Stop();
// Stop completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
}
TEST_F(RegistrationRegistrationPipeStateTest, CancelIoWhenCloseIsComplete) {
// -> Connecting
ASSERT_TRUE(pipe_state().Initialize());
ScopedFileHANDLE client(Connect());
ASSERT_TRUE(client.is_valid());
// Connect completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Connecting -> Reading
ASSERT_TRUE(pipe_state().OnCompletion());
RegistrationRequest request = {0};
CrashpadInfo crashpad_info;
request.client_process_id = GetCurrentProcessId();
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
ASSERT_TRUE(WriteRequest(client.get(), &request, sizeof(request)));
// Read completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Reading -> Writing -> Waiting for Close
ASSERT_TRUE(pipe_state().OnCompletion());
RegistrationResponse response = {0};
ASSERT_TRUE(ReadResponse(client.get(), &response));
client.reset();
// Wait for close completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Waiting for Close -> Stopping
pipe_state().Stop();
// Stop completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
}
TEST_F(RegistrationRegistrationPipeStateTest, FullCycle) {
// -> Connecting
ASSERT_TRUE(pipe_state().Initialize());
ScopedFileHANDLE client(Connect());
ASSERT_TRUE(client.is_valid());
// Connect completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Connecting -> Reading
ASSERT_TRUE(pipe_state().OnCompletion());
RegistrationRequest request = {0};
CrashpadInfo crashpad_info;
request.client_process_id = GetCurrentProcessId();
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
ASSERT_TRUE(WriteRequest(client.get(), &request, sizeof(request)));
// Read completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Reading -> Writing -> Waiting for Close
ASSERT_TRUE(pipe_state().OnCompletion());
RegistrationResponse response = {0};
ASSERT_TRUE(ReadResponse(client.get(), &response));
client.reset();
// Wait for close completion.
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
// Waiting for Close -> Reset -> Connecting
ASSERT_TRUE(pipe_state().OnCompletion());
client = Connect();
ASSERT_TRUE(client.is_valid());
pipe_state().Stop();
ASSERT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(pipe_state().completion_event(), INFINITE));
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,136 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/win/registration_server.h"
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "handler/win/registration_pipe_state.h"
#include "util/stdlib/pointer_container.h"
namespace crashpad {
RegistrationServer::RegistrationServer() : stop_event_() {
stop_event_.reset(CreateEvent(nullptr, false, false, nullptr));
DPCHECK(stop_event_.is_valid());
}
RegistrationServer::~RegistrationServer() {
}
bool RegistrationServer::Run(const base::string16& pipe_name,
Delegate* delegate) {
if (!stop_event_.is_valid()) {
LOG(ERROR) << "Failed to create stop_event_.";
return false;
}
PointerVector<RegistrationPipeState> pipes;
std::vector<HANDLE> handles;
const int kNumPipes = 3;
// Create the named pipes.
for (int i = 0; i < kNumPipes; ++i) {
DWORD open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED;
if (pipes.size() == 0)
open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
ScopedFileHANDLE pipe(
CreateNamedPipe(pipe_name.c_str(),
open_mode,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
kNumPipes,
512, // nOutBufferSize
512, // nInBufferSize
20, // nDefaultTimeOut
nullptr)); // lpSecurityAttributes
if (pipe.is_valid()) {
scoped_ptr<RegistrationPipeState> pipe_state(
new RegistrationPipeState(pipe.Pass(), delegate));
if (pipe_state->Initialize()) {
pipes.push_back(pipe_state.release());
handles.push_back(pipes.back()->completion_event());
}
} else {
PLOG(ERROR) << "CreateNamedPipe";
}
}
if (pipes.size() == 0) {
LOG(ERROR) << "Failed to initialize any pipes.";
return false;
}
delegate->OnStarted();
// Add stop_event_ to the list of events we will observe.
handles.push_back(stop_event_.get());
bool stopped = false;
// Run the main loop, dispatching completion event signals to the pipe
// instances.
while (true) {
DWORD wait_result = WaitForMultipleObjects(
static_cast<DWORD>(handles.size()), handles.data(), false, INFINITE);
if (wait_result >= WAIT_OBJECT_0 &&
wait_result < WAIT_OBJECT_0 + pipes.size()) {
int index = wait_result - WAIT_OBJECT_0;
// Handle a completion event.
if (!pipes[index]->OnCompletion()) {
pipes.erase(pipes.begin() + index);
handles.erase(handles.begin() + index);
}
if (pipes.size())
continue;
// Exit due to all pipes having failed.
} else if (wait_result == WAIT_OBJECT_0 + pipes.size()) {
// Exit due to stop_event_.
stopped = true;
} else if (wait_result == WAIT_FAILED) {
// Exit due to error.
PLOG(ERROR) << "WaitForMultipleObjects";
} else {
// Exit due to unexpected return code.
NOTREACHED();
}
break;
}
// Remove |stop_event_| from the wait list.
handles.pop_back();
// Cancel any ongoing asynchronous operations.
for (auto& pipe : pipes) {
pipe->Stop();
}
// Wait until all of the pipe instances are ready to be destroyed.
DWORD wait_result = WaitForMultipleObjects(
static_cast<DWORD>(handles.size()), handles.data(), true, INFINITE);
PCHECK(wait_result != WAIT_FAILED);
DCHECK_GE(wait_result, WAIT_OBJECT_0);
DCHECK_LT(wait_result, WAIT_OBJECT_0 + handles.size());
return stopped;
}
void RegistrationServer::Stop() {
if (!SetEvent(stop_event_.get()))
PLOG(FATAL) << "SetEvent";
}
} // namespace crashpad

View File

@ -0,0 +1,83 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_HANDLER_WIN_REGISTRATION_SERVER_H_
#define CRASHPAD_HANDLER_WIN_REGISTRATION_SERVER_H_
#include <windows.h>
#include "base/basictypes.h"
#include "base/strings/string16.h"
#include "util/win/address_types.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
//! \brief Implements the server side of the Crashpad client registration
//! protocol for Windows.
class RegistrationServer {
public:
//! \brief Handles registration requests.
class Delegate {
public:
virtual ~Delegate() {}
//! \brief Receives notification that clients may now connect to the named
//! pipe.
virtual void OnStarted() = 0;
//! \brief Responds to a request to register a client process for crash
//! handling.
//!
//! \param[in] client_process The client that is making the request.
//! \param[in] crashpad_info_address The address of a CrashpadInfo structure
//! in the client's address space.
//! \param[out] request_dump_event A `HANDLE`, valid in the client process,
//! to an event that, when signaled, triggers a crash report.
//! \param[out] dump_complete_event A `HANDLE`, valid in the client process,
//! to an event that will be signaled when the crash report has been
//! successfully captured.
virtual bool RegisterClient(ScopedKernelHANDLE client_process,
WinVMAddress crashpad_info_address,
HANDLE* request_dump_event,
HANDLE* dump_complete_event) = 0;
};
//! \brief Instantiates a client registration server.
RegistrationServer();
~RegistrationServer();
//! \brief Runs the RegistrationServer, receiving and processing requests and
//! sending responses. Blocks until Stop() is invoked.
//!
//! \param[in] pipe_name The pipe name to be used by the server.
//! \param[in] delegate The delegate to be used to handle requests.
//!
//! \return Returns `true` if it terminates due to invocation of Stop().
//! `false` indicates early termination due to a failure.
bool Run(const base::string16& pipe_name, Delegate* delegate);
//! \brief Stops the RegistrationServer. Returns immediately. The instance
//! must not be destroyed until the call to Run() completes.
void Stop();
private:
ScopedKernelHANDLE stop_event_;
DISALLOW_COPY_AND_ASSIGN(RegistrationServer);
};
} // namespace crashpad
#endif // CRASHPAD_HANDLER_WIN_REGISTRATION_SERVER_H_

View File

@ -0,0 +1,271 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/win/registration_server.h"
#include <windows.h>
#include <vector>
#include "base/basictypes.h"
#include "base/strings/string16.h"
#include "client/crashpad_info.h"
#include "client/registration_protocol_win.h"
#include "gtest/gtest.h"
#include "handler/win/registration_test_base.h"
#include "util/thread/thread.h"
#include "util/win/address_types.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
namespace test {
namespace {
// Runs the RegistrationServer on a background thread.
class RunServerThread : public Thread {
public:
// Instantiates a thread which will invoke server->Run(pipe_name, delegate).
RunServerThread(RegistrationServer* server,
const base::string16& pipe_name,
RegistrationServer::Delegate* delegate)
: server_(server), pipe_name_(pipe_name), delegate_(delegate) {}
~RunServerThread() override {}
private:
// Thread:
void ThreadMain() override { server_->Run(pipe_name_, delegate_); }
RegistrationServer* server_;
base::string16 pipe_name_;
RegistrationServer::Delegate* delegate_;
DISALLOW_COPY_AND_ASSIGN(RunServerThread);
};
class RegistrationServerTest : public RegistrationTestBase {
public:
RegistrationServerTest()
: server_(), server_thread_(&server_, pipe_name(), &delegate()) {}
RegistrationServer& server() { return server_; }
Thread& server_thread() { return server_thread_; }
private:
RegistrationServer server_;
RunServerThread server_thread_;
DISALLOW_COPY_AND_ASSIGN(RegistrationServerTest);
};
// During destruction, ensures that the server is stopped and the background
// thread joined.
class ScopedStopServerAndJoinThread {
public:
explicit ScopedStopServerAndJoinThread(RegistrationServer* server,
Thread* thread)
: server_(server), thread_(thread) {}
~ScopedStopServerAndJoinThread() {
server_->Stop();
thread_->Join();
}
private:
RegistrationServer* server_;
Thread* thread_;
DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread);
};
TEST_F(RegistrationServerTest, Instantiate) {
}
TEST_F(RegistrationServerTest, StartAndStop) {
server_thread().Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
&server(), &server_thread());
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
}
TEST_F(RegistrationServerTest, StopWhileConnected) {
ScopedFileHANDLE connection;
{
server_thread().Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
&server(), &server_thread());
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
connection = Connect();
ASSERT_TRUE(connection.is_valid());
// Leaving this scope causes the server to be stopped, while the connection
// is still open.
}
}
TEST_F(RegistrationServerTest, Register) {
RegistrationRequest request = {0};
RegistrationResponse response = {0};
CrashpadInfo crashpad_info;
request.client_process_id = GetCurrentProcessId();
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
server_thread().Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
&server(), &server_thread());
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
ASSERT_TRUE(SendRequest(Connect(), &request, sizeof(request), &response));
ASSERT_EQ(1, delegate().registered_processes().size());
VerifyRegistration(*delegate().registered_processes()[0], request, response);
}
TEST_F(RegistrationServerTest, ForgedClientId) {
// Skip this test on pre-Vista as the forged PID detection is not supported
// there.
OSVERSIONINFO vi = {0};
vi.dwOSVersionInfoSize = sizeof(vi);
GetVersionEx(&vi);
if (vi.dwMajorVersion < 6)
return;
RegistrationRequest request = {0};
RegistrationResponse response = {0};
CrashpadInfo crashpad_info;
// Note that we forge the PID here.
request.client_process_id = GetCurrentProcessId() + 1;
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
server_thread().Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
&server(), &server_thread());
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
ASSERT_FALSE(SendRequest(Connect(), &request, sizeof(request), &response));
ASSERT_EQ(0, delegate().registered_processes().size());
// Correct the PID and verify that this was the only reason we failed.
request.client_process_id = GetCurrentProcessId();
ASSERT_TRUE(SendRequest(Connect(), &request, sizeof(request), &response));
ASSERT_EQ(1, delegate().registered_processes().size());
VerifyRegistration(*delegate().registered_processes()[0], request, response);
}
TEST_F(RegistrationServerTest, RegisterClientFails) {
RegistrationRequest request = {0};
RegistrationResponse response = {0};
CrashpadInfo crashpad_info;
request.client_process_id = GetCurrentProcessId();
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
server_thread().Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
&server(), &server_thread());
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
// Simulate some failures
delegate().set_fail_mode(true);
for (int i = 0; i < 10; ++i) {
ASSERT_FALSE(SendRequest(Connect(), &request, sizeof(request), &response));
ASSERT_EQ(0, delegate().registered_processes().size());
}
// Now verify that a valid response may still be processed.
delegate().set_fail_mode(false);
ASSERT_TRUE(SendRequest(Connect(), &request, sizeof(request), &response));
ASSERT_EQ(1, delegate().registered_processes().size());
VerifyRegistration(*delegate().registered_processes()[0], request, response);
}
TEST_F(RegistrationServerTest, BadRequests) {
server_thread().Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
&server(), &server_thread());
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
RegistrationRequest request = {0};
RegistrationResponse response = {0};
CrashpadInfo crashpad_info;
request.client_process_id = GetCurrentProcessId();
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
// Concatenate a valid request with a single byte of garbage.
std::vector<char> extra_long;
extra_long.insert(extra_long.begin(),
reinterpret_cast<char*>(&request),
reinterpret_cast<char*>(&request) + sizeof(request));
extra_long.push_back('x');
for (int i = 0; i < 10; ++i) {
ASSERT_FALSE(SendRequest(Connect(), "a", 1, &response));
ASSERT_FALSE(SendRequest(
Connect(), extra_long.data(), extra_long.size(), &response));
ASSERT_TRUE(Connect().is_valid());
}
// Now verify that a valid response may still be processed.
ASSERT_TRUE(SendRequest(Connect(), &request, sizeof(request), &response));
ASSERT_EQ(1, delegate().registered_processes().size());
VerifyRegistration(*delegate().registered_processes()[0], request, response);
}
TEST_F(RegistrationServerTest, OverlappingRequests) {
server_thread().Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
&server(), &server_thread());
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
RegistrationRequest request = {0};
RegistrationResponse response_1 = {0};
RegistrationResponse response_2 = {0};
RegistrationResponse response_3 = {0};
CrashpadInfo crashpad_info;
request.client_process_id = GetCurrentProcessId();
request.crashpad_info_address =
reinterpret_cast<WinVMAddress>(&crashpad_info);
ScopedFileHANDLE connection_1 = Connect();
ASSERT_TRUE(connection_1.is_valid());
ScopedFileHANDLE connection_2 = Connect();
ASSERT_TRUE(connection_2.is_valid());
ScopedFileHANDLE connection_3 = Connect();
ASSERT_TRUE(connection_3.is_valid());
ASSERT_FALSE(SendRequest(connection_1.Pass(), "a", 1, &response_1));
ASSERT_TRUE(
SendRequest(connection_2.Pass(), &request, sizeof(request), &response_2));
ASSERT_TRUE(Connect().is_valid());
ASSERT_TRUE(
SendRequest(connection_3.Pass(), &request, sizeof(request), &response_3));
ASSERT_EQ(2, delegate().registered_processes().size());
VerifyRegistration(
*delegate().registered_processes()[0], request, response_2);
VerifyRegistration(
*delegate().registered_processes()[1], request, response_3);
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,166 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/win/registration_test_base.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
namespace crashpad {
namespace test {
RegistrationTestBase::MockDelegate::Entry::Entry(
ScopedKernelHANDLE client_process,
WinVMAddress crashpad_info_address,
uint32_t fake_request_dump_event,
uint32_t fake_dump_complete_event)
: client_process(client_process.Pass()),
crashpad_info_address(crashpad_info_address),
fake_request_dump_event(fake_request_dump_event),
fake_dump_complete_event(fake_dump_complete_event) {
}
RegistrationTestBase::MockDelegate::MockDelegate()
: started_event_(CreateEvent(nullptr, true, false, nullptr)),
registered_processes_(),
next_fake_handle_(1),
fail_(false) {
EXPECT_TRUE(started_event_.is_valid());
}
RegistrationTestBase::MockDelegate::~MockDelegate() {
}
void RegistrationTestBase::MockDelegate::WaitForStart() {
DWORD wait_result = WaitForSingleObject(started_event_.get(), INFINITE);
if (wait_result == WAIT_FAILED)
PLOG(ERROR);
ASSERT_EQ(wait_result, WAIT_OBJECT_0);
}
void RegistrationTestBase::MockDelegate::OnStarted() {
EXPECT_EQ(WAIT_TIMEOUT, WaitForSingleObject(started_event_.get(), 0));
SetEvent(started_event_.get());
}
bool RegistrationTestBase::MockDelegate::RegisterClient(
ScopedKernelHANDLE client_process,
WinVMAddress crashpad_info_address,
HANDLE* request_dump_event,
HANDLE* dump_complete_event) {
if (fail_)
return false;
if (!request_dump_event || !dump_complete_event) {
ADD_FAILURE() << "NULL 'out' parameter.";
return false;
}
*request_dump_event = reinterpret_cast<HANDLE>(next_fake_handle_++);
*dump_complete_event = reinterpret_cast<HANDLE>(next_fake_handle_++);
registered_processes_.push_back(
new Entry(client_process.Pass(),
crashpad_info_address,
reinterpret_cast<uint32_t>(*request_dump_event),
reinterpret_cast<uint32_t>(*dump_complete_event)));
return true;
}
RegistrationTestBase::RegistrationTestBase()
: pipe_name_(
L"\\\\.\\pipe\\registration_server_test_pipe_" +
base::UTF8ToUTF16(base::StringPrintf("%08x", GetCurrentProcessId()))),
delegate_() {
}
RegistrationTestBase::~RegistrationTestBase() {
}
// Returns a pipe handle connected to the RegistrationServer.
ScopedFileHANDLE RegistrationTestBase::Connect() {
ScopedFileHANDLE pipe;
const int kMaxRetries = 5;
for (int retries = 0; !pipe.is_valid() && retries < kMaxRetries; ++retries) {
if (!WaitNamedPipe(pipe_name_.c_str(), NMPWAIT_WAIT_FOREVER))
break;
pipe.reset(CreateFile(pipe_name_.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION,
NULL));
if (pipe.is_valid()) {
DWORD mode = PIPE_READMODE_MESSAGE;
if (!SetNamedPipeHandleState(pipe.get(),
&mode,
nullptr, // don't set maximum bytes
nullptr)) { // don't set maximum time
pipe.reset();
}
}
}
EXPECT_TRUE(pipe.is_valid());
return pipe.Pass();
}
// Sends the provided request and receives a response via the provided pipe.
bool RegistrationTestBase::SendRequest(ScopedFileHANDLE pipe,
const void* request_buffer,
size_t request_size,
RegistrationResponse* response) {
return WriteRequest(pipe.get(), request_buffer, request_size) &&
ReadResponse(pipe.get(), response);
}
bool RegistrationTestBase::WriteRequest(HANDLE pipe,
const void* request_buffer,
size_t request_size) {
DWORD byte_count = 0;
if (!WriteFile(pipe,
request_buffer,
static_cast<DWORD>(request_size),
&byte_count,
nullptr)) {
return false;
}
return byte_count == request_size;
}
bool RegistrationTestBase::ReadResponse(HANDLE pipe,
RegistrationResponse* response) {
DWORD byte_count = 0;
if (!ReadFile(pipe, response, sizeof(*response), &byte_count, nullptr))
return false;
return byte_count == sizeof(*response);
}
// Verifies that the request and response match what was received and sent by
// the MockDelegate.
void RegistrationTestBase::VerifyRegistration(
const MockDelegate::Entry& registered_process,
const RegistrationRequest& request,
const RegistrationResponse& response) {
EXPECT_EQ(request.crashpad_info_address,
registered_process.crashpad_info_address);
EXPECT_EQ(registered_process.fake_request_dump_event,
response.request_report_event);
EXPECT_EQ(registered_process.fake_dump_complete_event,
response.report_complete_event);
EXPECT_EQ(request.client_process_id,
GetProcessId(registered_process.client_process.get()));
}
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,114 @@
// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <windows.h>
#include "base/basictypes.h"
#include "base/strings/string16.h"
#include "client/registration_protocol_win.h"
#include "gtest/gtest.h"
#include "handler/win/registration_server.h"
#include "util/stdlib/pointer_container.h"
#include "util/win/address_types.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
namespace test {
class RegistrationTestBase : public testing::Test {
public:
// Simulates a registrar to collect requests from and feed responses to the
// RegistrationServer.
class MockDelegate : public RegistrationServer::Delegate {
public:
// Records a single simulated client registration.
struct Entry {
Entry(ScopedKernelHANDLE client_process,
WinVMAddress crashpad_info_address,
uint32_t fake_request_dump_event,
uint32_t fake_dump_complete_event);
ScopedKernelHANDLE client_process;
WinVMAddress crashpad_info_address;
uint32_t fake_request_dump_event;
uint32_t fake_dump_complete_event;
};
MockDelegate();
~MockDelegate() override;
// Blocks until RegistrationServer::Delegate::OnStarted is invoked.
void WaitForStart();
// RegistrationServer::Delegate:
void OnStarted() override;
bool RegisterClient(ScopedKernelHANDLE client_process,
WinVMAddress crashpad_info_address,
HANDLE* request_dump_event,
HANDLE* dump_complete_event) override;
// Provides access to the registered process data.
const std::vector<Entry*> registered_processes() {
return registered_processes_;
}
// If true, causes RegisterClient to simulate registration failure.
void set_fail_mode(bool fail) { fail_ = fail; }
private:
ScopedKernelHANDLE started_event_;
PointerVector<Entry> registered_processes_;
uint32_t next_fake_handle_;
bool fail_;
DISALLOW_COPY_AND_ASSIGN(MockDelegate);
};
RegistrationTestBase();
~RegistrationTestBase() override;
MockDelegate& delegate() { return delegate_; }
base::string16 pipe_name() { return pipe_name_; }
// Returns a pipe handle connected to the RegistrationServer.
ScopedFileHANDLE Connect();
// Sends the provided request and receives a response via the provided pipe.
bool SendRequest(ScopedFileHANDLE pipe,
const void* request_buffer,
size_t request_size,
RegistrationResponse* response);
bool WriteRequest(HANDLE pipe,
const void* request_buffer,
size_t request_size);
bool ReadResponse(HANDLE pipe, RegistrationResponse* response);
// Verifies that the request and response match what was received and sent by
// the MockDelegate.
void VerifyRegistration(const MockDelegate::Entry& registered_process,
const RegistrationRequest& request,
const RegistrationResponse& response);
private:
base::string16 pipe_name_;
MockDelegate delegate_;
DISALLOW_COPY_AND_ASSIGN(RegistrationTestBase);
};
} // namespace test
} // namespace crashpad