crashpad/client/crashpad_client_mac.cc
Mark Mentovai 2602e9d5d0 Add CrashpadClient.
CrashpadClient is the primary interface for an application to have
Crashpad monitor it for crashes. It contains StartHandler(), which
starts a Crashpad handler process, and UseHandler(), which configures
the process to direct its crashes to a handler process.

R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/785233011
2014-12-30 14:24:52 -05:00

193 lines
7.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 arent needed until execve()
// is called, but its 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 shouldnt 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.
//
// Its 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 clients
// 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