mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-26 23:01:05 +08:00
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:
parent
6d121a1b88
commit
9ff3d9335f
@ -15,6 +15,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
@ -50,6 +51,10 @@ def main(args):
|
||||
'crashpad_test_test',
|
||||
'crashpad_util_test',
|
||||
]
|
||||
if platform.system() == 'Windows':
|
||||
tests += [
|
||||
'crashpad_handler_test',
|
||||
]
|
||||
for test in tests:
|
||||
print '-' * 80
|
||||
print test
|
||||
|
@ -40,6 +40,7 @@
|
||||
'crashpad_client_win.cc',
|
||||
'crashpad_info.cc',
|
||||
'crashpad_info.h',
|
||||
'registration_protocol_win.h',
|
||||
'settings.cc',
|
||||
'settings.h',
|
||||
'simple_string_dictionary.cc',
|
||||
|
55
client/registration_protocol_win.h
Normal file
55
client/registration_protocol_win.h
Normal 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_
|
@ -23,6 +23,7 @@
|
||||
'client/client_test.gyp:*',
|
||||
'compat/compat.gyp:*',
|
||||
'handler/handler.gyp:*',
|
||||
'handler/handler_test.gyp:*',
|
||||
'minidump/minidump.gyp:*',
|
||||
'minidump/minidump_test.gyp:*',
|
||||
'snapshot/snapshot.gyp:*',
|
||||
|
@ -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': [],
|
||||
}],
|
||||
|
48
handler/handler_test.gyp
Normal file
48
handler/handler_test.gyp
Normal 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',
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
289
handler/win/registration_pipe_state.cc
Normal file
289
handler/win/registration_pipe_state.cc
Normal 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
|
112
handler/win/registration_pipe_state.h
Normal file
112
handler/win/registration_pipe_state.h
Normal 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_
|
259
handler/win/registration_pipe_state_test.cc
Normal file
259
handler/win/registration_pipe_state_test.cc
Normal 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
|
136
handler/win/registration_server.cc
Normal file
136
handler/win/registration_server.cc
Normal 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
|
83
handler/win/registration_server.h
Normal file
83
handler/win/registration_server.h
Normal 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_
|
271
handler/win/registration_server_test.cc
Normal file
271
handler/win/registration_server_test.cc
Normal 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
|
166
handler/win/registration_test_base.cc
Normal file
166
handler/win/registration_test_base.cc
Normal 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
|
114
handler/win/registration_test_base.h
Normal file
114
handler/win/registration_test_base.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user