diff --git a/client/BUILD.gn b/client/BUILD.gn index 9d8b736f..9d1caaf1 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -87,6 +87,16 @@ static_library("client") { libs = [ "rpcrt4.lib" ] cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union } + + if (crashpad_is_fuchsia) { + if (crashpad_is_in_fuchsia) { + deps += [ + "//zircon/public/lib/launchpad", + ] + } else { + libs = [ "launchpad" ] + } + } } source_set("client_test") { diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 267a6e20..ae409d43 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -75,6 +75,9 @@ class CrashpadClient { //! Crashpad. Optionally, use WaitForHandlerStart() to join with the //! background thread and retrieve the status of handler startup. //! + //! On Fuchsia, this method binds to the exception port of the current default + //! job, and starts a Crashpad handler to monitor that port. + //! //! \param[in] handler The path to a Crashpad handler executable. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. diff --git a/client/crashpad_client_fuchsia.cc b/client/crashpad_client_fuchsia.cc index ea179b85..59cf3873 100644 --- a/client/crashpad_client_fuchsia.cc +++ b/client/crashpad_client_fuchsia.cc @@ -14,7 +14,16 @@ #include "client/crashpad_client.h" +#include +#include +#include + +#include "base/fuchsia/fuchsia_logging.h" +#include "base/fuchsia/scoped_zx_handle.h" #include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "client/client_argv_handling.h" +#include "util/fuchsia/system_exception_port_key.h" namespace crashpad { @@ -31,8 +40,79 @@ bool CrashpadClient::StartHandler( const std::vector& arguments, bool restartable, bool asynchronous_start) { - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196 - return false; + DCHECK_EQ(restartable, false); // Not used on Fuchsia. + DCHECK_EQ(asynchronous_start, false); // Not used on Fuchsia. + + zx_handle_t exception_port_raw; + zx_status_t status = zx_port_create(0, &exception_port_raw); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_port_create"; + return false; + } + base::ScopedZxHandle exception_port(exception_port_raw); + + status = zx_task_bind_exception_port( + zx_job_default(), exception_port.get(), kSystemExceptionPortKey, 0); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_task_bind_exception_port"; + return false; + } + + std::vector argv_strings; + BuildHandlerArgvStrings(handler, + database, + metrics_dir, + url, + annotations, + arguments, + &argv_strings); + + std::vector argv; + ConvertArgvStrings(argv_strings, &argv); + // ConvertArgvStrings adds an unnecessary nullptr at the end of the argv list, + // which causes launchpad_set_args() to hang. + argv.pop_back(); + + launchpad_t* lp; + launchpad_create(zx_job_default(), argv[0], &lp); + launchpad_load_from_file(lp, argv[0]); + launchpad_set_args(lp, argv.size(), &argv[0]); + + // TODO(scottmg): https://crashpad.chromium.org/bug/196, this is useful during + // bringup, but should probably be made minimal for real usage. + launchpad_clone(lp, + LP_CLONE_FDIO_NAMESPACE | LP_CLONE_FDIO_STDIO | + LP_CLONE_ENVIRON | LP_CLONE_DEFAULT_JOB); + + // Follow the same protocol as devmgr and crashlogger in Zircon (that is, + // process handle as handle 0, with type USER0, exception port handle as + // handle 1, also with type PA_USER0) so that it's trivial to replace + // crashlogger with crashpad_handler. The exception port is passed on, so + // released here. Currently it is assumed that this process's default job + // handle is the exception port that should be monitored. In the future, it + // might be useful for this to be configurable by the client. + zx_handle_t handles[] = {ZX_HANDLE_INVALID, ZX_HANDLE_INVALID}; + status = + zx_handle_duplicate(zx_job_default(), ZX_RIGHT_SAME_RIGHTS, &handles[0]); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_handle_duplicate"; + return false; + } + handles[1] = exception_port.release(); + uint32_t handle_types[] = {PA_HND(PA_USER0, 0), PA_HND(PA_USER0, 1)}; + + launchpad_add_handles(lp, arraysize(handles), handles, handle_types); + + const char* error_message; + zx_handle_t child_raw; + status = launchpad_go(lp, &child_raw, &error_message); + base::ScopedZxHandle child(child_raw); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "launchpad_go: " << error_message; + return false; + } + + return true; } } // namespace crashpad diff --git a/handler/fuchsia/crash_report_exception_handler.h b/handler/fuchsia/crash_report_exception_handler.h index c987a5dc..eefbd54c 100644 --- a/handler/fuchsia/crash_report_exception_handler.h +++ b/handler/fuchsia/crash_report_exception_handler.h @@ -15,6 +15,8 @@ #ifndef CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_ #define CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_ +#include + #include #include @@ -56,6 +58,24 @@ class CrashReportExceptionHandler { ~CrashReportExceptionHandler(); + //! \brief Called when the exception handler server has caught an exception + //! and wants a crash dump to be taken. + //! + //! This function is expected to call `zx_task_resume()` in order to complete + //! handling of the exception. + //! + //! \note TODO(scottmg): This is not yet implemented. + //! + //! \param[in] type The type of exception, a `ZX_EXCP_*` value. + //! \param[in] pid The koid of the process which sustained the exception. + //! \param[in] tid The koid of the thread which sustained the exception. + //! \return `true` on success, or `false` with an error logged. + bool HandleException(uint32_t type, + uint64_t pid, + uint64_t tid) { + return false; + } + private: DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler); }; diff --git a/handler/fuchsia/exception_handler_server.cc b/handler/fuchsia/exception_handler_server.cc index d1dfcdf0..550a04af 100644 --- a/handler/fuchsia/exception_handler_server.cc +++ b/handler/fuchsia/exception_handler_server.cc @@ -14,16 +14,47 @@ #include "handler/fuchsia/exception_handler_server.h" +#include +#include + +#include + +#include "base/fuchsia/fuchsia_logging.h" #include "base/logging.h" +#include "handler/fuchsia/crash_report_exception_handler.h" +#include "util/fuchsia/system_exception_port_key.h" namespace crashpad { -ExceptionHandlerServer::ExceptionHandlerServer() {} +ExceptionHandlerServer::ExceptionHandlerServer( + base::ScopedZxHandle root_job, + base::ScopedZxHandle exception_port) + : root_job_(std::move(root_job)), + exception_port_(std::move(exception_port)) {} -ExceptionHandlerServer::~ExceptionHandlerServer() {} +ExceptionHandlerServer::~ExceptionHandlerServer() = default; void ExceptionHandlerServer::Run(CrashReportExceptionHandler* handler) { - NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196 + while (true) { + zx_port_packet_t packet; + zx_status_t status = + zx_port_wait(exception_port_.get(), ZX_TIME_INFINITE, &packet, 1); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_port_wait, aborting"; + return; + } + + if (packet.key != kSystemExceptionPortKey) { + LOG(ERROR) << "unexpected packet key, ignoring"; + continue; + } + + bool result = handler->HandleException( + packet.type, packet.exception.pid, packet.exception.tid); + if (!result) { + LOG(ERROR) << "HandleException failed"; + } + } } } // namespace crashpad diff --git a/handler/fuchsia/exception_handler_server.h b/handler/fuchsia/exception_handler_server.h index b998ba9c..184d1489 100644 --- a/handler/fuchsia/exception_handler_server.h +++ b/handler/fuchsia/exception_handler_server.h @@ -16,17 +16,25 @@ #define CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_ #include "base/macros.h" +#include "base/fuchsia/scoped_zx_handle.h" namespace crashpad { class CrashReportExceptionHandler; //! \brief Runs the main exception-handling server in Crashpad's handler -//! process. This class is not yet implemented. +//! process. class ExceptionHandlerServer { public: //! \brief Constructs an ExceptionHandlerServer object. - ExceptionHandlerServer(); + //! + //! \param[in] root_job The root of the tree of processes that will be handled + //! by this server. It is assumed that \a exception_port is the exception + //! port of this job. + //! \param[in] exception_port The exception port that this server will + //! monitor. + ExceptionHandlerServer(base::ScopedZxHandle root_job, + base::ScopedZxHandle exception_port); ~ExceptionHandlerServer(); //! \brief Runs the exception-handling server. @@ -36,6 +44,9 @@ class ExceptionHandlerServer { void Run(CrashReportExceptionHandler* handler); private: + base::ScopedZxHandle root_job_; + base::ScopedZxHandle exception_port_; + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer); }; diff --git a/handler/handler_main.cc b/handler/handler_main.cc index dd7adf65..70192cf4 100644 --- a/handler/handler_main.cc +++ b/handler/handler_main.cc @@ -83,6 +83,9 @@ #include "util/win/initial_client_data.h" #include "util/win/session_end_watcher.h" #elif defined(OS_FUCHSIA) +#include +#include + #include "handler/fuchsia/crash_report_exception_handler.h" #include "handler/fuchsia/exception_handler_server.h" #elif defined(OS_LINUX) @@ -390,17 +393,19 @@ void InstallCrashHandler() { ALLOW_UNUSED_LOCAL(terminate_handler); } -#elif defined(OS_FUCHSIA) || defined(OS_LINUX) +#elif defined(OS_FUCHSIA) void InstallCrashHandler() { - // TODO(scottmg): Fuchsia: https://crashpad.chromium.org/bug/196 - // TODO(jperaza): Linux: https://crashpad.chromium.org/bug/30 - NOTREACHED(); + // There's nothing to do here. Crashes in this process will already be caught + // here because this handler process is in the same job that has had its + // exception port bound. + + // TODO(scottmg): This should collect metrics on handler crashes, at a + // minimum. https://crashpad.chromium.org/bug/230. } void ReinstallCrashHandler() { // TODO(scottmg): Fuchsia: https://crashpad.chromium.org/bug/196 - // TODO(jperaza): Linux: https://crashpad.chromium.org/bug/30 NOTREACHED(); } @@ -901,7 +906,28 @@ int HandlerMain(int argc, if (!options.pipe_name.empty()) { exception_handler_server.SetPipeName(base::UTF8ToUTF16(options.pipe_name)); } -#elif defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_FUCHSIA) +#elif defined(OS_FUCHSIA) + // These handles are logically "moved" into these variables when retrieved by + // zx_get_startup_handle(). Both are given to ExceptionHandlerServer which + // owns them in this process. There is currently no "connect-later" mode on + // Fuchsia, all the binding must be done by the client before starting + // crashpad_handler. + base::ScopedZxHandle root_job(zx_get_startup_handle(PA_HND(PA_USER0, 0))); + if (!root_job.is_valid()) { + LOG(ERROR) << "no process handle passed in startup handle 0"; + return EXIT_FAILURE; + } + + base::ScopedZxHandle exception_port( + zx_get_startup_handle(PA_HND(PA_USER0, 1))); + if (!exception_port.is_valid()) { + LOG(ERROR) << "no exception port handle passed in startup handle 1"; + return EXIT_FAILURE; + } + + ExceptionHandlerServer exception_handler_server(std::move(root_job), + std::move(exception_port)); +#elif defined(OS_LINUX) || defined(OS_ANDROID) ExceptionHandlerServer exception_handler_server; #endif // OS_MACOSX