diff --git a/DEPS b/DEPS index 34313f8e..b5e38f1b 100644 --- a/DEPS +++ b/DEPS @@ -29,9 +29,40 @@ deps = { 'crashpad/third_party/mini_chromium/mini_chromium': Var('chromium_git') + '/chromium/mini_chromium@' + '8e12d3df2f1c0fcd84d649f4619323558db63a85', + 'buildtools': + Var('chromium_git') + '/chromium/buildtools.git@' + + 'c2f259809d5ede3275df5ea0842f0431990c4f98', } hooks = [ + { + 'name': 'clang_format_mac', + 'pattern': '.', + 'action': [ + 'download_from_google_storage', + '--platform=^darwin$', + '--no_resume', + '--no_auth', + '--bucket=chromium-clang-format', + '--output=buildtools/mac/clang-format', + '--sha1_file', + 'buildtools/mac/clang-format.sha1', + ], + }, + { + 'name': 'clang_format_win', + 'pattern': '.', + 'action': [ + 'download_from_google_storage', + '--platform=^win32$', + '--no_resume', + '--no_auth', + '--bucket=chromium-clang-format', + '--output=buildtools/win/clang-format.exe', + '--sha1_file', + 'buildtools/win/clang-format.exe.sha1', + ], + }, { 'name': 'gyp', 'pattern': '\.gypi?$', diff --git a/client/crashpad_client.h b/client/crashpad_client.h index eb3fc6de..79a4900e 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -142,6 +142,16 @@ class CrashpadClient { //! \param[in] context A `CONTEXT`, generally captured by CaptureContext() or //! similar. static void DumpWithoutCrash(const CONTEXT& context); + + //! \brief Requests that the handler capture a dump using the given \a + //! 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(). + //! + //! \param[in] exception_pointers An `EXCEPTION_POINTERS`, as would generally + //! passed to an unhandled exception filter. + static void DumpAndCrash(EXCEPTION_POINTERS* exception_pointers); #endif //! \brief Configures the process to direct its crashes to a Crashpad handler. diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index a2506cb7..8faebbf2 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -19,6 +19,8 @@ #include "base/atomicops.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/scoped_generic.h" #include "base/strings/string16.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" @@ -26,6 +28,8 @@ #include "util/file/file_io.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/registration_protocol_win.h" #include "util/win/scoped_handle.h" @@ -109,6 +113,40 @@ std::wstring FormatArgumentString(const std::string& name, return std::wstring(L"--") + base::UTF8ToUTF16(name) + L"=" + value; } +struct ScopedProcThreadAttributeListTraits { + 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() + // because it will only be called if InitializeProcThreadAttributeList() and + // UpdateProcThreadAttribute() are present. + static const auto delete_proc_thread_attribute_list = + GET_FUNCTION_REQUIRED(L"kernel32.dll", ::DeleteProcThreadAttributeList); + delete_proc_thread_attribute_list(proc_thread_attribute_list); + } +}; + +using ScopedProcThreadAttributeList = + base::ScopedGeneric; + +// Adds |handle| to |handle_list| if it appears valid. +// +// Invalid handles (including INVALID_HANDLE_VALUE and null handles) cannot be +// added to a PPROC_THREAD_ATTRIBUTE_LIST’s PROC_THREAD_ATTRIBUTE_HANDLE_LIST. +// If INVALID_HANDLE_VALUE appears, CreateProcess() will fail with +// ERROR_INVALID_PARAMETER. If a null handle appears, the child process will +// silently not inherit any handles. +// +// Use this function to add handles with uncertain validities. +void AddHandleToListIfValid(std::vector* handle_list, HANDLE handle) { + if (handle && handle != INVALID_HANDLE_VALUE) { + handle_list->push_back(handle); + } +} + } // namespace namespace crashpad { @@ -166,31 +204,90 @@ bool CrashpadClient::StartHandler( base::UTF8ToUTF16(kv.first + '=' + kv.second)), &command_line); } - - // According to - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203, HANDLEs - // are always 32 bits. AppendCommandLineArgument( base::UTF8ToUTF16(base::StringPrintf("--handshake-handle=0x%x", - pipe_write)), + HandleToInt(pipe_write))), &command_line); - STARTUPINFO startup_info = {}; - startup_info.cb = sizeof(startup_info); - startup_info.dwFlags = STARTF_USESTDHANDLES; - startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + DWORD creation_flags; + STARTUPINFOEX startup_info = {}; + startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + startup_info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + std::vector handle_list; + scoped_ptr proc_thread_attribute_list_storage; + ScopedProcThreadAttributeList proc_thread_attribute_list_owner; + + static const auto initialize_proc_thread_attribute_list = + GET_FUNCTION(L"kernel32.dll", ::InitializeProcThreadAttributeList); + static const auto update_proc_thread_attribute = + initialize_proc_thread_attribute_list + ? GET_FUNCTION(L"kernel32.dll", ::UpdateProcThreadAttribute) + : nullptr; + if (!initialize_proc_thread_attribute_list || !update_proc_thread_attribute) { + // The OS doesn’t allow handle inheritance to be restricted, so the handler + // will inherit every inheritable handle. + creation_flags = 0; + startup_info.StartupInfo.cb = sizeof(startup_info.StartupInfo); + } else { + // Restrict handle inheritance to just those needed in the handler. + + creation_flags = EXTENDED_STARTUPINFO_PRESENT; + startup_info.StartupInfo.cb = sizeof(startup_info); + SIZE_T size; + rv = initialize_proc_thread_attribute_list(nullptr, 1, 0, &size); + if (rv) { + LOG(ERROR) << "InitializeProcThreadAttributeList (size) succeeded, " + "expected failure"; + return false; + } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + PLOG(ERROR) << "InitializeProcThreadAttributeList (size)"; + return false; + } + + proc_thread_attribute_list_storage.reset(new uint8_t[size]); + startup_info.lpAttributeList = + reinterpret_cast( + proc_thread_attribute_list_storage.get()); + rv = initialize_proc_thread_attribute_list( + startup_info.lpAttributeList, 1, 0, &size); + if (!rv) { + PLOG(ERROR) << "InitializeProcThreadAttributeList"; + return false; + } + proc_thread_attribute_list_owner.reset(startup_info.lpAttributeList); + + handle_list.reserve(4); + handle_list.push_back(pipe_write); + AddHandleToListIfValid(&handle_list, startup_info.StartupInfo.hStdInput); + AddHandleToListIfValid(&handle_list, startup_info.StartupInfo.hStdOutput); + AddHandleToListIfValid(&handle_list, startup_info.StartupInfo.hStdError); + rv = update_proc_thread_attribute( + startup_info.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + &handle_list[0], + handle_list.size() * sizeof(handle_list[0]), + nullptr, + nullptr); + if (!rv) { + PLOG(ERROR) << "UpdateProcThreadAttribute"; + return false; + } + } + PROCESS_INFORMATION process_info; rv = CreateProcess(handler.value().c_str(), &command_line[0], nullptr, nullptr, true, - 0, + creation_flags, nullptr, nullptr, - &startup_info, + &startup_info.StartupInfo, &process_info); if (!rv) { PLOG(ERROR) << "CreateProcess"; @@ -271,12 +368,12 @@ bool CrashpadClient::UseHandler() { } // The server returns these already duplicated to be valid in this process. - g_signal_exception = reinterpret_cast( - static_cast(response.registration.request_crash_dump_event)); - g_signal_non_crash_dump = reinterpret_cast(static_cast( - response.registration.request_non_crash_dump_event)); - g_non_crash_dump_done = reinterpret_cast(static_cast( - response.registration.non_crash_dump_completed_event)); + g_signal_exception = + IntToHandle(response.registration.request_crash_dump_event); + g_signal_non_crash_dump = + IntToHandle(response.registration.request_non_crash_dump_event); + g_non_crash_dump_done = + IntToHandle(response.registration.non_crash_dump_completed_event); g_non_crash_dump_lock = new base::Lock(); @@ -338,4 +435,9 @@ void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) { PLOG_IF(ERROR, wfso_result != WAIT_OBJECT_0) << "WaitForSingleObject"; } +// static +void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) { + UnhandledExceptionHandler(exception_pointers); +} + } // namespace crashpad diff --git a/handler/handler.gyp b/handler/handler.gyp index 20df3d73..e7f54c82 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -19,8 +19,10 @@ ], 'targets': [ { - 'target_name': 'crashpad_handler', - 'type': 'executable', + # This target exists so that the crashpad_handler can be embedded into + # another binary. + 'target_name': 'crashpad_handler_lib', + 'type': 'static_library', 'dependencies': [ '../client/client.gyp:crashpad_client', '../compat/compat.gyp:crashpad_compat', @@ -40,10 +42,26 @@ 'mac/crash_report_exception_handler.h', 'mac/exception_handler_server.cc', 'mac/exception_handler_server.h', - 'main.cc', + 'handler_main.cc', + 'handler_main.h', 'win/crash_report_exception_handler.cc', 'win/crash_report_exception_handler.h', ], + }, + { + 'target_name': 'crashpad_handler', + 'type': 'executable', + 'dependencies': [ + '../third_party/mini_chromium/mini_chromium.gyp:base', + '../tools/tools.gyp:crashpad_tool_support', + 'crashpad_handler_lib', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'main.cc', + ], 'conditions': [ ['OS=="mac"', { diff --git a/handler/handler_main.cc b/handler/handler_main.cc new file mode 100644 index 00000000..11f14ef1 --- /dev/null +++ b/handler/handler_main.cc @@ -0,0 +1,331 @@ +// Copyright 2014 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/handler_main.h" + +#include +#include + +#include +#include + +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "client/crash_report_database.h" +#include "client/crashpad_client.h" +#include "tools/tool_support.h" +#include "handler/crash_report_upload_thread.h" +#include "util/file/file_io.h" +#include "util/stdlib/map_insert.h" +#include "util/stdlib/string_number_conversion.h" +#include "util/string/split_string.h" +#include "util/synchronization/semaphore.h" + +#if defined(OS_MACOSX) +#include +#include "base/mac/scoped_mach_port.h" +#include "handler/mac/crash_report_exception_handler.h" +#include "handler/mac/exception_handler_server.h" +#include "util/mach/child_port_handshake.h" +#include "util/mach/mach_extensions.h" +#include "util/posix/close_stdio.h" +#elif defined(OS_WIN) +#include +#include "handler/win/crash_report_exception_handler.h" +#include "util/win/exception_handler_server.h" +#include "util/win/handle.h" +#endif // OS_MACOSX + +namespace crashpad { + +namespace { + +void Usage(const base::FilePath& me) { + fprintf(stderr, +"Usage: %" PRFilePath " [OPTION]...\n" +"Crashpad's exception handler server.\n" +"\n" +" --annotation=KEY=VALUE set a process annotation in each crash report\n" +" --database=PATH store the crash report database at PATH\n" +#if defined(OS_MACOSX) +" --handshake-fd=FD establish communication with the client over FD\n" +" --mach-service=SERVICE register SERVICE with the bootstrap server\n" +" --reset-own-crash-exception-port-to-system-default\n" +" reset the server's exception handler to default\n" +#elif defined(OS_WIN) +" --handshake-handle=HANDLE\n" +" create a new pipe and send its name via HANDLE\n" +" --pipe-name=PIPE communicate with the client over PIPE\n" +#endif // OS_MACOSX +" --url=URL send crash reports to this Breakpad server URL,\n" +" only if uploads are enabled for the database\n" +" --help display this help and exit\n" +" --version output version information and exit\n", + me.value().c_str()); + ToolSupport::UsageTail(me); +} + +} // namespace + +int HandlerMain(int argc, char* argv[]) { + const base::FilePath argv0( + ToolSupport::CommandLineArgumentToFilePathStringType(argv[0])); + const base::FilePath me(argv0.BaseName()); + + enum OptionFlags { + // Long options without short equivalents. + kOptionLastChar = 255, + kOptionAnnotation, + kOptionDatabase, +#if defined(OS_MACOSX) + kOptionHandshakeFD, + kOptionMachService, + kOptionResetOwnCrashExceptionPortToSystemDefault, +#elif defined(OS_WIN) + kOptionHandshakeHandle, + kOptionPipeName, +#endif // OS_MACOSX + kOptionURL, + + // Standard options. + kOptionHelp = -2, + kOptionVersion = -3, + }; + + struct { + std::map annotations; + std::string url; + const char* database; +#if defined(OS_MACOSX) + int handshake_fd; + std::string mach_service; + bool reset_own_crash_exception_port_to_system_default; +#elif defined(OS_WIN) + HANDLE handshake_handle; + std::string pipe_name; +#endif // OS_MACOSX + } options = {}; +#if defined(OS_MACOSX) + options.handshake_fd = -1; +#elif defined(OS_WIN) + options.handshake_handle = INVALID_HANDLE_VALUE; +#endif + + const option long_options[] = { + {"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}, + {"reset-own-crash-exception-port-to-system-default", + no_argument, + nullptr, + kOptionResetOwnCrashExceptionPortToSystemDefault}, +#elif defined(OS_WIN) + {"handshake-handle", required_argument, nullptr, kOptionHandshakeHandle}, + {"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; + while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) { + switch (opt) { + case kOptionAnnotation: { + std::string key; + std::string value; + if (!SplitString(optarg, '=', &key, &value)) { + ToolSupport::UsageHint(me, "--annotation requires KEY=VALUE"); + return EXIT_FAILURE; + } + std::string old_value; + if (!MapInsertOrReplace(&options.annotations, key, value, &old_value)) { + LOG(WARNING) << "duplicate key " << key << ", discarding value " + << old_value; + } + break; + } + case kOptionDatabase: { + options.database = optarg; + break; + } +#if defined(OS_MACOSX) + case kOptionHandshakeFD: { + if (!StringToNumber(optarg, &options.handshake_fd) || + options.handshake_fd < 0) { + ToolSupport::UsageHint(me, + "--handshake-fd requires a file descriptor"); + return EXIT_FAILURE; + } + break; + } + case kOptionMachService: { + options.mach_service = optarg; + break; + } + case kOptionResetOwnCrashExceptionPortToSystemDefault: { + options.reset_own_crash_exception_port_to_system_default = true; + 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"); + return EXIT_FAILURE; + } + break; + } + case kOptionPipeName: { + options.pipe_name = optarg; + break; + } +#endif // OS_MACOSX + case kOptionURL: { + options.url = optarg; + break; + } + case kOptionHelp: { + Usage(me); + return EXIT_SUCCESS; + } + case kOptionVersion: { + ToolSupport::Version(me); + return EXIT_SUCCESS; + } + default: { + ToolSupport::UsageHint(me, nullptr); + return EXIT_FAILURE; + } + } + } + argc -= optind; + argv += optind; + +#if defined(OS_MACOSX) + if (options.handshake_fd < 0 && options.mach_service.empty()) { + ToolSupport::UsageHint(me, "--handshake-fd or --mach-service is required"); + return EXIT_FAILURE; + } + if (options.handshake_fd >= 0 && !options.mach_service.empty()) { + ToolSupport::UsageHint( + me, "--handshake-fd and --mach-service are incompatible"); + 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"); + return EXIT_FAILURE; + } + if (options.handshake_handle != INVALID_HANDLE_VALUE && + !options.pipe_name.empty()) { + ToolSupport::UsageHint( + me, "--handshake-handle and --pipe-name are incompatible"); + return EXIT_FAILURE; + } +#endif // OS_MACOSX + + if (!options.database) { + ToolSupport::UsageHint(me, "--database is required"); + return EXIT_FAILURE; + } + + if (argc) { + ToolSupport::UsageHint(me, nullptr); + return EXIT_FAILURE; + } + +#if defined(OS_MACOSX) + if (options.mach_service.empty()) { + // Don’t do this when being run by launchd. See launchd.plist(5). + CloseStdinAndStdout(); + } + + if (options.reset_own_crash_exception_port_to_system_default) { + CrashpadClient::UseSystemDefaultHandler(); + } + + base::mac::ScopedMachReceiveRight receive_right; + + if (options.handshake_fd >= 0) { + receive_right.reset( + ChildPortHandshake::RunServerForFD( + base::ScopedFD(options.handshake_fd), + ChildPortHandshake::PortRightType::kReceiveRight)); + } else if (!options.mach_service.empty()) { + receive_right = BootstrapCheckIn(options.mach_service); + } + + if (!receive_right.is_valid()) { + return EXIT_FAILURE; + } + + ExceptionHandlerServer exception_handler_server( + receive_right.Pass(), !options.mach_service.empty()); +#elif defined(OS_WIN) + ExceptionHandlerServer exception_handler_server(!options.pipe_name.empty()); + + std::string pipe_name; + 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(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 + + scoped_ptr database(CrashReportDatabase::Initialize( + base::FilePath(ToolSupport::CommandLineArgumentToFilePathStringType( + options.database)))); + if (!database) { + return EXIT_FAILURE; + } + + CrashReportUploadThread upload_thread(database.get(), options.url); + upload_thread.Start(); + + CrashReportExceptionHandler exception_handler( + database.get(), &upload_thread, &options.annotations); + + exception_handler_server.Run(&exception_handler); + + upload_thread.Stop(); + + return EXIT_SUCCESS; +} + +} // namespace crashpad diff --git a/handler/handler_main.h b/handler/handler_main.h new file mode 100644 index 00000000..5b5568ec --- /dev/null +++ b/handler/handler_main.h @@ -0,0 +1,28 @@ +// 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_HANDLER_MAIN_H_ +#define CRASHPAD_HANDLER_HANDLER_MAIN_H_ + +namespace crashpad { + +//! \brief The `main()` of the `crashpad_handler` binary. +//! +//! This is exposed so that `crashpad_handler` can be embedded into another +//! binary, but called and used as if it were a standalone executable. +int HandlerMain(int argc, char* argv[]); + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_HANDLER_MAIN_H_ diff --git a/handler/main.cc b/handler/main.cc index 7b1f7304..f326bd7c 100644 --- a/handler/main.cc +++ b/handler/main.cc @@ -1,4 +1,4 @@ -// Copyright 2014 The Crashpad Authors. All rights reserved. +// 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. @@ -12,321 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include "handler/handler_main.h" -#include -#include - -#include "base/files/file_path.h" -#include "base/files/scoped_file.h" -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "base/strings/utf_string_conversions.h" #include "build/build_config.h" -#include "client/crash_report_database.h" -#include "client/crashpad_client.h" #include "tools/tool_support.h" -#include "handler/crash_report_upload_thread.h" -#include "util/file/file_io.h" -#include "util/stdlib/map_insert.h" -#include "util/stdlib/string_number_conversion.h" -#include "util/string/split_string.h" -#include "util/synchronization/semaphore.h" - -#if defined(OS_MACOSX) -#include -#include "base/mac/scoped_mach_port.h" -#include "handler/mac/crash_report_exception_handler.h" -#include "handler/mac/exception_handler_server.h" -#include "util/mach/child_port_handshake.h" -#include "util/mach/mach_extensions.h" -#include "util/posix/close_stdio.h" -#elif defined(OS_WIN) -#include -#include "handler/win/crash_report_exception_handler.h" -#include "util/win/exception_handler_server.h" -#endif // OS_MACOSX - -namespace crashpad { -namespace { - -void Usage(const base::FilePath& me) { - fprintf(stderr, -"Usage: %" PRFilePath " [OPTION]...\n" -"Crashpad's exception handler server.\n" -"\n" -" --annotation=KEY=VALUE set a process annotation in each crash report\n" -" --database=PATH store the crash report database at PATH\n" -#if defined(OS_MACOSX) -" --handshake-fd=FD establish communication with the client over FD\n" -" --mach-service=SERVICE register SERVICE with the bootstrap server\n" -" --reset-own-crash-exception-port-to-system-default\n" -" reset the server's exception handler to default\n" -#elif defined(OS_WIN) -" --handshake-handle=HANDLE\n" -" create a new pipe and send its name via HANDLE\n" -" --pipe-name=PIPE communicate with the client over PIPE\n" -#endif // OS_MACOSX -" --url=URL send crash reports to this Breakpad server URL,\n" -" only if uploads are enabled for the database\n" -" --help display this help and exit\n" -" --version output version information and exit\n", - me.value().c_str()); - ToolSupport::UsageTail(me); -} - -int HandlerMain(int argc, char* argv[]) { - const base::FilePath argv0( - ToolSupport::CommandLineArgumentToFilePathStringType(argv[0])); - const base::FilePath me(argv0.BaseName()); - - enum OptionFlags { - // Long options without short equivalents. - kOptionLastChar = 255, - kOptionAnnotation, - kOptionDatabase, -#if defined(OS_MACOSX) - kOptionHandshakeFD, - kOptionMachService, - kOptionResetOwnCrashExceptionPortToSystemDefault, -#elif defined(OS_WIN) - kOptionHandshakeHandle, - kOptionPipeName, -#endif // OS_MACOSX - kOptionURL, - - // Standard options. - kOptionHelp = -2, - kOptionVersion = -3, - }; - - struct { - std::map annotations; - std::string url; - const char* database; -#if defined(OS_MACOSX) - int handshake_fd; - std::string mach_service; - bool reset_own_crash_exception_port_to_system_default; -#elif defined(OS_WIN) - HANDLE handshake_handle; - std::string pipe_name; -#endif // OS_MACOSX - } options = {}; -#if defined(OS_MACOSX) - options.handshake_fd = -1; -#elif defined(OS_WIN) - options.handshake_handle = INVALID_HANDLE_VALUE; -#endif - - const option long_options[] = { - {"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}, - {"reset-own-crash-exception-port-to-system-default", - no_argument, - nullptr, - kOptionResetOwnCrashExceptionPortToSystemDefault}, -#elif defined(OS_WIN) - {"handshake-handle", required_argument, nullptr, kOptionHandshakeHandle}, - {"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; - while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) { - switch (opt) { - case kOptionAnnotation: { - std::string key; - std::string value; - if (!SplitString(optarg, '=', &key, &value)) { - ToolSupport::UsageHint(me, "--annotation requires KEY=VALUE"); - return EXIT_FAILURE; - } - std::string old_value; - if (!MapInsertOrReplace(&options.annotations, key, value, &old_value)) { - LOG(WARNING) << "duplicate key " << key << ", discarding value " - << old_value; - } - break; - } - case kOptionDatabase: { - options.database = optarg; - break; - } -#if defined(OS_MACOSX) - case kOptionHandshakeFD: { - if (!StringToNumber(optarg, &options.handshake_fd) || - options.handshake_fd < 0) { - ToolSupport::UsageHint(me, - "--handshake-fd requires a file descriptor"); - return EXIT_FAILURE; - } - break; - } - case kOptionMachService: { - options.mach_service = optarg; - break; - } - case kOptionResetOwnCrashExceptionPortToSystemDefault: { - options.reset_own_crash_exception_port_to_system_default = true; - break; - } -#elif defined(OS_WIN) - case kOptionHandshakeHandle: { - // According to - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203, - // HANDLEs are always 32 bits. This construction is used to read a full - // 32-bit number presented by the client, which uses 0x%x format, and - // sign-extend it. - unsigned int handle_uint; - if (!StringToNumber(optarg, &handle_uint) || - (options.handshake_handle = reinterpret_cast( - static_cast(handle_uint))) == INVALID_HANDLE_VALUE) { - ToolSupport::UsageHint(me, "--handshake-handle requires a HANDLE"); - return EXIT_FAILURE; - } - break; - } - case kOptionPipeName: { - options.pipe_name = optarg; - break; - } -#endif // OS_MACOSX - case kOptionURL: { - options.url = optarg; - break; - } - case kOptionHelp: { - Usage(me); - return EXIT_SUCCESS; - } - case kOptionVersion: { - ToolSupport::Version(me); - return EXIT_SUCCESS; - } - default: { - ToolSupport::UsageHint(me, nullptr); - return EXIT_FAILURE; - } - } - } - argc -= optind; - argv += optind; - -#if defined(OS_MACOSX) - if (options.handshake_fd < 0 && options.mach_service.empty()) { - ToolSupport::UsageHint(me, "--handshake-fd or --mach-service is required"); - return EXIT_FAILURE; - } - if (options.handshake_fd >= 0 && !options.mach_service.empty()) { - ToolSupport::UsageHint( - me, "--handshake-fd and --mach-service are incompatible"); - 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"); - return EXIT_FAILURE; - } - if (options.handshake_handle != INVALID_HANDLE_VALUE && - !options.pipe_name.empty()) { - ToolSupport::UsageHint( - me, "--handshake-handle and --pipe-name are incompatible"); - return EXIT_FAILURE; - } -#endif // OS_MACOSX - - if (!options.database) { - ToolSupport::UsageHint(me, "--database is required"); - return EXIT_FAILURE; - } - - if (argc) { - ToolSupport::UsageHint(me, nullptr); - return EXIT_FAILURE; - } - -#if defined(OS_MACOSX) - if (options.mach_service.empty()) { - // Don’t do this when being run by launchd. See launchd.plist(5). - CloseStdinAndStdout(); - } - - if (options.reset_own_crash_exception_port_to_system_default) { - CrashpadClient::UseSystemDefaultHandler(); - } - - base::mac::ScopedMachReceiveRight receive_right; - - if (options.handshake_fd >= 0) { - receive_right.reset( - ChildPortHandshake::RunServerForFD( - base::ScopedFD(options.handshake_fd), - ChildPortHandshake::PortRightType::kReceiveRight)); - } else if (!options.mach_service.empty()) { - receive_right = BootstrapCheckIn(options.mach_service); - } - - if (!receive_right.is_valid()) { - return EXIT_FAILURE; - } - - ExceptionHandlerServer exception_handler_server( - receive_right.Pass(), !options.mach_service.empty()); -#elif defined(OS_WIN) - ExceptionHandlerServer exception_handler_server(!options.pipe_name.empty()); - - std::string pipe_name; - 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(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 - - scoped_ptr database(CrashReportDatabase::Initialize( - base::FilePath(ToolSupport::CommandLineArgumentToFilePathStringType( - options.database)))); - if (!database) { - return EXIT_FAILURE; - } - - CrashReportUploadThread upload_thread(database.get(), options.url); - upload_thread.Start(); - - CrashReportExceptionHandler exception_handler( - database.get(), &upload_thread, &options.annotations); - - exception_handler_server.Run(&exception_handler); - - upload_thread.Stop(); - - return EXIT_SUCCESS; -} - -} // namespace -} // namespace crashpad #if defined(OS_MACOSX) int main(int argc, char* argv[]) { diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index 3bbcdb98..ddf48ef2 100644 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -21,6 +21,7 @@ import re import subprocess import sys import tempfile +import time g_temp_dirs = [] @@ -101,6 +102,14 @@ def GetDumpFromProgram(out_dir, pipe_name, executable_name): '--database=' + test_database ]) + # Wait until the server is ready. + printed = False + while not os.path.exists(pipe_name): + if not printed: + print 'Waiting for crashpad_handler to be ready...' + printed = True + time.sleep(0.1) + subprocess.call([os.path.join(out_dir, executable_name), pipe_name]) else: subprocess.call([os.path.join(out_dir, executable_name), diff --git a/test/win/win_child_process.cc b/test/win/win_child_process.cc index 577b6b63..6b7a1a07 100644 --- a/test/win/win_child_process.cc +++ b/test/win/win_child_process.cc @@ -25,6 +25,8 @@ #include "gtest/gtest.h" #include "util/stdlib/string_number_conversion.h" #include "util/string/split_string.h" +#include "util/win/handle.h" +#include "util/win/scoped_local_alloc.h" #include "test/paths.h" namespace crashpad { @@ -33,20 +35,11 @@ namespace test { namespace { const char kIsMultiprocessChild[] = "--is-multiprocess-child"; -struct LocalFreeTraits { - static HLOCAL InvalidValue() { return nullptr; } - static void Free(HLOCAL mem) { - if (LocalFree(mem) != nullptr) - PLOG(ERROR) << "LocalFree"; - } -}; - -using ScopedLocalFree = base::ScopedGeneric; bool GetSwitch(const char* switch_name, std::string* value) { int num_args; wchar_t** args = CommandLineToArgvW(GetCommandLine(), &num_args); - ScopedLocalFree scoped_args(args); // Take ownership. + ScopedLocalAlloc scoped_args(args); // Take ownership. if (!args) { PLOG(FATAL) << "CommandLineToArgvW"; return false; @@ -149,11 +142,15 @@ WinChildProcess::WinChildProcess() { // values are passed to the child on the command line. std::string left, right; CHECK(SplitString(switch_value, '|', &left, &right)); + + // left and right were formatted as 0x%x, so they need to be converted as + // unsigned ints. unsigned int write, read; CHECK(StringToNumber(left, &write)); CHECK(StringToNumber(right, &read)); - pipe_write_.reset(reinterpret_cast(static_cast(write))); - pipe_read_.reset(reinterpret_cast(static_cast(read))); + + pipe_write_.reset(IntToHandle(write)); + pipe_read_.reset(IntToHandle(read)); // Notify the parent that it's OK to proceed. We only need to wait to get to // the process entry point, but this is the easiest place we can notify. @@ -193,8 +190,8 @@ scoped_ptr WinChildProcess::Launch() { test_info->test_case_name(), test_info->name(), kIsMultiprocessChild, - write_for_child, - read_for_child.get())); + HandleToInt(write_for_child.get()), + HandleToInt(read_for_child.get()))); // Command-line buffer cannot be constant, per CreateProcess signature. handles_for_parent->process = LaunchCommandLine(&command_line[0]); diff --git a/util/misc/clock_win.cc b/util/misc/clock_win.cc index 731e98d4..7231205d 100644 --- a/util/misc/clock_win.cc +++ b/util/misc/clock_win.cc @@ -39,7 +39,7 @@ uint64_t ClockMonotonicNanoseconds() { QueryPerformanceCounter(&time); int64_t frequency = QpcFrequency(); int64_t whole_seconds = time.QuadPart / frequency; - int64_t leftover_ticks = time.QuadPart / (whole_seconds * frequency); + int64_t leftover_ticks = time.QuadPart % frequency; const int64_t kNanosecondsPerSecond = static_cast(1E9); return (whole_seconds * kNanosecondsPerSecond) + ((leftover_ticks * kNanosecondsPerSecond) / frequency); diff --git a/util/util.gyp b/util/util.gyp index 65791b5a..5f4064a1 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -160,6 +160,8 @@ 'win/exception_handler_server.h', 'win/get_function.cc', 'win/get_function.h', + 'win/handle.cc', + 'win/handle.h', 'win/module_version.cc', 'win/module_version.h', 'win/nt_internals.cc', @@ -173,6 +175,8 @@ 'win/registration_protocol_win.h', 'win/scoped_handle.cc', 'win/scoped_handle.h', + 'win/scoped_local_alloc.cc', + 'win/scoped_local_alloc.h', 'win/scoped_process_suspend.cc', 'win/scoped_process_suspend.h', 'win/time.cc', @@ -244,6 +248,7 @@ ['OS=="win"', { 'link_settings': { 'libraries': [ + '-ladvapi32.lib', '-lrpcrt4.lib', '-lwinhttp.lib', ], diff --git a/util/util_test.gyp b/util/util_test.gyp index 40bb64fd..9d235fa3 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -84,6 +84,7 @@ 'win/critical_section_with_debug_info_test.cc', 'win/exception_handler_server_test.cc', 'win/get_function_test.cc', + 'win/handle_test.cc', 'win/process_info_test.cc', 'win/scoped_process_suspend_test.cc', 'win/time_test.cc', diff --git a/util/win/command_line_test.cc b/util/win/command_line_test.cc index f3955697..d3179275 100644 --- a/util/win/command_line_test.cc +++ b/util/win/command_line_test.cc @@ -22,22 +22,12 @@ #include "base/scoped_generic.h" #include "gtest/gtest.h" #include "test/errors.h" +#include "util/win/scoped_local_alloc.h" namespace crashpad { namespace test { namespace { -struct LocalAllocTraits { - static HLOCAL InvalidValue() { - return nullptr; - } - - static void Free(HLOCAL memory) { - PLOG_IF(ERROR, LocalFree(memory) != nullptr) << "LocalFree"; - } -}; -using ScopedLocalAlloc = base::ScopedGeneric; - // Calls AppendCommandLineArgument() for every argument in argv, then calls // CommandLineToArgvW() to decode the string into a vector again, and compares // the input and output. diff --git a/util/win/exception_handler_server.cc b/util/win/exception_handler_server.cc index 2035c578..3a41a1de 100644 --- a/util/win/exception_handler_server.cc +++ b/util/win/exception_handler_server.cc @@ -14,6 +14,7 @@ #include "util/win/exception_handler_server.h" +#include #include #include "base/logging.h" @@ -28,7 +29,9 @@ #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 { @@ -43,19 +46,50 @@ const size_t kPipeInstances = 2; // // 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. +// 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) { - 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, - nullptr); + 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() { @@ -319,7 +353,7 @@ void ExceptionHandlerServer::Run(Delegate* delegate) { if (first_pipe_instance_.is_valid()) { pipe = first_pipe_instance_.release(); } else { - pipe = CreateNamedPipeInstance(pipe_name_, false); + pipe = CreateNamedPipeInstance(pipe_name_, i == 0); PCHECK(pipe != INVALID_HANDLE_VALUE) << "CreateNamedPipe"; } @@ -489,14 +523,14 @@ bool ExceptionHandlerServer::ServiceClientConnection( // Duplicate the events back to the client so they can request a dump. ServerToClientMessage response; response.registration.request_crash_dump_event = - base::checked_cast(reinterpret_cast(DuplicateEvent( - client->process(), client->crash_dump_requested_event()))); + HandleToInt(DuplicateEvent( + client->process(), client->crash_dump_requested_event())); response.registration.request_non_crash_dump_event = - base::checked_cast(reinterpret_cast(DuplicateEvent( - client->process(), client->non_crash_dump_requested_event()))); + HandleToInt(DuplicateEvent( + client->process(), client->non_crash_dump_requested_event())); response.registration.non_crash_dump_completed_event = - base::checked_cast(reinterpret_cast(DuplicateEvent( - client->process(), client->non_crash_dump_completed_event()))); + HandleToInt(DuplicateEvent( + client->process(), client->non_crash_dump_completed_event())); if (!LoggingWriteFile(service_context.pipe(), &response, sizeof(response))) return false; diff --git a/util/win/handle.cc b/util/win/handle.cc new file mode 100644 index 00000000..c53f5438 --- /dev/null +++ b/util/win/handle.cc @@ -0,0 +1,36 @@ +// 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/handle.h" + +#include + +#include "base/numerics/safe_conversions.h" + +namespace crashpad { + +// These functions use “int” for the 32-bit integer handle type because +// sign-extension needs to work correctly. INVALID_HANDLE_VALUE is defined as +// ((HANDLE)(LONG_PTR)-1), and this needs to round-trip through an integer and +// back to the same HANDLE value. + +int HandleToInt(HANDLE handle) { + return base::checked_cast(reinterpret_cast(handle)); +} + +HANDLE IntToHandle(int handle_int) { + return reinterpret_cast(static_cast(handle_int)); +} + +} // namespace crashpad diff --git a/util/win/handle.h b/util/win/handle.h new file mode 100644 index 00000000..8a630690 --- /dev/null +++ b/util/win/handle.h @@ -0,0 +1,65 @@ +// 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_HANDLE_TO_INT_H_ +#define CRASHPAD_UTIL_WIN_HANDLE_TO_INT_H_ + +#include + +namespace crashpad { + +//! \brief Converts a `HANDLE` to an `int`. +//! +//! `HANDLE` is a `typedef` for `void *`, but kernel `HANDLE` values aren’t +//! pointers to anything. Only 32 bits of kernel `HANDLE`s are significant, even +//! in 64-bit processes on 64-bit operating systems. See Interprocess +//! Communication Between 32-bit and 64-bit Applications. +//! +//! This function safely converts a kernel `HANDLE` to an `int` similarly to a +//! cast operation. It checks that the operation can be performed safely, and +//! aborts execution if it cannot. +//! +//! \param[in] handle The kernel `HANDLE` to convert. +//! +//! \return An equivalent `int`, truncated (if necessary) from \a handle. If +//! truncation would have resulted in an `int` that could not be converted +//! back to \a handle, aborts execution. +//! +//! \sa IntToHandle() +int HandleToInt(HANDLE handle); + +//! \brief Converts an `int` to an `HANDLE`. +//! +//! `HANDLE` is a `typedef` for `void *`, but kernel `HANDLE` values aren’t +//! pointers to anything. Only 32 bits of kernel `HANDLE`s are significant, even +//! in 64-bit processes on 64-bit operating systems. See Interprocess +//! Communication Between 32-bit and 64-bit Applications. +//! +//! This function safely convert an `int` to a kernel `HANDLE` similarly to a +//! cast operation. +//! +//! \param[in] handle_int The `int` to convert. This must have been produced by +//! HandleToInt(), possibly in a different process. +//! +//! \return An equivalent kernel `HANDLE`, sign-extended (if necessary) from \a +//! handle_int. +//! +//! \sa HandleToInt() +HANDLE IntToHandle(int handle_int); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_HANDLE_TO_INT_H_ diff --git a/util/win/handle_test.cc b/util/win/handle_test.cc new file mode 100644 index 00000000..60e5037b --- /dev/null +++ b/util/win/handle_test.cc @@ -0,0 +1,53 @@ +// 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/handle.h" + +#include + +#include + +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(Handle, HandleToInt) { + EXPECT_EQ(0, HandleToInt(nullptr)); + EXPECT_EQ(-1, HandleToInt(INVALID_HANDLE_VALUE)); + EXPECT_EQ(1, HandleToInt(reinterpret_cast(1))); + EXPECT_EQ(std::numeric_limits::max(), + HandleToInt(reinterpret_cast( + static_cast(std::numeric_limits::max())))); + EXPECT_EQ(std::numeric_limits::min(), + HandleToInt(reinterpret_cast( + static_cast(std::numeric_limits::min())))); +} + +TEST(Handle, IntToHandle) { + EXPECT_EQ(nullptr, IntToHandle(0)); + EXPECT_EQ(INVALID_HANDLE_VALUE, IntToHandle(-1)); + EXPECT_EQ(reinterpret_cast(1), IntToHandle(1)); + EXPECT_EQ(reinterpret_cast( + static_cast(std::numeric_limits::max())), + IntToHandle(std::numeric_limits::max())); + EXPECT_EQ(reinterpret_cast( + static_cast(std::numeric_limits::min())), + IntToHandle(std::numeric_limits::min())); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/win/process_info.cc b/util/win/process_info.cc index d5316590..50f947ce 100644 --- a/util/win/process_info.cc +++ b/util/win/process_info.cc @@ -26,6 +26,7 @@ #include "build/build_config.h" #include "util/numeric/safe_assignment.h" #include "util/win/get_function.h" +#include "util/win/handle.h" #include "util/win/nt_internals.h" #include "util/win/ntstatus_logging.h" #include "util/win/process_structs.h" @@ -181,10 +182,8 @@ bool GetProcessBasicInformation(HANDLE process, return false; } - // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203 on - // 32 bit being the correct size for HANDLEs for proceses, even on Windows - // x64. API functions (e.g. OpenProcess) take only a DWORD, so there's no - // sense in maintaining the top bits. + // API functions (e.g. OpenProcess) take only a DWORD, so there's no sense in + // maintaining the top bits. process_info->process_id_ = static_cast(process_basic_information.UniqueProcessId); process_info->inherited_from_process_id_ = static_cast( @@ -390,15 +389,14 @@ std::vector ProcessInfo::BuildHandleVector( continue; Handle result_handle; - result_handle.handle = - static_cast(reinterpret_cast(handle.HandleValue)); + result_handle.handle = HandleToInt(handle.HandleValue); result_handle.attributes = handle.HandleAttributes; result_handle.granted_access = handle.GrantedAccess; // TODO(scottmg): Could special case for self. HANDLE dup_handle; if (DuplicateHandle(process, - reinterpret_cast(handle.HandleValue), + handle.HandleValue, GetCurrentProcess(), &dup_handle, 0, diff --git a/util/win/process_info.h b/util/win/process_info.h index ca1935bb..fb1b8b3e 100644 --- a/util/win/process_info.h +++ b/util/win/process_info.h @@ -58,10 +58,7 @@ class ProcessInfo { std::wstring type_name; //! \brief The handle's value. - //! - //! See https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203 on - //! 32 bits being the correct size for `HANDLE`s, even on Windows x64. - uint32_t handle; + int handle; //! \brief The attributes for the handle, e.g. `OBJ_INHERIT`, //! `OBJ_CASE_INSENSITIVE`, etc. diff --git a/util/win/process_info_test.cc b/util/win/process_info_test.cc index 0fc52a58..74f086db 100644 --- a/util/win/process_info_test.cc +++ b/util/win/process_info_test.cc @@ -33,6 +33,7 @@ #include "util/misc/uuid.h" #include "util/win/command_line.h" #include "util/win/get_function.h" +#include "util/win/handle.h" #include "util/win/scoped_handle.h" namespace crashpad { @@ -537,14 +538,16 @@ TEST(ProcessInfo, Handles) { ASSERT_TRUE(file.is_valid()); SECURITY_ATTRIBUTES security_attributes = {0}; + security_attributes.nLength = sizeof(security_attributes); security_attributes.bInheritHandle = true; - ScopedFileHandle inherited_file(CreateFile(L"CONOUT$", - GENERIC_WRITE, - 0, - &security_attributes, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr)); + ScopedFileHandle inherited_file(CreateFile( + temp_dir.path().Append(FILE_PATH_LITERAL("inheritable")).value().c_str(), + GENERIC_WRITE, + 0, + &security_attributes, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + nullptr)); ASSERT_TRUE(inherited_file.is_valid()); HKEY key; @@ -573,7 +576,7 @@ TEST(ProcessInfo, Handles) { bool found_key_handle = false; bool found_mapping_handle = false; for (auto handle : info.Handles()) { - if (reinterpret_cast(file.get()) == handle.handle) { + if (handle.handle == HandleToInt(file.get())) { EXPECT_FALSE(found_file_handle); found_file_handle = true; EXPECT_EQ(L"File", handle.type_name); @@ -583,7 +586,7 @@ TEST(ProcessInfo, Handles) { handle.granted_access & STANDARD_RIGHTS_ALL); EXPECT_EQ(0, handle.attributes); } - if (reinterpret_cast(inherited_file.get()) == handle.handle) { + if (handle.handle == HandleToInt(inherited_file.get())) { EXPECT_FALSE(found_inherited_file_handle); found_inherited_file_handle = true; EXPECT_EQ(L"File", handle.type_name); @@ -597,7 +600,7 @@ TEST(ProcessInfo, Handles) { const int kObjInherit = 0x2; EXPECT_EQ(kObjInherit, handle.attributes); } - if (reinterpret_cast(scoped_key.get()) == handle.handle) { + if (handle.handle == HandleToInt(scoped_key.get())) { EXPECT_FALSE(found_key_handle); found_key_handle = true; EXPECT_EQ(L"Key", handle.type_name); @@ -607,7 +610,7 @@ TEST(ProcessInfo, Handles) { handle.granted_access & STANDARD_RIGHTS_ALL); EXPECT_EQ(0, handle.attributes); } - if (reinterpret_cast(mapping.get()) == handle.handle) { + if (handle.handle == HandleToInt(mapping.get())) { EXPECT_FALSE(found_mapping_handle); found_mapping_handle = true; EXPECT_EQ(L"Section", handle.type_name); @@ -620,6 +623,7 @@ TEST(ProcessInfo, Handles) { } } EXPECT_TRUE(found_file_handle); + EXPECT_TRUE(found_inherited_file_handle); EXPECT_TRUE(found_key_handle); EXPECT_TRUE(found_mapping_handle); } diff --git a/util/win/registration_protocol_win.cc b/util/win/registration_protocol_win.cc index 3e0fdf21..38b1b451 100644 --- a/util/win/registration_protocol_win.cc +++ b/util/win/registration_protocol_win.cc @@ -24,8 +24,8 @@ namespace crashpad { bool SendToCrashHandlerServer(const base::string16& pipe_name, const crashpad::ClientToServerMessage& message, crashpad::ServerToClientMessage* response) { - int tries = 5; - while (tries > 0) { + int tries = 0; + for (;;) { ScopedFileHANDLE pipe( CreateFile(pipe_name.c_str(), GENERIC_READ | GENERIC_WRITE, @@ -35,10 +35,20 @@ bool SendToCrashHandlerServer(const base::string16& pipe_name, SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, nullptr)); if (!pipe.is_valid()) { - Sleep(10); - --tries; + if (++tries == 5 || GetLastError() != ERROR_PIPE_BUSY) { + PLOG(ERROR) << "CreateFile"; + return false; + } + + if (!WaitNamedPipe(pipe_name.c_str(), 1000) && + GetLastError() != ERROR_SEM_TIMEOUT) { + PLOG(ERROR) << "WaitNamedPipe"; + return false; + } + continue; } + DWORD mode = PIPE_READMODE_MESSAGE; if (!SetNamedPipeHandleState(pipe.get(), &mode, nullptr, nullptr)) { PLOG(ERROR) << "SetNamedPipeHandleState"; @@ -55,7 +65,8 @@ bool SendToCrashHandlerServer(const base::string16& pipe_name, &bytes_read, nullptr); if (!result) { - PLOG(ERROR) << "TransactNamedPipe"; + LOG(ERROR) << "TransactNamedPipe: expected " << sizeof(*response) + << ", observed " << bytes_read; return false; } if (bytes_read != sizeof(*response)) { @@ -64,9 +75,6 @@ bool SendToCrashHandlerServer(const base::string16& pipe_name, } return true; } - - LOG(ERROR) << "failed to connect after retrying"; - return false; } } // namespace crashpad diff --git a/util/win/registration_protocol_win.h b/util/win/registration_protocol_win.h index a691931d..c57c6e20 100644 --- a/util/win/registration_protocol_win.h +++ b/util/win/registration_protocol_win.h @@ -92,27 +92,22 @@ struct ClientToServerMessage { }; }; -//! \brief A client registration response. -//! -//! See Interprocess -//! Communication Between 32-bit and 64-bit Applications for details on -//! communicating handle values between processes of varying bitness. +//! \brief A client registration response. 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_crash_dump_event; + //! signaled to request a crash report. Clients should convert the value + //! to a `HANDLE` by calling IntToHandle(). + int request_crash_dump_event; //! \brief An event `HANDLE`, valid in the client process, that should be - //! signaled to request a non-crashing dump be taken. 64-bit clients - //! should convert the value to `HANDLEEE` using sign-extension. - uint32_t request_non_crash_dump_event; + //! signaled to request a non-crashing dump be taken. Clients should + //! convert the value to a `HANDLE` by calling IntToHandle(). + int request_non_crash_dump_event; //! \brief An event `HANDLE`, valid in the client process, that will be - //! signaled by the server when the non-crashing dump is complete. 64-bit - //! clients should convert the value to `HANDLEEE` using sign-extension. - uint32_t non_crash_dump_completed_event; + //! signaled by the server when the non-crashing dump is complete. Clients + //! should convert the value to a `HANDLE` by calling IntToHandle(). + int non_crash_dump_completed_event; }; //! \brief The response sent back to the client via SendToCrashHandlerServer(). diff --git a/util/win/scoped_local_alloc.cc b/util/win/scoped_local_alloc.cc new file mode 100644 index 00000000..e9388ee1 --- /dev/null +++ b/util/win/scoped_local_alloc.cc @@ -0,0 +1,26 @@ +// 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/scoped_local_alloc.h" + +#include "base/logging.h" + +namespace crashpad { + +// static +void LocalAllocTraits::Free(HLOCAL memory) { + PLOG_IF(ERROR, LocalFree(memory) != nullptr) << "LocalFree"; +} + +} // namespace crashpad diff --git a/util/win/scoped_local_alloc.h b/util/win/scoped_local_alloc.h new file mode 100644 index 00000000..26a59ed8 --- /dev/null +++ b/util/win/scoped_local_alloc.h @@ -0,0 +1,33 @@ +// 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_SCOPED_LOCAL_ALLOC_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_LOCAL_ALLOC_H_ + +#include + +#include "base/scoped_generic.h" + +namespace crashpad { + +struct LocalAllocTraits { + static HLOCAL InvalidValue() { return nullptr; } + static void Free(HLOCAL mem); +}; + +using ScopedLocalAlloc = base::ScopedGeneric; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_LOCAL_ALLOC_H_