android: Add methods to start handler with /system/bin/linker

Starting with Android Q, Bionic's linker will support loading
executables from an APK, replacing the /system/bin/app_process
workaround.

libhandler_trampoline.so is a small executable, which `dlopen()`s
the handler code from another native library allowing
de-duplicating shared code with that library without having that
library available for a more direct link time dependency.

Bug: 928422
Change-Id: Ib126b8fca6005a34b9e4ef103eb1383dc0c554ea
Reviewed-on: https://chromium-review.googlesource.com/c/1477336
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
diff --git a/client/crashpad_client.h b/client/crashpad_client.h
index ea0e7b6..8bf43ac 100644
--- a/client/crashpad_client.h
+++ b/client/crashpad_client.h
@@ -188,6 +188,97 @@
       const std::map<std::string, std::string>& annotations,
       const std::vector<std::string>& arguments,
       int socket);
+
+  //! \brief Installs a signal handler to start a Crashpad handler process by
+  //!     loading it with `/system/bin/linker`.
+  //!
+  //! This method is only supported by Android Q+.
+  //!
+  //! \param[in] handler_trampoline The path to a Crashpad handler trampoline
+  //!     executable, possibly located within an apk, e.g.
+  //!     "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so".
+  //! \param[in] handler_library The name of a library exporting the symbol
+  //!     `CrashpadHandlerMain()`. The path to this library must be present in
+  //!     `LD_LIBRARY_PATH`.
+  //! \param[in] is_64_bit `true` if \a handler_trampoline and \a
+  //!     handler_library are 64-bit objects. They must have the same bitness.
+  //! \param[in] env A vector of environment variables of the form `var=value`
+  //!     defining the environment in which to execute `app_process`. If this
+  //!     value is `nullptr`, the application's environment at the time of the
+  //!     crash will be used.
+  //! \param[in] database The path to a Crashpad database. The handler will be
+  //!     started with this path as its `--database` argument.
+  //! \param[in] metrics_dir The path to an already existing directory where
+  //!     metrics files can be stored. The handler will be started with this
+  //!     path as its `--metrics-dir` argument.
+  //! \param[in] url The URL of an upload server. The handler will be started
+  //!     with this URL as its `--url` argument.
+  //! \param[in] annotations Process annotations to set in each crash report.
+  //!     The handler will be started with an `--annotation` argument for each
+  //!     element in this map.
+  //! \param[in] arguments Additional arguments to pass to the Crashpad handler.
+  //!     Arguments passed in other parameters and arguments required to perform
+  //!     the handshake are the responsibility of this method, and must not be
+  //!     specified in this parameter.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  static bool StartHandlerWithLinkerAtCrash(
+      const std::string& handler_trampoline,
+      const std::string& handler_library,
+      bool is_64_bit,
+      const std::vector<std::string>* env,
+      const base::FilePath& database,
+      const base::FilePath& metrics_dir,
+      const std::string& url,
+      const std::map<std::string, std::string>& annotations,
+      const std::vector<std::string>& arguments);
+
+  //! \brief Starts a Crashpad handler process with an initial client by loading
+  //!     it with `/system/bin/linker`.
+  //!
+  //! This method is only supported by Android Q+.
+  //!
+  //! \param[in] handler_trampoline The path to a Crashpad handler trampoline
+  //!     executable, possibly located within an apk, e.g.
+  //!     "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so".
+  //! \param[in] handler_library The name of a library exporting the symbol
+  //!     `CrashpadHandlerMain()`. The path to this library must be present in
+  //!     `LD_LIBRARY_PATH`.
+  //! \param[in] is_64_bit `true` if \a handler_trampoline and \a
+  //!     handler_library are 64-bit objects. They must have the same bitness.
+  //! \param[in] env A vector of environment variables of the form `var=value`
+  //!     defining the environment in which to execute `app_process`. If this
+  //!     value is `nullptr`, the application's current environment will be
+  //!     used.
+  //! \param[in] database The path to a Crashpad database. The handler will be
+  //!     started with this path as its `--database` argument.
+  //! \param[in] metrics_dir The path to an already existing directory where
+  //!     metrics files can be stored. The handler will be started with this
+  //!     path as its `--metrics-dir` argument.
+  //! \param[in] url The URL of an upload server. The handler will be started
+  //!     with this URL as its `--url` argument.
+  //! \param[in] annotations Process annotations to set in each crash report.
+  //!     The handler will be started with an `--annotation` argument for each
+  //!     element in this map.
+  //! \param[in] arguments Additional arguments to pass to the Crashpad handler.
+  //!     Arguments passed in other parameters and arguments required to perform
+  //!     the handshake are the responsibility of this method, and must not be
+  //!     specified in this parameter.
+  //! \param[in] socket The server end of a socket pair. The client end should
+  //!     be used with an ExceptionHandlerClient.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  static bool StartHandlerWithLinkerForClient(
+      const std::string& handler_trampoline,
+      const std::string& handler_library,
+      bool is_64_bit,
+      const std::vector<std::string>* env,
+      const base::FilePath& database,
+      const base::FilePath& metrics_dir,
+      const std::string& url,
+      const std::map<std::string, std::string>& annotations,
+      const std::vector<std::string>& arguments,
+      int socket);
 #endif  // OS_ANDROID || DOXYGEN
 
 #if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN
diff --git a/client/crashpad_client_linux.cc b/client/crashpad_client_linux.cc
index 556a91f..fe5798a 100644
--- a/client/crashpad_client_linux.cc
+++ b/client/crashpad_client_linux.cc
@@ -77,6 +77,36 @@
   return argv;
 }
 
