mac: Run the exception swallower server in the parent test process

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 <mark@chromium.org>
Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
Mark Mentovai 2017-11-20 14:36:54 -05:00 committed by Commit Bot
parent 94a5a72efa
commit cd1d773a40
7 changed files with 172 additions and 470 deletions

View File

@ -80,9 +80,7 @@ static_library("test") {
if (is_mac) { if (is_mac) {
libs = [ "bsm" ] libs = [ "bsm" ]
data_deps = [ deps += [ "//third_party/crashpad/crashpad/handler" ]
":crashpad_exception_swallower",
]
} }
} }
@ -158,19 +156,3 @@ static_library("gtest_main") {
"//testing/gtest", "//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",
]
}
}

View File

@ -37,10 +37,13 @@
//! //!
//! \sa ASSERT_DEATH_CHECK() //! \sa ASSERT_DEATH_CHECK()
//! \sa EXPECT_DEATH_CRASH() //! \sa EXPECT_DEATH_CRASH()
#define ASSERT_DEATH_CRASH(statement, regex) \ #define ASSERT_DEATH_CRASH(statement, regex) \
crashpad::test::ExceptionSwallower::Parent_PrepareForGtestDeathTest(); \ do { \
ASSERT_DEATH(crashpad::test::ExceptionSwallower::Child_SwallowExceptions(); \ crashpad::test::ExceptionSwallower exception_swallower; \
{ statement; }, regex) ASSERT_DEATH(crashpad::test::ExceptionSwallower::SwallowExceptions(); \
{ statement; }, \
regex); \
} while (false)
//! \brief Wraps the gtest `EXPECT_DEATH()` macro to make assertions about death //! \brief Wraps the gtest `EXPECT_DEATH()` macro to make assertions about death
//! caused by crashes. //! caused by crashes.
@ -52,10 +55,13 @@
//! //!
//! \sa EXPECT_DEATH_CHECK() //! \sa EXPECT_DEATH_CHECK()
//! \sa ASSERT_DEATH_CRASH() //! \sa ASSERT_DEATH_CRASH()
#define EXPECT_DEATH_CRASH(statement, regex) \ #define EXPECT_DEATH_CRASH(statement, regex) \
crashpad::test::ExceptionSwallower::Parent_PrepareForGtestDeathTest(); \ do { \
EXPECT_DEATH(crashpad::test::ExceptionSwallower::Child_SwallowExceptions(); \ crashpad::test::ExceptionSwallower exception_swallower; \
{ statement; }, regex) EXPECT_DEATH(crashpad::test::ExceptionSwallower::SwallowExceptions(); \
{ statement; }, \
regex); \
} while (false)
#else // OS_MACOSX #else // OS_MACOSX

View File

