Chromium Code Reviews| Index: client/crashpad_client_mac.cc |
| diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2d4aa91b7ec0e7023646fad2a01c0af09a108d2d |
| --- /dev/null |
| +++ b/client/crashpad_client_mac.cc |
| @@ -0,0 +1,192 @@ |
| +// 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 "client/crashpad_client.h" |
| + |
| +#include <mach/mach.h> |
| +#include <sys/wait.h> |
| +#include <unistd.h> |
| + |
| +#include "base/logging.h" |
| +#include "base/posix/eintr_wrapper.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "client/crashpad_client.h" |
| +#include "util/mach/child_port_handshake.h" |
| +#include "util/mach/exception_ports.h" |
| +#include "util/mach/mach_extensions.h" |
| +#include "util/posix/close_multiple.h" |
| + |
| +namespace crashpad { |
| + |
| +CrashpadClient::CrashpadClient() |
| + : exception_port_() { |
| +} |
| + |
| +CrashpadClient::~CrashpadClient() { |
| +} |
| + |
| +bool CrashpadClient::StartHandler( |
| + const base::FilePath& handler, |
| + const std::vector<std::string>& handler_arguments) { |
| + DCHECK_EQ(exception_port_, kMachPortNull); |
| + |
| + // 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; |
| + int handshake_fd = child_port_handshake.ReadPipeFD(); |
| + std::string handshake_fd_arg = |
| + base::StringPrintf("--handshake-fd=%d", handshake_fd); |
| + |
| + const std::string& handler_s = handler.value(); |
| + const char* const handler_c = handler_s.c_str(); |
| + |
| + // Use handler as argv[0], followed by handler_arguments, handshake_fd_arg, |
| + // and a nullptr terminator. |
| + std::vector<const char*> argv(1, handler_c); |
| + argv.reserve(1 + handler_arguments.size() + 1 + 1); |
| + for (const std::string& handler_argument : handler_arguments) { |
| + argv.push_back(handler_argument.c_str()); |
| + } |
| + argv.push_back(handshake_fd_arg.c_str()); |
| + argv.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; |
| + } |
| + |
| + 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 terminale. The |
|
Robert Sesek
2014/12/29 16:34:03
sp: terminale
|
| + // 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, handshake_fd); |
| + |
| + // &argv[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[0])); |
| + PLOG(FATAL) << "execvp " << handler_s; |
| + } |
| + |
| + // Parent process. |
| + |
| + // 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); |
| + } |
| + |
| + // Rendezvous with the handler running in the grandchild process. |
| + exception_port_.reset(child_port_handshake.RunServer()); |
| + |
| + return exception_port_ ? true : false; |
| +} |
| + |
| +bool CrashpadClient::UseHandler() { |
| + DCHECK_NE(exception_port_, kMachPortNull); |
| + |
| + // Set the exception handler for EXC_CRASH, EXC_RESOURCE, and EXC_GUARD. |
| + // |
| + // EXC_CRASH is how most crashes are received. Most other exception types such |
| + // as EXC_BAD_ACCESS are delivered to a host-level exception handler in the |
| + // kernel where they are converted to POSIX signals. See 10.9.5 |
| + // xnu-2422.115.4/bsd/uxkern/ux_exception.c catch_mach_exception_raise(). If a |
| + // core-generating signal (triggered through this hardware mechanism or a |
| + // software mechanism such as abort() sending SIGABRT) is unhandled and the |
| + // process exits, the exception becomes EXC_CRASH. See 10.9.5 |
| + // xnu-2422.115.4/bsd/kern/kern_exit.c proc_prepareexit(). |
| + // |
| + // EXC_RESOURCE and EXC_GUARD do not become signals or EXC_CRASH exceptions. |
| + // The host-level exception handler in the kernel does not receive these |
| + // exception types, and even if it did, it would not map them to signals. |
| + // Instead, the first Mach service loaded by the root (process ID 1) launchd |
| + // with a boolean “ExceptionServer” property in its job dictionary (regardless |
| + // of its value) or with any subdictionary property will become the host-level |
| + // exception handler for EXC_CRASH, EXC_RESOURCE, and EXC_GUARD. See 10.9.5 |
| + // launchd-842.92.1/src/core.c job_setup_exception_port(). Normally, this job |
| + // is com.apple.ReportCrash.Root, the systemwide Apple Crash Reporter. Since |
| + // it is impossible to receive EXC_RESOURCE and EXC_GUARD exceptions through |
| + // the EXC_CRASH mechanism, an exception handler must be registered for them |
| + // by name if it is to receive these exception types. The default task-level |
| + // handler for these exception types is set by launchd in a similar manner. |
| + // |
| + // EXC_MASK_RESOURCE and EXC_MASK_GUARD are not available on all systems, and |
| + // the kernel will reject attempts to use them if it does not understand them, |
| + // so AND them with ExcMaskAll(). EXC_MASK_CRASH is not present in |
| + // ExcMaskAll() but is always supported. See the documentation for |
| + // ExcMaskAll(). |
| + ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); |
| + if (!exception_ports.SetExceptionPort( |
| + EXC_MASK_CRASH | |
| + ((EXC_MASK_RESOURCE | EXC_MASK_GUARD) & ExcMaskAll()), |
| + exception_port_, |
| + EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, |
| + MACHINE_THREAD_STATE)) { |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +} // namespace crashpad |