mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 07:14:10 +08:00
win: Crash handler server
This replaces the registration server, and adds dispatch to a delegate on crash requests. (As you are already aware) we went around in circles on trying to come up with a slightly-too-fancy threading design. All of them seemed to have problems when it comes to out of order events, and orderly shutdown, so I've gone back to something not-too-fancy. Two named pipe instances (that clients connect to) are created. These are used only for registration (which should take <1ms), so 2 should be sufficient to avoid any waits. When a client registers, we duplicate an event to it, which is used to signal when it wants a dump taken. The server registers threadpool waits on that event, and also on the process handle (which will be signalled when the client process exits). These requests (in particular the taking of the dump) are serviced on the threadpool, which avoids us needing to manage those threads, but still allows parallelism in taking dumps. On process termination, we use an IO Completion Port to post a message back to the main thread to request cleanup. This complexity is necessary so that we can unregister the threadpool waits without being on the threadpool, which we need to do synchronously so that we can be sure that no further callbacks will execute (and expect to have the client data around still). In a followup, I will readd support for DumpWithoutCrashing -- I don't think it will be too difficult now that we have an orderly way to clean up client records in the server. R=cpu@chromium.org, mark@chromium.org, jschuh@chromium.org BUG=crashpad:1,crashpad:45 Review URL: https://codereview.chromium.org/1301853002 .
This commit is contained in:
parent
754cc3609c
commit
6978bf7646
@ -51,10 +51,6 @@ 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,7 +40,6 @@
|
||||
'crashpad_client_win.cc',
|
||||
'crashpad_info.cc',
|
||||
'crashpad_info.h',
|
||||
'registration_protocol_win.h',
|
||||
'settings.cc',
|
||||
'settings.h',
|
||||
'simple_string_dictionary.cc',
|
||||
|
@ -14,111 +14,71 @@
|
||||
|
||||
#include "client/crashpad_client.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/atomicops.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string16.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "client/crashpad_info.h"
|
||||
#include "client/registration_protocol_win.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/win/registration_protocol_win.h"
|
||||
#include "util/win/scoped_handle.h"
|
||||
|
||||
namespace {
|
||||
// Time to wait for the handler to create a dump. This is tricky to figure out.
|
||||
const DWORD kMillisecondsUntilTerminate = 5000;
|
||||
|
||||
// This is the exit code that the process will return to the system once the
|
||||
// crash has been handled by Crashpad. We don't want to clash with the
|
||||
// application-defined exit codes but we don't know them so we use one that is
|
||||
// unlikely to be used.
|
||||
const UINT kCrashExitCode = 0xffff7001;
|
||||
// This handle is never closed.
|
||||
HANDLE g_signal_exception = INVALID_HANDLE_VALUE;
|
||||
|
||||
// These two handles to events are leaked.
|
||||
HANDLE g_signal_exception = nullptr;
|
||||
HANDLE g_wait_termination = nullptr;
|
||||
|
||||
// Tracks whether a thread has already entered UnhandledExceptionHandler.
|
||||
base::subtle::AtomicWord g_have_crashed;
|
||||
// Where we store the exception information that the crash handler reads.
|
||||
crashpad::ExceptionInformation g_exception_information;
|
||||
|
||||
LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
||||
// Tracks whether a thread has already entered UnhandledExceptionHandler.
|
||||
static base::subtle::AtomicWord have_crashed;
|
||||
|
||||
// This is a per-process handler. While this handler is being invoked, other
|
||||
// threads are still executing as usual, so multiple threads could enter at
|
||||
// the same time. Because we're in a crashing state, we shouldn't be doing
|
||||
// anything that might cause allocations, call into kernel mode, etc. So, we
|
||||
// don't want to take a critical section here to avoid simultaneous access to
|
||||
// the global exception pointers in CrashpadInfo. Because the crash handler
|
||||
// will record all threads, it's fine to simply have the second and subsequent
|
||||
// entrants block here. They will soon be suspended by the crash handler, and
|
||||
// then the entire process will be terminated below. This means that we won't
|
||||
// save the exception pointers from the second and further crashes, but
|
||||
// contention here is very unlikely, and we'll still have a stack that's
|
||||
// blocked at this location.
|
||||
if (base::subtle::Barrier_AtomicIncrement(&g_have_crashed, 1) > 1) {
|
||||
// the global exception pointers in ExceptionInformation. Because the crash
|
||||
// handler will record all threads, it's fine to simply have the second and
|
||||
// subsequent entrants block here. They will soon be suspended by the crash
|
||||
// handler, and then the entire process will be terminated below. This means
|
||||
// that we won't save the exception pointers from the second and further
|
||||
// crashes, but contention here is very unlikely, and we'll still have a stack
|
||||
// that's blocked at this location.
|
||||
if (base::subtle::Barrier_AtomicIncrement(&have_crashed, 1) > 1) {
|
||||
SleepEx(INFINITE, false);
|
||||
}
|
||||
|
||||
// Otherwise, we're the first thread, so record the exception pointer and
|
||||
// signal the crash handler.
|
||||
crashpad::CrashpadInfo::GetCrashpadInfo()->set_thread_id(
|
||||
GetCurrentThreadId());
|
||||
crashpad::CrashpadInfo::GetCrashpadInfo()->set_exception_pointers(
|
||||
exception_pointers);
|
||||
DWORD rv = SignalObjectAndWait(g_signal_exception,
|
||||
g_wait_termination,
|
||||
kMillisecondsUntilTerminate,
|
||||
false);
|
||||
if (rv != WAIT_OBJECT_0) {
|
||||
// Something went wrong. It is likely that a dump has not been created.
|
||||
if (rv == WAIT_TIMEOUT) {
|
||||
LOG(WARNING) << "SignalObjectAndWait timed out";
|
||||
} else {
|
||||
PLOG(WARNING) << "SignalObjectAndWait error";
|
||||
}
|
||||
}
|
||||
// We don't want to generate more exceptions, so we take the fast route.
|
||||
TerminateProcess(GetCurrentProcess(), kCrashExitCode);
|
||||
return 0L;
|
||||
}
|
||||
g_exception_information.thread_id = GetCurrentThreadId();
|
||||
g_exception_information.exception_pointers =
|
||||
reinterpret_cast<crashpad::WinVMAddress>(exception_pointers);
|
||||
|
||||
// Returns a pipe handle connected to the RegistrationServer.
|
||||
crashpad::ScopedFileHANDLE Connect(const base::string16& pipe_name) {
|
||||
crashpad::ScopedFileHANDLE pipe;
|
||||
const int kMaxTries = 5;
|
||||
for (int tries = 0; tries < kMaxTries; ++tries) {
|
||||
pipe.reset(CreateFile(pipe_name.c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS,
|
||||
nullptr));
|
||||
if (pipe.is_valid())
|
||||
break;
|
||||
// Now signal the crash server, which will take a dump and then terminate us
|
||||
// when it's complete.
|
||||
SetEvent(g_signal_exception);
|
||||
|
||||
// If busy, wait 60s before retrying.
|
||||
if (GetLastError() != ERROR_PIPE_BUSY) {
|
||||
PLOG(ERROR) << "CreateFile pipe connection";
|
||||
return crashpad::ScopedFileHANDLE();
|
||||
} else if (!WaitNamedPipe(pipe_name.c_str(), 60000)) {
|
||||
PLOG(ERROR) << "WaitNamedPipe";
|
||||
}
|
||||
}
|
||||
// Time to wait for the handler to create a dump.
|
||||
const DWORD kMillisecondsUntilTerminate = 60 * 1000;
|
||||
|
||||
if (!pipe.is_valid())
|
||||
return crashpad::ScopedFileHANDLE();
|
||||
// Sleep for a while to allow it to process us. Eventually, we terminate
|
||||
// ourselves in case the crash server is gone, so that we don't leave zombies
|
||||
// around. This would ideally never happen.
|
||||
// TODO(scottmg): Re-add the "reply" event here, for implementing
|
||||
// DumpWithoutCrashing.
|
||||
Sleep(kMillisecondsUntilTerminate);
|
||||
|
||||
DWORD mode = PIPE_READMODE_MESSAGE;
|
||||
if (!SetNamedPipeHandleState(pipe.get(),
|
||||
&mode,
|
||||
nullptr, // Don't set maximum bytes.
|
||||
nullptr)) { // Don't set maximum time.
|
||||
PLOG(ERROR) << "SetNamedPipeHandleState";
|
||||
return crashpad::ScopedFileHANDLE();
|
||||
}
|
||||
LOG(ERROR) << "crash server did not respond, self-terminating";
|
||||
|
||||
return pipe.Pass();
|
||||
const UINT kCrashExitCodeNoDump = 0xffff7001;
|
||||
TerminateProcess(GetCurrentProcess(), kCrashExitCodeNoDump);
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@ -137,36 +97,33 @@ bool CrashpadClient::StartHandler(
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations,
|
||||
const std::vector<std::string>& arguments) {
|
||||
LOG(FATAL) << "SetHandler should be used on Windows";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CrashpadClient::SetHandler(const std::string& ipc_port) {
|
||||
RegistrationRequest request = {0};
|
||||
request.client_process_id = GetCurrentProcessId();
|
||||
request.crashpad_info_address =
|
||||
reinterpret_cast<WinVMAddress>(CrashpadInfo::GetCrashpadInfo());
|
||||
ClientToServerMessage message;
|
||||
memset(&message, 0, sizeof(message));
|
||||
message.type = ClientToServerMessage::kRegister;
|
||||
message.registration.client_process_id = GetCurrentProcessId();
|
||||
message.registration.exception_information =
|
||||
reinterpret_cast<WinVMAddress>(&g_exception_information);
|
||||
|
||||
RegistrationResponse response = {0};
|
||||
ServerToClientMessage response = {0};
|
||||
|
||||
ScopedFileHANDLE pipe = Connect(base::UTF8ToUTF16(ipc_port));
|
||||
if (!pipe.is_valid())
|
||||
if (!SendToCrashHandlerServer(
|
||||
base::UTF8ToUTF16(ipc_port), message, &response)) {
|
||||
return false;
|
||||
bool result = LoggingWriteFile(pipe.get(), &request, sizeof(request)) &&
|
||||
LoggingReadFile(pipe.get(), &response, sizeof(response));
|
||||
if (!result)
|
||||
return result;
|
||||
}
|
||||
|
||||
// The server returns these already duplicated to be valid in this process.
|
||||
g_signal_exception = reinterpret_cast<HANDLE>(response.request_report_event);
|
||||
g_wait_termination =
|
||||
reinterpret_cast<HANDLE>(response.report_complete_event);
|
||||
g_signal_exception =
|
||||
reinterpret_cast<HANDLE>(response.registration.request_report_event);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrashpadClient::UseHandler() {
|
||||
if (!g_signal_exception)
|
||||
return false;
|
||||
if (!g_wait_termination)
|
||||
if (g_signal_exception == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
// In theory we could store the previous handler but it is not clear what
|
||||
// use we have for it.
|
||||
|
@ -1,57 +0,0 @@
|
||||
// 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 <a
|
||||
//! href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203">Interprocess
|
||||
//! Communication Between 32-bit and 64-bit Applications</a> for details on
|
||||
//! communicating handle values between processes of varying bitness.
|
||||
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,7 +23,6 @@
|
||||
'client/client_test.gyp:*',
|
||||
'compat/compat.gyp:*',
|
||||
'handler/handler.gyp:*',
|
||||
'handler/handler_test.gyp:*',
|
||||
'minidump/minidump.gyp:*',
|
||||
'minidump/minidump_test.gyp:*',
|
||||
'snapshot/snapshot.gyp:*',
|
||||
|
@ -73,11 +73,10 @@
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'crashpad_handler',
|
||||
# TODO(scottmg): This will soon be an executable, once main.cc exists.
|
||||
'type': 'static_library',
|
||||
'dependencies': [
|
||||
'../client/client.gyp:crashpad_client',
|
||||
'../compat/compat.gyp:crashpad_compat',
|
||||
'../snapshot/snapshot.gyp:crashpad_snapshot',
|
||||
'../third_party/mini_chromium/mini_chromium.gyp:base',
|
||||
'../util/util.gyp:crashpad_util',
|
||||
],
|
||||
@ -87,10 +86,6 @@
|
||||
'sources': [
|
||||
'crash_report_upload_thread.cc',
|
||||
'crash_report_upload_thread.h',
|
||||
'win/registration_pipe_state.cc',
|
||||
'win/registration_pipe_state.h',
|
||||
'win/registration_server.cc',
|
||||
'win/registration_server.h',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -1,48 +0,0 @@
|
||||
# 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',
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
@ -1,289 +0,0 @@
|
||||
// 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
|
@ -1,112 +0,0 @@
|
||||
// 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_
|
@ -1,259 +0,0 @@
|
||||
// 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
|
@ -1,136 +0,0 @@
|
||||
// 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
|
@ -1,83 +0,0 @@
|
||||
// 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_
|
@ -1,271 +0,0 @@
|
||||
// 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
|
@ -1,166 +0,0 @@
|
||||
// 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
|
@ -1,114 +0,0 @@
|
||||
// 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
|
@ -54,7 +54,6 @@
|
||||
'snapshot.gyp:crashpad_snapshot',
|
||||
'../client/client.gyp:crashpad_client',
|
||||
'../compat/compat.gyp:crashpad_compat',
|
||||
'../handler/handler.gyp:crashpad_handler',
|
||||
'../test/test.gyp:crashpad_test',
|
||||
'../third_party/gtest/gtest.gyp:gtest',
|
||||
'../third_party/gtest/gtest.gyp:gtest_main',
|
||||
|
@ -19,13 +19,13 @@
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "client/crashpad_client.h"
|
||||
#include "client/crashpad_info.h"
|
||||
#include "handler/win/registration_server.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "snapshot/win/process_reader_win.h"
|
||||
#include "snapshot/win/process_snapshot_win.h"
|
||||
#include "test/win/win_child_process.h"
|
||||
#include "util/thread/thread.h"
|
||||
#include "util/win/exception_handler_server.h"
|
||||
#include "util/win/registration_protocol_win.h"
|
||||
#include "util/win/scoped_handle.h"
|
||||
|
||||
namespace crashpad {
|
||||
@ -48,62 +48,37 @@ HANDLE DuplicateEvent(HANDLE process, HANDLE event) {
|
||||
|
||||
class ExceptionSnapshotWinTest : public testing::Test {
|
||||
public:
|
||||
class Delegate : public RegistrationServer::Delegate {
|
||||
class Delegate : public ExceptionHandlerServer::Delegate {
|
||||
public:
|
||||
Delegate()
|
||||
: crashpad_info_address_(0),
|
||||
client_process_(),
|
||||
started_event_(CreateEvent(nullptr, false, false, nullptr)),
|
||||
request_dump_event_(CreateEvent(nullptr, false, false, nullptr)),
|
||||
dump_complete_event_(CreateEvent(nullptr, true, false, nullptr)) {
|
||||
EXPECT_TRUE(started_event_.is_valid());
|
||||
EXPECT_TRUE(request_dump_event_.is_valid());
|
||||
EXPECT_TRUE(dump_complete_event_.is_valid());
|
||||
}
|
||||
Delegate(HANDLE server_ready, HANDLE completed_test_event)
|
||||
: server_ready_(server_ready),
|
||||
completed_test_event_(completed_test_event),
|
||||
break_near_(nullptr) {}
|
||||
~Delegate() override {}
|
||||
|
||||
~Delegate() override {
|
||||
}
|
||||
void set_break_near(void* break_near) { break_near_ = break_near; }
|
||||
|
||||
void OnStarted() override {
|
||||
EXPECT_EQ(WAIT_TIMEOUT, WaitForSingleObject(started_event_.get(), 0));
|
||||
SetEvent(started_event_.get());
|
||||
}
|
||||
|
||||
bool RegisterClient(ScopedKernelHANDLE client_process,
|
||||
WinVMAddress crashpad_info_address,
|
||||
HANDLE* request_dump_event,
|
||||
HANDLE* dump_complete_event) override {
|
||||
client_process_ = client_process.Pass();
|
||||
crashpad_info_address_ = crashpad_info_address;
|
||||
*request_dump_event =
|
||||
DuplicateEvent(client_process_.get(), request_dump_event_.get());
|
||||
*dump_complete_event =
|
||||
DuplicateEvent(client_process_.get(), dump_complete_event_.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
void WaitForStart() {
|
||||
DWORD wait_result = WaitForSingleObject(started_event_.get(), INFINITE);
|
||||
if (wait_result == WAIT_FAILED)
|
||||
PLOG(ERROR);
|
||||
ASSERT_EQ(wait_result, WAIT_OBJECT_0);
|
||||
}
|
||||
|
||||
void WaitForDumpRequestAndValidateException(void* break_near) {
|
||||
// Wait until triggered, and then grab information from the child.
|
||||
WaitForSingleObject(request_dump_event_.get(), INFINITE);
|
||||
void ExceptionHandlerServerStarted() override { SetEvent(server_ready_); }
|
||||
|
||||
unsigned int ExceptionHandlerServerException(
|
||||
HANDLE process,
|
||||
WinVMAddress exception_information_address) override {
|
||||
// Snapshot the process and exception.
|
||||
ProcessReaderWin process_reader;
|
||||
ASSERT_TRUE(process_reader.Initialize(client_process_.get()));
|
||||
CrashpadInfo crashpad_info;
|
||||
ASSERT_TRUE(process_reader.ReadMemory(
|
||||
crashpad_info_address_, sizeof(crashpad_info), &crashpad_info));
|
||||
EXPECT_TRUE(process_reader.Initialize(process));
|
||||
if (HasFatalFailure())
|
||||
return 0xffffffff;
|
||||
ExceptionInformation exception_information;
|
||||
EXPECT_TRUE(
|
||||
process_reader.ReadMemory(exception_information_address,
|
||||
sizeof(exception_information),
|
||||
&exception_information));
|
||||
if (HasFatalFailure())
|
||||
return 0xffffffff;
|
||||
ProcessSnapshotWin snapshot;
|
||||
snapshot.Initialize(client_process_.get());
|
||||
snapshot.InitializeException(
|
||||
crashpad_info.thread_id(),
|
||||
reinterpret_cast<WinVMAddress>(crashpad_info.exception_pointers()));
|
||||
snapshot.Initialize(process);
|
||||
snapshot.InitializeException(exception_information.thread_id,
|
||||
exception_information.exception_pointers);
|
||||
|
||||
// Confirm the exception record was read correctly.
|
||||
EXPECT_NE(snapshot.Exception()->ThreadID(), 0u);
|
||||
@ -114,43 +89,42 @@ class ExceptionSnapshotWinTest : public testing::Test {
|
||||
// happens. See CrashingChildProcess::Run().
|
||||
const uint64_t kAllowedOffset = 64;
|
||||
EXPECT_GT(snapshot.Exception()->ExceptionAddress(),
|
||||
reinterpret_cast<uint64_t>(break_near));
|
||||
reinterpret_cast<uint64_t>(break_near_));
|
||||
EXPECT_LT(snapshot.Exception()->ExceptionAddress(),
|
||||
reinterpret_cast<uint64_t>(break_near) + kAllowedOffset);
|
||||
reinterpret_cast<uint64_t>(break_near_) + kAllowedOffset);
|
||||
|
||||
// Notify the child that we're done.
|
||||
SetEvent(dump_complete_event_.get());
|
||||
SetEvent(completed_test_event_);
|
||||
|
||||
return snapshot.Exception()->Exception();
|
||||
}
|
||||
|
||||
ScopedKernelHANDLE* request_dump_event() { return &request_dump_event_; }
|
||||
ScopedKernelHANDLE* dump_complete_event() { return &dump_complete_event_; }
|
||||
|
||||
private:
|
||||
WinVMAddress crashpad_info_address_;
|
||||
ScopedKernelHANDLE client_process_;
|
||||
ScopedKernelHANDLE started_event_;
|
||||
ScopedKernelHANDLE request_dump_event_;
|
||||
ScopedKernelHANDLE dump_complete_event_;
|
||||
HANDLE server_ready_; // weak
|
||||
HANDLE completed_test_event_; // weak
|
||||
void* break_near_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Delegate);
|
||||
};
|
||||
|
||||
private:
|
||||
ScopedKernelHANDLE exception_happened_;
|
||||
};
|
||||
|
||||
// Runs the RegistrationServer on a background thread.
|
||||
// Runs the ExceptionHandlerServer 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) {}
|
||||
// Instantiates a thread which will invoke server->Run(pipe_name);
|
||||
explicit RunServerThread(ExceptionHandlerServer* server,
|
||||
const std::string& pipe_name)
|
||||
: server_(server), pipe_name_(pipe_name) {}
|
||||
~RunServerThread() override {}
|
||||
|
||||
private:
|
||||
// Thread:
|
||||
void ThreadMain() override { server_->Run(pipe_name_, delegate_); }
|
||||
void ThreadMain() override { server_->Run(pipe_name_); }
|
||||
|
||||
RegistrationServer* server_;
|
||||
base::string16 pipe_name_;
|
||||
RegistrationServer::Delegate* delegate_;
|
||||
ExceptionHandlerServer* server_;
|
||||
std::string pipe_name_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RunServerThread);
|
||||
};
|
||||
@ -159,7 +133,7 @@ class RunServerThread : public Thread {
|
||||
// thread joined.
|
||||
class ScopedStopServerAndJoinThread {
|
||||
public:
|
||||
explicit ScopedStopServerAndJoinThread(RegistrationServer* server,
|
||||
explicit ScopedStopServerAndJoinThread(ExceptionHandlerServer* server,
|
||||
Thread* thread)
|
||||
: server_(server), thread_(thread) {}
|
||||
~ScopedStopServerAndJoinThread() {
|
||||
@ -168,7 +142,7 @@ class ScopedStopServerAndJoinThread {
|
||||
}
|
||||
|
||||
private:
|
||||
RegistrationServer* server_;
|
||||
ExceptionHandlerServer* server_;
|
||||
Thread* thread_;
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread);
|
||||
};
|
||||
@ -213,29 +187,36 @@ class CrashingChildProcess final : public WinChildProcess {
|
||||
};
|
||||
|
||||
TEST_F(ExceptionSnapshotWinTest, ChildCrash) {
|
||||
// Set up the registration server on a background thread.
|
||||
RegistrationServer server;
|
||||
std::string pipe_name = "\\\\.\\pipe\\handler_test_pipe_" +
|
||||
base::StringPrintf("%08x", GetCurrentProcessId());
|
||||
base::string16 pipe_name_16 = base::UTF8ToUTF16(pipe_name);
|
||||
Delegate delegate;
|
||||
RunServerThread server_thread(&server, pipe_name_16, &delegate);
|
||||
server_thread.Start();
|
||||
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
|
||||
&server, &server_thread);
|
||||
ASSERT_NO_FATAL_FAILURE(delegate.WaitForStart());
|
||||
|
||||
// Spawn a child process that immediately crashes.
|
||||
// Spawn a child process that will immediately crash (once we let it
|
||||
// run below by telling it what to connect to).
|
||||
WinChildProcess::EntryPoint<CrashingChildProcess>();
|
||||
scoped_ptr<WinChildProcess::Handles> handle = WinChildProcess::Launch();
|
||||
|
||||
// Set up the registration server on a background thread.
|
||||
std::string pipe_name = "\\\\.\\pipe\\handler_test_pipe_" +
|
||||
base::StringPrintf("%08x", GetCurrentProcessId());
|
||||
ScopedKernelHANDLE server_ready(CreateEvent(nullptr, false, false, nullptr));
|
||||
ScopedKernelHANDLE completed(CreateEvent(nullptr, false, false, nullptr));
|
||||
Delegate delegate(server_ready.get(), completed.get());
|
||||
|
||||
ExceptionHandlerServer exception_handler_server(&delegate);
|
||||
RunServerThread server_thread(&exception_handler_server, pipe_name);
|
||||
server_thread.Start();
|
||||
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
|
||||
&exception_handler_server, &server_thread);
|
||||
|
||||
WaitForSingleObject(server_ready.get(), INFINITE);
|
||||
// Allow the child to continue and tell it where to connect to.
|
||||
WriteString(handle->write.get(), pipe_name);
|
||||
|
||||
// The child tells us (approximately) where it will crash.
|
||||
void* break_near_address;
|
||||
LoggingReadFile(
|
||||
handle->read.get(), &break_near_address, sizeof(break_near_address));
|
||||
delegate.set_break_near(break_near_address);
|
||||
|
||||
// Verify the exception information is as expected.
|
||||
delegate.WaitForDumpRequestAndValidateException(break_near_address);
|
||||
// Wait for the child to crash and the exception information to be validated.
|
||||
WaitForSingleObject(completed.get(), INFINITE);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -149,6 +149,8 @@
|
||||
'thread/thread_win.cc',
|
||||
'win/address_types.h',
|
||||
'win/checked_win_address_range.h',
|
||||
'win/exception_handler_server.cc',
|
||||
'win/exception_handler_server.h',
|
||||
'win/module_version.cc',
|
||||
'win/module_version.h',
|
||||
'win/nt_internals.cc',
|
||||
@ -156,6 +158,8 @@
|
||||
'win/process_info.cc',
|
||||
'win/process_info.h',
|
||||
'win/process_structs.h',
|
||||
'win/registration_protocol_win.cc',
|
||||
'win/registration_protocol_win.h',
|
||||
'win/scoped_handle.cc',
|
||||
'win/scoped_handle.h',
|
||||
'win/time.cc',
|
||||
|
@ -22,6 +22,7 @@
|
||||
'type': 'executable',
|
||||
'dependencies': [
|
||||
'util.gyp:crashpad_util',
|
||||
'../client/client.gyp:crashpad_client',
|
||||
'../compat/compat.gyp:crashpad_compat',
|
||||
'../test/test.gyp:crashpad_test',
|
||||
'../third_party/gmock/gmock.gyp:gmock',
|
||||
@ -78,6 +79,7 @@
|
||||
'synchronization/semaphore_test.cc',
|
||||
'thread/thread_log_messages_test.cc',
|
||||
'thread/thread_test.cc',
|
||||
'win/exception_handler_server_test.cc',
|
||||
'win/process_info_test.cc',
|
||||
'win/time_test.cc',
|
||||
],
|
||||
|
422
util/win/exception_handler_server.cc
Normal file
422
util/win/exception_handler_server.cc
Normal file
@ -0,0 +1,422 @@
|
||||
// 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 "util/win/exception_handler_server.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/rand_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "minidump/minidump_file_writer.h"
|
||||
#include "snapshot/crashpad_info_client_options.h"
|
||||
#include "snapshot/win/process_snapshot_win.h"
|
||||
#include "util/file/file_writer.h"
|
||||
#include "util/misc/tri_state.h"
|
||||
#include "util/misc/uuid.h"
|
||||
#include "util/win/registration_protocol_win.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() {
|
||||
static decltype(GetNamedPipeClientProcessId)* func =
|
||||
reinterpret_cast<decltype(GetNamedPipeClientProcessId)*>(GetProcAddress(
|
||||
GetModuleHandle(L"kernel32.dll"), "GetNamedPipeClientProcessId"));
|
||||
return func;
|
||||
}
|
||||
|
||||
HANDLE DuplicateEvent(HANDLE process, HANDLE event) {
|
||||
HANDLE handle;
|
||||
if (DuplicateHandle(GetCurrentProcess(),
|
||||
event,
|
||||
process,
|
||||
&handle,
|
||||
SYNCHRONIZE | EVENT_MODIFY_STATE,
|
||||
false,
|
||||
0)) {
|
||||
return handle;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
//! \brief Context information for the named pipe handler threads.
|
||||
class PipeServiceContext {
|
||||
public:
|
||||
PipeServiceContext(HANDLE port,
|
||||
HANDLE pipe,
|
||||
ExceptionHandlerServer::Delegate* delegate,
|
||||
base::Lock* clients_lock,
|
||||
std::set<internal::ClientData*>* clients,
|
||||
uint64_t shutdown_token)
|
||||
: port_(port),
|
||||
pipe_(pipe),
|
||||
delegate_(delegate),
|
||||
clients_lock_(clients_lock),
|
||||
clients_(clients),
|
||||
shutdown_token_(shutdown_token) {}
|
||||
|
||||
HANDLE port() const { return port_; }
|
||||
HANDLE pipe() const { return pipe_.get(); }
|
||||
ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
|
||||
base::Lock* clients_lock() const { return clients_lock_; }
|
||||
std::set<internal::ClientData*>* clients() const { return clients_; }
|
||||
uint64_t shutdown_token() const { return shutdown_token_; }
|
||||
|
||||
private:
|
||||
HANDLE port_; // weak
|
||||
ScopedKernelHANDLE pipe_;
|
||||
ExceptionHandlerServer::Delegate* delegate_; // weak
|
||||
base::Lock* clients_lock_; // weak
|
||||
std::set<internal::ClientData*>* clients_; // weak
|
||||
uint64_t shutdown_token_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PipeServiceContext);
|
||||
};
|
||||
|
||||
//! \brief The context data for registered threadpool waits.
|
||||
//!
|
||||
//! This object must be created and destroyed on the main thread. Access must be
|
||||
//! guarded by use of the lock() with the exception of the threadpool wait
|
||||
//! variables which are accessed only by the main thread.
|
||||
class ClientData {
|
||||
public:
|
||||
ClientData(HANDLE port,
|
||||
ExceptionHandlerServer::Delegate* delegate,
|
||||
ScopedKernelHANDLE process,
|
||||
WinVMAddress exception_information_address,
|
||||
WAITORTIMERCALLBACK dump_request_callback,
|
||||
WAITORTIMERCALLBACK process_end_callback)
|
||||
: dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
|
||||
process_end_thread_pool_wait_(INVALID_HANDLE_VALUE),
|
||||
lock_(),
|
||||
port_(port),
|
||||
delegate_(delegate),
|
||||
dump_requested_event_(
|
||||
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
|
||||
process_(process.Pass()),
|
||||
exception_information_address_(exception_information_address) {
|
||||
RegisterThreadPoolWaits(dump_request_callback, process_end_callback);
|
||||
}
|
||||
|
||||
~ClientData() {
|
||||
// It is important that this only access the threadpool waits (it's called
|
||||
// from the main thread) until the waits are unregistered, to ensure that
|
||||
// any outstanding callbacks are complete.
|
||||
UnregisterThreadPoolWaits();
|
||||
}
|
||||
|
||||
base::Lock* lock() { return &lock_; }
|
||||
HANDLE port() const { return port_; }
|
||||
ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
|
||||
HANDLE dump_requested_event() const { return dump_requested_event_.get(); }
|
||||
WinVMAddress exception_information_address() const {
|
||||
return exception_information_address_;
|
||||
}
|
||||
HANDLE process() const { return process_.get(); }
|
||||
|
||||
private:
|
||||
void RegisterThreadPoolWaits(WAITORTIMERCALLBACK dump_request_callback,
|
||||
WAITORTIMERCALLBACK process_end_callback) {
|
||||
if (!RegisterWaitForSingleObject(&dump_request_thread_pool_wait_,
|
||||
dump_requested_event_.get(),
|
||||
dump_request_callback,
|
||||
this,
|
||||
INFINITE,
|
||||
WT_EXECUTEDEFAULT)) {
|
||||
LOG(ERROR) << "RegisterWaitForSingleObject dump requested";
|
||||
}
|
||||
|
||||
if (!RegisterWaitForSingleObject(&process_end_thread_pool_wait_,
|
||||
process_.get(),
|
||||
process_end_callback,
|
||||
this,
|
||||
INFINITE,
|
||||
WT_EXECUTEONLYONCE)) {
|
||||
LOG(ERROR) << "RegisterWaitForSingleObject process end";
|
||||
}
|
||||
}
|
||||
|
||||
// This blocks until outstanding calls complete so that we know it's safe to
|
||||
// delete this object. Because of this, it must be executed on the main
|
||||
// thread, not a threadpool thread.
|
||||
void UnregisterThreadPoolWaits() {
|
||||
UnregisterWaitEx(dump_request_thread_pool_wait_, INVALID_HANDLE_VALUE);
|
||||
dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
|
||||
UnregisterWaitEx(process_end_thread_pool_wait_, INVALID_HANDLE_VALUE);
|
||||
process_end_thread_pool_wait_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// These are only accessed on the main thread.
|
||||
HANDLE dump_request_thread_pool_wait_;
|
||||
HANDLE process_end_thread_pool_wait_;
|
||||
|
||||
base::Lock lock_;
|
||||
// Access to these fields must be guarded by lock_.
|
||||
HANDLE port_; // weak
|
||||
ExceptionHandlerServer::Delegate* delegate_; // weak
|
||||
ScopedKernelHANDLE dump_requested_event_;
|
||||
ScopedKernelHANDLE process_;
|
||||
WinVMAddress exception_information_address_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ClientData);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
ExceptionHandlerServer::Delegate::~Delegate() {
|
||||
}
|
||||
|
||||
ExceptionHandlerServer::ExceptionHandlerServer(Delegate* delegate)
|
||||
: delegate_(delegate),
|
||||
port_(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)),
|
||||
clients_lock_(),
|
||||
clients_() {
|
||||
}
|
||||
|
||||
ExceptionHandlerServer::~ExceptionHandlerServer() {
|
||||
}
|
||||
|
||||
void ExceptionHandlerServer::Run(const std::string& pipe_name) {
|
||||
uint64_t shutdown_token = base::RandUint64();
|
||||
// We create two pipe instances, so that there's one listening while the
|
||||
// PipeServiceProc is processing a registration.
|
||||
ScopedKernelHANDLE thread_handles[2];
|
||||
base::string16 pipe_name_16(base::UTF8ToUTF16(pipe_name));
|
||||
for (int i = 0; i < arraysize(thread_handles); ++i) {
|
||||
HANDLE pipe =
|
||||
CreateNamedPipe(pipe_name_16.c_str(),
|
||||
PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
||||
arraysize(thread_handles),
|
||||
512,
|
||||
512,
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
// Ownership of this object (and the pipe instance) is given to the new
|
||||
// thread. We close the thread handles at the end of the scope. They clean
|
||||
// up the context object and the pipe instance on termination.
|
||||
internal::PipeServiceContext* context =
|
||||
new internal::PipeServiceContext(port_.get(),
|
||||
pipe,
|
||||
delegate_,
|
||||
&clients_lock_,
|
||||
&clients_,
|
||||
shutdown_token);
|
||||
thread_handles[i].reset(
|
||||
CreateThread(nullptr, 0, &PipeServiceProc, context, 0, nullptr));
|
||||
}
|
||||
|
||||
delegate_->ExceptionHandlerServerStarted();
|
||||
|
||||
// This is the main loop of the server. Most work is done on the threadpool,
|
||||
// other than process end handling which is posted back to this main thread,
|
||||
// as we must unregister the threadpool waits here.
|
||||
for (;;) {
|
||||
OVERLAPPED* ov = nullptr;
|
||||
ULONG_PTR key = 0;
|
||||
DWORD bytes = 0;
|
||||
GetQueuedCompletionStatus(port_.get(), &bytes, &key, &ov, INFINITE);
|
||||
if (!key) {
|
||||
// Shutting down.
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, this is a request to unregister and destroy the given client.
|
||||
// delete'ing the ClientData blocks in UnregisterWaitEx to ensure all
|
||||
// outstanding threadpool waits are complete. This is important because the
|
||||
// process handle can be signalled *before* the dump request is signalled.
|
||||
internal::ClientData* client = reinterpret_cast<internal::ClientData*>(key);
|
||||
{
|
||||
base::AutoLock lock(clients_lock_);
|
||||
clients_.erase(client);
|
||||
}
|
||||
delete client;
|
||||
}
|
||||
|
||||
// Signal to the named pipe instances that they should terminate.
|
||||
for (int i = 0; i < arraysize(thread_handles); ++i) {
|
||||
ClientToServerMessage message;
|
||||
memset(&message, 0, sizeof(message));
|
||||
message.type = ClientToServerMessage::kShutdown;
|
||||
message.shutdown.token = shutdown_token;
|
||||
ServerToClientMessage response;
|
||||
SendToCrashHandlerServer(base::UTF8ToUTF16(pipe_name),
|
||||
reinterpret_cast<ClientToServerMessage&>(message),
|
||||
&response);
|
||||
}
|
||||
|
||||
for (auto& handle : thread_handles)
|
||||
WaitForSingleObject(handle.get(), INFINITE);
|
||||
|
||||
// Deleting ClientData does a blocking wait until the threadpool executions
|
||||
// have terminated when unregistering them.
|
||||
{
|
||||
base::AutoLock lock(clients_lock_);
|
||||
for (auto* client : clients_)
|
||||
delete client;
|
||||
clients_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ExceptionHandlerServer::Stop() {
|
||||
// Post a null key (third argument) to trigger shutdown.
|
||||
PostQueuedCompletionStatus(port_.get(), 0, 0, nullptr);
|
||||
}
|
||||
|
||||
// This function must be called with service_context.pipe() already connected to
|
||||
// a client pipe. It exchanges data with the client and adds a ClientData record
|
||||
// to service_context->clients().
|
||||
//
|
||||
// static
|
||||
bool ExceptionHandlerServer::ServiceClientConnection(
|
||||
const internal::PipeServiceContext& service_context) {
|
||||
ClientToServerMessage message;
|
||||
|
||||
if (!LoggingReadFile(service_context.pipe(), &message, sizeof(message)))
|
||||
return false;
|
||||
|
||||
switch (message.type) {
|
||||
case ClientToServerMessage::kShutdown: {
|
||||
if (message.shutdown.token != service_context.shutdown_token()) {
|
||||
LOG(ERROR) << "forged shutdown request, got: "
|
||||
<< message.shutdown.token;
|
||||
return false;
|
||||
}
|
||||
ServerToClientMessage shutdown_response = {0};
|
||||
LoggingWriteFile(service_context.pipe(),
|
||||
&shutdown_response,
|
||||
sizeof(shutdown_response));
|
||||
return true;
|
||||
}
|
||||
|
||||
case ClientToServerMessage::kRegister:
|
||||
// Handled below.
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG(ERROR) << "unhandled message type: " << message.type;
|
||||
return false;
|
||||
}
|
||||
|
||||
decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id =
|
||||
GetNamedPipeClientProcessIdFunction();
|
||||
if (get_named_pipe_client_process_id) {
|
||||
// GetNamedPipeClientProcessId is only available on Vista+.
|
||||
DWORD real_pid = 0;
|
||||
if (get_named_pipe_client_process_id(service_context.pipe(), &real_pid) &&
|
||||
message.registration.client_process_id != real_pid) {
|
||||
LOG(ERROR) << "forged client pid, real pid: " << real_pid
|
||||
<< ", got: " << message.registration.client_process_id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We attempt to open the process as us. This is the main case that should
|
||||
// almost always succeed as the server will generally be more privileged. If
|
||||
// we're running as a different user, it may be that we will fail to open
|
||||
// the process, but the client will be able to, so we make a second attempt
|
||||
// having impersonated the client.
|
||||
HANDLE client_process = OpenProcess(
|
||||
PROCESS_ALL_ACCESS, false, message.registration.client_process_id);
|
||||
if (!client_process) {
|
||||
if (!ImpersonateNamedPipeClient(service_context.pipe())) {
|
||||
PLOG(ERROR) << "ImpersonateNamedPipeClient";
|
||||
return false;
|
||||
}
|
||||
HANDLE client_process = OpenProcess(
|
||||
PROCESS_ALL_ACCESS, false, message.registration.client_process_id);
|
||||
PCHECK(RevertToSelf());
|
||||
if (!client_process) {
|
||||
LOG(ERROR) << "failed to open " << message.registration.client_process_id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal::ClientData* client;
|
||||
{
|
||||
base::AutoLock lock(*service_context.clients_lock());
|
||||
client =
|
||||
new internal::ClientData(service_context.port(),
|
||||
service_context.delegate(),
|
||||
ScopedKernelHANDLE(client_process),
|
||||
message.registration.exception_information,
|
||||
&OnDumpEvent,
|
||||
&OnProcessEnd);
|
||||
service_context.clients()->insert(client);
|
||||
}
|
||||
|
||||
// Duplicate the events back to the client so they can request a dump.
|
||||
ServerToClientMessage response;
|
||||
response.registration.request_report_event = reinterpret_cast<uint32_t>(
|
||||
DuplicateEvent(client->process(), client->dump_requested_event()));
|
||||
|
||||
if (!LoggingWriteFile(service_context.pipe(), &response, sizeof(response)))
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
DWORD __stdcall ExceptionHandlerServer::PipeServiceProc(void* ctx) {
|
||||
internal::PipeServiceContext* service_context =
|
||||
reinterpret_cast<internal::PipeServiceContext*>(ctx);
|
||||
DCHECK(service_context);
|
||||
|
||||
for (;;) {
|
||||
bool ret = ConnectNamedPipe(service_context->pipe(), nullptr);
|
||||
if (!ret && GetLastError() != ERROR_PIPE_CONNECTED) {
|
||||
PLOG(ERROR) << "ConnectNamedPipe";
|
||||
} else if (ServiceClientConnection(*service_context)) {
|
||||
break;
|
||||
}
|
||||
DisconnectNamedPipe(service_context->pipe());
|
||||
}
|
||||
|
||||
delete service_context;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// static
|
||||
void __stdcall ExceptionHandlerServer::OnDumpEvent(void* ctx, BOOLEAN) {
|
||||
// This function is executed on the thread pool.
|
||||
internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
|
||||
base::AutoLock lock(*client->lock());
|
||||
|
||||
// Capture the exception.
|
||||
unsigned int exit_code = client->delegate()->ExceptionHandlerServerException(
|
||||
client->process(), client->exception_information_address());
|
||||
|
||||
TerminateProcess(client->process(), exit_code);
|
||||
}
|
||||
|
||||
// static
|
||||
void __stdcall ExceptionHandlerServer::OnProcessEnd(void* ctx, BOOLEAN) {
|
||||
// This function is executed on the thread pool.
|
||||
internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
|
||||
base::AutoLock lock(*client->lock());
|
||||
|
||||
// Post back to the main thread to have it delete this client record.
|
||||
PostQueuedCompletionStatus(client->port(), 0, ULONG_PTR(client), nullptr);
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
95
util/win/exception_handler_server.h
Normal file
95
util/win/exception_handler_server.h
Normal file
@ -0,0 +1,95 @@
|
||||
// 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_UTIL_WIN_EXCEPTION_HANDLER_SERVER_H_
|
||||
#define CRASHPAD_UTIL_WIN_EXCEPTION_HANDLER_SERVER_H_
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "util/win/address_types.h"
|
||||
#include "util/win/scoped_handle.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace internal {
|
||||
class PipeServiceContext;
|
||||
class ClientData;
|
||||
} // namespace internal
|
||||
|
||||
//! \brief Runs the main exception-handling server in Crashpad's handler
|
||||
//! process.
|
||||
class ExceptionHandlerServer {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual ~Delegate();
|
||||
|
||||
//! \brief Called when the server has created the named pipe connection
|
||||
//! points and is ready to service requests.
|
||||
virtual void ExceptionHandlerServerStarted() = 0;
|
||||
|
||||
//! \brief Called when the client has signalled that it has encountered an
|
||||
//! exception and so wants a crash dump to be taken.
|
||||
//!
|
||||
//! \param[in] process A handle to the client process. Ownership of the
|
||||
//! lifetime of this handle is not passed to the delegate.
|
||||
//! \param[in] exception_information_address The address in the client's
|
||||
//! address space of an ExceptionInformation structure.
|
||||
//! \return The exit code that should be used when terminating the client
|
||||
//! process.
|
||||
virtual unsigned int ExceptionHandlerServerException(
|
||||
HANDLE process,
|
||||
WinVMAddress exception_information_address) = 0;
|
||||
};
|
||||
|
||||
//! \brief Constructs the exception handling server.
|
||||
//!
|
||||
//! \param[in] delegate The interface to which the exceptions are delegated
|
||||
//! when they are caught in Run(). Ownership is not transferred and it
|
||||
//! must outlive this object.
|
||||
explicit ExceptionHandlerServer(Delegate* delegate);
|
||||
~ExceptionHandlerServer();
|
||||
|
||||
//! \brief Runs the exception-handling server.
|
||||
//!
|
||||
//! \param[in] pipe_name The name of the pipe to listen on. Must be of the
|
||||
//! form "\\.\pipe\<some_name>".
|
||||
void Run(const std::string& pipe_name);
|
||||
|
||||
//! \brief Stops the exception-handling server. Returns immediately. The
|
||||
//! object must not be destroyed until Run() returns.
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
static bool ServiceClientConnection(
|
||||
const internal::PipeServiceContext& service_context);
|
||||
static DWORD __stdcall PipeServiceProc(void* ctx);
|
||||
static void __stdcall OnDumpEvent(void* ctx, BOOLEAN);
|
||||
static void __stdcall OnProcessEnd(void* ctx, BOOLEAN);
|
||||
|
||||
Delegate* delegate_; // Weak.
|
||||
ScopedKernelHANDLE port_;
|
||||
|
||||
base::Lock clients_lock_;
|
||||
std::set<internal::ClientData*> clients_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_WIN_EXCEPTION_HANDLER_SERVER_H_
|
206
util/win/exception_handler_server_test.cc
Normal file
206
util/win/exception_handler_server_test.cc
Normal file
@ -0,0 +1,206 @@
|
||||
// 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 "util/win/exception_handler_server.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "client/crashpad_client.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/win/win_child_process.h"
|
||||
#include "util/thread/thread.h"
|
||||
#include "util/win/address_types.h"
|
||||
#include "util/win/registration_protocol_win.h"
|
||||
#include "util/win/scoped_handle.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
// Runs the ExceptionHandlerServer on a background thread.
|
||||
class RunServerThread : public Thread {
|
||||
public:
|
||||
// Instantiates a thread which will invoke server->Run(pipe_name).
|
||||
RunServerThread(ExceptionHandlerServer* server, const std::string& pipe_name)
|
||||
: server_(server), pipe_name_(pipe_name) {}
|
||||
~RunServerThread() override {}
|
||||
|
||||
private:
|
||||
// Thread:
|
||||
void ThreadMain() override { server_->Run(pipe_name_); }
|
||||
|
||||
ExceptionHandlerServer* server_;
|
||||
std::string pipe_name_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RunServerThread);
|
||||
};
|
||||
|
||||
class TestDelegate : public ExceptionHandlerServer::Delegate {
|
||||
public:
|
||||
explicit TestDelegate(HANDLE server_ready) : server_ready_(server_ready) {}
|
||||
~TestDelegate() override {}
|
||||
|
||||
void ExceptionHandlerServerStarted() override {
|
||||
SetEvent(server_ready_);
|
||||
}
|
||||
unsigned int ExceptionHandlerServerException(
|
||||
HANDLE process,
|
||||
WinVMAddress exception_information_address) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WaitForStart() { WaitForSingleObject(server_ready_, INFINITE); }
|
||||
|
||||
private:
|
||||
HANDLE server_ready_; // weak
|
||||
bool started_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
|
||||
};
|
||||
|
||||
class ExceptionHandlerServerTest : public testing::Test {
|
||||
public:
|
||||
ExceptionHandlerServerTest()
|
||||
: pipe_name_("\\\\.\\pipe\\exception_handler_server_test_pipe_" +
|
||||
base::StringPrintf("%08x", GetCurrentProcessId())),
|
||||
server_ready_(CreateEvent(nullptr, false, false, nullptr)),
|
||||
delegate_(server_ready_.get()),
|
||||
server_(&delegate_),
|
||||
server_thread_(&server_, pipe_name_) {}
|
||||
|
||||
TestDelegate& delegate() { return delegate_; }
|
||||
ExceptionHandlerServer& server() { return server_; }
|
||||
Thread& server_thread() { return server_thread_; }
|
||||
const std::string& pipe_name() const { return pipe_name_; }
|
||||
|
||||
private:
|
||||
std::string pipe_name_;
|
||||
ScopedKernelHANDLE server_ready_;
|
||||
TestDelegate delegate_;
|
||||
ExceptionHandlerServer server_;
|
||||
RunServerThread server_thread_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest);
|
||||
};
|
||||
|
||||
// During destruction, ensures that the server is stopped and the background
|
||||
// thread joined.
|
||||
class ScopedStopServerAndJoinThread {
|
||||
public:
|
||||
ScopedStopServerAndJoinThread(ExceptionHandlerServer* server, Thread* thread)
|
||||
: server_(server), thread_(thread) {}
|
||||
~ScopedStopServerAndJoinThread() {
|
||||
server_->Stop();
|
||||
thread_->Join();
|
||||
}
|
||||
|
||||
private:
|
||||
ExceptionHandlerServer* server_;
|
||||
Thread* thread_;
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread);
|
||||
};
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, Instantiate) {
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, StartAndStop) {
|
||||
server_thread().Start();
|
||||
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
|
||||
&server(), &server_thread());
|
||||
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
|
||||
}
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, StopWhileConnected) {
|
||||
server_thread().Start();
|
||||
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
|
||||
&server(), &server_thread());
|
||||
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
|
||||
CrashpadClient client;
|
||||
client.SetHandler(pipe_name()); // Connect to server.
|
||||
// Leaving this scope causes the server to be stopped, while the connection
|
||||
// is still open.
|
||||
}
|
||||
|
||||
std::string ReadString(FileHandle handle) {
|
||||
size_t length = 0;
|
||||
EXPECT_TRUE(LoggingReadFile(handle, &length, sizeof(length)));
|
||||
scoped_ptr<char[]> buffer(new char[length]);
|
||||
EXPECT_TRUE(LoggingReadFile(handle, &buffer[0], length));
|
||||
return std::string(&buffer[0], length);
|
||||
}
|
||||
|
||||
void WriteString(FileHandle handle, const std::string& str) {
|
||||
size_t length = str.size();
|
||||
EXPECT_TRUE(LoggingWriteFile(handle, &length, sizeof(length)));
|
||||
EXPECT_TRUE(LoggingWriteFile(handle, &str[0], length));
|
||||
}
|
||||
|
||||
class TestClient final : public WinChildProcess {
|
||||
public:
|
||||
TestClient() : WinChildProcess() {}
|
||||
|
||||
~TestClient() {}
|
||||
|
||||
private:
|
||||
int Run() override {
|
||||
std::string pipe_name = ReadString(ReadPipeHandle());
|
||||
CrashpadClient client;
|
||||
if (!client.SetHandler(pipe_name)) {
|
||||
ADD_FAILURE();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!client.UseHandler()) {
|
||||
ADD_FAILURE();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
WriteString(WritePipeHandle(), "OK");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestClient);
|
||||
};
|
||||
|
||||
TEST_F(ExceptionHandlerServerTest, MultipleConnections) {
|
||||
WinChildProcess::EntryPoint<TestClient>();
|
||||
|
||||
scoped_ptr<WinChildProcess::Handles> handles_1 = WinChildProcess::Launch();
|
||||
scoped_ptr<WinChildProcess::Handles> handles_2 = WinChildProcess::Launch();
|
||||
scoped_ptr<WinChildProcess::Handles> handles_3 = WinChildProcess::Launch();
|
||||
|
||||
// Must ensure the delegate outlasts the server.
|
||||
{
|
||||
server_thread().Start();
|
||||
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
|
||||
&server(), &server_thread());
|
||||
ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart());
|
||||
|
||||
// Tell all the children where to connect.
|
||||
WriteString(handles_1->write.get(), pipe_name());
|
||||
WriteString(handles_2->write.get(), pipe_name());
|
||||
WriteString(handles_3->write.get(), pipe_name());
|
||||
|
||||
ASSERT_EQ("OK", ReadString(handles_3->read.get()));
|
||||
ASSERT_EQ("OK", ReadString(handles_2->read.get()));
|
||||
ASSERT_EQ("OK", ReadString(handles_1->read.get()));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
70
util/win/registration_protocol_win.cc
Normal file
70
util/win/registration_protocol_win.cc
Normal file
@ -0,0 +1,70 @@
|
||||
// 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 "util/win/registration_protocol_win.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
bool SendToCrashHandlerServer(const base::string16& pipe_name,
|
||||
const crashpad::ClientToServerMessage& message,
|
||||
crashpad::ServerToClientMessage* response) {
|
||||
int tries = 5;
|
||||
while (tries > 0) {
|
||||
HANDLE pipe = CreateFile(pipe_name.c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION,
|
||||
nullptr);
|
||||
if (pipe == INVALID_HANDLE_VALUE) {
|
||||
Sleep(10);
|
||||
--tries;
|
||||
continue;
|
||||
}
|
||||
DWORD mode = PIPE_READMODE_MESSAGE;
|
||||
if (!SetNamedPipeHandleState(pipe, &mode, nullptr, nullptr)) {
|
||||
PLOG(ERROR) << "SetNamedPipeHandleState";
|
||||
return false;
|
||||
}
|
||||
DWORD bytes_read = 0;
|
||||
BOOL result = TransactNamedPipe(
|
||||
pipe,
|
||||
// This is [in], but is incorrectly declared non-const.
|
||||
const_cast<crashpad::ClientToServerMessage*>(&message),
|
||||
sizeof(message),
|
||||
response,
|
||||
sizeof(*response),
|
||||
&bytes_read,
|
||||
nullptr);
|
||||
if (!result) {
|
||||
PLOG(ERROR) << "TransactNamedPipe";
|
||||
return false;
|
||||
}
|
||||
if (bytes_read != sizeof(*response)) {
|
||||
LOG(ERROR) << "TransactNamedPipe read incorrect number of bytes";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG(ERROR) << "failed to connect after retrying";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
106
util/win/registration_protocol_win.h
Normal file
106
util/win/registration_protocol_win.h
Normal file
@ -0,0 +1,106 @@
|
||||
// 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_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_
|
||||
#define CRASHPAD_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/strings/string16.h"
|
||||
#include "util/win/address_types.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
//! \brief Structure read out of the client process by the crash handler when an
|
||||
//! exception occurs.
|
||||
struct ExceptionInformation {
|
||||
//! \brief The address of an EXCEPTION_POINTERS structure in the client
|
||||
//! process that describes the exception.
|
||||
WinVMAddress exception_pointers;
|
||||
|
||||
//! \brief The thread on which the exception happened.
|
||||
DWORD thread_id;
|
||||
};
|
||||
|
||||
//! \brief A client registration request.
|
||||
struct RegistrationRequest {
|
||||
//! \brief The address, in the client process address space, of an
|
||||
//! ExceptionInformation structure.
|
||||
WinVMAddress exception_information;
|
||||
|
||||
//! \brief The PID of the client process.
|
||||
DWORD client_process_id;
|
||||
};
|
||||
|
||||
//! \brief A message only sent to the server by itself to trigger shutdown.
|
||||
struct ShutdownRequest {
|
||||
//! \brief A randomly generated token used to validate the the shutdown
|
||||
//! request was not sent from another process.
|
||||
uint64_t token;
|
||||
};
|
||||
|
||||
//! \brief The message passed from client to server by
|
||||
//! SendToCrashHandlerServer().
|
||||
struct ClientToServerMessage {
|
||||
//! \brief Indicates which field of the union is in use.
|
||||
enum Type : uint32_t {
|
||||
//! \brief For RegistrationRequest.
|
||||
kRegister,
|
||||
//! \brief For ShutdownRequest.
|
||||
kShutdown,
|
||||
} type;
|
||||
|
||||
union {
|
||||
RegistrationRequest registration;
|
||||
ShutdownRequest shutdown;
|
||||
};
|
||||
};
|
||||
|
||||
//! \brief A client registration response.
|
||||
//!
|
||||
//! See <a
|
||||
//! href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203">Interprocess
|
||||
//! Communication Between 32-bit and 64-bit Applications</a> for details on
|
||||
//! communicating handle values between processes of varying bitness.
|
||||
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 The response sent back to the client via SendToCrashHandlerServer().
|
||||
union ServerToClientMessage {
|
||||
RegistrationResponse registration;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
//! \brief Connect over the given \a pipe_name, passing \a message to the
|
||||
//! server, storing the server's reply into \a response.
|
||||
//!
|
||||
//! Typically clients will not use this directly, instead using
|
||||
//! CrashpadClient::SetHandler().
|
||||
//!
|
||||
//! \sa CrashpadClient::SetHandler()
|
||||
bool SendToCrashHandlerServer(const base::string16& pipe_name,
|
||||
const ClientToServerMessage& message,
|
||||
ServerToClientMessage* response);
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_
|
Loading…
x
Reference in New Issue
Block a user