diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 05766fba..eb3fc6de 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -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& 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 `"\\.\pipe\NAME"`. //! @@ -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 task’s exception port for `EXC_CRASH`, //! `EXC_RESOURCE`, and `EXC_GUARD` exceptions to the Mach send right obtained diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc index bb6a7805..d5e3545b 100644 --- a/client/crashpad_client_mac.cc +++ b/client/crashpad_client_mac.cc @@ -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()); diff --git a/handler/crashpad_handler.ad b/handler/crashpad_handler.ad index a97e2088..f359af76 100644 --- a/handler/crashpad_handler.ad +++ b/handler/crashpad_handler.ad @@ -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 job’s +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, diff --git a/handler/mac/exception_handler_server.cc b/handler/mac/exception_handler_server.cc index 3757c454..19038293 100644 --- a/handler/mac/exception_handler_server.cc +++ b/handler/mac/exception_handler_server.cc @@ -14,7 +14,12 @@ #include "handler/mac/exception_handler_server.h" +#include + +#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; + +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 that’s 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> 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( + &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(); } diff --git a/handler/mac/exception_handler_server.h b/handler/mac/exception_handler_server.h index 05c4056f..df9d5a0d 100644 --- a/handler/mac/exception_handler_server.h +++ b/handler/mac/exception_handler_server.h @@ -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); }; diff --git a/handler/main.cc b/handler/main.cc index 9a9b6f5d..879122d5 100644 --- a/handler/main.cc +++ b/handler/main.cc @@ -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 @@ -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()) { + // Don’t 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);