+std::vector<std::string> BuildArgsToLaunchWithLinker(
+    const std::string& handler_trampoline,
+    const std::string& handler_library,
+    bool is_64_bit,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments,
+    int socket) {
+  std::vector<std::string> argv;
+  if (is_64_bit) {
+    argv.push_back("/system/bin/linker64");
+  } else {
+    argv.push_back("/system/bin/linker");
+  }
+  argv.push_back(handler_trampoline);
+  argv.push_back(handler_library);
+
+  std::vector<std::string> handler_argv = BuildHandlerArgvStrings(
+      base::FilePath(), database, metrics_dir, url, annotations, arguments);
+
+  if (socket != kInvalidFileHandle) {
+    handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket));
+  }
+
+  argv.insert(argv.end(), handler_argv.begin() + 1, handler_argv.end());
+  return argv;
+}
+
 #endif  // OS_ANDROID
 
 // Launches a single use handler to snapshot this process.
@@ -251,6 +281,61 @@
   return DoubleForkAndExec(argv, env, socket, false, nullptr);
 }
 
+// static
+bool CrashpadClient::StartHandlerWithLinkerAtCrash(
+    const std::string& handler_trampoline,
+    const std::string& handler_library,
+    bool is_64_bit,
+    const std::vector<std::string>* env,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments) {
+  std::vector<std::string> argv =
+      BuildArgsToLaunchWithLinker(handler_trampoline,
+                                  handler_library,
+                                  is_64_bit,
+                                  database,
+                                  metrics_dir,
+                                  url,
+                                  annotations,
+                                  arguments,
+                                  kInvalidFileHandle);
+  auto signal_handler = LaunchAtCrashHandler::Get();
+  if (signal_handler->Initialize(&argv, env)) {
+    DCHECK(!g_crash_handler);
+    g_crash_handler = signal_handler;
+    return true;
+  }
+  return false;
+}
+
+// static
+bool CrashpadClient::StartHandlerWithLinkerForClient(
+    const std::string& handler_trampoline,
+    const std::string& handler_library,
+    bool is_64_bit,
+    const std::vector<std::string>* env,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments,
+    int socket) {
+  std::vector<std::string> argv =
+      BuildArgsToLaunchWithLinker(handler_trampoline,
+                                  handler_library,
+                                  is_64_bit,
+                                  database,
+                                  metrics_dir,
+                                  url,
+                                  annotations,
+                                  arguments,
+                                  socket);
+  return DoubleForkAndExec(argv, env, socket, false, nullptr);
+}
+
 #endif
 
 // static
diff --git a/handler/BUILD.gn b/handler/BUILD.gn
index 32d4d6c..bbd68ab 100644
--- a/handler/BUILD.gn
+++ b/handler/BUILD.gn
@@ -166,6 +166,21 @@
       "$root_out_dir/libcrashpad_handler.so",
     ]
   }
+
+  crashpad_executable("crashpad_handler_trampoline") {
+    set_sources_assignment_filter([])
+    output_name = "libcrashpad_handler_trampoline.so"
+
+    sources = [
+      "linux/handler_trampoline.cc",
+    ]
+
+    ldflags = [ "-llog" ]
+
+    if (crashpad_is_in_chromium) {
+      no_default_deps = true
+    }
+  }
 }
 
 crashpad_executable("crashpad_handler_test_extended_handler") {
diff --git a/handler/handler_main.cc b/handler/handler_main.cc
index b3e0a68..31686b3 100644
--- a/handler/handler_main.cc
+++ b/handler/handler_main.cc
@@ -497,6 +497,18 @@
 
 }  // namespace
 
+#if defined(OS_ANDROID)
+
+extern "C" {
+__attribute__((visibility("default"), used)) int CrashpadHandlerMain(
+    int argc,
+    char* argv[]) {
+  return HandlerMain(argc, argv, nullptr);
+}
+}  // extern "C"
+
+#endif  // OS_ANDROID
+
 int HandlerMain(int argc,
                 char* argv[],
                 const UserStreamDataSources* user_stream_sources) {
diff --git a/handler/linux/handler_trampoline.cc b/handler/linux/handler_trampoline.cc
new file mode 100644
index 0000000..3453563
--- /dev/null
+++ b/handler/linux/handler_trampoline.cc
@@ -0,0 +1,45 @@
+// Copyright 2019 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 <android/log.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+
+// The first argument passed to the trampoline is the name of the native library
+// exporting the symbol `CrashpadHandlerMain`. The remaining arguments are the
+// same as for `HandlerMain()`.
+int main(int argc, char* argv[]) {
+  static constexpr char kTag[] = "crashpad";
+
+  if (argc < 2) {
+    __android_log_print(ANDROID_LOG_FATAL, kTag, "usage: %s <path>", argv[0]);
+    return EXIT_FAILURE;
+  }
+
+  void* handle = dlopen(argv[1], RTLD_LAZY | RTLD_GLOBAL);
+  if (!handle) {
+    __android_log_print(ANDROID_LOG_FATAL, kTag, "dlopen: %s", dlerror());
+    return EXIT_FAILURE;
+  }
+
+  using MainType = int (*)(int, char*[]);
+  MainType crashpad_main =
+      reinterpret_cast<MainType>(dlsym(handle, "CrashpadHandlerMain"));
+  if (!crashpad_main) {
+    __android_log_print(ANDROID_LOG_FATAL, kTag, "dlsym: %s", dlerror());
+    return EXIT_FAILURE;
+  }
+
+  return crashpad_main(argc - 1, argv + 1);
+}