win: Start crashpad_handler by inheriting connection data to it

Previously, StartHandler() launched the handler process, then connected
over a pipe to register for crash handling. Instead, the initial client
can create and inherit handles to the handler and pass those handle
values and other data (addresses, etc.) on the command line.

This should improve startup time as there's no need to synchronize with
the process at startup, and allows avoiding a call to CreateProcess()
directly in StartHandler(), which is important for registration for
crash reporting from DllMain().

Incidentally adds new utility functions for string/number conversion and
string splitting.

Note: API change; UseHandler() is removed for all platforms.

BUG=chromium:567850,chromium:656800

Change-Id: I1602724183cb107f805f109674c53e95841b24fd
Reviewed-on: https://chromium-review.googlesource.com/400015
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Scott Graham 2016-10-21 13:08:18 -07:00
parent 1e6dbcb300
commit 2d87606bb5
32 changed files with 926 additions and 397 deletions

View File

@ -29,6 +29,7 @@
#include "base/mac/scoped_mach_port.h"
#elif defined(OS_WIN)
#include <windows.h>
#include "util/win/scoped_handle.h"
#endif
namespace crashpad {
@ -43,14 +44,32 @@ class CrashpadClient {
//! \brief Starts a Crashpad handler process, performing any necessary
//! handshake to configure it.
//!
//! This method does not actually direct any crashes to the Crashpad handler,
//! because there are alternative ways to use an existing Crashpad handler. To
//! begin directing crashes to the handler started by this method, call
//! UseHandler() after this method returns successfully.
//! This method directs crashes to the Crashpad handler. On Mac OS X, this
//! is applicable to this process and all child processes. On Windows, child
//! processes must also register by using SetHandlerIPCPipe().
//!
//! On Mac OS X, this method starts a Crashpad handler and obtains a Mach
//! send right corresponding to a receive right held by the handler process.
//! The handler process runs an exception server on this port.
//! On Mac OS X, this method starts a Crashpad handler and obtains a Mach send
//! right corresponding to a receive right held by the handler process. The
//! handler process runs an exception server on this port. This method sets
//! the tasks exception port for `EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD`
//! exceptions to the Mach send right obtained. The handler will be installed
//! with behavior `EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES` and thread
//! state flavor `MACHINE_THREAD_STATE`. Exception ports are inherited, so a
//! Crashpad handler started here will remain the handler for any child
//! processes created after StartHandler() is called. Child processes do not
//! need to call StartHandler() or be aware of Crashpad in any way. The
//! Crashpad handler will receive crashes from child processes that have
//! inherited it as their exception handler even after the process that called
//! StartHandler() exits.
//!
//! On Windows, if \a asynchronous_start is `true`, this function will not
//! directly call `CreateProcess()`, making it suitable for use in a
//! `DllMain()`. In that case, the handler is started from a background
//! thread, deferring the handler's startup. Nevertheless, regardless of the
//! value of \a asynchronous_start, after calling this method, the global
//! unhandled exception filter is set up, and all crashes will be handled by
//! Crashpad. Optionally, use WaitForHandlerStart() to join with the
//! background thread and retrieve the status of handler startup.
//!
//! \param[in] handler The path to a Crashpad handler executable.
//! \param[in] database The path to a Crashpad database. The handler will be
@ -71,6 +90,10 @@ class CrashpadClient {
//! dies, if this behavior is supported. This option is not available on
//! all platforms, and does not function on all OS versions. If it is
//! not supported, it will be ignored.
//! \param[out] asynchronous_start If `true`, the handler will be started from
//! a background thread. Optionally, WaitForHandlerStart() can be used at
//! a suitable time to retreive the result of background startup. This
//! option is only used on Windows.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool StartHandler(const base::FilePath& handler,
@ -79,19 +102,18 @@ class CrashpadClient {
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable);
bool restartable,
bool asynchronous_start);
#if defined(OS_MACOSX) || DOXYGEN
//! \brief Sets the process crash handler to a Mach service registered with
//! the bootstrap server.
//!
//! This method does not actually direct any crashes to the Crashpad handler,
//! because there are alternative ways to start or use an existing Crashpad
//! handler. To begin directing crashes to the handler set by this method,
//! call UseHandler() after this method returns successfully.
//!
//! This method is only defined on OS X.
//!
//! See StartHandler() for more detail on how the port and handler are
//! configured.
//!
//! \param[in] service_name The service name of a Crashpad exception handler
//! service previously registered with the bootstrap server.
//!
@ -100,27 +122,30 @@ class CrashpadClient {
//! \brief Sets the process crash handler to a Mach port.
//!
//! This method does not actually direct any crashes to the Crashpad handler,
//! because there are alternative ways to start or use an existing Crashpad
//! handler. To begin directing crashes to the handler set by this method,
//! call UseHandler() after this method.
//!
//! This method is only defined on OS X.
//!
//! See StartHandler() for more detail on how the port and handler are
//! configured.
//!
//! \param[in] exception_port An `exception_port_t` corresponding to a
//! Crashpad exception handler service.
void SetHandlerMachPort(base::mac::ScopedMachSendRight exception_port);
//!
//! \return `true` on success, `false` on failure with a message logged.
bool SetHandlerMachPort(base::mac::ScopedMachSendRight exception_port);
#endif
#if defined(OS_WIN) || DOXYGEN
//! \brief Sets the IPC pipe of a presumably-running Crashpad handler process
//! which was started with StartHandler() or by other compatible means
//! and does an IPC message exchange to register this process with the
//! handler. However, just like StartHandler(), crashes are not serviced
//! until UseHandler() is called.
//! handler. Crashes will be serviced once this method returns.
//!
//! This method is only defined on Windows.
//!
//! This method sets the unhandled exception handler to a local
//! function that when reached will "signal and wait" for the crash handler
//! process to create the dump.
//!
//! \param[in] ipc_pipe The full name of the crash handler IPC pipe. This is
//! a string of the form `&quot;\\.\pipe\NAME&quot;`.
//!
@ -142,6 +167,16 @@ class CrashpadClient {
//! `&quot;\\.\pipe\NAME&quot;`.
std::wstring GetHandlerIPCPipe() const;
//! \brief When `asynchronous_start` is used with StartHandler(), this method
//! can be used to block until the handler launch has been completed to
//! retrieve status information.
//!
//! This method should not be used unless `asynchronous_start` was `true`.
//!
//! \return `true` if the hander startup succeeded, `false` otherwise, and an
//! error message will have been logged.
bool WaitForHandlerStart();
//! \brief Requests that the handler capture a dump even though there hasn't
//! been a crash.
//!
@ -153,7 +188,7 @@ class CrashpadClient {
//! exception_pointers to get the `EXCEPTION_RECORD` and `CONTEXT`.
//!
//! This function is not necessary in general usage as an unhandled exception
//! filter is installed by UseHandler().
//! filter is installed by StartHandler() or SetHandlerIPCPipe().
//!
//! \param[in] exception_pointers An `EXCEPTION_POINTERS`, as would generally
//! passed to an unhandled exception filter.
@ -195,44 +230,21 @@ class CrashpadClient {
};
#endif
//! \brief Configures the process to direct its crashes to a Crashpad handler.
//!
//! The Crashpad handler must previously have been started by StartHandler()
//! or configured by SetHandlerMachService(), SetHandlerMachPort(), or
//! SetHandlerIPCPipe().
//!
//! On Mac OS X, this method sets the tasks exception port for `EXC_CRASH`,
//! `EXC_RESOURCE`, and `EXC_GUARD` exceptions to the Mach send right obtained
//! by StartHandler(). The handler will be installed with behavior
//! `EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES` and thread state flavor
//! `MACHINE_THREAD_STATE`. Exception ports are inherited, so a Crashpad
//! handler chosen by UseHandler() will remain the handler for any child
//! processes created after UseHandler() is called. Child processes do not
//! need to call StartHandler() or UseHandler() or be aware of Crashpad in any
//! way. The Crashpad handler will receive crashes from child processes that
//! have inherited it as their exception handler even after the process that
//! called StartHandler() exits.
//!
//! On Windows, this method sets the unhandled exception handler to a local
//! function that when reached will "signal and wait" for the crash handler
//! process to create the dump.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool UseHandler();
#if defined(OS_MACOSX) || DOXYGEN
//! \brief Configures the process to direct its crashes to the default handler
//! for the operating system.
//!
//! On OS X, this sets the tasks exception port as in UseHandler(), but the
//! exception handler used is obtained from SystemCrashReporterHandler(). If
//! the systems crash reporter handler cannot be determined or set, the
//! tasks exception ports for crash-type exceptions are cleared.
//! On OS X, this sets the tasks exception port as in SetHandlerMachPort(),
//! but the exception handler used is obtained from
//! SystemCrashReporterHandler(). If the systems crash reporter handler
//! cannot be determined or set, the tasks exception ports for crash-type
//! exceptions are cleared.
//!
//! Use of this function is strongly discouraged.
//!
//! \warning After a call to this function, Crashpad will no longer monitor
//! the process for crashes until a subsequent call to UseHandler().
//! the process for crashes until a subsequent call to
//! SetHandlerMachPort().
//!
//! \note This is provided as a static function to allow it to be used in
//! situations where a CrashpadClient object is not otherwise available.
@ -242,10 +254,9 @@ class CrashpadClient {
#endif
private:
#if defined(OS_MACOSX)
base::mac::ScopedMachSendRight exception_port_;
#elif defined(OS_WIN)
#if defined(OS_WIN)
std::wstring ipc_pipe_;
ScopedKernelHANDLE handler_start_thread_;
#endif
DISALLOW_COPY_AND_ASSIGN(CrashpadClient);

View File

@ -523,8 +523,7 @@ class HandlerStarter final : public NotifyServer::DefaultInterface {
} // namespace
CrashpadClient::CrashpadClient()
: exception_port_() {
CrashpadClient::CrashpadClient() {
}
CrashpadClient::~CrashpadClient() {
@ -537,9 +536,8 @@ bool CrashpadClient::StartHandler(
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable) {
DCHECK(!exception_port_.is_valid());
bool restartable,
bool asynchronous_start) {
// The “restartable” behavior can only be selected on OS X 10.10 and later. In
// previous OS versions, if the initial client were to crash while attempting
// to restart the handler, it would become an unkillable process.
@ -569,16 +567,10 @@ bool CrashpadClient::SetHandlerMachService(const std::string& service_name) {
return true;
}
void CrashpadClient::SetHandlerMachPort(
bool CrashpadClient::SetHandlerMachPort(
base::mac::ScopedMachSendRight exception_port) {
DCHECK(exception_port.is_valid());
exception_port_ = std::move(exception_port);
}
bool CrashpadClient::UseHandler() {
DCHECK(exception_port_.is_valid());
return SetCrashExceptionPorts(exception_port_.get());
return SetCrashExceptionPorts(exception_port.get());
}
// static

View File

@ -28,18 +28,22 @@
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "util/file/file_io.h"
#include "util/misc/random_string.h"
#include "util/win/address_types.h"
#include "util/win/command_line.h"
#include "util/win/critical_section_with_debug_info.h"
#include "util/win/get_function.h"
#include "util/win/handle.h"
#include "util/win/initial_client_data.h"
#include "util/win/nt_internals.h"
#include "util/win/ntstatus_logging.h"
#include "util/win/process_info.h"
#include "util/win/registration_protocol_win.h"
#include "util/win/scoped_handle.h"
#include "util/win/scoped_process_suspend.h"
#include "util/win/termination_codes.h"
#include "util/win/xp_compat.h"
namespace crashpad {
namespace {
@ -48,7 +52,7 @@ namespace {
HANDLE g_signal_exception = INVALID_HANDLE_VALUE;
// Where we store the exception information that the crash handler reads.
crashpad::ExceptionInformation g_crash_exception_information;
ExceptionInformation g_crash_exception_information;
// These handles are never closed. g_signal_non_crash_dump is used to signal to
// the server to take a dump (not due to an exception), and the server will
@ -61,7 +65,7 @@ base::Lock* g_non_crash_dump_lock;
// Where we store a pointer to the context information when taking a non-crash
// dump.
crashpad::ExceptionInformation g_non_crash_exception_information;
ExceptionInformation g_non_crash_exception_information;
// A CRITICAL_SECTION initialized with
// RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO to force it to be allocated with a
@ -94,7 +98,7 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
// signal the crash handler.
g_crash_exception_information.thread_id = GetCurrentThreadId();
g_crash_exception_information.exception_pointers =
reinterpret_cast<crashpad::WinVMAddress>(exception_pointers);
reinterpret_cast<WinVMAddress>(exception_pointers);
// Now signal the crash server, which will take a dump and then terminate us
// when it's complete.
@ -110,7 +114,7 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
LOG(ERROR) << "crash server did not respond, self-terminating";
TerminateProcess(GetCurrentProcess(), crashpad::kTerminationCodeCrashNoDump);
TerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump);
return EXCEPTION_CONTINUE_SEARCH;
}
@ -121,9 +125,7 @@ std::wstring FormatArgumentString(const std::string& name,
}
struct ScopedProcThreadAttributeListTraits {
static PPROC_THREAD_ATTRIBUTE_LIST InvalidValue() {
return nullptr;
}
static PPROC_THREAD_ATTRIBUTE_LIST InvalidValue() { return nullptr; }
static void Free(PPROC_THREAD_ATTRIBUTE_LIST proc_thread_attribute_list) {
// This is able to use GET_FUNCTION_REQUIRED() instead of GET_FUNCTION()
@ -186,74 +188,113 @@ void AddUint64(std::vector<unsigned char>* data_vector, uint64_t data) {
static_cast<uint32_t>((data & 0xffffffff00000000ULL) >> 32));
}
} // namespace
//! \brief Creates a randomized pipe name to listen for client registrations
//! on and returns its name.
//!
//! \param[out] pipe_name The pipe name that will be listened on.
//! \param[out] pipe_handle The first pipe instance corresponding for the pipe.
void CreatePipe(std::wstring* pipe_name, ScopedFileHANDLE* pipe_instance) {
int tries = 5;
std::string pipe_name_base =
base::StringPrintf("\\\\.\\pipe\\crashpad_%d_", GetCurrentProcessId());
do {
*pipe_name = base::UTF8ToUTF16(pipe_name_base + RandomString());
namespace crashpad {
pipe_instance->reset(CreateNamedPipeInstance(*pipe_name, true));
CrashpadClient::CrashpadClient()
: ipc_pipe_() {
// CreateNamedPipe() is documented as setting the error to
// ERROR_ACCESS_DENIED if FILE_FLAG_FIRST_PIPE_INSTANCE is specified and the
// pipe name is already in use. However it may set the error to other codes
// such as ERROR_PIPE_BUSY (if the pipe already exists and has reached its
// maximum instance count) or ERROR_INVALID_PARAMETER (if the pipe already
// exists and its attributes differ from those specified to
// CreateNamedPipe()). Some of these errors may be ambiguous: for example,
// ERROR_INVALID_PARAMETER may also occur if CreateNamedPipe() is called
// incorrectly even in the absence of an existing pipe by the same name.
// Rather than chasing down all of the possible errors that might indicate
// that a pipe name is already in use, retry up to a few times on any error.
} while (!pipe_instance->is_valid() && --tries);
PCHECK(pipe_instance->is_valid()) << "CreateNamedPipe";
}
CrashpadClient::~CrashpadClient() {
}
struct BackgroundHandlerStartThreadData {
BackgroundHandlerStartThreadData(
const base::FilePath& handler,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
ScopedFileHANDLE ipc_pipe_handle)
: handler(handler),
database(database),
metrics_dir(metrics_dir),
url(url),
annotations(annotations),
arguments(arguments),
ipc_pipe_handle(std::move(ipc_pipe_handle)) {}
bool CrashpadClient::StartHandler(
const base::FilePath& handler,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable) {
DCHECK(ipc_pipe_.empty());
HANDLE pipe_read;
HANDLE pipe_write;
SECURITY_ATTRIBUTES security_attributes = {};
security_attributes.nLength = sizeof(security_attributes);
security_attributes.bInheritHandle = TRUE;
if (!CreatePipe(&pipe_read, &pipe_write, &security_attributes, 0)) {
PLOG(ERROR) << "CreatePipe";
return false;
}
ScopedFileHandle pipe_read_owner(pipe_read);
ScopedFileHandle pipe_write_owner(pipe_write);
// The new process only needs the write side of the pipe.
BOOL rv = SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0);
PLOG_IF(WARNING, !rv) << "SetHandleInformation";
base::FilePath handler;
base::FilePath database;
base::FilePath metrics_dir;
std::string url;
std::map<std::string, std::string> annotations;
std::vector<std::string> arguments;
ScopedFileHANDLE ipc_pipe_handle;
};
bool StartHandlerProcess(
std::unique_ptr<BackgroundHandlerStartThreadData> data) {
std::wstring command_line;
AppendCommandLineArgument(handler.value(), &command_line);
for (const std::string& argument : arguments) {
AppendCommandLineArgument(data->handler.value(), &command_line);
for (const std::string& argument : data->arguments) {
AppendCommandLineArgument(base::UTF8ToUTF16(argument), &command_line);
}
if (!database.value().empty()) {
AppendCommandLineArgument(FormatArgumentString("database",
database.value()),
&command_line);
}
if (!metrics_dir.value().empty()) {
if (!data->database.value().empty()) {
AppendCommandLineArgument(
FormatArgumentString("metrics-dir", metrics_dir.value()),
FormatArgumentString("database", data->database.value()),
&command_line);
}
if (!url.empty()) {
AppendCommandLineArgument(FormatArgumentString("url",
base::UTF8ToUTF16(url)),
&command_line);
if (!data->metrics_dir.value().empty()) {
AppendCommandLineArgument(
FormatArgumentString("metrics-dir", data->metrics_dir.value()),
&command_line);
}
for (const auto& kv : annotations) {
if (!data->url.empty()) {
AppendCommandLineArgument(
FormatArgumentString("url", base::UTF8ToUTF16(data->url)),
&command_line);
}
for (const auto& kv : data->annotations) {
AppendCommandLineArgument(
FormatArgumentString("annotation",
base::UTF8ToUTF16(kv.first + '=' + kv.second)),
&command_line);
}
ScopedKernelHANDLE this_process(
OpenProcess(kXPProcessAllAccess, true, GetCurrentProcessId()));
if (!this_process.is_valid()) {
PLOG(ERROR) << "OpenProcess";
return false;
}
InitialClientData initial_client_data(
g_signal_exception,
g_signal_non_crash_dump,
g_non_crash_dump_done,
data->ipc_pipe_handle.get(),
this_process.get(),
reinterpret_cast<WinVMAddress>(&g_crash_exception_information),
reinterpret_cast<WinVMAddress>(&g_non_crash_exception_information),
reinterpret_cast<WinVMAddress>(&g_critical_section_with_debug_info));
AppendCommandLineArgument(
base::UTF8ToUTF16(base::StringPrintf("--handshake-handle=0x%x",
HandleToInt(pipe_write))),
base::UTF8ToUTF16(std::string("--initial-client-data=") +
initial_client_data.StringRepresentation()),
&command_line);
BOOL rv;
DWORD creation_flags;
STARTUPINFOEX startup_info = {};
startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
@ -304,8 +345,12 @@ bool CrashpadClient::StartHandler(
}
proc_thread_attribute_list_owner.reset(startup_info.lpAttributeList);
handle_list.reserve(4);
handle_list.push_back(pipe_write);
handle_list.reserve(8);
handle_list.push_back(g_signal_exception);
handle_list.push_back(g_signal_non_crash_dump);
handle_list.push_back(g_non_crash_dump_done);
handle_list.push_back(data->ipc_pipe_handle.get());
handle_list.push_back(this_process.get());
AddHandleToListIfValidAndInheritable(&handle_list,
startup_info.StartupInfo.hStdInput);
AddHandleToListIfValidAndInheritable(&handle_list,
@ -327,7 +372,7 @@ bool CrashpadClient::StartHandler(
}
PROCESS_INFORMATION process_info;
rv = CreateProcess(handler.value().c_str(),
rv = CreateProcess(data->handler.value().c_str(),
&command_line[0],
nullptr,
nullptr,
@ -348,38 +393,109 @@ bool CrashpadClient::StartHandler(
rv = CloseHandle(process_info.hProcess);
PLOG_IF(WARNING, !rv) << "CloseHandle process";
pipe_write_owner.reset();
uint32_t ipc_pipe_length;
if (!LoggingReadFile(pipe_read, &ipc_pipe_length, sizeof(ipc_pipe_length))) {
return false;
}
ipc_pipe_.resize(ipc_pipe_length);
if (ipc_pipe_length &&
!LoggingReadFile(
pipe_read, &ipc_pipe_[0], ipc_pipe_length * sizeof(ipc_pipe_[0]))) {
return false;
}
return true;
}
DWORD WINAPI BackgroundHandlerStartThreadProc(void* data) {
std::unique_ptr<BackgroundHandlerStartThreadData> data_as_ptr(
reinterpret_cast<BackgroundHandlerStartThreadData*>(data));
return StartHandlerProcess(std::move(data_as_ptr)) ? 0 : 1;
}
void CommonInProcessInitialization() {
// We create this dummy CRITICAL_SECTION with the
// RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO flag set to have an entry point
// into the doubly-linked list of RTL_CRITICAL_SECTION_DEBUG objects. This
// allows us to walk the list at crash time to gather data for !locks. A
// debugger would instead inspect ntdll!RtlCriticalSectionList to get the head
// of the list. But that is not an exported symbol, so on an arbitrary client
// machine, we don't have a way of getting that pointer.
InitializeCriticalSectionWithDebugInfoIfPossible(
&g_critical_section_with_debug_info);
g_non_crash_dump_lock = new base::Lock();
// In theory we could store the previous handler but it is not clear what
// use we have for it.
SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
}
} // namespace
CrashpadClient::CrashpadClient() : ipc_pipe_(), handler_start_thread_() {}
CrashpadClient::~CrashpadClient() {}
bool CrashpadClient::StartHandler(
const base::FilePath& handler,
const base::FilePath& database,
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable,
bool asynchronous_start) {
DCHECK(ipc_pipe_.empty());
// Both the pipe and the signalling events have to be created on the main
// thread (not the spawning thread) so that they're valid after we return from
// this function.
ScopedFileHANDLE ipc_pipe_handle;
CreatePipe(&ipc_pipe_, &ipc_pipe_handle);
SECURITY_ATTRIBUTES security_attributes = {0};
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle = true;
g_signal_exception =
CreateEvent(&security_attributes, false /* auto reset */, false, nullptr);
g_signal_non_crash_dump =
CreateEvent(&security_attributes, false /* auto reset */, false, nullptr);
g_non_crash_dump_done =
CreateEvent(&security_attributes, false /* auto reset */, false, nullptr);
CommonInProcessInitialization();
auto data = new BackgroundHandlerStartThreadData(handler,
database,
metrics_dir,
url,
annotations,
arguments,
std::move(ipc_pipe_handle));
if (asynchronous_start) {
// It is important that the current thread not be synchronized with the
// thread that is created here. StartHandler() needs to be callable inside a
// DllMain(). In that case, the background thread will not start until the
// current DllMain() completes, which would cause deadlock if it was waited
// upon.
handler_start_thread_.reset(CreateThread(nullptr,
0,
&BackgroundHandlerStartThreadProc,
reinterpret_cast<void*>(data),
0,
nullptr));
if (!handler_start_thread_.is_valid()) {
PLOG(ERROR) << "CreateThread";
return false;
}
// In asynchronous mode, we can't report on the overall success or failure
// of initialization at this point.
return true;
} else {
return StartHandlerProcess(
std::unique_ptr<BackgroundHandlerStartThreadData>(data));
}
}
bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) {
DCHECK(ipc_pipe_.empty());
DCHECK(!ipc_pipe.empty());
ipc_pipe_ = ipc_pipe;
return true;
}
std::wstring CrashpadClient::GetHandlerIPCPipe() const {
DCHECK(!ipc_pipe_.empty());
return ipc_pipe_;
}
bool CrashpadClient::UseHandler() {
DCHECK(!ipc_pipe_.empty());
DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE);
DCHECK_EQ(g_signal_non_crash_dump, INVALID_HANDLE_VALUE);
@ -397,18 +513,10 @@ bool CrashpadClient::UseHandler() {
message.registration.non_crash_exception_information =
reinterpret_cast<WinVMAddress>(&g_non_crash_exception_information);
// We create this dummy CRITICAL_SECTION with the
// RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO flag set to have an entry point
// into the doubly-linked list of RTL_CRITICAL_SECTION_DEBUG objects. This
// allows us to walk the list at crash time to gather data for !locks. A
// debugger would instead inspect ntdll!RtlCriticalSectionList to get the head
// of the list. But that is not an exported symbol, so on an arbitrary client
// machine, we don't have a way of getting that pointer.
if (InitializeCriticalSectionWithDebugInfoIfPossible(
&g_critical_section_with_debug_info)) {
message.registration.critical_section_address =
reinterpret_cast<WinVMAddress>(&g_critical_section_with_debug_info);
}
CommonInProcessInitialization();
message.registration.critical_section_address =
reinterpret_cast<WinVMAddress>(&g_critical_section_with_debug_info);
ServerToClientMessage response = {};
@ -424,14 +532,32 @@ bool CrashpadClient::UseHandler() {
g_non_crash_dump_done =
IntToHandle(response.registration.non_crash_dump_completed_event);
g_non_crash_dump_lock = new base::Lock();
// In theory we could store the previous handler but it is not clear what
// use we have for it.
SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
return true;
}
std::wstring CrashpadClient::GetHandlerIPCPipe() const {
DCHECK(!ipc_pipe_.empty());
return ipc_pipe_;
}
bool CrashpadClient::WaitForHandlerStart() {
DCHECK(handler_start_thread_.is_valid());
if (WaitForSingleObject(handler_start_thread_.get(), INFINITE) !=
WAIT_OBJECT_0) {
PLOG(ERROR) << "WaitForSingleObject";
return false;
}
DWORD exit_code;
if (!GetExitCodeThread(handler_start_thread_.get(), &exit_code)) {
PLOG(ERROR) << "GetExitCodeThread";
return false;
}
handler_start_thread_.reset();
return exit_code == 0;
}
// static
void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) {
if (g_signal_non_crash_dump == INVALID_HANDLE_VALUE ||
@ -475,7 +601,7 @@ void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) {
g_non_crash_exception_information.thread_id = GetCurrentThreadId();
g_non_crash_exception_information.exception_pointers =
reinterpret_cast<crashpad::WinVMAddress>(&exception_pointers);
reinterpret_cast<WinVMAddress>(&exception_pointers);
bool set_event_result = !!SetEvent(g_signal_non_crash_dump);
PLOG_IF(ERROR, !set_event_result) << "SetEvent";

View File

@ -35,8 +35,9 @@ void StartAndUseHandler() {
"",
std::map<std::string, std::string>(),
std::vector<std::string>(),
false));
EXPECT_TRUE(client.UseHandler());
true,
true));
ASSERT_TRUE(client.WaitForHandlerStart());
}
class StartWithInvalidHandles final : public WinMultiprocess {

View File

@ -51,14 +51,17 @@ when it determines that the server should exit.
On Windows, clients register with this server by communicating with it via the
named pipe identified by the *--pipe-name* argument. Alternatively, the server
can create a new pipe with a random name and inform a client of this name via
the *--handshake-handle* mechanism; clients may then register by communicating
with it via that named pipe. During registration, a client provides the server
with an OS event object that it will signal should it crash. The server obtains
the clients process handle and waits on the crash event object for a crash, as
well as the clients process handle for the client to exit cleanly without
crashing. When a server started via the *--handshake-handle* mechanism loses all
of its clients, it exits after allowing any upload in progress to complete.
can inherit an already-created pipe from a parent process by using the
*--initial-client-data* mechanism. That argument also takes all of the arguments
that would normally be passed in a registration message, and so constitutes
registration of the first client. Subsequent clients may then register by
communicating with the server via the pipe. During registration, a client
provides the server with an OS event object that it will signal should it crash.
The server obtains the clients process handle and waits on the crash event
object for a crash, as well as the clients process handle for the client to
exit cleanly without crashing. When a server started via the
*--initial-client-data* mechanism loses all of its clients, it exits after
allowing any upload in progress to complete.
On Windows, this executable is built by default as a Windows GUI app, so no
console will appear in normal usage. This is the version that will typically be
@ -107,10 +110,11 @@ Perform the handshake with the initial client on the file descriptor at 'FD'.
Either this option or *--mach-service*, but not both, is required. This option
is only valid on OS X.
*--handshake-handle*='HANDLE'::
Perform the handshake with the initial client on the HANDLE at 'HANDLE'. Either
this option or *--pipe-name*, but not both, is required. This option is only
valid on Windows.
*--initial-client-data*='HANDLE_request_crash_dump','HANDLE_request_non_crash_dump','HANDLE_non_crash_dump_completed','HANDLE_first_pipe_instance','HANDLE_client_process','Address_crash_exception_information','Address_non_crash_exception_information','Address_debug_critical_section'::
Register the initial client using the inherited handles and data provided. For
more information on the arguments, see the implementations of +CrashpadClient+
and +ExceptionHandlerServer+. Either this option or *--pipe-name*, but not both,
is required. This option is only valid on Windows.
+
When this option is present, the server creates a new named pipe at a random
name and informs its client of the name. The server waits for at least one
@ -134,8 +138,8 @@ attempt to upload all captured reports.
*--pipe-name*='PIPE'::
Listen on the given pipe name for connections from clients. 'PIPE' must be of
the form +\\.\pipe\<somename>+. Either this option or *--handshake-handle*, but
not both, is required. This option is only valid on Windows.
the form +\\.\pipe\<somename>+. Either this option or *--initial-client-data*,
but not both, is required. This option is only valid on Windows.
+
When this option is present, the server creates a named pipe at 'PIPE', a name
known to both the server and its clients. The server continues running even

View File

@ -59,6 +59,7 @@
#include "handler/win/crash_report_exception_handler.h"
#include "util/win/exception_handler_server.h"
#include "util/win/handle.h"
#include "util/win/initial_client_data.h"
#endif // OS_MACOSX
namespace crashpad {
@ -76,8 +77,15 @@ void Usage(const base::FilePath& me) {
" --handshake-fd=FD establish communication with the client over FD\n"
" --mach-service=SERVICE register SERVICE with the bootstrap server\n"
#elif defined(OS_WIN)
" --handshake-handle=HANDLE\n"
" create a new pipe and send its name via HANDLE\n"
" --initial-client-data=HANDLE_request_crash_dump,\n"
" HANDLE_request_non_crash_dump,\n"
" HANDLE_non_crash_dump_completed,\n"
" HANDLE_pipe,\n"
" HANDLE_client_process,\n"
" Address_crash_exception_information,\n"
" Address_non_crash_exception_information,\n"
" Address_debug_critical_section\n"
" use precreated data to register initial client\n"
#endif // OS_MACOSX
" --metrics-dir=DIR store metrics files in DIR (only in Chromium)\n"
" --no-rate-limit don't rate limit crash uploads\n"
@ -125,7 +133,7 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
Metrics::HandlerCrashed(exception_pointers->ExceptionRecord->ExceptionCode);
return EXCEPTION_CONTINUE_SEARCH;
}
#endif
#endif // OS_WIN
} // namespace
@ -145,9 +153,12 @@ int HandlerMain(int argc, char* argv[]) {
kOptionDatabase,
#if defined(OS_MACOSX)
kOptionHandshakeFD,
#endif // OS_MACOSX
#if defined(OS_WIN)
kOptionInitialClientData,
#endif // OS_WIN
#if defined(OS_MACOSX)
kOptionMachService,
#elif defined(OS_WIN)
kOptionHandshakeHandle,
#endif // OS_MACOSX
kOptionMetrics,
kOptionNoRateLimit,
@ -173,41 +184,45 @@ int HandlerMain(int argc, char* argv[]) {
std::string mach_service;
bool reset_own_crash_exception_port_to_system_default;
#elif defined(OS_WIN)
HANDLE handshake_handle;
std::string pipe_name;
InitialClientData initial_client_data;
#endif // OS_MACOSX
bool rate_limit;
} options = {};
#if defined(OS_MACOSX)
options.handshake_fd = -1;
#elif defined(OS_WIN)
options.handshake_handle = INVALID_HANDLE_VALUE;
#endif
options.rate_limit = true;
const option long_options[] = {
{"annotation", required_argument, nullptr, kOptionAnnotation},
{"database", required_argument, nullptr, kOptionDatabase},
{"annotation", required_argument, nullptr, kOptionAnnotation},
{"database", required_argument, nullptr, kOptionDatabase},
#if defined(OS_MACOSX)
{"handshake-fd", required_argument, nullptr, kOptionHandshakeFD},
{"mach-service", required_argument, nullptr, kOptionMachService},
#elif defined(OS_WIN)
{"handshake-handle", required_argument, nullptr, kOptionHandshakeHandle},
{"handshake-fd", required_argument, nullptr, kOptionHandshakeFD},
#endif // OS_MACOSX
#if defined(OS_WIN)
{"initial-client-data",
required_argument,
nullptr,
kOptionInitialClientData},
#endif // OS_MACOSX
{"metrics-dir", required_argument, nullptr, kOptionMetrics},
{"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit},
#if defined(OS_MACOSX)
{"reset-own-crash-exception-port-to-system-default",
no_argument,
nullptr,
kOptionResetOwnCrashExceptionPortToSystemDefault},
#elif defined(OS_WIN)
{"pipe-name", required_argument, nullptr, kOptionPipeName},
{"mach-service", required_argument, nullptr, kOptionMachService},
#endif // OS_MACOSX
{"url", required_argument, nullptr, kOptionURL},
{"help", no_argument, nullptr, kOptionHelp},
{"version", no_argument, nullptr, kOptionVersion},
{nullptr, 0, nullptr, 0},
{"metrics-dir", required_argument, nullptr, kOptionMetrics},
{"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit},
#if defined(OS_MACOSX)
{"reset-own-crash-exception-port-to-system-default",
no_argument,
nullptr,
kOptionResetOwnCrashExceptionPortToSystemDefault},
#elif defined(OS_WIN)
{"pipe-name", required_argument, nullptr, kOptionPipeName},
#endif // OS_MACOSX
{"url", required_argument, nullptr, kOptionURL},
{"help", no_argument, nullptr, kOptionHelp},
{"version", no_argument, nullptr, kOptionVersion},
{nullptr, 0, nullptr, 0},
};
int opt;
@ -216,7 +231,7 @@ int HandlerMain(int argc, char* argv[]) {
case kOptionAnnotation: {
std::string key;
std::string value;
if (!SplitString(optarg, '=', &key, &value)) {
if (!SplitStringFirst(optarg, '=', &key, &value)) {
ToolSupport::UsageHint(me, "--annotation requires KEY=VALUE");
return EXIT_FAILURE;
}
@ -246,17 +261,12 @@ int HandlerMain(int argc, char* argv[]) {
break;
}
#elif defined(OS_WIN)
case kOptionHandshakeHandle: {
// Use unsigned int, because the handle was presented by the client in
// 0x%x format.
unsigned int handle_uint;
if (!StringToNumber(optarg, &handle_uint) ||
(options.handshake_handle = IntToHandle(handle_uint)) ==
INVALID_HANDLE_VALUE) {
ToolSupport::UsageHint(me, "--handshake-handle requires a HANDLE");
case kOptionInitialClientData: {
if (!options.initial_client_data.InitializeFromString(optarg)) {
ToolSupport::UsageHint(
me, "failed to parse --initial-client-data");
return EXIT_FAILURE;
}
break;
}
#endif // OS_MACOSX
case kOptionMetrics: {
@ -310,15 +320,14 @@ int HandlerMain(int argc, char* argv[]) {
return EXIT_FAILURE;
}
#elif defined(OS_WIN)
if (options.handshake_handle == INVALID_HANDLE_VALUE &&
options.pipe_name.empty()) {
ToolSupport::UsageHint(me, "--handshake-handle or --pipe-name is required");
if (!options.initial_client_data.IsValid() && options.pipe_name.empty()) {
ToolSupport::UsageHint(me,
"--initial-client-data or --pipe-name is required");
return EXIT_FAILURE;
}
if (options.handshake_handle != INVALID_HANDLE_VALUE &&
!options.pipe_name.empty()) {
if (options.initial_client_data.IsValid() && !options.pipe_name.empty()) {
ToolSupport::UsageHint(
me, "--handshake-handle and --pipe-name are incompatible");
me, "--initial-client-data and --pipe-name are incompatible");
return EXIT_FAILURE;
}
#endif // OS_MACOSX
@ -389,20 +398,6 @@ int HandlerMain(int argc, char* argv[]) {
if (!options.pipe_name.empty()) {
exception_handler_server.SetPipeName(base::UTF8ToUTF16(options.pipe_name));
} else if (options.handshake_handle != INVALID_HANDLE_VALUE) {
std::wstring pipe_name = exception_handler_server.CreatePipe();
uint32_t pipe_name_length = static_cast<uint32_t>(pipe_name.size());
if (!LoggingWriteFile(options.handshake_handle,
&pipe_name_length,
sizeof(pipe_name_length))) {
return EXIT_FAILURE;
}
if (!LoggingWriteFile(options.handshake_handle,
pipe_name.c_str(),
pipe_name.size() * sizeof(pipe_name[0]))) {
return EXIT_FAILURE;
}
}
#endif // OS_MACOSX
@ -440,6 +435,13 @@ int HandlerMain(int argc, char* argv[]) {
CrashReportExceptionHandler exception_handler(
database.get(), &upload_thread, &options.annotations);
#if defined(OS_WIN)
if (options.initial_client_data.IsValid()) {
exception_handler_server.InitializeWithInheritedDataForInitialClient(
options.initial_client_data, &exception_handler);
}
#endif // OS_WIN
exception_handler_server.Run(&exception_handler);
upload_thread.Stop();

View File

@ -81,11 +81,6 @@ int CrashOtherProgram(int argc, wchar_t* argv[]) {
return EXIT_FAILURE;
}
if (!client.UseHandler()) {
LOG(ERROR) << "UseHandler";
return EXIT_FAILURE;
}
// Launch another process that hangs.
base::FilePath test_executable = Paths::Executable();
std::wstring child_test_executable =

View File

@ -182,7 +182,8 @@ int CrashyMain(int argc, wchar_t* argv[]) {
std::string(),
std::map<std::string, std::string>(),
std::vector<std::string>(),
false)) {
false,
true)) {
LOG(ERROR) << "StartHandler";
return EXIT_FAILURE;
}
@ -192,11 +193,6 @@ int CrashyMain(int argc, wchar_t* argv[]) {
return EXIT_FAILURE;
}
if (!client.UseHandler()) {
LOG(ERROR) << "UseHandler";
return EXIT_FAILURE;
}
crashpad::SimpleAddressRangeBag* extra_ranges =
new crashpad::SimpleAddressRangeBag();
crashpad::CrashpadInfo::GetCrashpadInfo()->set_extra_memory_ranges(

View File

@ -39,10 +39,6 @@ int CrashyLoadZ7Main(int argc, wchar_t* argv[]) {
LOG(ERROR) << "SetHandler";
return EXIT_FAILURE;
}
if (!client.UseHandler()) {
LOG(ERROR) << "UseHandler";
return EXIT_FAILURE;
}
// The DLL has /Z7 symbols embedded in the binary (rather than in a .pdb).
// There's only an x86 version of this dll as newer x64 toolchains can't

View File

@ -56,11 +56,6 @@ int wmain(int argc, wchar_t* argv[]) {
return EXIT_FAILURE;
}
if (!client.UseHandler()) {
LOG(ERROR) << "UseHandler";
return EXIT_FAILURE;
}
// Make sure this module has a CrashpadInfo structure.
crashpad::CrashpadInfo* crashpad_info =
crashpad::CrashpadInfo::GetCrashpadInfo();

View File

@ -75,10 +75,6 @@ int SelfDestroyingMain(int argc, wchar_t* argv[]) {
LOG(ERROR) << "SetHandler";
return EXIT_FAILURE;
}
if (!client.UseHandler()) {
LOG(ERROR) << "UseHandler";
return EXIT_FAILURE;
}
if (!FreeOwnStackAndBreak())
return EXIT_FAILURE;

View File

@ -33,7 +33,6 @@ int wmain(int argc, wchar_t* argv[]) {
crashpad::CrashpadClient client;
CHECK(client.SetHandlerIPCPipe(argv[1]));
CHECK(client.UseHandler());
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
PCHECK(out != INVALID_HANDLE_VALUE) << "GetStdHandle";

View File

@ -33,7 +33,6 @@ int wmain(int argc, wchar_t* argv[]) {
crashpad::CrashpadClient client;
CHECK(client.SetHandlerIPCPipe(argv[1]));
CHECK(client.UseHandler());
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
PCHECK(out != INVALID_HANDLE_VALUE) << "GetStdHandle";

View File

@ -25,6 +25,7 @@ import time
g_temp_dirs = []
g_had_failures = False
def MakeTempDir():
@ -181,7 +182,8 @@ class CdbRun(object):
print >>sys.stderr, 'remaining output was:\n %s' % self.out
print >>sys.stderr, '-' * 80
sys.stderr.flush()
sys.exit(1)
global g_had_failures
g_had_failures = True
def RunTests(cdb_path,
@ -397,7 +399,7 @@ def main(args):
other_program_no_exception_path,
pipe_name)
return 0
return 1 if g_had_failures else 0
finally:
CleanUpTempDirs()

View File

@ -126,7 +126,8 @@ void TestCrashingChild(const base::string16& directory_modification) {
CrashingDelegate delegate(server_ready.get(), completed.get());
ExceptionHandlerServer exception_handler_server(true);
std::wstring pipe_name = exception_handler_server.CreatePipe();
std::wstring pipe_name(L"\\\\.\\pipe\\test_name");
exception_handler_server.SetPipeName(pipe_name);
RunServerThread server_thread(&exception_handler_server, &delegate);
server_thread.Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
@ -227,7 +228,8 @@ void TestDumpWithoutCrashingChild(
SimulateDelegate delegate(server_ready.get(), completed.get());
ExceptionHandlerServer exception_handler_server(true);
std::wstring pipe_name = exception_handler_server.CreatePipe();
std::wstring pipe_name(L"\\\\.\\pipe\\test_name");
exception_handler_server.SetPipeName(pipe_name);
RunServerThread server_thread(&exception_handler_server, &delegate);
server_thread.Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(

View File

@ -142,7 +142,7 @@ WinChildProcess::WinChildProcess() {
// the parent and so are open and have the same value as in the parent. The
// values are passed to the child on the command line.
std::string left, right;
CHECK(SplitString(switch_value, '|', &left, &right));
CHECK(SplitStringFirst(switch_value, '|', &left, &right));
// left and right were formatted as 0x%x, so they need to be converted as
// unsigned ints.

View File

@ -115,7 +115,7 @@ int RunWithCrashpadMain(int argc, char* argv[]) {
case kOptionAnnotation: {
std::string key;
std::string value;
if (!SplitString(optarg, '=', &key, &value)) {
if (!SplitStringFirst(optarg, '=', &key, &value)) {
ToolSupport::UsageHint(me, "--annotation requires KEY=VALUE");
return EXIT_FAILURE;
}
@ -168,14 +168,11 @@ int RunWithCrashpadMain(int argc, char* argv[]) {
options.url,
options.annotations,
options.arguments,
false,
false)) {
return kExitFailure;
}
if (!crashpad_client.UseHandler()) {
return kExitFailure;
}
// Using the remaining arguments, start a new program with the new exception
// port in effect.
execvp(argv[0], argv);

View File

@ -16,6 +16,7 @@
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
@ -108,6 +109,16 @@ struct StringToUnsignedIntTraits
}
};
struct StringToUnsignedInt64Traits
: public StringToUnsignedIntegerTraits<uint64_t, uint64_t> {
static LongType Convert(const char* str, char** end, int base) {
if (str[0] == '-') {
return 0;
}
return strtoull(str, end, base);
}
};
template <typename Traits>
bool StringToIntegerInternal(const base::StringPiece& string,
typename Traits::IntType* number) {
@ -153,4 +164,8 @@ bool StringToNumber(const base::StringPiece& string, unsigned int* number) {
return StringToIntegerInternal<StringToUnsignedIntTraits>(string, number);
}
bool StringToNumber(const base::StringPiece& string, uint64_t* number) {
return StringToIntegerInternal<StringToUnsignedInt64Traits>(string, number);
}
} // namespace crashpad

View File

@ -56,6 +56,7 @@ namespace crashpad {
//! where such prefix recognition is desirable.
bool StringToNumber(const base::StringPiece& string, int* number);
bool StringToNumber(const base::StringPiece& string, unsigned int* number);
bool StringToNumber(const base::StringPiece& string, uint64_t* number);
//! \}
} // namespace crashpad

View File

@ -18,10 +18,10 @@
namespace crashpad {
bool SplitString(const std::string& string,
char delimiter,
std::string* left,
std::string* right) {
bool SplitStringFirst(const std::string& string,
char delimiter,
std::string* left,
std::string* right) {
size_t delimiter_pos = string.find(delimiter);
if (delimiter_pos == 0 || delimiter_pos == std::string::npos) {
return false;
@ -32,4 +32,27 @@ bool SplitString(const std::string& string,
return true;
}
std::vector<std::string> SplitString(const std::string& str, char delimiter) {
std::vector<std::string> result;
if (str.empty())
return result;
size_t start = 0;
while (start != std::string::npos) {
size_t end = str.find_first_of(delimiter, start);
std::string part;
if (end == std::string::npos) {
part = str.substr(start);
start = std::string::npos;
} else {
part = str.substr(start, end - start);
start = end + 1;
}
result.push_back(part);
}
return result;
}
} // namespace crashpad

View File

@ -16,6 +16,7 @@
#define CRASHPAD_UTIL_STRING_SPLIT_STRING_H_
#include <string>
#include <vector>
namespace crashpad {
@ -31,10 +32,17 @@ namespace crashpad {
//! \return `true` if \a string was split successfully. `false` if \a string
//! did not contain a \delimiter character or began with a \delimiter
//! character.
bool SplitString(const std::string& string,
char delimiter,
std::string* left,
std::string* right);
bool SplitStringFirst(const std::string& string,
char delimiter,
std::string* left,
std::string* right);
//! \brief Splits a string into multiple parts on the given delimiter.
//!
//! \param[in] string The string to split.
//! \param[in] delimiter The delimiter to split at.
//! \return The individual parts of the string.
std::vector<std::string> SplitString(const std::string& str, char delimiter);
} // namespace crashpad

View File

@ -20,42 +20,74 @@ namespace crashpad {
namespace test {
namespace {
TEST(SplitString, SplitString) {
TEST(SplitString, SplitStringFirst) {
std::string left;
std::string right;
EXPECT_FALSE(SplitString("", '=', &left, &right));
EXPECT_FALSE(SplitString("no equals", '=', &left, &right));
EXPECT_FALSE(SplitString("=", '=', &left, &right));
EXPECT_FALSE(SplitString("=beginequals", '=', &left, &right));
EXPECT_FALSE(SplitStringFirst("", '=', &left, &right));
EXPECT_FALSE(SplitStringFirst("no equals", '=', &left, &right));
EXPECT_FALSE(SplitStringFirst("=", '=', &left, &right));
EXPECT_FALSE(SplitStringFirst("=beginequals", '=', &left, &right));
ASSERT_TRUE(SplitString("a=b", '=', &left, &right));
ASSERT_TRUE(SplitStringFirst("a=b", '=', &left, &right));
EXPECT_EQ("a", left);
EXPECT_EQ("b", right);
ASSERT_TRUE(SplitString("EndsEquals=", '=', &left, &right));
ASSERT_TRUE(SplitStringFirst("EndsEquals=", '=', &left, &right));
EXPECT_EQ("EndsEquals", left);
EXPECT_TRUE(right.empty());
ASSERT_TRUE(SplitString("key=VALUE", '=', &left, &right));
ASSERT_TRUE(SplitStringFirst("key=VALUE", '=', &left, &right));
EXPECT_EQ("key", left);
EXPECT_EQ("VALUE", right);
EXPECT_FALSE(SplitString("a=b", '|', &left, &right));
EXPECT_FALSE(SplitStringFirst("a=b", '|', &left, &right));
ASSERT_TRUE(SplitString("ls | less", '|', &left, &right));
ASSERT_TRUE(SplitStringFirst("ls | less", '|', &left, &right));
EXPECT_EQ("ls ", left);
EXPECT_EQ(" less", right);
ASSERT_TRUE(SplitString("when in", ' ', &left, &right));
ASSERT_TRUE(SplitStringFirst("when in", ' ', &left, &right));
EXPECT_EQ("when", left);
EXPECT_EQ("in", right);
ASSERT_TRUE(SplitString("zoo", 'o', &left, &right));
ASSERT_TRUE(SplitStringFirst("zoo", 'o', &left, &right));
EXPECT_EQ("z", left);
EXPECT_EQ("o", right);
ASSERT_FALSE(SplitString("ooze", 'o', &left, &right));
ASSERT_FALSE(SplitStringFirst("ooze", 'o', &left, &right));
}
TEST(SplitString, SplitString) {
std::vector<std::string> parts;
parts = SplitString("", '.');
EXPECT_EQ(0u, parts.size());
parts = SplitString(".", '.');
ASSERT_EQ(2u, parts.size());
EXPECT_EQ("", parts[0]);
EXPECT_EQ("", parts[1]);
parts = SplitString("a,b", ',');
ASSERT_EQ(2u, parts.size());
EXPECT_EQ("a", parts[0]);
EXPECT_EQ("b", parts[1]);
parts = SplitString("zoo", 'o');
ASSERT_EQ(3u, parts.size());
EXPECT_EQ("z", parts[0]);
EXPECT_EQ("", parts[1]);
EXPECT_EQ("", parts[2]);
parts = SplitString("0x100,0x200,0x300,0x400,0x500,0x600", ',');
ASSERT_EQ(6u, parts.size());
EXPECT_EQ("0x100", parts[0]);
EXPECT_EQ("0x200", parts[1]);
EXPECT_EQ("0x300", parts[2]);
EXPECT_EQ("0x400", parts[3]);
EXPECT_EQ("0x500", parts[4]);
EXPECT_EQ("0x600", parts[5]);
}
} // namespace

View File

@ -172,6 +172,8 @@
'win/get_module_information.h',
'win/handle.cc',
'win/handle.h',
'win/initial_client_data.cc',
'win/initial_client_data.h',
'win/module_version.cc',
'win/module_version.h',
'win/nt_internals.cc',

View File

@ -88,6 +88,7 @@
'win/exception_handler_server_test.cc',
'win/get_function_test.cc',
'win/handle_test.cc',
'win/initial_client_data_test.cc',
'win/process_info_test.cc',
'win/scoped_process_suspend_test.cc',
'win/time_test.cc',

View File

@ -14,7 +14,6 @@
#include "util/win/exception_handler_server.h"
#include <sddl.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
@ -30,73 +29,17 @@
#include "snapshot/crashpad_info_client_options.h"
#include "snapshot/win/process_snapshot_win.h"
#include "util/file/file_writer.h"
#include "util/misc/random_string.h"
#include "util/misc/tri_state.h"
#include "util/misc/uuid.h"
#include "util/win/get_function.h"
#include "util/win/handle.h"
#include "util/win/registration_protocol_win.h"
#include "util/win/scoped_local_alloc.h"
#include "util/win/xp_compat.h"
namespace crashpad {
namespace {
// We create two pipe instances, so that there's one listening while the
// PipeServiceProc is processing a registration.
const size_t kPipeInstances = 2;
// Wraps CreateNamedPipe() to create a single named pipe instance.
//
// If first_instance is true, the named pipe instance will be created with
// FILE_FLAG_FIRST_PIPE_INSTANCE. This ensures that the the pipe name is not
// already in use when created. The first instance will be created with an
// untrusted integrity SACL so instances of this pipe can be connected to by
// processes of any integrity level.
HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name,
bool first_instance) {
SECURITY_ATTRIBUTES security_attributes;
SECURITY_ATTRIBUTES* security_attributes_pointer = nullptr;
ScopedLocalAlloc scoped_sec_desc;
if (first_instance) {
// Pre-Vista does not have integrity levels.
const DWORD version = GetVersion();
const DWORD major_version = LOBYTE(LOWORD(version));
const bool is_vista_or_later = major_version >= 6;
if (is_vista_or_later) {
// Mandatory Label, no ACE flags, no ObjectType, integrity level
// untrusted.
const wchar_t kSddl[] = L"S:(ML;;;;;S-1-16-0)";
PSECURITY_DESCRIPTOR sec_desc;
PCHECK(ConvertStringSecurityDescriptorToSecurityDescriptor(
kSddl, SDDL_REVISION_1, &sec_desc, nullptr))
<< "ConvertStringSecurityDescriptorToSecurityDescriptor";
// Take ownership of the allocated SECURITY_DESCRIPTOR.
scoped_sec_desc.reset(sec_desc);
memset(&security_attributes, 0, sizeof(security_attributes));
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.lpSecurityDescriptor = sec_desc;
security_attributes.bInheritHandle = FALSE;
security_attributes_pointer = &security_attributes;
}
}
return CreateNamedPipe(
pipe_name.c_str(),
PIPE_ACCESS_DUPLEX | (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0),
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
kPipeInstances,
512,
512,
0,
security_attributes_pointer);
}
decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() {
static const auto get_named_pipe_client_process_id =
GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId);
@ -165,6 +108,9 @@ class ClientData {
ClientData(HANDLE port,
ExceptionHandlerServer::Delegate* delegate,
ScopedKernelHANDLE process,
ScopedKernelHANDLE crash_dump_requested_event,
ScopedKernelHANDLE non_crash_dump_requested_event,
ScopedKernelHANDLE non_crash_dump_completed_event,
WinVMAddress crash_exception_information_address,
WinVMAddress non_crash_exception_information_address,
WinVMAddress debug_critical_section_address,
@ -177,12 +123,11 @@ class ClientData {
lock_(),
port_(port),
delegate_(delegate),
crash_dump_requested_event_(
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
crash_dump_requested_event_(std::move(crash_dump_requested_event)),
non_crash_dump_requested_event_(
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
std::move(non_crash_dump_requested_event)),
non_crash_dump_completed_event_(
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
std::move(non_crash_dump_completed_event)),
process_(std::move(process)),
crash_exception_information_address_(
crash_exception_information_address),
@ -315,36 +260,45 @@ void ExceptionHandlerServer::SetPipeName(const std::wstring& pipe_name) {
pipe_name_ = pipe_name;
}
std::wstring ExceptionHandlerServer::CreatePipe() {
void ExceptionHandlerServer::InitializeWithInheritedDataForInitialClient(
const InitialClientData& initial_client_data,
Delegate* delegate) {
DCHECK(pipe_name_.empty());
DCHECK(!first_pipe_instance_.is_valid());
int tries = 5;
std::string pipe_name_base =
base::StringPrintf("\\\\.\\pipe\\crashpad_%d_", GetCurrentProcessId());
std::wstring pipe_name;
do {
pipe_name = base::UTF8ToUTF16(pipe_name_base + RandomString());
first_pipe_instance_.reset(initial_client_data.first_pipe_instance());
first_pipe_instance_.reset(CreateNamedPipeInstance(pipe_name, true));
// TODO(scottmg): Vista+. Might need to pass through or possibly find an Nt*.
size_t bytes = sizeof(wchar_t) * _MAX_PATH + sizeof(FILE_NAME_INFO);
std::unique_ptr<uint8_t[]> data(new uint8_t[bytes]);
if (!GetFileInformationByHandleEx(
first_pipe_instance_.get(), FileNameInfo, data.get(), bytes)) {
PLOG(FATAL) << "GetFileInformationByHandleEx";
}
FILE_NAME_INFO* file_name_info =
reinterpret_cast<FILE_NAME_INFO*>(data.get());
pipe_name_ =
L"\\\\.\\pipe" + std::wstring(file_name_info->FileName,
file_name_info->FileNameLength /
sizeof(file_name_info->FileName[0]));
// CreateNamedPipe() is documented as setting the error to
// ERROR_ACCESS_DENIED if FILE_FLAG_FIRST_PIPE_INSTANCE is specified and the
// pipe name is already in use. However it may set the error to other codes
// such as ERROR_PIPE_BUSY (if the pipe already exists and has reached its
// maximum instance count) or ERROR_INVALID_PARAMETER (if the pipe already
// exists and its attributes differ from those specified to
// CreateNamedPipe()). Some of these errors may be ambiguous: for example,
// ERROR_INVALID_PARAMETER may also occur if CreateNamedPipe() is called
// incorrectly even in the absence of an existing pipe by the same name.
//
// Rather than chasing down all of the possible errors that might indicate
// that a pipe name is already in use, retry up to a few times on any error.
} while (!first_pipe_instance_.is_valid() && --tries);
PCHECK(first_pipe_instance_.is_valid()) << "CreateNamedPipe";
SetPipeName(pipe_name);
return pipe_name;
{
base::AutoLock lock(clients_lock_);
internal::ClientData* client = new internal::ClientData(
port_.get(),
delegate,
ScopedKernelHANDLE(initial_client_data.client_process()),
ScopedKernelHANDLE(initial_client_data.request_crash_dump()),
ScopedKernelHANDLE(initial_client_data.request_non_crash_dump()),
ScopedKernelHANDLE(initial_client_data.non_crash_dump_completed()),
initial_client_data.crash_exception_information(),
initial_client_data.non_crash_exception_information(),
initial_client_data.debug_critical_section_address(),
&OnCrashDumpEvent,
&OnNonCrashDumpEvent,
&OnProcessEnd);
clients_.insert(client);
}
}
void ExceptionHandlerServer::Run(Delegate* delegate) {
@ -513,6 +467,12 @@ bool ExceptionHandlerServer::ServiceClientConnection(
service_context.port(),
service_context.delegate(),
ScopedKernelHANDLE(client_process),
ScopedKernelHANDLE(
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
ScopedKernelHANDLE(
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
ScopedKernelHANDLE(
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
message.registration.crash_exception_information,
message.registration.non_crash_exception_information,
message.registration.critical_section_address,

View File

@ -22,6 +22,7 @@
#include "base/synchronization/lock.h"
#include "util/file/file_io.h"
#include "util/win/address_types.h"
#include "util/win/initial_client_data.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
@ -72,21 +73,32 @@ class ExceptionHandlerServer {
//! \brief Sets the pipe name to listen for client registrations on.
//!
//! Either this method or CreatePipe(), but not both, must be called before
//! Run().
//! This method, or InitializeWithInheritedDataForInitialClient(), must be
//! called before Run().
//!
//! \param[in] pipe_name The name of the pipe to listen on. Must be of the
//! form "\\.\pipe\<some_name>".
void SetPipeName(const std::wstring& pipe_name);
//! \brief Creates a randomized pipe name to listen for client registrations
//! on and returns its name.
//! \brief Sets the pipe to listen for client registrations on, providing
//! the first precreated instance.
//!
//! Either this method or CreatePipe(), but not both, must be called before
//! Run().
//! This method, or SetPipeName(), must be called before Run(). All of these
//! parameters are generally created in a parent process that launches the
//! handler. For more details see the Windows implementation of
//! CrashpadClient.
//!
//! \return The pipe name that will be listened on.
std::wstring CreatePipe();
//! \sa CrashpadClient
//! \sa RegistrationRequest
//!
//! \param[in] initial_client_data The handles and addresses of data inherited
//! from a parent process needed to initialize and register the first
//! client. Ownership of these handles is taken.
//! \param[in] delegate The interface to which the exceptions are delegated
//! when they are caught in Run(). Ownership is not transferred.
void InitializeWithInheritedDataForInitialClient(
const InitialClientData& initial_client_data,
Delegate* delegate);
//! \brief Runs the exception-handling server.
//!
@ -98,6 +110,10 @@ class ExceptionHandlerServer {
//! object must not be destroyed until Run() returns.
void Stop();
//! \brief The number of server-side pipe instances that the exception handler
//! server creates to listen for connections from clients.
static const size_t kPipeInstances = 2;
private:
static bool ServiceClientConnection(
const internal::PipeServiceContext& service_context);

View File

@ -81,10 +81,12 @@ class ExceptionHandlerServerTest : public testing::Test {
public:
ExceptionHandlerServerTest()
: server_(true),
pipe_name_(server_.CreatePipe()),
pipe_name_(L"\\\\.\\pipe\\test_name"),
server_ready_(CreateEvent(nullptr, false, false, nullptr)),
delegate_(server_ready_.get()),
server_thread_(&server_, &delegate_) {}
server_thread_(&server_, &delegate_) {
server_.SetPipeName(pipe_name_);
}
TestDelegate& delegate() { return delegate_; }
ExceptionHandlerServer& server() { return server_; }
@ -171,10 +173,6 @@ class TestClient final : public WinChildProcess {
ADD_FAILURE();
return EXIT_FAILURE;
}
if (!client.UseHandler()) {
ADD_FAILURE();
return EXIT_FAILURE;
}
WriteWString(WritePipeHandle(), L"OK");
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,113 @@
// Copyright 2016 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/initial_client_data.h"
#include <vector>
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "util/stdlib/string_number_conversion.h"
#include "util/string/split_string.h"
#include "util/win/handle.h"
namespace crashpad {
namespace {
bool HandleFromString(const std::string& str, HANDLE* handle) {
unsigned int handle_uint;
if (!StringToNumber(str, &handle_uint) ||
(*handle = IntToHandle(handle_uint)) == INVALID_HANDLE_VALUE) {
LOG(ERROR) << "could not convert '" << str << "' to HANDLE";
return false;
}
return true;
}
bool AddressFromString(const std::string& str, WinVMAddress* address) {
if (!StringToNumber(str, address)) {
LOG(ERROR) << "could not convert '" << str << "' to WinVMAddress";
return false;
}
return true;
}
} // namespace
InitialClientData::InitialClientData()
: crash_exception_information_(0),
non_crash_exception_information_(0),
debug_critical_section_address_(0),
request_crash_dump_(nullptr),
request_non_crash_dump_(nullptr),
non_crash_dump_completed_(nullptr),
first_pipe_instance_(INVALID_HANDLE_VALUE),
client_process_(nullptr),
is_valid_(false) {}
InitialClientData::InitialClientData(
HANDLE request_crash_dump,
HANDLE request_non_crash_dump,
HANDLE non_crash_dump_completed,
HANDLE first_pipe_instance,
HANDLE client_process,
WinVMAddress crash_exception_information,
WinVMAddress non_crash_exception_information,
WinVMAddress debug_critical_section_address)
: crash_exception_information_(crash_exception_information),
non_crash_exception_information_(non_crash_exception_information),
debug_critical_section_address_(debug_critical_section_address),
request_crash_dump_(request_crash_dump),
request_non_crash_dump_(request_non_crash_dump),
non_crash_dump_completed_(non_crash_dump_completed),
first_pipe_instance_(first_pipe_instance),
client_process_(client_process),
is_valid_(true) {}
bool InitialClientData::InitializeFromString(const std::string& str) {
std::vector<std::string> parts(SplitString(str, ','));
if (parts.size() != 8) {
LOG(ERROR) << "expected 8 comma separated arguments";
return false;
}
if (!HandleFromString(parts[0], &request_crash_dump_) ||
!HandleFromString(parts[1], &request_non_crash_dump_) ||
!HandleFromString(parts[2], &non_crash_dump_completed_) ||
!HandleFromString(parts[3], &first_pipe_instance_) ||
!HandleFromString(parts[4], &client_process_) ||
!AddressFromString(parts[5], &crash_exception_information_) ||
!AddressFromString(parts[6], &non_crash_exception_information_) ||
!AddressFromString(parts[7], &debug_critical_section_address_)) {
return false;
}
is_valid_ = true;
return true;
}
std::string InitialClientData::StringRepresentation() const {
return base::StringPrintf("0x%x,0x%x,0x%x,0x%x,0x%x,0x%I64x,0x%I64x,0x%I64x",
HandleToInt(request_crash_dump_),
HandleToInt(request_non_crash_dump_),
HandleToInt(non_crash_dump_completed_),
HandleToInt(first_pipe_instance_),
HandleToInt(client_process_),
crash_exception_information_,
non_crash_exception_information_,
debug_critical_section_address_);
}
} // namespace crashpad

View File

@ -0,0 +1,116 @@
// Copyright 2016 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_INITIAL_CLIENT_DATA_H_
#define CRASHPAD_UTIL_WIN_INITIAL_CLIENT_DATA_H_
#include <windows.h>
#include <string>
#include "base/macros.h"
#include "util/win/address_types.h"
namespace crashpad {
//! \brief A container for the data associated with the `--initial-client-data`
//! method for initializing the handler process on Windows.
class InitialClientData {
public:
//! \brief Constructs an unintialized instance to be used with
//! InitializeFromString().
InitialClientData();
//! \brief Constructs an instance of InitialClientData. This object does not
//! take ownership of any of the referenced HANDLEs.
//!
//! \param[in] request_crash_dump An event signalled from the client on crash.
//! \param[in] request_non_crash_dump An event signalled from the client when
//! it would like a dump to be taken, but allowed to continue afterwards.
//! \param[in] non_crash_dump_completed An event signalled from the handler to
//! tell the client that the non-crash dump has completed, and it can
//! continue execution.
//! \param[in] first_pipe_instance The server end and first instance of a pipe
//! that will be used for communication with all other clients after this
//! initial one.
//! \param[in] client_process A process handle for the client being
//! registered.
//! \param[in] crash_exception_information The address, in the client's
//! address space, of an ExceptionInformation structure, used when
//! handling a crash dump request.
//! \param[in] non_crash_exception_information The address, in the client's
//! address space, of an ExceptionInformation structure, used when
//! handling a non-crashing dump request.
//! \param[in] debug_critical_section_address The address, in the client
//! process's address space, of a `CRITICAL_SECTION` allocated with a
//! valid .DebugInfo field. This can be accomplished by using
//! InitializeCriticalSectionWithDebugInfoIfPossible() or equivalent. This
//! value can be `0`, however then limited lock data will be available in
//! minidumps.
InitialClientData(HANDLE request_crash_dump,
HANDLE request_non_crash_dump,
HANDLE non_crash_dump_completed,
HANDLE first_pipe_instance,
HANDLE client_process,
WinVMAddress crash_exception_information,
WinVMAddress non_crash_exception_information,
WinVMAddress debug_critical_section_address);
//! \brief Returns whether the object has been initialized successfully.
bool IsValid() const { return is_valid_; }
//! Initializes this object from a string representation presumed to have been
//! created by StringRepresentation().
//!
//! \param[in] str The output of StringRepresentation().
//!
//! \return `true` on success, or `false` with a message logged on failure.
bool InitializeFromString(const std::string& str);
//! \brief Returns a string representation of the data of this object,
//! suitable for passing on the command line.
std::string StringRepresentation() const;
HANDLE request_crash_dump() const { return request_crash_dump_; }
HANDLE request_non_crash_dump() const { return request_non_crash_dump_; }
HANDLE non_crash_dump_completed() const { return non_crash_dump_completed_; }
HANDLE first_pipe_instance() const { return first_pipe_instance_; }
HANDLE client_process() const { return client_process_; }
WinVMAddress crash_exception_information() const {
return crash_exception_information_;
}
WinVMAddress non_crash_exception_information() const {
return non_crash_exception_information_;
}
WinVMAddress debug_critical_section_address() const {
return debug_critical_section_address_;
}
private:
WinVMAddress crash_exception_information_;
WinVMAddress non_crash_exception_information_;
WinVMAddress debug_critical_section_address_;
HANDLE request_crash_dump_;
HANDLE request_non_crash_dump_;
HANDLE non_crash_dump_completed_;
HANDLE first_pipe_instance_;
HANDLE client_process_;
bool is_valid_;
DISALLOW_COPY_AND_ASSIGN(InitialClientData);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_WIN_INITIAL_CLIENT_DATA_H_

View File

@ -0,0 +1,74 @@
// Copyright 2016 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/initial_client_data.h"
#include "gtest/gtest.h"
namespace crashpad {
namespace test {
namespace {
TEST(InitialClientData, Validity) {
InitialClientData icd1;
EXPECT_FALSE(icd1.IsValid());
InitialClientData icd2(
reinterpret_cast<HANDLE>(0x123),
reinterpret_cast<HANDLE>(0x456),
reinterpret_cast<HANDLE>(0x789),
reinterpret_cast<HANDLE>(0xabc),
reinterpret_cast<HANDLE>(0xdef),
0x7fff000012345678ull,
0x100000ull,
0xccccddddeeeeffffull);
EXPECT_TRUE(icd2.IsValid());
}
TEST(InitialClientData, RoundTrip) {
InitialClientData first(
reinterpret_cast<HANDLE>(0x123),
reinterpret_cast<HANDLE>(0x456),
reinterpret_cast<HANDLE>(0x789),
reinterpret_cast<HANDLE>(0xabc),
reinterpret_cast<HANDLE>(0xdef),
0x7fff000012345678ull,
0x100000ull,
0xccccddddeeeeffffull);
std::string as_string = first.StringRepresentation();
EXPECT_EQ(
"0x123,0x456,0x789,0xabc,0xdef,"
"0x7fff000012345678,0x100000,0xccccddddeeeeffff",
as_string);
InitialClientData second;
ASSERT_TRUE(second.InitializeFromString(as_string));
EXPECT_EQ(first.request_crash_dump(), second.request_crash_dump());
EXPECT_EQ(first.request_non_crash_dump(), second.request_non_crash_dump());
EXPECT_EQ(first.non_crash_dump_completed(),
second.non_crash_dump_completed());
EXPECT_EQ(first.first_pipe_instance(), second.first_pipe_instance());
EXPECT_EQ(first.client_process(), second.client_process());
EXPECT_EQ(first.crash_exception_information(),
second.crash_exception_information());
EXPECT_EQ(first.non_crash_exception_information(),
second.non_crash_exception_information());
EXPECT_EQ(first.debug_critical_section_address(),
second.debug_critical_section_address());
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -15,9 +15,12 @@
#include "util/win/registration_protocol_win.h"
#include <windows.h>
#include <sddl.h>
#include "base/logging.h"
#include "util/win/exception_handler_server.h"
#include "util/win/scoped_handle.h"
#include "util/win/scoped_local_alloc.h"
namespace crashpad {
@ -91,4 +94,47 @@ bool SendToCrashHandlerServer(const base::string16& pipe_name,
}
}
HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name,
bool first_instance) {
SECURITY_ATTRIBUTES security_attributes;
SECURITY_ATTRIBUTES* security_attributes_pointer = nullptr;
ScopedLocalAlloc scoped_sec_desc;
if (first_instance) {
// Pre-Vista does not have integrity levels.
const DWORD version = GetVersion();
const DWORD major_version = LOBYTE(LOWORD(version));
const bool is_vista_or_later = major_version >= 6;
if (is_vista_or_later) {
// Mandatory Label, no ACE flags, no ObjectType, integrity level
// untrusted.
const wchar_t kSddl[] = L"S:(ML;;;;;S-1-16-0)";
PSECURITY_DESCRIPTOR sec_desc;
PCHECK(ConvertStringSecurityDescriptorToSecurityDescriptor(
kSddl, SDDL_REVISION_1, &sec_desc, nullptr))
<< "ConvertStringSecurityDescriptorToSecurityDescriptor";
// Take ownership of the allocated SECURITY_DESCRIPTOR.
scoped_sec_desc.reset(sec_desc);
memset(&security_attributes, 0, sizeof(security_attributes));
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.lpSecurityDescriptor = sec_desc;
security_attributes.bInheritHandle = TRUE;
security_attributes_pointer = &security_attributes;
}
}
return CreateNamedPipe(
pipe_name.c_str(),
PIPE_ACCESS_DUPLEX | (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0),
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
ExceptionHandlerServer::kPipeInstances,
512,
512,
0,
security_attributes_pointer);
}
} // namespace crashpad

View File

@ -128,6 +128,17 @@ bool SendToCrashHandlerServer(const base::string16& pipe_name,
const ClientToServerMessage& message,
ServerToClientMessage* response);
//! \brief Wraps CreateNamedPipe() to create a single named pipe instance.
//!
//! \param[in] pipe_name The name to use for the pipe.
//! \param[in] first_instance If `true`, the named pipe instance will be
//! created with `FILE_FLAG_FIRST_PIPE_INSTANCE`. This ensures that the the
//! pipe name is not already in use when created. The first instance will be
//! created with an untrusted integrity SACL so instances of this pipe can
//! be connected to by processes of any integrity level.
HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name,
bool first_instance);
} // namespace crashpad
#endif // CRASHPAD_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_