fuchsia: Implement StartHandler() and ExceptionHandlerServer

StartHandler() binds to the default job's exception port, and launches
the handler process (normally this is crashpad_handler), passing it the
task handle and a handle to the exception port as startup parameters.
This follows the protocol used by crashlogger.

Additionally, implement ExceptionHandlerServer in crashpad_handler,
which contains the exception processing loop. It currently dispatches to
an empty CrashReportExceptionHandler where a report will be written
eventually.

Bug: crashpad:196
Change-Id: Ie27ff6f67adfbcc7d03551ae7e84a885da43df5a
Reviewed-on: https://chromium-review.googlesource.com/1043282
Commit-Queue: Scott Graham <scottmg@chromium.org>
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
diff --git a/client/BUILD.gn b/client/BUILD.gn
index 9d8b736..9d1caaf 100644
--- a/client/BUILD.gn
+++ b/client/BUILD.gn
@@ -87,6 +87,16 @@
     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 267a6e2..ae409d4 100644
--- a/client/crashpad_client.h
+++ b/client/crashpad_client.h
@@ -75,6 +75,9 @@
   //! 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 ea179b8..59cf387 100644
--- a/client/crashpad_client_fuchsia.cc
+++ b/client/crashpad_client_fuchsia.cc
@@ -14,7 +14,16 @@
 
 #include "client/crashpad_client.h"
 
+#include <launchpad/launchpad.h>
+#include <zircon/process.h>
+#include <zircon/processargs.h>
+
+#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 @@
     const std::vector<std::string>& 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<std::string> argv_strings;
+  BuildHandlerArgvStrings(handler,
+                          database,
+                          metrics_dir,
+                          url,
+                          annotations,
+                          arguments,
+                          &argv_strings);
+
+  std::vector<const char*> 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 c987a5d..eefbd54 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 <stdint.h>
+
 #include <map>
 #include <string>
 
@@ -56,6 +58,24 @@
 
   ~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 d1dfcdf..550a04a 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 <zircon/syscalls/exception.h>
+#include <zircon/syscalls/port.h>
+
+#include <utility>
+
+#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 b998ba9..184d148 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 @@
   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 dd7adf6..70192cf 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 <zircon/process.h>
+#include <zircon/processargs.h>
+
 #include "handler/fuchsia/crash_report_exception_handler.h"
 #include "handler/fuchsia/exception_handler_server.h"
 #elif defined(OS_LINUX)
@@ -390,17 +393,19 @@
   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 @@
   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