@ -14,118 +14,156 @@
#include "test/mac/exception_swallower.h" #include "test/mac/exception_swallower.h"
#include <fcntl.h> #include <errno.h>
#include <sys/socket.h> #include <stdlib.h>
#include <unistd.h>
#include <string> #include <string>
#include <vector>
#include "base/logging.h" #include "base/logging.h"
#include "base/mac/scoped_mach_port.h" #include "base/mac/scoped_mach_port.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "gtest/gtest.h" #include "handler/mac/exception_handler_server.h"
#include "test/test_paths.h" #include "util/mach/exc_server_variants.h"
#include "util/file/file_io.h"
#include "util/mach/exception_ports.h" #include "util/mach/exception_ports.h"
#include "util/mach/mach_extensions.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 crashpad {
namespace test { namespace test {
// static namespace {
void ExceptionSwallower::Parent_PrepareForCrashingChild() {
Get()->SetParent(); constexpr char kServiceEnvironmentVariable[] =
"CRASHPAD_EXCEPTION_SWALLOWER_SERVICE";
ExceptionSwallower* g_exception_swallower;
// Like getenv(), but fails a CHECK() if the underlying function fails. Its 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 } // namespace
void ExceptionSwallower::Parent_PrepareForGtestDeathTest() {
if (testing::FLAGS_gtest_death_test_style == "fast") {
Parent_PrepareForCrashingChild();
} else {
// This is the only other death test style thats known to gtest.
DCHECK_EQ(testing::FLAGS_gtest_death_test_style, "threadsafe");
}
}
// static class ExceptionSwallower::ExceptionSwallowerThread
void ExceptionSwallower::Child_SwallowExceptions() { : public Thread,
Get()->SwallowExceptions(); public UniversalMachExcServer::Interface {
} public:
explicit ExceptionSwallowerThread(
ExceptionSwallower::ExceptionSwallower() base::mac::ScopedMachReceiveRight receive_right)
: service_name_(), fd_(), parent_pid_(0) { : Thread(),
base::FilePath exception_swallower_server_path = UniversalMachExcServer::Interface(),
TestPaths::Executable().DirName().Append("crashpad_exception_swallower"); exception_handler_server_(std::move(receive_right), true),
pid_(getpid()) {
// Use socketpair() as a full-duplex pipe(). Start();
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";
} }
std::vector<std::string> argv; ~ExceptionSwallowerThread() override {}
argv.reserve(2);
argv.push_back(exception_swallower_server_path.value());
argv.push_back(
base::StringPrintf("--socket-fd=%d", exception_swallower_fd.get()));
CHECK(DoubleForkAndExec(argv, exception_swallower_fd.get(), false, nullptr)); void Stop() { exception_handler_server_.Stop(); }
// Close the exception swallower servers side of the socket, so that its the // Returns the process ID that the thread is running in. This is used to
// only process that can use it. // detect misuses that place the exception swallower server thread and code
exception_swallower_fd.reset(); // 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, private:
// its ready to go. // Thread:
uint8_t service_name_size;
CheckedReadFileExactly( void ThreadMain() override { exception_handler_server_.Run(this); }
fd_.get(), &service_name_size, sizeof(service_name_size));
service_name_.resize(service_name_size); // UniversalMachExcServer::Interface:
if (!service_name_.empty()) {
CheckedReadFileExactly(fd_.get(), &service_name_[0], service_name_.size()); 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 everythings set up. ExceptionHandlerServer exception_handler_server_;
base::mac::ScopedMachSendRight exception_swallower_port( pid_t pid_;
BootstrapLookUp(service_name_));
CHECK(exception_swallower_port.is_valid()); 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 // static
ExceptionSwallower* ExceptionSwallower::Get() {
static ExceptionSwallower* const instance = new ExceptionSwallower();
return instance;
}
void ExceptionSwallower::SetParent() {
parent_pid_ = getpid();
}
void ExceptionSwallower::SwallowExceptions() { void ExceptionSwallower::SwallowExceptions() {
CHECK_NE(getpid(), parent_pid_); // The exception swallower thread cant 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( base::mac::ScopedMachSendRight exception_swallower_port(
BootstrapLookUp(service_name_)); BootstrapLookUp(service_name));
CHECK(exception_swallower_port.is_valid()); CHECK(exception_swallower_port.is_valid());
ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask, ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask,

View File

@ -15,11 +15,8 @@
#ifndef CRASHPAD_TEST_MAC_EXCEPTION_SWALLOWER_H_ #ifndef CRASHPAD_TEST_MAC_EXCEPTION_SWALLOWER_H_
#define CRASHPAD_TEST_MAC_EXCEPTION_SWALLOWER_H_ #define CRASHPAD_TEST_MAC_EXCEPTION_SWALLOWER_H_
#include <sys/types.h> #include <memory>
#include <string>
#include "base/files/scoped_file.h"
#include "base/macros.h" #include "base/macros.h"
namespace crashpad { namespace crashpad {
@ -37,116 +34,48 @@ namespace test {
//! Reports generated for code that crashes intentionally have no value, and //! Reports generated for code that crashes intentionally have no value, and
//! many Crashpad tests do crash intentionally. //! many Crashpad tests do crash intentionally.
//! //!
//! In order to prevent the systems crash reporter from handling intentional //! Instantiate an ExceptionSwallower object in a parent test process (a process
//! crashes, use this class. First, ensure that the exception swallower server //! where `TEST()`, `TEST_F()`, and `TEST_P()` execute) to create an exception
//! process, `crashpad_exception_swallower`, is running by calling //! swallower server running on a dedicated thread. A service mapping for this
//! Parent_PrepareForCrashingChild() or Parent_PrepareForGtestDeathTest() from //! server will be published with the bootstrap server and made available in the
//! the parent test process. Then, call Child_SwallowExceptions() from the test //! `CRASHPAD_EXCEPTION_SWALLOWER_SERVICE` environment variable. In a child
//! child process that crashes intentionally. //! 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, theyll be handled by the exception swallower
//! server, which performs no action but reports that exceptions were
//! successfully handled so that the systems crash reporter, ReportCrash, will
//! not be invoked.
//! //!
//! Dont call Child_SwallowExceptions() except in test child processes that are //! At most one ExceptionSwallower may be instantiated in a process at a time.
//! expected to crash. It is invalid to call Child_SwallowExceptions() in the //! If `CRASHPAD_EXCEPTION_SWALLOWER_SERVICE` is already set, ExceptionSwallower
//! parent test process. //! leaves it in place and takes no additional action.
//!
//! An exception swallower server process started by this interface will live as
//! long as the process that created it, and will then exit.
//! //!
//! Crashpads ASSERT_DEATH_CRASH(), EXPECT_DEATH_CRASH(), ASSERT_DEATH_CHECK(), //! Crashpads ASSERT_DEATH_CRASH(), EXPECT_DEATH_CRASH(), ASSERT_DEATH_CHECK(),
//! and EXPECT_DEATH_CHECK() macros make use of this class on macOS, as does the //! and EXPECT_DEATH_CHECK() macros make use of this class on macOS, as does the
//! Multiprocess test interface. //! Multiprocess test interface.
class ExceptionSwallower { class ExceptionSwallower {
public: 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 <a
//! href="https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#death-test-styles">gtest
//! death test style</a> 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 parents
//! 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” <a
//! href="https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#death-test-styles">gtest
//! death test style</a> are not qualified.
static void Child_SwallowExceptions();
private:
ExceptionSwallower(); ExceptionSwallower();
~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
//! its called, SwallowExceptions() must not be called from the same process.
void SetParent();
//! \brief In a test child process, arranges to swallow `EXC_CRASH` and //! \brief In a test child process, arranges to swallow `EXC_CRASH` and
//! `EXC_CORPSE_NOTIFY` exceptions. //! `EXC_CORPSE_NOTIFY` exceptions.
//! //!
//! This must be called in a test child process. It must not be called from a //! This must be called in a test child process. It must not be called from a
//! parent test process directly. //! parent test process directly. Parent test processes are those that execute
void SwallowExceptions(); //! `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 std::unique_ptr<ExceptionSwallowerThread> exception_swallower_thread_;
// 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 its safe to exit.
base::ScopedFD fd_;
pid_t parent_pid_;
DISALLOW_COPY_AND_ASSIGN(ExceptionSwallower); DISALLOW_COPY_AND_ASSIGN(ExceptionSwallower);
}; };

View File

@ -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 <getopt.h>
#include <libgen.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string>
#include <utility>
#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 doesnt 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() wont do
// because the client started this process with a double-fork().
pid_t peer_pid;
socklen_t peer_pid_size = base::checked_cast<socklen_t>(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<uint8_t>(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);
}

View File

@ -74,13 +74,12 @@ void Multiprocess::Run() {
ASSERT_NO_FATAL_FAILURE(PreFork()); ASSERT_NO_FATAL_FAILURE(PreFork());
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
// If the child is expected to crash, set up an exception swallower process // If the child is expected to crash, set up an exception swallower to swallow
// to swallow the exception instead of allowing it to be seen by the systems // the exception instead of allowing it to be seen by the systems crash
// crash reporter. // reporter.
const bool swallow_exceptions = std::unique_ptr<ExceptionSwallower> exception_swallower;
reason_ == kTerminationSignal && Signals::IsCrashSignal(code_); if (reason_ == kTerminationSignal && Signals::IsCrashSignal(code_)) {
if (swallow_exceptions) { exception_swallower.reset(new ExceptionSwallower());
ExceptionSwallower::Parent_PrepareForCrashingChild();
} }
#endif // OS_MACOSX #endif // OS_MACOSX
@ -141,8 +140,8 @@ void Multiprocess::Run() {
} }
} else { } else {
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
if (swallow_exceptions) { if (exception_swallower.get()) {
ExceptionSwallower::Child_SwallowExceptions(); ExceptionSwallower::SwallowExceptions();
} }
#endif // OS_MACOSX #endif // OS_MACOSX

View File

@ -84,7 +84,7 @@
'conditions': [ 'conditions': [
['OS=="mac"', { ['OS=="mac"', {
'dependencies': [ 'dependencies': [
'crashpad_exception_swallower', '../handler/handler.gyp:crashpad_handler_lib',
], ],
'link_settings': { 'link_settings': {
'libraries': [ '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',
],
},
],
}],
],
} }