From cd1d773a40e4d75addc19f47ead67bfbd058f007 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Mon, 20 Nov 2017 14:36:54 -0500 Subject: [PATCH] mac: Run the exception swallower server in the parent test process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The exception swallower server’s design and interface are both considerably simpler when the server runs in a thread in the parent test process, as opposed to a separate process. The only caveat is that this results in calls to fork() while threaded. Uses of gtest {ASSERT,EXPECT}_DEATH with the default “fast” gtest death test style result in this warning: [WARNING] ../../third_party/gtest/gtest/googletest/src/gtest-death-test.cc:836:: Death tests use fork(), which is unsafe particularly in a threaded context. For this test, Google Test detected 2 threads. Bug: crashpad:33 Change-Id: Ib8f418064ea4ab942859c3393cb15cf71365614d Reviewed-on: https://chromium-review.googlesource.com/779481 Commit-Queue: Mark Mentovai Reviewed-by: Robert Sesek --- test/BUILD.gn | 20 +-- test/gtest_death.h | 22 ++- test/mac/exception_swallower.cc | 204 +++++++++++++++---------- test/mac/exception_swallower.h | 125 ++++----------- test/mac/exception_swallower_exe.cc | 229 ---------------------------- test/multiprocess_posix.cc | 17 +-- test/test.gyp | 25 +-- 7 files changed, 172 insertions(+), 470 deletions(-) delete mode 100644 test/mac/exception_swallower_exe.cc diff --git a/test/BUILD.gn b/test/BUILD.gn index 9337fa99..c3208ad6 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -80,9 +80,7 @@ static_library("test") { if (is_mac) { libs = [ "bsm" ] - data_deps = [ - ":crashpad_exception_swallower", - ] + deps += [ "//third_party/crashpad/crashpad/handler" ] } } @@ -158,19 +156,3 @@ static_library("gtest_main") { "//testing/gtest", ] } - -if (is_mac) { - executable("crashpad_exception_swallower") { - testonly = true - sources = [ - "mac/exception_swallower_exe.cc", - ] - deps = [ - "//base", - "//third_party/crashpad/crashpad/compat", - "//third_party/crashpad/crashpad/handler", - "//third_party/crashpad/crashpad/tools:tool_support", - "//third_party/crashpad/crashpad/util", - ] - } -} diff --git a/test/gtest_death.h b/test/gtest_death.h index 1c7e5f6a..493c9c6b 100644 --- a/test/gtest_death.h +++ b/test/gtest_death.h @@ -37,10 +37,13 @@ //! //! \sa ASSERT_DEATH_CHECK() //! \sa EXPECT_DEATH_CRASH() -#define ASSERT_DEATH_CRASH(statement, regex) \ - crashpad::test::ExceptionSwallower::Parent_PrepareForGtestDeathTest(); \ - ASSERT_DEATH(crashpad::test::ExceptionSwallower::Child_SwallowExceptions(); \ - { statement; }, regex) +#define ASSERT_DEATH_CRASH(statement, regex) \ + do { \ + crashpad::test::ExceptionSwallower exception_swallower; \ + ASSERT_DEATH(crashpad::test::ExceptionSwallower::SwallowExceptions(); \ + { statement; }, \ + regex); \ + } while (false) //! \brief Wraps the gtest `EXPECT_DEATH()` macro to make assertions about death //! caused by crashes. @@ -52,10 +55,13 @@ //! //! \sa EXPECT_DEATH_CHECK() //! \sa ASSERT_DEATH_CRASH() -#define EXPECT_DEATH_CRASH(statement, regex) \ - crashpad::test::ExceptionSwallower::Parent_PrepareForGtestDeathTest(); \ - EXPECT_DEATH(crashpad::test::ExceptionSwallower::Child_SwallowExceptions(); \ - { statement; }, regex) +#define EXPECT_DEATH_CRASH(statement, regex) \ + do { \ + crashpad::test::ExceptionSwallower exception_swallower; \ + EXPECT_DEATH(crashpad::test::ExceptionSwallower::SwallowExceptions(); \ + { statement; }, \ + regex); \ + } while (false) #else // OS_MACOSX diff --git a/test/mac/exception_swallower.cc b/test/mac/exception_swallower.cc index 307680c0..18c5cf71 100644 --- a/test/mac/exception_swallower.cc +++ b/test/mac/exception_swallower.cc @@ -14,118 +14,156 @@ #include "test/mac/exception_swallower.h" -#include -#include +#include +#include +#include #include -#include #include "base/logging.h" #include "base/mac/scoped_mach_port.h" #include "base/strings/stringprintf.h" -#include "gtest/gtest.h" -#include "test/test_paths.h" -#include "util/file/file_io.h" +#include "handler/mac/exception_handler_server.h" +#include "util/mach/exc_server_variants.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" -#include "util/posix/double_fork_and_exec.h" +#include "util/misc/random_string.h" +#include "util/thread/thread.h" namespace crashpad { namespace test { -// static -void ExceptionSwallower::Parent_PrepareForCrashingChild() { - Get()->SetParent(); +namespace { + +constexpr char kServiceEnvironmentVariable[] = + "CRASHPAD_EXCEPTION_SWALLOWER_SERVICE"; + +ExceptionSwallower* g_exception_swallower; + +// Like getenv(), but fails a CHECK() if the underlying function fails. It’s not +// considered a failure for |name| to be unset in the environment. In that case, +// nullptr is returned. +const char* CheckedGetenv(const char* name) { + errno = 0; + const char* value; + PCHECK((value = getenv(name)) || errno == 0) << "getenv"; + return value; } -// static -void ExceptionSwallower::Parent_PrepareForGtestDeathTest() { - if (testing::FLAGS_gtest_death_test_style == "fast") { - Parent_PrepareForCrashingChild(); - } else { - // This is the only other death test style that’s known to gtest. - DCHECK_EQ(testing::FLAGS_gtest_death_test_style, "threadsafe"); - } -} +} // namespace -// static -void ExceptionSwallower::Child_SwallowExceptions() { - Get()->SwallowExceptions(); -} - -ExceptionSwallower::ExceptionSwallower() - : service_name_(), fd_(), parent_pid_(0) { - base::FilePath exception_swallower_server_path = - TestPaths::Executable().DirName().Append("crashpad_exception_swallower"); - - // Use socketpair() as a full-duplex pipe(). - int socket_fds[2]; - PCHECK(socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socket_fds) == 0) - << "socketpair"; - - fd_.reset(socket_fds[0]); - base::ScopedFD exception_swallower_fd(socket_fds[1]); - - // fd_ is long-lived. Make sure that nobody accidentaly inherits it. - PCHECK(fcntl(fd_.get(), F_SETFD, FD_CLOEXEC) != -1) << "fcntl"; - - // SIGPIPE is undesirable when writing to this socket. Allow broken-pipe - // writes to fail with EPIPE instead. - for (size_t index = 0; index < arraysize(socket_fds); ++index) { - constexpr int value = 1; - PCHECK(setsockopt(socket_fds[index], - SOL_SOCKET, - SO_NOSIGPIPE, - &value, - sizeof(value)) == 0) - << "setsockopt"; +class ExceptionSwallower::ExceptionSwallowerThread + : public Thread, + public UniversalMachExcServer::Interface { + public: + explicit ExceptionSwallowerThread( + base::mac::ScopedMachReceiveRight receive_right) + : Thread(), + UniversalMachExcServer::Interface(), + exception_handler_server_(std::move(receive_right), true), + pid_(getpid()) { + Start(); } - std::vector argv; - argv.reserve(2); - argv.push_back(exception_swallower_server_path.value()); - argv.push_back( - base::StringPrintf("--socket-fd=%d", exception_swallower_fd.get())); + ~ExceptionSwallowerThread() override {} - CHECK(DoubleForkAndExec(argv, exception_swallower_fd.get(), false, nullptr)); + void Stop() { exception_handler_server_.Stop(); } - // Close the exception swallower server’s side of the socket, so that it’s the - // only process that can use it. - exception_swallower_fd.reset(); + // Returns the process ID that the thread is running in. This is used to + // detect misuses that place the exception swallower server thread and code + // that wants its exceptions swallowed in the same process. + pid_t ProcessID() const { return pid_; } - // When the exception swallower server provides its registered service name, - // it’s ready to go. - uint8_t service_name_size; - CheckedReadFileExactly( - fd_.get(), &service_name_size, sizeof(service_name_size)); - service_name_.resize(service_name_size); - if (!service_name_.empty()) { - CheckedReadFileExactly(fd_.get(), &service_name_[0], service_name_.size()); + private: + // Thread: + + void ThreadMain() override { exception_handler_server_.Run(this); } + + // UniversalMachExcServer::Interface: + + kern_return_t CatchMachException(exception_behavior_t behavior, + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count, + const mach_msg_trailer_t* trailer, + bool* destroy_complex_request) override { + *destroy_complex_request = true; + + // Swallow. + + ExcServerCopyState( + behavior, old_state, old_state_count, new_state, new_state_count); + return ExcServerSuccessfulReturnValue(exception, behavior, false); } - // Verify that everything’s set up. - base::mac::ScopedMachSendRight exception_swallower_port( - BootstrapLookUp(service_name_)); - CHECK(exception_swallower_port.is_valid()); + ExceptionHandlerServer exception_handler_server_; + pid_t pid_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionSwallowerThread); +}; + +ExceptionSwallower::ExceptionSwallower() : exception_swallower_thread_() { + CHECK(!g_exception_swallower); + g_exception_swallower = this; + + if (CheckedGetenv(kServiceEnvironmentVariable)) { + // The environment variable is already set, so just proceed with the + // existing service. This normally happens when the gtest “threadsafe” death + // test style is chosen, because the test child process will re-execute code + // already run in the test parent process. See + // https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#death-test-styles. + return; + } + + std::string service_name = + base::StringPrintf("org.chromium.crashpad.test.exception_swallower.%d.%s", + getpid(), + RandomString().c_str()); + base::mac::ScopedMachReceiveRight receive_right( + BootstrapCheckIn(service_name)); + CHECK(receive_right.is_valid()); + + exception_swallower_thread_.reset( + new ExceptionSwallowerThread(std::move(receive_right))); + + PCHECK(setenv(kServiceEnvironmentVariable, service_name.c_str(), 1) == 0) + << "setenv"; } -ExceptionSwallower::~ExceptionSwallower() {} +ExceptionSwallower::~ExceptionSwallower() { + PCHECK(unsetenv(kServiceEnvironmentVariable) == 0) << "unsetenv"; + + exception_swallower_thread_->Stop(); + exception_swallower_thread_->Join(); + + CHECK_EQ(g_exception_swallower, this); + g_exception_swallower = nullptr; +} // static -ExceptionSwallower* ExceptionSwallower::Get() { - static ExceptionSwallower* const instance = new ExceptionSwallower(); - return instance; -} - -void ExceptionSwallower::SetParent() { - parent_pid_ = getpid(); -} - void ExceptionSwallower::SwallowExceptions() { - CHECK_NE(getpid(), parent_pid_); + // The exception swallower thread can’t be in this process, because the + // EXC_CRASH or EXC_CORPSE_NOTIFY exceptions that it needs to swallow will be + // delivered after a crash has occurred and none of its threads will be + // scheduled to run. + CHECK(!g_exception_swallower || + !g_exception_swallower->exception_swallower_thread_ || + g_exception_swallower->exception_swallower_thread_->ProcessID() != + getpid()); + + const char* service_name = CheckedGetenv(kServiceEnvironmentVariable); + CHECK(service_name); base::mac::ScopedMachSendRight exception_swallower_port( - BootstrapLookUp(service_name_)); + BootstrapLookUp(service_name)); CHECK(exception_swallower_port.is_valid()); ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask, diff --git a/test/mac/exception_swallower.h b/test/mac/exception_swallower.h index ddf49b46..7c3a4421 100644 --- a/test/mac/exception_swallower.h +++ b/test/mac/exception_swallower.h @@ -15,11 +15,8 @@ #ifndef CRASHPAD_TEST_MAC_EXCEPTION_SWALLOWER_H_ #define CRASHPAD_TEST_MAC_EXCEPTION_SWALLOWER_H_ -#include +#include -#include - -#include "base/files/scoped_file.h" #include "base/macros.h" namespace crashpad { @@ -37,116 +34,48 @@ namespace test { //! Reports generated for code that crashes intentionally have no value, and //! many Crashpad tests do crash intentionally. //! -//! In order to prevent the system’s crash reporter from handling intentional -//! crashes, use this class. First, ensure that the exception swallower server -//! process, `crashpad_exception_swallower`, is running by calling -//! Parent_PrepareForCrashingChild() or Parent_PrepareForGtestDeathTest() from -//! the parent test process. Then, call Child_SwallowExceptions() from the test -//! child process that crashes intentionally. +//! Instantiate an ExceptionSwallower object in a parent test process (a process +//! where `TEST()`, `TEST_F()`, and `TEST_P()` execute) to create an exception +//! swallower server running on a dedicated thread. A service mapping for this +//! server will be published with the bootstrap server and made available in the +//! `CRASHPAD_EXCEPTION_SWALLOWER_SERVICE` environment variable. In a child +//! process, call SwallowExceptions() to look up this service and set it as the +//! `EXC_CRASH` and `EXC_CORPSE_NOTIFY` handler. When these exceptions are +//! raised in the child process, they’ll be handled by the exception swallower +//! server, which performs no action but reports that exceptions were +//! successfully handled so that the system’s crash reporter, ReportCrash, will +//! not be invoked. //! -//! Don’t call Child_SwallowExceptions() except in test child processes that are -//! expected to crash. It is invalid to call Child_SwallowExceptions() in the -//! parent test process. -//! -//! An exception swallower server process started by this interface will live as -//! long as the process that created it, and will then exit. +//! At most one ExceptionSwallower may be instantiated in a process at a time. +//! If `CRASHPAD_EXCEPTION_SWALLOWER_SERVICE` is already set, ExceptionSwallower +//! leaves it in place and takes no additional action. //! //! Crashpad’s ASSERT_DEATH_CRASH(), EXPECT_DEATH_CRASH(), ASSERT_DEATH_CHECK(), //! and EXPECT_DEATH_CHECK() macros make use of this class on macOS, as does the //! Multiprocess test interface. class ExceptionSwallower { public: - //! \brief In a parent test process, prepares for a crashing child process - //! whose exceptions are to be swallowed. - //! - //! Calling this in a parent test process starts an exception swallower server - //! process if none has been started yet. Subsequently, a forked child process - //! expecting to crash can call Child_SwallowExceptions() to direct exceptions - //! to the exception swallower server process. Multiple children can share a - //! single exception swallower server process. - //! - //! This function establishes the exception swallower server unconditionally. - //! This is not appropriate for gtest death tests, which should use - //! Parent_PrepareForGtestDeathTest() instead. - static void Parent_PrepareForCrashingChild(); - - //! \brief In a parent test process, prepares for a gtest death test whose - //! exceptions are to be swallowed. - //! - //! This is similar to Parent_PrepareForCrashingChild(), except it only starts - //! an exception swallower server process if the gtest - //! death test style is “fast”. With the “fast” style, the death test is - //! run directly from a forked child. The alternative, “threadsafe”, - //! reexecutes the test executable to run the death test. Since the death test - //! does not run directly forked from the parent test process, the parent’s - //! ExceptionSwallower object would not be available to the child, rendering - //! any exception swallower server process started by a parent test process - //! unavailable to the child. Since such an exception swallower server process - //! would go unused, this function will not start one when running under the - //! “threadsafe” style. In that case, each child death test is responsible for - //! starting its own exception swallower server process, and this will occur - //! when child death tests call Child_SwallowExceptions(). - //! - //! This function establishes the exception swallower server conditionally - //! based on the gtest death test style. This is not appropriate for tests - //! that unconditionally fork a child that intentionally crashes without an - //! intervening execv(). For such tests, use Parent_PrepareForCrashingChild() - //! instead. - static void Parent_PrepareForGtestDeathTest(); - - //! \brief In a test child process, arranges to swallow `EXC_CRASH` and - //! `EXC_CORPSE_NOTIFY` exceptions. - //! - //! This must be called in a test child process. It must not be called from a - //! parent test process directly. - //! - //! It is not an error to call this in a child process without having first - //! called Parent_PrepareForCrashingChild() or - //! Parent_PrepareForGtestDeathTest() in the parent process, but failing to do - //! so precludes the possibility of multiple qualified child processes sharing - //! a single exception swallower server process. In this context, children - //! running directly from a forked parent are qualified. gtest death tests - //! under the “threadsafe” gtest - //! death test style are not qualified. - static void Child_SwallowExceptions(); - - private: ExceptionSwallower(); ~ExceptionSwallower(); - //! \brief Returns the ExceptionSwallower singleton. - //! - //! If the object does not yet exist, it will be created, and the exception - //! swallower server process, `crashpad_exception_swallower`, will be started. - static ExceptionSwallower* Get(); - - //! \brief Identifies the calling process as the test parent. - //! - //! This is used to check for interface abuses. Its use is optional, but if - //! it’s called, SwallowExceptions() must not be called from the same process. - void SetParent(); - //! \brief In a test child process, arranges to swallow `EXC_CRASH` and //! `EXC_CORPSE_NOTIFY` exceptions. //! //! This must be called in a test child process. It must not be called from a - //! parent test process directly. - void SwallowExceptions(); + //! parent test process directly. Parent test processes are those that execute + //! `TEST()`, `TEST_F()`, and `TEST_P()`. Test child processes execute + //! ASSERT_DEATH_CRASH(), EXPECT_DEATH_CRASH(), ASSERT_DEATH_CHECK(), + //! EXPECT_DEATH_CHECK(), and Multiprocess::RunChild(). + //! + //! It is an error to call this in a test child process without having first + //! instantiated an ExceptionSwallower object in a parent test project. It is + //! also an error to call this in a parent test process. + static void SwallowExceptions(); - std::string service_name_; + private: + class ExceptionSwallowerThread; - // fd_ is half of a socketpair() that serves a dual purpose. The exception - // swallower server process writes its service name to the socket once - // registered, allowing the parent test process to obtain a reference to the - // service. The socket remains open in the parent test process so that the - // exception swallower server process can observe, based on reading - // end-of-file, when the parent test process has died. The exception swallower - // server process uses this as a signal that it’s safe to exit. - base::ScopedFD fd_; - - pid_t parent_pid_; + std::unique_ptr exception_swallower_thread_; DISALLOW_COPY_AND_ASSIGN(ExceptionSwallower); }; diff --git a/test/mac/exception_swallower_exe.cc b/test/mac/exception_swallower_exe.cc deleted file mode 100644 index b5850418..00000000 --- a/test/mac/exception_swallower_exe.cc +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2017 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 -#include -#include -#include -#include - -#include -#include - -#include "base/logging.h" -#include "base/numerics/safe_conversions.h" -#include "base/strings/stringprintf.h" -#include "handler/mac/exception_handler_server.h" -#include "tools/tool_support.h" -#include "util/file/file_io.h" -#include "util/mach/exc_server_variants.h" -#include "util/mach/mach_extensions.h" -#include "util/misc/random_string.h" -#include "util/posix/close_stdio.h" -#include "util/stdlib/string_number_conversion.h" -#include "util/thread/thread.h" - -namespace crashpad { -namespace { - -// A Mach exception handler that accepts all exceptions but doesn’t do anything -// with any of them. -class SwallowingExceptionHandler : public UniversalMachExcServer::Interface { - public: - SwallowingExceptionHandler() {} - ~SwallowingExceptionHandler() {} - - kern_return_t CatchMachException(exception_behavior_t behavior, - exception_handler_t exception_port, - thread_t thread, - task_t task, - exception_type_t exception, - const mach_exception_data_type_t* code, - mach_msg_type_number_t code_count, - thread_state_flavor_t* flavor, - ConstThreadState old_state, - mach_msg_type_number_t old_state_count, - thread_state_t new_state, - mach_msg_type_number_t* new_state_count, - const mach_msg_trailer_t* trailer, - bool* destroy_complex_request) override { - *destroy_complex_request = true; - - // Swallow. - - ExcServerCopyState( - behavior, old_state, old_state_count, new_state, new_state_count); - return ExcServerSuccessfulReturnValue(exception, behavior, false); - } - - private: - DISALLOW_COPY_AND_ASSIGN(SwallowingExceptionHandler); -}; - -// Watches a file descriptor, and when it reaches end-of-file, asks the -// ExceptionHandlerServer to stop. Because the file descriptor is one end of a -// socketpair(), and the other end is kept open in the client process, -// end-of-file will be reached when the client process terminates. -class EOFWatcherThread : public Thread { - public: - EOFWatcherThread(int fd, ExceptionHandlerServer* exception_handler_server) - : Thread(), - exception_handler_server_(exception_handler_server), - fd_(fd) {} - - private: - void ThreadMain() override { - char c; - ssize_t rv = ReadFile(fd_.get(), &c, 1); - PCHECK(rv >= 0) << internal::kNativeReadFunctionName; - CHECK(rv == 0); - - exception_handler_server_->Stop(); - } - - ExceptionHandlerServer* exception_handler_server_; // weak - base::ScopedFD fd_; - - DISALLOW_COPY_AND_ASSIGN(EOFWatcherThread); -}; - -void Usage(const std::string& me) { - fprintf(stderr, -"Usage: %s [OPTION]...\n" -"Crashpad's exception swallower.\n" -"\n" -" --socket-fd=FD synchronize with the client over FD\n" -" --help display this help and exit\n" -" --version output version information and exit\n", - me.c_str()); - ToolSupport::UsageTail(me); -} - -int ExceptionSwallowerMain(int argc, char* argv[]) { - const std::string me(basename(argv[0])); - - enum OptionFlags { - // Long options without short equivalents. - kOptionLastChar = 255, - kOptionSocketFD, - - // Standard options. - kOptionHelp = -2, - kOptionVersion = -3, - }; - - struct { - int socket_fd; - } options = {}; - options.socket_fd = -1; - - const option long_options[] = { - {"socket-fd", required_argument, nullptr, kOptionSocketFD}, - {"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 kOptionSocketFD: - if (!StringToNumber(optarg, &options.socket_fd) || - options.socket_fd <= STDERR_FILENO) { - ToolSupport::UsageHint(me, "--socket-fd requires a file descriptor"); - return EXIT_FAILURE; - } - 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 (options.socket_fd < 0) { - ToolSupport::UsageHint(me, "--socket-fd is required"); - return EXIT_FAILURE; - } - - if (argc) { - ToolSupport::UsageHint(me, nullptr); - return EXIT_FAILURE; - } - - CloseStdinAndStdout(); - - // Build a service name. Include the PID of the client at the other end of the - // socket, so that the service name has a meaningful relation back to the - // client that started this server process. A simple getppid() won’t do - // because the client started this process with a double-fork(). - pid_t peer_pid; - socklen_t peer_pid_size = base::checked_cast(sizeof(peer_pid)); - PCHECK(getsockopt(options.socket_fd, - SOL_LOCAL, - LOCAL_PEERPID, - &peer_pid, - &peer_pid_size) == 0) - << "getsockopt"; - CHECK_EQ(peer_pid_size, sizeof(peer_pid)); - - std::string service_name = - base::StringPrintf("org.chromium.crashpad.test.exception_swallower.%d.%s", - peer_pid, - RandomString().c_str()); - - base::mac::ScopedMachReceiveRight receive_right( - BootstrapCheckIn(service_name)); - CHECK(receive_right.is_valid()); - - // Tell the client that the service has been checked in, providing the - // service name. - uint8_t service_name_size = base::checked_cast(service_name.size()); - CheckedWriteFile( - options.socket_fd, &service_name_size, sizeof(service_name_size)); - CheckedWriteFile( - options.socket_fd, service_name.c_str(), service_name.size()); - - ExceptionHandlerServer exception_handler_server(std::move(receive_right), - true); - - EOFWatcherThread eof_watcher_thread(options.socket_fd, - &exception_handler_server); - eof_watcher_thread.Start(); - - // This runs until stopped by eof_watcher_thread. - SwallowingExceptionHandler swallowing_exception_handler; - exception_handler_server.Run(&swallowing_exception_handler); - - eof_watcher_thread.Join(); - - return EXIT_SUCCESS; -} - -} // namespace -} // namespace crashpad - -int main(int argc, char* argv[]) { - return crashpad::ExceptionSwallowerMain(argc, argv); -} diff --git a/test/multiprocess_posix.cc b/test/multiprocess_posix.cc index 2a8a15cc..c638b48a 100644 --- a/test/multiprocess_posix.cc +++ b/test/multiprocess_posix.cc @@ -74,13 +74,12 @@ void Multiprocess::Run() { ASSERT_NO_FATAL_FAILURE(PreFork()); #if defined(OS_MACOSX) - // If the child is expected to crash, set up an exception swallower process - // to swallow the exception instead of allowing it to be seen by the system’s - // crash reporter. - const bool swallow_exceptions = - reason_ == kTerminationSignal && Signals::IsCrashSignal(code_); - if (swallow_exceptions) { - ExceptionSwallower::Parent_PrepareForCrashingChild(); + // If the child is expected to crash, set up an exception swallower to swallow + // the exception instead of allowing it to be seen by the system’s crash + // reporter. + std::unique_ptr exception_swallower; + if (reason_ == kTerminationSignal && Signals::IsCrashSignal(code_)) { + exception_swallower.reset(new ExceptionSwallower()); } #endif // OS_MACOSX @@ -141,8 +140,8 @@ void Multiprocess::Run() { } } else { #if defined(OS_MACOSX) - if (swallow_exceptions) { - ExceptionSwallower::Child_SwallowExceptions(); + if (exception_swallower.get()) { + ExceptionSwallower::SwallowExceptions(); } #endif // OS_MACOSX diff --git a/test/test.gyp b/test/test.gyp index f6c1f251..79e0671a 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -84,7 +84,7 @@ 'conditions': [ ['OS=="mac"', { 'dependencies': [ - 'crashpad_exception_swallower', + '../handler/handler.gyp:crashpad_handler_lib', ], 'link_settings': { 'libraries': [ @@ -146,27 +146,4 @@ ], }, ], - 'conditions': [ - ['OS=="mac"', { - 'targets': [ - { - 'target_name': 'crashpad_exception_swallower', - 'type': 'executable', - 'dependencies': [ - '../compat/compat.gyp:crashpad_compat', - '../handler/handler.gyp:crashpad_handler_lib', - '../third_party/mini_chromium/mini_chromium.gyp:base', - '../tools/tools.gyp:crashpad_tool_support', - '../util/util.gyp:crashpad_util', - ], - 'include_dirs': [ - '..', - ], - 'sources': [ - 'mac/exception_swallower_exe.cc', - ], - }, - ], - }], - ], }