| Index: client/crashpad_client_mac.cc
|
| diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc
|
| index b95c07aa058bb3d4364520399f698331b74e631e..caf7e91bfd499fd39e607671186f9fe0526accf9 100644
|
| --- a/client/crashpad_client_mac.cc
|
| +++ b/client/crashpad_client_mac.cc
|
| @@ -19,11 +19,13 @@
|
| #include <unistd.h>
|
|
|
| #include "base/logging.h"
|
| +#include "base/mac/mach_logging.h"
|
| #include "base/posix/eintr_wrapper.h"
|
| #include "base/strings/stringprintf.h"
|
| #include "util/mach/child_port_handshake.h"
|
| #include "util/mach/exception_ports.h"
|
| #include "util/mach/mach_extensions.h"
|
| +#include "util/misc/implicit_cast.h"
|
| #include "util/posix/close_multiple.h"
|
|
|
| namespace crashpad {
|
| @@ -77,144 +79,223 @@ bool SetCrashExceptionPorts(exception_handler_t exception_handler) {
|
| MACHINE_THREAD_STATE);
|
| }
|
|
|
| -} // namespace
|
| -
|
| -CrashpadClient::CrashpadClient()
|
| - : exception_port_() {
|
| -}
|
| -
|
| -CrashpadClient::~CrashpadClient() {
|
| -}
|
| +//! \brief Starts a Crashpad handler.
|
| +class HandlerStarter final {
|
| + public:
|
| + //! \brief Starts a Crashpad handler.
|
| + //!
|
| + //! All parameters are as in CrashpadClient::StartHandler().
|
| + //!
|
| + //! \return On success, a send right to the Crashpad handler that has been
|
| + //! started. On failure, `MACH_PORT_NULL` with a message logged.
|
| + static base::mac::ScopedMachSendRight Start(
|
| + const base::FilePath& handler,
|
| + const base::FilePath& database,
|
| + const std::string& url,
|
| + const std::map<std::string, std::string>& annotations,
|
| + const std::vector<std::string>& arguments) {
|
| + base::mac::ScopedMachReceiveRight receive_right(
|
| + NewMachPort(MACH_PORT_RIGHT_RECEIVE));
|
| + if (receive_right == kMachPortNull) {
|
| + return base::mac::ScopedMachSendRight();
|
| + }
|
|
|
| -bool CrashpadClient::StartHandler(
|
| - const base::FilePath& handler,
|
| - const base::FilePath& database,
|
| - const std::string& url,
|
| - const std::map<std::string, std::string>& annotations,
|
| - const std::vector<std::string>& arguments) {
|
| - DCHECK(!exception_port_.is_valid());
|
| + mach_port_t port;
|
| + mach_msg_type_name_t right_type;
|
| + kern_return_t kr = mach_port_extract_right(mach_task_self(),
|
| + receive_right.get(),
|
| + MACH_MSG_TYPE_MAKE_SEND,
|
| + &port,
|
| + &right_type);
|
| + if (kr != KERN_SUCCESS) {
|
| + MACH_LOG(ERROR, kr) << "mach_port_extract_right";
|
| + return base::mac::ScopedMachSendRight();
|
| + }
|
| + base::mac::ScopedMachSendRight send_right(port);
|
| + DCHECK_EQ(port, receive_right.get());
|
| + DCHECK_EQ(right_type,
|
| + implicit_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND));
|
| +
|
| + if (!CommonStart(handler,
|
| + database,
|
| + url,
|
| + annotations,
|
| + arguments,
|
| + receive_right.Pass())) {
|
| + return base::mac::ScopedMachSendRight();
|
| + }
|
|
|
| - // Set up the arguments for execve() first. These aren’t needed until execve()
|
| - // is called, but it’s dangerous to do this in a child process after fork().
|
| - ChildPortHandshake child_port_handshake;
|
| - base::ScopedFD client_read_fd = child_port_handshake.ClientReadFD();
|
| -
|
| - // Use handler as argv[0], followed by arguments directed by this method’s
|
| - // parameters and a --handshake-fd argument. |arguments| are added first so
|
| - // that if it erroneously contains an argument such as --url, the actual |url|
|
| - // argument passed to this method will supersede it. In normal command-line
|
| - // processing, the last parameter wins in the case of a conflict.
|
| - std::vector<std::string> argv(1, handler.value());
|
| - argv.reserve(1 + arguments.size() + 2 + annotations.size() + 1);
|
| - for (const std::string& argument : arguments) {
|
| - argv.push_back(argument);
|
| - }
|
| - if (!database.value().empty()) {
|
| - argv.push_back(FormatArgumentString("database", database.value()));
|
| - }
|
| - if (!url.empty()) {
|
| - argv.push_back(FormatArgumentString("url", url));
|
| - }
|
| - for (const auto& kv : annotations) {
|
| - argv.push_back(
|
| - FormatArgumentString("annotation", kv.first + '=' + kv.second));
|
| - }
|
| - argv.push_back(FormatArgumentInt("handshake-fd", client_read_fd.get()));
|
| -
|
| - // argv_c contains const char* pointers and is terminated by nullptr. argv
|
| - // is required because the pointers in argv_c need to point somewhere, and
|
| - // they can’t point to temporaries such as those returned by
|
| - // FormatArgumentString().
|
| - std::vector<const char*> argv_c;
|
| - argv_c.reserve(argv.size() + 1);
|
| - for (const std::string& argument : argv) {
|
| - argv_c.push_back(argument.c_str());
|
| - }
|
| - argv_c.push_back(nullptr);
|
| -
|
| - // Double-fork(). The three processes involved are parent, child, and
|
| - // grandchild. The grandchild will become the handler process. The child exits
|
| - // immediately after spawning the grandchild, so the grandchild becomes an
|
| - // orphan and its parent process ID becomes 1. This relieves the parent and
|
| - // child of the responsibility for reaping the grandchild with waitpid() or
|
| - // similar. The handler process is expected to outlive the parent process, so
|
| - // the parent shouldn’t be concerned with reaping it. This approach means that
|
| - // accidental early termination of the handler process will not result in a
|
| - // zombie process.
|
| - pid_t pid = fork();
|
| - if (pid < 0) {
|
| - PLOG(ERROR) << "fork";
|
| - return false;
|
| + return send_right;
|
| }
|
|
|
| - if (pid == 0) {
|
| - // Child process.
|
| -
|
| - // Call setsid(), creating a new process group and a new session, both led
|
| - // by this process. The new process group has no controlling terminal. This
|
| - // disconnects it from signals generated by the parent process’ terminal.
|
| - //
|
| - // setsid() is done in the child instead of the grandchild so that the
|
| - // grandchild will not be a session leader. If it were a session leader, an
|
| - // accidental open() of a terminal device without O_NOCTTY would make that
|
| - // terminal the controlling terminal.
|
| - //
|
| - // It’s not desirable for the handler to have a controlling terminal. The
|
| - // handler monitors clients on its own and manages its own lifetime, exiting
|
| - // when it loses all clients and when it deems it appropraite to do so. It
|
| - // may serve clients in different process groups or sessions than its
|
| - // original client, and receiving signals intended for its original client’s
|
| - // process group could be harmful in that case.
|
| - PCHECK(setsid() != -1) << "setsid";
|
| -
|
| - pid = fork();
|
| + private:
|
| + static bool CommonStart(const base::FilePath& handler,
|
| + const base::FilePath& database,
|
| + const std::string& url,
|
| + const std::map<std::string, std::string>& annotations,
|
| + const std::vector<std::string>& arguments,
|
| + base::mac::ScopedMachReceiveRight receive_right) {
|
| + // Set up the arguments for execve() first. These aren’t needed until
|
| + // execve() is called, but it’s dangerous to do this in a child process
|
| + // after fork().
|
| + ChildPortHandshake child_port_handshake;
|
| + base::ScopedFD server_write_fd = child_port_handshake.ServerWriteFD();
|
| +
|
| + // Use handler as argv[0], followed by arguments directed by this method’s
|
| + // parameters and a --handshake-fd argument. |arguments| are added first so
|
| + // that if it erroneously contains an argument such as --url, the actual
|
| + // |url| argument passed to this method will supersede it. In normal
|
| + // command-line processing, the last parameter wins in the case of a
|
| + // conflict.
|
| + std::vector<std::string> argv(1, handler.value());
|
| + argv.reserve(1 + arguments.size() + 2 + annotations.size() + 1);
|
| + for (const std::string& argument : arguments) {
|
| + argv.push_back(argument);
|
| + }
|
| + if (!database.value().empty()) {
|
| + argv.push_back(FormatArgumentString("database", database.value()));
|
| + }
|
| + if (!url.empty()) {
|
| + argv.push_back(FormatArgumentString("url", url));
|
| + }
|
| + for (const auto& kv : annotations) {
|
| + argv.push_back(
|
| + FormatArgumentString("annotation", kv.first + '=' + kv.second));
|
| + }
|
| + argv.push_back(FormatArgumentInt("handshake-fd", server_write_fd.get()));
|
| +
|
| + const char* handler_c = handler.value().c_str();
|
| +
|
| + // argv_c contains const char* pointers and is terminated by nullptr. argv
|
| + // is required because the pointers in argv_c need to point somewhere, and
|
| + // they can’t point to temporaries such as those returned by
|
| + // FormatArgumentString().
|
| + std::vector<const char*> argv_c;
|
| + argv_c.reserve(argv.size() + 1);
|
| + for (const std::string& argument : argv) {
|
| + argv_c.push_back(argument.c_str());
|
| + }
|
| + argv_c.push_back(nullptr);
|
| +
|
| + // Double-fork(). The three processes involved are parent, child, and
|
| + // grandchild. The grandchild will become the handler process. The child
|
| + // exits immediately after spawning the grandchild, so the grandchild
|
| + // becomes an orphan and its parent process ID becomes 1. This relieves the
|
| + // parent and child of the responsibility for reaping the grandchild with
|
| + // waitpid() or similar. The handler process is expected to outlive the
|
| + // parent process, so the parent shouldn’t be concerned with reaping it.
|
| + // This approach means that accidental early termination of the handler
|
| + // process will not result in a zombie process.
|
| + pid_t pid = fork();
|
| if (pid < 0) {
|
| - PLOG(FATAL) << "fork";
|
| + PLOG(ERROR) << "fork";
|
| + return false;
|
| }
|
|
|
| - if (pid > 0) {
|
| + if (pid == 0) {
|
| // Child process.
|
|
|
| - // _exit() instead of exit(), because fork() was called.
|
| - _exit(EXIT_SUCCESS);
|
| + // Call setsid(), creating a new process group and a new session, both led
|
| + // by this process. The new process group has no controlling terminal.
|
| + // This disconnects it from signals generated by the parent process’
|
| + // terminal.
|
| + //
|
| + // setsid() is done in the child instead of the grandchild so that the
|
| + // grandchild will not be a session leader. If it were a session leader,
|
| + // an accidental open() of a terminal device without O_NOCTTY would make
|
| + // that terminal the controlling terminal.
|
| + //
|
| + // It’s not desirable for the handler to have a controlling terminal. The
|
| + // handler monitors clients on its own and manages its own lifetime,
|
| + // exiting when it loses all clients and when it deems it appropraite to
|
| + // do so. It may serve clients in different process groups or sessions
|
| + // than its original client, and receiving signals intended for its
|
| + // original client’s process group could be harmful in that case.
|
| + PCHECK(setsid() != -1) << "setsid";
|
| +
|
| + pid = fork();
|
| + if (pid < 0) {
|
| + PLOG(FATAL) << "fork";
|
| + }
|
| +
|
| + if (pid > 0) {
|
| + // Child process.
|
| +
|
| + // _exit() instead of exit(), because fork() was called.
|
| + _exit(EXIT_SUCCESS);
|
| + }
|
| +
|
| + // Grandchild process.
|
| +
|
| + CloseMultipleNowOrOnExec(STDERR_FILENO + 1, server_write_fd.get());
|
| +
|
| + // &argv_c[0] is a pointer to a pointer to const char data, but because of
|
| + // how C (not C++) works, execvp() wants a pointer to a const pointer to
|
| + // char data. It modifies neither the data nor the pointers, so the
|
| + // const_cast is safe.
|
| + execvp(handler_c, const_cast<char* const*>(&argv_c[0]));
|
| + PLOG(FATAL) << "execvp " << handler_c;
|
| }
|
|
|
| - // Grandchild process.
|
| + // Parent process.
|
| +
|
| + // Close the write side of the pipe, so that the handler process is the only
|
| + // process that can write to it.
|
| + server_write_fd.reset();
|
| +
|
| + // waitpid() for the child, so that it does not become a zombie process. The
|
| + // child normally exits quickly.
|
| + int status;
|
| + pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
|
| + PCHECK(wait_pid != -1) << "waitpid";
|
| + DCHECK_EQ(wait_pid, pid);
|
| +
|
| + if (WIFSIGNALED(status)) {
|
| + LOG(WARNING) << "intermediate process: signal " << WTERMSIG(status);
|
| + } else if (!WIFEXITED(status)) {
|
| + DLOG(WARNING) << "intermediate process: unknown termination " << status;
|
| + } else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
|
| + LOG(WARNING) << "intermediate process: exit status "
|
| + << WEXITSTATUS(status);
|
| + }
|
|
|
| - CloseMultipleNowOrOnExec(STDERR_FILENO + 1, client_read_fd.get());
|
| + // Rendezvous with the handler running in the grandchild process.
|
| + if (!child_port_handshake.RunClient(receive_right.get(),
|
| + MACH_MSG_TYPE_MOVE_RECEIVE)) {
|
| + return false;
|
| + }
|
|
|
| - // &argv_c[0] is a pointer to a pointer to const char data, but because of
|
| - // how C (not C++) works, execvp() wants a pointer to a const pointer to
|
| - // char data. It modifies neither the data nor the pointers, so the
|
| - // const_cast is safe.
|
| - execvp(handler.value().c_str(), const_cast<char* const*>(&argv_c[0]));
|
| - PLOG(FATAL) << "execvp " << handler.value();
|
| + ignore_result(receive_right.release());
|
| + return true;
|
| }
|
|
|
| - // Parent process.
|
| + DISALLOW_IMPLICIT_CONSTRUCTORS(HandlerStarter);
|
| +};
|
|
|
| - client_read_fd.reset();
|
| +} // namespace
|
|
|
| - // waitpid() for the child, so that it does not become a zombie process. The
|
| - // child normally exits quickly.
|
| - int status;
|
| - pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
|
| - PCHECK(wait_pid != -1) << "waitpid";
|
| - DCHECK_EQ(wait_pid, pid);
|
| +CrashpadClient::CrashpadClient()
|
| + : exception_port_() {
|
| +}
|
|
|
| - if (WIFSIGNALED(status)) {
|
| - LOG(WARNING) << "intermediate process: signal " << WTERMSIG(status);
|
| - } else if (!WIFEXITED(status)) {
|
| - DLOG(WARNING) << "intermediate process: unknown termination " << status;
|
| - } else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
|
| - LOG(WARNING) << "intermediate process: exit status " << WEXITSTATUS(status);
|
| - }
|
| +CrashpadClient::~CrashpadClient() {
|
| +}
|
| +
|
| +bool CrashpadClient::StartHandler(
|
| + const base::FilePath& handler,
|
| + const base::FilePath& database,
|
| + const std::string& url,
|
| + const std::map<std::string, std::string>& annotations,
|
| + const std::vector<std::string>& arguments) {
|
| + DCHECK(!exception_port_.is_valid());
|
|
|
| - // Rendezvous with the handler running in the grandchild process.
|
| - exception_port_.reset(child_port_handshake.RunServer(
|
| - ChildPortHandshake::PortRightType::kSendRight));
|
| + exception_port_ = HandlerStarter::Start(
|
| + handler, database, url, annotations, arguments);
|
| + if (!exception_port_.is_valid()) {
|
| + return false;
|
| + }
|
|
|
| - return exception_port_.is_valid();
|
| + return true;
|
| }
|
|
|
| bool CrashpadClient::UseHandler() {
|
|
|