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..2858ac4802a704269bdaeb86fedeb3f5e354e21a |
--- /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 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, 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 |