mac: Add a mode to crashpad_handler to run from launchd

By invoking crashpad_handler with --mach-service instead of
--handshake-fd, the handler will run as a well-behaved launchd job. The
launchd job may be as a launch agent or launch daemon, or be submitted
to launchd by on_demand_service_tool.

BUG=crashpad:25
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/1414533006 .
This commit is contained in:
Mark Mentovai 2015-11-03 19:20:29 -05:00
parent ee6fc23fb3
commit 809affe793
6 changed files with 238 additions and 55 deletions

View File

@ -42,10 +42,9 @@ class CrashpadClient {
//! handshake to configure it.
//!
//! This method does not actually direct any crashes to the Crashpad handler,
//! because there may be alternative ways to use an existing Crashpad handler
//! without having to start one. To begin directing crashes to the handler,
//! started by this method, call UseHandler() after this method returns
//! successfully.
//! because there are alternative ways to use an existing Crashpad handler. To
//! begin directing crashes to the handler started by this method, call
//! UseHandler() after this method returns successfully.
//!
//! On Mac OS X, this method starts a Crashpad handler and obtains a Mach
//! send right corresponding to a receive right held by the handler process.
@ -76,6 +75,37 @@ class CrashpadClient {
const std::vector<std::string>& arguments,
bool restartable);
#if defined(OS_MACOSX) || DOXYGEN
//! \brief Sets the process crash handler to a Mach service registered with
//! the bootstrap server.
//!
//! This method does not actually direct any crashes to the Crashpad handler,
//! because there are alternative ways to start or use an existing Crashpad
//! handler. To begin directing crashes to the handler set by this method,
//! call UseHandler() after this method returns successfully.
//!
//! This method is only defined on OS X.
//!
//! \param[in] service_name The service name of a Crashpad exception handler
//! service previously registered with the bootstrap server.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool SetHandlerMachService(const std::string& service_name);
//! \brief Sets the process crash handler to a Mach port.
//!
//! This method does not actually direct any crashes to the Crashpad handler,
//! because there are alternative ways to start or use an existing Crashpad
//! handler. To begin directing crashes to the handler set by this method,
//! call UseHandler() after this method.
//!
//! This method is only defined on OS X.
//!
//! \param[in] exception_port An `exception_port_t` corresponding to a
//! Crashpad exception handler service.
void SetHandlerMachPort(base::mac::ScopedMachSendRight exception_port);
#endif
#if defined(OS_WIN) || DOXYGEN
//! \brief Sets the IPC pipe of a presumably-running Crashpad handler process
//! which was started with StartHandler() or by other compatible means
@ -83,6 +113,8 @@ class CrashpadClient {
//! handler. However, just like StartHandler(), crashes are not serviced
//! until UseHandler() is called.
//!
//! This method is only defined on Windows.
//!
//! \param[in] ipc_pipe The full name of the crash handler IPC pipe. This is
//! a string of the form `&quot;\\.\pipe\NAME&quot;`.
//!
@ -115,7 +147,8 @@ class CrashpadClient {
//! \brief Configures the process to direct its crashes to a Crashpad handler.
//!
//! The Crashpad handler must previously have been started by StartHandler()
//! or configured by SetHandlerIPCPipe().
//! or configured by SetHandlerMachService(), SetHandlerMachPort(), or
//! SetHandlerIPCPipe().
//!
//! On Mac OS X, this method sets the tasks exception port for `EXC_CRASH`,
//! `EXC_RESOURCE`, and `EXC_GUARD` exceptions to the Mach send right obtained

View File

@ -517,20 +517,37 @@ bool CrashpadClient::StartHandler(
// The “restartable” behavior can only be selected on OS X 10.10 and later. In
// previous OS versions, if the initial client were to crash while attempting
// to restart the handler, it would become an unkillable process.
exception_port_ = HandlerStarter::InitialStart(
base::mac::ScopedMachSendRight exception_port(HandlerStarter::InitialStart(
handler,
database,
url,
annotations,
arguments,
restartable && MacOSXMinorVersion() >= 10);
if (!exception_port_.is_valid()) {
restartable && MacOSXMinorVersion() >= 10));
if (!exception_port.is_valid()) {
return false;
}
SetHandlerMachPort(exception_port.Pass());
return true;
}
bool CrashpadClient::SetHandlerMachService(const std::string& service_name) {
base::mac::ScopedMachSendRight exception_port(BootstrapLookUp(service_name));
if (!exception_port.is_valid()) {
return false;
}
SetHandlerMachPort(exception_port.Pass());
return true;
}
void CrashpadClient::SetHandlerMachPort(
base::mac::ScopedMachSendRight exception_port) {
DCHECK(exception_port.is_valid());
exception_port_ = exception_port.Pass();
}
bool CrashpadClient::UseHandler() {
DCHECK(exception_port_.is_valid());

View File

@ -33,13 +33,21 @@ collection server. Uploads are disabled by default, and can only be enabled by a
Crashpad client using the Crashpad client library, typically in response to a
user requesting this behavior.
On OS X, this server is normally started by its initial client, and it performs
a handshake with this client via a pipe established by the client that is
inherited by the server, referenced by the *--handshake-fd* argument. During the
handshake, the server furnishes the client with a send right that the client may
use as an exception port. The server retains the corresponding receive right,
which it monitors for exception messages. When the receive right loses all
senders, the server exits after allowing any upload in progress to complete.
On OS X, this server may be started by its initial client, in which case it
performs a handshake with this client via a pipe established by the client that
is inherited by the server, referenced by the *--handshake-fd* argument. During
the handshake, the server furnishes the client with a send right that the client
may use as an exception port. The server retains the corresponding receive
right, which it monitors for exception messages. When the receive right loses
all senders, the server exits after allowing any upload in progress to complete.
Alternatively, on OS X, this server may be started from launchd(8), where it
receives the Mach service name in a *--mach-service* argument. It checks in with
the bootstrap server under this service name, and clients may look it up with
the bootstrap server under this service name. It monitors this service for
exception messages. Upon receipt of +SIGTERM+, the server exits after allowing
any upload in progress to complete. +SIGTERM+ is normally sent by launchd(8)
when it determines that the server should exit.
On Windows, clients register with this server by communicating with it via the
named pipe identified by the *--pipe-name* argument. During registration, a
@ -51,10 +59,10 @@ to exit cleanly without crashing. When the server loses all clients and
to complete.
It is not normally appropriate to invoke this program directly. Usually, it will
be invoked by a Crashpad client using the Crashpad client library. Arbitrary
programs may be run with a Crashpad handler by using
man_link:run_with_crashpad[1] to establish the Crashpad client environment
before running a program.
be invoked by a Crashpad client using the Crashpad client library, or started by
another system service. On OS X, arbitrary programs may be run with a Crashpad
handler by using man_link:run_with_crashpad[1] to establish the Crashpad client
environment before running a program.
== Options
*--annotation*='KEY=VALUE'::
@ -84,7 +92,18 @@ of 'PATH' exists.
*--handshake-fd*='FD'::
Perform the handshake with the initial client on the file descriptor at 'FD'.
This option is required. This option is only valid on Mac OS X.
Either this option or *--mach-service*, but not both, is required. This option
is only valid on OS X.
*--mach-service*='SERVICE'::
Check in with the bootstrap server under the name 'SERVICE'. Either this option
or *--handshake-fd*, but not both, is required. This option is only valid on OS
X.
+
'SERVICE' may already be reserved with the bootstrap server in cases where this
tool is started by launchd(8) as a result of a message being sent to a service
declared in a jobs +MachServices+ dictionary (see launchd.plist(5)). The
service name may also be completely unknown to the system.
*--persistent*::
Continue running after the last client exits. If this option is not specified,

View File

@ -14,7 +14,12 @@
#include "handler/mac/exception_handler_server.h"
#include <signal.h>
#include "base/auto_reset.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/scoped_generic.h"
#include "base/mac/mach_logging.h"
#include "util/mach/composite_mach_message_server.h"
#include "util/mach/mach_extensions.h"
@ -26,11 +31,59 @@ namespace crashpad {
namespace {
struct ResetSIGTERMTraits {
static struct sigaction* InvalidValue() {
return nullptr;
}
static void Free(struct sigaction* sa) {
int rv = sigaction(SIGTERM, sa, nullptr);
PLOG_IF(ERROR, rv != 0) << "sigaction";
}
};
using ScopedResetSIGTERM =
base::ScopedGeneric<struct sigaction*, ResetSIGTERMTraits>;
mach_port_t g_signal_notify_port;
// This signal handler is only operative when being run from launchd. It causes
// the exception handler server to stop running by sending it a synthesized
// no-senders notification.
void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) {
DCHECK(g_signal_notify_port);
// mach_no_senders_notification_t defines the receive side of this structure,
// with a trailer element thats undesirable for the send side.
struct {
mach_msg_header_t header;
NDR_record_t ndr;
mach_msg_type_number_t mscount;
} no_senders_notification = {};
no_senders_notification.header.msgh_bits =
MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND_ONCE, 0);
no_senders_notification.header.msgh_size = sizeof(no_senders_notification);
no_senders_notification.header.msgh_remote_port = g_signal_notify_port;
no_senders_notification.header.msgh_local_port = MACH_PORT_NULL;
no_senders_notification.header.msgh_id = MACH_NOTIFY_NO_SENDERS;
no_senders_notification.ndr = NDR_record;
no_senders_notification.mscount = 0;
kern_return_t kr = mach_msg(&no_senders_notification.header,
MACH_SEND_MSG,
sizeof(no_senders_notification),
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_msg";
}
class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface,
public NotifyServer::DefaultInterface {
public:
ExceptionHandlerServerRun(
mach_port_t exception_port,
bool launchd,
UniversalMachExcServer::Interface* exception_interface)
: UniversalMachExcServer::Interface(),
NotifyServer::DefaultInterface(),
@ -40,7 +93,8 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface,
exception_interface_(exception_interface),
exception_port_(exception_port),
notify_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)),
running_(true) {
running_(true),
launchd_(launchd) {
CHECK(notify_port_.is_valid());
composite_mach_message_server_.AddHandler(&mach_exc_server_);
@ -53,22 +107,45 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface,
void Run() {
DCHECK(running_);
// Request that a no-senders notification for exception_port_ be sent to
// notify_port_.
mach_port_t previous;
kern_return_t kr =
mach_port_request_notification(mach_task_self(),
exception_port_,
MACH_NOTIFY_NO_SENDERS,
0,
notify_port_.get(),
MACH_MSG_TYPE_MAKE_SEND_ONCE,
&previous);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_request_notification";
kern_return_t kr;
scoped_ptr<base::AutoReset<mach_port_t>> reset_signal_notify_port;
struct sigaction old_sa;
ScopedResetSIGTERM reset_sigterm;
if (!launchd_) {
// Request that a no-senders notification for exception_port_ be sent to
// notify_port_.
mach_port_t previous;
kr = mach_port_request_notification(mach_task_self(),
exception_port_,
MACH_NOTIFY_NO_SENDERS,
0,
notify_port_.get(),
MACH_MSG_TYPE_MAKE_SEND_ONCE,
&previous);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_request_notification";
if (previous != MACH_PORT_NULL) {
kr = mach_port_deallocate(mach_task_self(), previous);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_deallocate";
if (previous != MACH_PORT_NULL) {
kr = mach_port_deallocate(mach_task_self(), previous);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_deallocate";
}
} else {
// A real no-senders notification would never be triggered, because
// launchd maintains a send right to the service. When launchd wants the
// job to exit, it will send a SIGTERM. See launchd.plist(5).
//
// Set up a SIGTERM handler that will cause Run() to return (incidentally,
// by sending a synthetic no-senders notification).
struct sigaction sa = {};
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = HandleSIGTERM;
int rv = sigaction(SIGTERM, &sa, &old_sa);
PCHECK(rv == 0) << "sigaction";
reset_sigterm.reset(&old_sa);
DCHECK(!g_signal_notify_port);
reset_signal_notify_port.reset(new base::AutoReset<mach_port_t>(
&g_signal_notify_port, notify_port_.get()));
}
// A single CompositeMachMessageServer will dispatch both exception messages
@ -81,6 +158,7 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface,
// the proper send right.
base::mac::ScopedMachPortSet server_port_set(
NewMachPort(MACH_PORT_RIGHT_PORT_SET));
CHECK(server_port_set.is_valid());
kr = mach_port_insert_member(
mach_task_self(), exception_port_, server_port_set.get());
@ -173,6 +251,7 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface,
mach_port_t exception_port_; // weak
base::mac::ScopedMachReceiveRight notify_port_;
bool running_;
bool launchd_;
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerRun);
};
@ -180,8 +259,10 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface,
} // namespace
ExceptionHandlerServer::ExceptionHandlerServer(
base::mac::ScopedMachReceiveRight receive_port)
: receive_port_(receive_port.Pass()) {
base::mac::ScopedMachReceiveRight receive_port,
bool launchd)
: receive_port_(receive_port.Pass()),
launchd_(launchd) {
CHECK(receive_port_.is_valid());
}
@ -190,7 +271,8 @@ ExceptionHandlerServer::~ExceptionHandlerServer() {
void ExceptionHandlerServer::Run(
UniversalMachExcServer::Interface* exception_interface) {
ExceptionHandlerServerRun run(receive_port_.get(), exception_interface);
ExceptionHandlerServerRun run(
receive_port_.get(), launchd_, exception_interface);
run.Run();
}

View File

@ -32,20 +32,26 @@ class ExceptionHandlerServer {
//!
//! \param[in] receive_port The port that exception messages and no-senders
//! notifications will be received on.
explicit ExceptionHandlerServer(
base::mac::ScopedMachReceiveRight receive_port);
//! \param[in] launchd If `true`, the exception handler is being run from
//! launchd. \a receive_port is not monitored for no-senders
//! notifications, and instead, the expected “quit” signal is receipt of
//! `SIGTERM`.
ExceptionHandlerServer(base::mac::ScopedMachReceiveRight receive_port,
bool launchd);
~ExceptionHandlerServer();
//! \brief Runs the exception-handling server.
//!
//! \param[in] exception_interface An object to send exception messages to.
//!
//! This method monitors the receive port for exception messages and
//! no-senders notifications. It continues running until it has no more
//! clients, indicated by the receipt of a no-senders notification. It is
//! important to assure that a send right exists in a client (or has been
//! queued by `mach_msg()` to be sent to a client) prior to calling this
//! method, or it will detect that it is sender-less and return immediately.
//! This method monitors the receive port for exception messages and, if
//! not being run by launchd, no-senders notifications. It continues running
//! until it has no more clients, indicated by the receipt of a no-senders
//! notification, or if being run by launchd, receipt of `SIGTERM`. When not
//! being run by launchd, it is important to assure that a send right exists
//! in a client (or has been queued by `mach_msg()` to be sent to a client)
//! prior to calling this method, or it will detect that it is sender-less and
//! return immediately.
//!
//! All exception messages will be passed to \a exception_interface.
//!
@ -59,6 +65,7 @@ class ExceptionHandlerServer {
private:
base::mac::ScopedMachReceiveRight receive_port_;
bool launchd_;
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer);
};

View File

@ -38,6 +38,7 @@
#include "handler/mac/crash_report_exception_handler.h"
#include "handler/mac/exception_handler_server.h"
#include "util/mach/child_port_handshake.h"
#include "util/mach/mach_extensions.h"
#include "util/posix/close_stdio.h"
#elif defined(OS_WIN)
#include <windows.h>
@ -57,6 +58,7 @@ void Usage(const base::FilePath& me) {
" --database=PATH store the crash report database at PATH\n"
#if defined(OS_MACOSX)
" --handshake-fd=FD establish communication with the client over FD\n"
" --mach-service=SERVICE register SERVICE with the bootstrap server\n"
" --reset-own-crash-exception-port-to-system-default\n"
" reset the server's exception handler to default\n"
#elif defined(OS_WIN)
@ -83,6 +85,7 @@ int HandlerMain(int argc, char* argv[]) {
kOptionDatabase,
#if defined(OS_MACOSX)
kOptionHandshakeFD,
kOptionMachService,
kOptionResetOwnCrashExceptionPortToSystemDefault,
#elif defined(OS_WIN)
kOptionPersistent,
@ -101,6 +104,7 @@ int HandlerMain(int argc, char* argv[]) {
const char* database;
#if defined(OS_MACOSX)
int handshake_fd;
std::string mach_service;
bool reset_own_crash_exception_port_to_system_default;
#elif defined(OS_WIN)
bool persistent;
@ -117,6 +121,7 @@ int HandlerMain(int argc, char* argv[]) {
{"database", required_argument, nullptr, kOptionDatabase},
#if defined(OS_MACOSX)
{"handshake-fd", required_argument, nullptr, kOptionHandshakeFD},
{"mach-service", required_argument, nullptr, kOptionMachService},
{"reset-own-crash-exception-port-to-system-default",
no_argument,
nullptr,
@ -162,6 +167,10 @@ int HandlerMain(int argc, char* argv[]) {
}
break;
}
case kOptionMachService: {
options.mach_service = optarg;
break;
}
case kOptionResetOwnCrashExceptionPortToSystemDefault: {
options.reset_own_crash_exception_port_to_system_default = true;
break;
@ -198,8 +207,13 @@ int HandlerMain(int argc, char* argv[]) {
argv += optind;
#if defined(OS_MACOSX)
if (options.handshake_fd < 0) {
ToolSupport::UsageHint(me, "--handshake-fd is required");
if (options.handshake_fd < 0 && options.mach_service.empty()) {
ToolSupport::UsageHint(me, "--handshake-fd or --mach-service is required");
return EXIT_FAILURE;
}
if (options.handshake_fd >= 0 && !options.mach_service.empty()) {
ToolSupport::UsageHint(
me, "--handshake-fd and --mach-service are incompatible");
return EXIT_FAILURE;
}
#elif defined(OS_WIN)
@ -220,21 +234,32 @@ int HandlerMain(int argc, char* argv[]) {
}
#if defined(OS_MACOSX)
CloseStdinAndStdout();
if (options.mach_service.empty()) {
// Dont do this when being run by launchd. See launchd.plist(5).
CloseStdinAndStdout();
}
if (options.reset_own_crash_exception_port_to_system_default) {
CrashpadClient::UseSystemDefaultHandler();
}
base::mac::ScopedMachReceiveRight receive_right(
ChildPortHandshake::RunServerForFD(
base::ScopedFD(options.handshake_fd),
ChildPortHandshake::PortRightType::kReceiveRight));
base::mac::ScopedMachReceiveRight receive_right;
if (options.handshake_fd >= 0) {
receive_right.reset(
ChildPortHandshake::RunServerForFD(
base::ScopedFD(options.handshake_fd),
ChildPortHandshake::PortRightType::kReceiveRight));
} else if (!options.mach_service.empty()) {
receive_right = BootstrapCheckIn(options.mach_service);
}
if (!receive_right.is_valid()) {
return EXIT_FAILURE;
}
ExceptionHandlerServer exception_handler_server(receive_right.Pass());
ExceptionHandlerServer exception_handler_server(
receive_right.Pass(), !options.mach_service.empty());
#elif defined(OS_WIN)
ExceptionHandlerServer exception_handler_server(options.pipe_name,
options.persistent);