mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 07:14:10 +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.
|
# 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
|
||||||
|
@ -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',
|
||||||
|
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:*',
|
'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:*',
|
||||||
|
@ -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
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