ios: Add iOS in-process handler for managing minidump generation.

Manage the intermediate minidump generation, and own the crash report
upload thread and database.

Change-Id: Ie2d6fe504c0fe4fdcd9326e6b46569e14da3ba19
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3087721
Commit-Queue: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
GitOrigin-RevId: 204abe16d225fc85779623063a8c4c85b707d577
diff --git a/client/BUILD.gn b/client/BUILD.gn
index 5c18c6b..ad2d71c 100644
--- a/client/BUILD.gn
+++ b/client/BUILD.gn
@@ -35,6 +35,8 @@
       "crashpad_client_ios.cc",
       "ios_handler/exception_processor.h",
       "ios_handler/exception_processor.mm",
+      "ios_handler/in_process_handler.cc",
+      "ios_handler/in_process_handler.h",
       "ios_handler/in_process_intermediate_dump_handler.cc",
       "ios_handler/in_process_intermediate_dump_handler.h",
       "simulate_crash_ios.h",
@@ -84,7 +86,6 @@
     cflags = [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
   }
 
-  # TODO(justincohen): Temporary dependency to bring up the iOS client.
   if (crashpad_is_ios) {
     deps += [
       "../handler:common",
diff --git a/client/crashpad_client.h b/client/crashpad_client.h
index c8016d2..468c836 100644
--- a/client/crashpad_client.h
+++ b/client/crashpad_client.h
@@ -465,7 +465,8 @@
   //! \param[in] database The path to a Crashpad database.
   //! \param[in] url The URL of an upload server.
   //! \param[in] annotations Process annotations to set in each crash report.
-  static void StartCrashpadInProcessHandler(
+  //! \return `true` on success, `false` on failure with a message logged.
+  static bool StartCrashpadInProcessHandler(
       const base::FilePath& database,
       const std::string& url,
       const std::map<std::string, std::string>& annotations);
@@ -503,7 +504,7 @@
       const std::map<std::string, std::string>& annotations = {});
 
   //! \brief Requests that the handler begin in-process uploading of any
-  //! pending reports.
+  //!     pending reports.
   //!
   //! Once called the handler will start looking for pending reports to upload
   //! on another thread. This method does not block.
diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc
index b32678d..9a911df 100644
--- a/client/crashpad_client_ios.cc
+++ b/client/crashpad_client_ios.cc
@@ -23,6 +23,7 @@
 #include "base/mac/mach_logging.h"
 #include "base/mac/scoped_mach_port.h"
 #include "client/ios_handler/exception_processor.h"
+#include "client/ios_handler/in_process_handler.h"
 #include "util/ios/ios_system_data_collector.h"
 #include "util/mach/exc_server_variants.h"
 #include "util/mach/exception_ports.h"
@@ -33,6 +34,19 @@
 #include "util/posix/signals.h"
 #include "util/thread/thread.h"
 
+namespace {
+
+bool IsBeingDebugged() {
+  kinfo_proc kern_proc_info;
+  int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
+  size_t len = sizeof(kern_proc_info);
+  if (sysctl(mib, base::size(mib), &kern_proc_info, &len, nullptr, 0) == 0)
+    return kern_proc_info.kp_proc.p_flag & P_TRACED;
+  return false;
+}
+
+}  // namespace
+
 namespace crashpad {
 
 namespace {
@@ -50,23 +64,33 @@
     return instance;
   }
 
-  void Initialize() {
+  bool Initialize(const base::FilePath& database,
+                  const std::string& url,
+                  const std::map<std::string, std::string>& annotations) {
     INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
-    InstallMachExceptionHandler();
-    CHECK(Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_));
+    if (!in_process_handler_.Initialize(database, url, annotations) ||
+        !InstallMachExceptionHandler() ||
+        !Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)) {
+      LOG(ERROR) << "Unable to initialize Crashpad.";
+      return false;
+    }
     INITIALIZATION_STATE_SET_VALID(initialized_);
+    return true;
   }
 
   void ProcessIntermediateDumps(
-      const std::map<std::string, std::string>& annotations = {}) {}
+      const std::map<std::string, std::string>& annotations) {
+    in_process_handler_.ProcessIntermediateDumps(annotations);
+  }
 
   void ProcessIntermediateDump(
       const base::FilePath& file,
-      const std::map<std::string, std::string>& annotations = {}) {}
+      const std::map<std::string, std::string>& annotations) {
+    in_process_handler_.ProcessIntermediateDump(file, annotations);
+  }
 
-  void DumpWithoutCrash(NativeCPUContext* context) {
-    INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-    mach_exception_data_type_t code[2] = {};
+  void DumpWithContext(NativeCPUContext* context) {
+    const mach_exception_data_type_t code[2] = {};
     static constexpr int kSimulatedException = -1;
     HandleMachException(MACH_EXCEPTION_CODES,
                         mach_thread_self(),
@@ -78,33 +102,68 @@
                         MACHINE_THREAD_STATE_COUNT);
   }
 
+  void DumpWithoutCrash(NativeCPUContext* context, bool process_dump) {
+    INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+    internal::InProcessHandler::ScopedAlternateWriter scoper(
+        &in_process_handler_);
+    if (scoper.Open()) {
+      DumpWithContext(context);
+      if (process_dump) {
+        in_process_handler_.ProcessIntermediateDump(scoper.path());
+      }
+    }
+  }
+
+  void DumpWithoutCrashAtPath(NativeCPUContext* context,
+                              const base::FilePath& path) {
+    internal::InProcessHandler::ScopedAlternateWriter scoper(
+        &in_process_handler_);
+    if (scoper.OpenAtPath(path))
+      DumpWithContext(context);
+  }
+
+  void StartProcessingPendingReports() {
+    INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+    in_process_handler_.StartProcessingPendingReports();
+  }
+
  private:
   CrashHandler() = default;
 
-  void InstallMachExceptionHandler() {
+  bool InstallMachExceptionHandler() {
     exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
-    CHECK(exception_port_.is_valid());
+    if (!exception_port_.is_valid()) {
+      return false;
+    }
 
     kern_return_t kr = mach_port_insert_right(mach_task_self(),
                                               exception_port_.get(),
                                               exception_port_.get(),
                                               MACH_MSG_TYPE_MAKE_SEND);
-    MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_right";
+    if (kr != KERN_SUCCESS) {
+      MACH_LOG(ERROR, kr) << "mach_port_insert_right";
+      return false;
+    }
 
     // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT.
-    const exception_mask_t mask =
+    // Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger.
+    exception_mask_t mask =
         ExcMaskAll() &
-        ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT |
-          EXC_MASK_RPC_ALERT | EXC_MASK_GUARD);
+        ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_RPC_ALERT |
+          EXC_MASK_GUARD | (IsBeingDebugged() ? EXC_MASK_BREAKPOINT : 0));
+
     ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
-    exception_ports.GetExceptionPorts(mask, &original_handlers_);
-    exception_ports.SetExceptionPort(
-        mask,
-        exception_port_.get(),
-        EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
-        MACHINE_THREAD_STATE);
+    if (!exception_ports.GetExceptionPorts(mask, &original_handlers_) ||
+        !exception_ports.SetExceptionPort(
+            mask,
+            exception_port_.get(),
+            EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
+            MACHINE_THREAD_STATE)) {
+      return false;
+    }
 
     Start();
+    return true;
   }
 
   // Thread:
@@ -175,7 +234,45 @@
                            thread_state_flavor_t flavor,
                            ConstThreadState old_state,
                            mach_msg_type_number_t old_state_count) {
-    // TODO(justincohen): This is incomplete.
+    in_process_handler_.DumpExceptionFromMachException(system_data_,
+                                                       behavior,
+                                                       thread,
+                                                       exception,
+                                                       code,
+                                                       code_count,
+                                                       flavor,
+                                                       old_state,
+                                                       old_state_count);
+  }
+
+  void HandleUncaughtNSException(const uint64_t* frames,
+                                 const size_t num_frames) override {
+    in_process_handler_.DumpExceptionFromNSExceptionFrames(
+        system_data_, frames, num_frames);
+    // After uncaught exceptions are reported, the system immediately triggers a
+    // call to std::terminate()/abort(). Remove the abort handler so a second
+    // dump isn't generated.
+    CHECK(Signals::InstallDefaultHandler(SIGABRT));
+  }
+
+  void HandleUncaughtNSExceptionWithContext(
+      NativeCPUContext* context) override {
+    const mach_exception_data_type_t code[2] = {0, 0};
+    in_process_handler_.DumpExceptionFromMachException(
+        system_data_,
+        MACH_EXCEPTION_CODES,
+        mach_thread_self(),
+        kMachExceptionFromNSException,
+        code,
+        base::size(code),
+        MACHINE_THREAD_STATE,
+        reinterpret_cast<ConstThreadState>(context),
+        MACHINE_THREAD_STATE_COUNT);
+
+    // After uncaught exceptions are reported, the system immediately triggers a
+    // call to std::terminate()/abort(). Remove the abort handler so a second
+    // dump isn't generated.
+    CHECK(Signals::InstallDefaultHandler(SIGABRT));
   }
 
   // The signal handler installed at OS-level.
@@ -187,35 +284,16 @@
   void HandleAndReraiseSignal(int signo,
                               siginfo_t* siginfo,
                               ucontext_t* context) {
-    // TODO(justincohen): This is incomplete.
+    in_process_handler_.DumpExceptionFromSignal(system_data_, siginfo, context);
 
     // Always call system handler.
     Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_);
   }
 
-  void HandleUncaughtNSException(const uint64_t* frames,
-                                 const size_t num_frames) override {
-    // TODO(justincohen): Call into in_process_handler.
-
-    // After uncaught exceptions are reported, the system immediately triggers a
-    // call to std::terminate()/abort(). Remove the abort handler so a second
-    // dump isn't generated.
-    CHECK(Signals::InstallDefaultHandler(SIGABRT));
-  }
-
-  void HandleUncaughtNSExceptionWithContext(
-      NativeCPUContext* context) override {
-    // TODO(justincohen): Call into in_process_handler.
-
-    // After uncaught exceptions are reported, the system immediately triggers a
-    // call to std::terminate()/abort(). Remove the abort handler so a second
-    // dump isn't generated.
-    CHECK(Signals::InstallDefaultHandler(SIGABRT));
-  }
-
   base::mac::ScopedMachReceiveRight exception_port_;
   ExceptionPorts::ExceptionHandlerVector original_handlers_;
   struct sigaction old_action_ = {};
+  internal::InProcessHandler in_process_handler_;
   internal::IOSSystemDataCollector system_data_;
   InitializationStateDcheck initialized_;
 };
@@ -227,15 +305,14 @@
 CrashpadClient::~CrashpadClient() {}
 
 // static
-void CrashpadClient::StartCrashpadInProcessHandler(
+bool CrashpadClient::StartCrashpadInProcessHandler(
     const base::FilePath& database,
     const std::string& url,
     const std::map<std::string, std::string>& annotations) {
-
   CrashHandler* crash_handler = CrashHandler::Get();
   DCHECK(crash_handler);
   InstallObjcExceptionPreprocessor(crash_handler);
-  crash_handler->Initialize();
+  return crash_handler->Initialize(database, url, annotations);
 }
 
 // static
@@ -257,17 +334,16 @@
 
 // static
 void CrashpadClient::StartProcessingPendingReports() {
-  // TODO(justincohen): Start the CrashReportUploadThread.
+  CrashHandler* crash_handler = CrashHandler::Get();
+  DCHECK(crash_handler);
+  crash_handler->StartProcessingPendingReports();
 }
 
 // static
 void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) {
   CrashHandler* crash_handler = CrashHandler::Get();
   DCHECK(crash_handler);
-  crash_handler->DumpWithoutCrash(context);
-  // TODO(justincohen): Change this to only process the dump from above, not all
-  // intermediate dump files.
-  crash_handler->ProcessIntermediateDumps();
+  crash_handler->DumpWithoutCrash(context, /*process_dump=*/true);
 }
 
 // static
@@ -275,7 +351,7 @@
     NativeCPUContext* context) {
   CrashHandler* crash_handler = CrashHandler::Get();
   DCHECK(crash_handler);
-  crash_handler->DumpWithoutCrash(context);
+  crash_handler->DumpWithoutCrash(context, /*process_dump=*/false);
 }
 
 // static
@@ -284,8 +360,7 @@
     const base::FilePath path) {
   CrashHandler* crash_handler = CrashHandler::Get();
   DCHECK(crash_handler);
-  // TODO(justincohen): Change to DumpWithoutCrashAtPath(context, path).
-  crash_handler->DumpWithoutCrash(context);
+  crash_handler->DumpWithoutCrashAtPath(context, path);
 }
 
 }  // namespace crashpad
diff --git a/client/crashpad_client_ios_test.mm b/client/crashpad_client_ios_test.mm
index 30fabae..3c350e9 100644
--- a/client/crashpad_client_ios_test.mm
+++ b/client/crashpad_client_ios_test.mm
@@ -18,6 +18,9 @@
 
 #include <vector>
 
+#include "base/strings/sys_string_conversions.h"
+#include "client/crash_report_database.h"
+#include "client/ios_handler/exception_processor.h"
 #include "client/simulate_crash.h"
 #include "gtest/gtest.h"
 #include "test/scoped_temp_dir.h"
@@ -32,9 +35,53 @@
 TEST_F(CrashpadIOSClient, DumpWithoutCrash) {
   CrashpadClient client;
   ScopedTempDir database_dir;
-  client.StartCrashpadInProcessHandler(
-      base::FilePath(database_dir.path()), "", {});
+  ASSERT_TRUE(client.StartCrashpadInProcessHandler(
+      base::FilePath(database_dir.path()), "", {}));
+  std::unique_ptr<CrashReportDatabase> database =
+      CrashReportDatabase::Initialize(database_dir.path());
+  std::vector<CrashReportDatabase::Report> reports;
+  EXPECT_EQ(database->GetPendingReports(&reports),
+            CrashReportDatabase::kNoError);
+  ASSERT_EQ(reports.size(), 0u);
   CRASHPAD_SIMULATE_CRASH();
+  reports.clear();
+  EXPECT_EQ(database->GetPendingReports(&reports),
+            CrashReportDatabase::kNoError);
+  ASSERT_EQ(reports.size(), 1u);
+
+  CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING();
+  reports.clear();
+  EXPECT_EQ(database->GetPendingReports(&reports),
+            CrashReportDatabase::kNoError);
+  ASSERT_EQ(reports.size(), 1u);
+  client.ProcessIntermediateDumps();
+  reports.clear();
+  EXPECT_EQ(database->GetPendingReports(&reports),
+            CrashReportDatabase::kNoError);
+  ASSERT_EQ(reports.size(), 2u);
+
+  ScopedTempDir crash_dir;
+  UUID uuid;
+  uuid.InitializeWithNew();
+  CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING_AT_PATH(
+      crash_dir.path().Append(uuid.ToString()));
+  reports.clear();
+  EXPECT_EQ(database->GetPendingReports(&reports),
+            CrashReportDatabase::kNoError);
+  ASSERT_EQ(reports.size(), 2u);
+
+  NSError* error = nil;
+  NSArray* paths = [[NSFileManager defaultManager]
+      contentsOfDirectoryAtPath:base::SysUTF8ToNSString(
+                                    crash_dir.path().value())
+                          error:&error];
+  ASSERT_EQ([paths count], 1u);
+  client.ProcessIntermediateDump(
+      crash_dir.path().Append([paths[0] fileSystemRepresentation]));
+  reports.clear();
+  EXPECT_EQ(database->GetPendingReports(&reports),
+            CrashReportDatabase::kNoError);
+  ASSERT_EQ(reports.size(), 3u);
 }
 
 // This test is covered by a similar XCUITest, but for development purposes it's
@@ -44,8 +91,8 @@
 TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) {
   CrashpadClient client;
   ScopedTempDir database_dir;
-  client.StartCrashpadInProcessHandler(
-      base::FilePath(database_dir.path()), "", {});
+  ASSERT_TRUE(client.StartCrashpadInProcessHandler(
+      base::FilePath(database_dir.path()), "", {}));
   [NSException raise:@"GoogleTestNSException" format:@"ThrowException"];
 }
 
@@ -56,8 +103,8 @@
 TEST_F(CrashpadIOSClient, DISABLED_ThrowException) {
   CrashpadClient client;
   ScopedTempDir database_dir;
-  client.StartCrashpadInProcessHandler(
-      base::FilePath(database_dir.path()), "", {});
+  ASSERT_TRUE(client.StartCrashpadInProcessHandler(
+      base::FilePath(database_dir.path()), "", {}));
   std::vector<int> empty_vector;
   empty_vector.at(42);
 }
diff --git a/client/ios_handler/in_process_handler.cc b/client/ios_handler/in_process_handler.cc
new file mode 100644
index 0000000..85fd422
--- /dev/null
+++ b/client/ios_handler/in_process_handler.cc
@@ -0,0 +1,298 @@
+// Copyright 2021 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 "client/ios_handler/in_process_handler.h"
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include "base/logging.h"
+#include "client/ios_handler/in_process_intermediate_dump_handler.h"
+#include "client/settings.h"
+#include "minidump/minidump_file_writer.h"
+#include "util/file/directory_reader.h"
+#include "util/file/filesystem.h"
+
+namespace {
+
+// Creates directory at |path|.
+void CreateDirectory(const base::FilePath& path) {
+  if (mkdir(path.value().c_str(), 0755) == 0) {
+    return;
+  }
+  if (errno != EEXIST) {
+    PLOG(ERROR) << "mkdir " << path.value();
+  }
+}
+
+}  // namespace
+
+namespace crashpad {
+namespace internal {
+
+InProcessHandler::InProcessHandler() = default;
+
+InProcessHandler::~InProcessHandler() {
+  upload_thread_->Stop();
+}
+
+bool InProcessHandler::Initialize(
+    const base::FilePath& database,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+  annotations_ = annotations;
+  database_ = CrashReportDatabase::Initialize(database);
+
+  if (!url.empty()) {
+    // TODO(scottmg): options.rate_limit should be removed when we have a
+    // configurable database setting to control upload limiting.
+    // See https://crashpad.chromium.org/bug/23.
+    CrashReportUploadThread::Options upload_thread_options;
+    upload_thread_options.rate_limit = false;
+    upload_thread_options.upload_gzip = true;
+    upload_thread_options.watch_pending_reports = true;
+    upload_thread_options.identify_client_via_url = true;
+
+    upload_thread_.reset(new CrashReportUploadThread(
+        database_.get(), url, upload_thread_options));
+  }
+
+  CreateDirectory(database);
+  static constexpr char kPendingSerializediOSDump[] =
+      "pending-serialized-ios-dump";
+  base_dir_ = database.Append(kPendingSerializediOSDump);
+  CreateDirectory(base_dir_);
+
+  if (!OpenNewFile())
+    return false;
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+void InProcessHandler::DumpExceptionFromSignal(
+    const IOSSystemDataCollector& system_data,
+    siginfo_t* siginfo,
+    ucontext_t* context) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  {
+    ScopedReport report(writer_.get(), system_data);
+    InProcessIntermediateDumpHandler::WriteExceptionFromSignal(
+        writer_.get(), system_data, siginfo, context);
+  }
+  PostReportCleanup();
+}
+
+void InProcessHandler::DumpExceptionFromMachException(
+    const IOSSystemDataCollector& system_data,
+    exception_behavior_t behavior,
+    thread_t thread,
+    exception_type_t exception,
+    const mach_exception_data_type_t* code,
+    mach_msg_type_number_t code_count,
+    thread_state_flavor_t flavor,
+    ConstThreadState old_state,
+    mach_msg_type_number_t old_state_count) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  {
+    ScopedReport report(writer_.get(), system_data);
+    InProcessIntermediateDumpHandler::WriteExceptionFromMachException(
+        writer_.get(),
+        behavior,
+        thread,
+        exception,
+        code,
+        code_count,
+        flavor,
+        old_state,
+        old_state_count);
+  }
+  PostReportCleanup();
+}
+
+void InProcessHandler::DumpExceptionFromNSExceptionFrames(
+    const IOSSystemDataCollector& system_data,
+    const uint64_t* frames,
+    const size_t num_frames) {
+  {
+    ScopedReport report(writer_.get(), system_data, frames, num_frames);
+    InProcessIntermediateDumpHandler::WriteExceptionFromNSException(
+        writer_.get());
+  }
+  PostReportCleanup();
+}
+
+void InProcessHandler::ProcessIntermediateDumps(
+    const std::map<std::string, std::string>& extra_annotations) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  std::map<std::string, std::string> annotations(annotations_);
+  annotations.insert(extra_annotations.begin(), extra_annotations.end());
+
+  for (auto& file : PendingFiles())
+    ProcessIntermediateDumpWithCompleteAnnotations(file, annotations);
+}
+
+void InProcessHandler::ProcessIntermediateDump(
+    const base::FilePath& file,
+    const std::map<std::string, std::string>& extra_annotations) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  std::map<std::string, std::string> annotations(annotations_);
+  annotations.insert(extra_annotations.begin(), extra_annotations.end());
+  ProcessIntermediateDumpWithCompleteAnnotations(file, annotations);
+}
+
+void InProcessHandler::StartProcessingPendingReports() {
+  if (!upload_thread_started_ && upload_thread_) {
+    upload_thread_->Start();
+    upload_thread_started_ = true;
+  }
+}
+
+void InProcessHandler::ProcessIntermediateDumpWithCompleteAnnotations(
+    const base::FilePath& file,
+    const std::map<std::string, std::string>& annotations) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  ProcessSnapshotIOSIntermediateDump process_snapshot;
+  if (process_snapshot.Initialize(file, annotations)) {
+    SaveSnapshot(process_snapshot);
+  }
+}
+
+void InProcessHandler::SaveSnapshot(
+    ProcessSnapshotIOSIntermediateDump& process_snapshot) {
+  std::unique_ptr<CrashReportDatabase::NewReport> new_report;
+  CrashReportDatabase::OperationStatus database_status =
+      database_->PrepareNewCrashReport(&new_report);
+  if (database_status != CrashReportDatabase::kNoError) {
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kPrepareNewCrashReportFailed);
+  }
+  process_snapshot.SetReportID(new_report->ReportID());
+
+  MinidumpFileWriter minidump;
+  minidump.InitializeFromSnapshot(&process_snapshot);
+  if (!minidump.WriteEverything(new_report->Writer())) {
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kMinidumpWriteFailed);
+  }
+  UUID uuid;
+  database_status =
+      database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
+  if (database_status != CrashReportDatabase::kNoError) {
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
+  }
+
+  if (upload_thread_) {
+    upload_thread_->ReportPending(uuid);
+  }
+}
+
+std::vector<base::FilePath> InProcessHandler::PendingFiles() {
+  DirectoryReader reader;
+  std::vector<base::FilePath> files;
+  if (!reader.Open(base_dir_)) {
+    return files;
+  }
+  base::FilePath file;
+  DirectoryReader::Result result;
+  while ((result = reader.NextFile(&file)) ==
+         DirectoryReader::Result::kSuccess) {
+    file = base_dir_.Append(file);
+    if (file != current_file_) {
+      ScopedFileHandle fd(LoggingOpenFileForRead(file));
+      if (LoggingLockFile(fd.get(),
+                          FileLocking::kExclusive,
+                          FileLockingBlocking::kNonBlocking) ==
+          FileLockingResult::kSuccess) {
+        files.push_back(file);
+      }
+    }
+  }
+  return files;
+}
+
+InProcessHandler::ScopedAlternateWriter::ScopedAlternateWriter(
+    InProcessHandler* handler)
+    : handler_(handler) {}
+
+bool InProcessHandler::ScopedAlternateWriter::Open() {
+  UUID uuid;
+  uuid.InitializeWithNew();
+  const std::string uuid_string = uuid.ToString();
+  return OpenAtPath(handler_->base_dir_.Append(uuid_string));
+}
+
+bool InProcessHandler::ScopedAlternateWriter::OpenAtPath(
+    const base::FilePath& path) {
+  path_ = path;
+  handler_->SetOpenNewFileAfterReport(false);
+  original_writer_ = handler_->GetWriter();
+  auto writer = std::make_unique<IOSIntermediateDumpWriter>();
+  if (!writer->Open(path_)) {
+    DLOG(ERROR) << "Unable to open alternate intermediate dump file: "
+                << path_.value();
+    return false;
+  }
+  handler_->SetWriter(std::move(writer));
+  return true;
+}
+
+InProcessHandler::ScopedAlternateWriter::~ScopedAlternateWriter() {
+  handler_->SetWriter(std::move(original_writer_));
+  handler_->SetOpenNewFileAfterReport(true);
+}
+
+InProcessHandler::ScopedReport::ScopedReport(
+    IOSIntermediateDumpWriter* writer,
+    const IOSSystemDataCollector& system_data,
+    const uint64_t* frames,
+    const size_t num_frames)
+    : rootMap_(writer) {
+  InProcessIntermediateDumpHandler::WriteHeader(writer);
+  InProcessIntermediateDumpHandler::WriteProcessInfo(writer);
+  InProcessIntermediateDumpHandler::WriteSystemInfo(writer, system_data);
+  InProcessIntermediateDumpHandler::WriteThreadInfo(writer, frames, num_frames);
+  InProcessIntermediateDumpHandler::WriteModuleInfo(writer);
+}
+
+bool InProcessHandler::OpenNewFile() {
+  UUID uuid;
+  uuid.InitializeWithNew();
+  const std::string uuid_string = uuid.ToString();
+  current_file_ = base_dir_.Append(uuid_string);
+  writer_ = std::make_unique<IOSIntermediateDumpWriter>();
+  if (!writer_->Open(current_file_)) {
+    DLOG(ERROR) << "Unable to open intermediate dump file: "
+                << current_file_.value();
+    return false;
+  }
+  return true;
+}
+
+void InProcessHandler::PostReportCleanup() {
+  if (writer_) {
+    writer_->Close();
+    writer_.reset();
+  }
+  if (open_new_file_after_report_)
+    OpenNewFile();
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/client/ios_handler/in_process_handler.h b/client/ios_handler/in_process_handler.h
new file mode 100644
index 0000000..877ae19
--- /dev/null
+++ b/client/ios_handler/in_process_handler.h
@@ -0,0 +1,199 @@
+// Copyright 2021 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 <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "handler/mac/crash_report_exception_handler.h"
+#include "snapshot/ios/process_snapshot_ios_intermediate_dump.h"
+#include "util/ios/ios_intermediate_dump_writer.h"
+#include "util/ios/ios_system_data_collector.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Manage intermediate minidump generation, and own the crash report
+//!     upload thread and database.
+class InProcessHandler {
+ public:
+  InProcessHandler();
+  ~InProcessHandler();
+  InProcessHandler(const InProcessHandler&) = delete;
+  InProcessHandler& operator=(const InProcessHandler&) = delete;
+
+  //! \brief Initializes the in-process handler.
+  //!
+  //! This method must be called only once, and must be successfully called
+  //! before any other method in this class may be called.
+  //!
+  //! \param[in] database The path to a Crashpad database.
+  //! \param[in] url The URL of an upload server.
+  //! \param[in] annotations Process annotations to set in each crash report.
+  //! \return `true` if a handler to a pending intermediate dump could be
+  //!     opened.
+  bool Initialize(const base::FilePath& database,
+                  const std::string& url,
+                  const std::map<std::string, std::string>& annotations);
+
+  //! \brief Generate an intermediate dump from a signal handler exception.
+  //!
+  //! \param[in] system_data An object containing various system data points.
+  //! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal
+  //!     handler.
+  //! \param[in] context A pointer to a `ucontext_t` object received by a
+  //!     signal.
+  void DumpExceptionFromSignal(const IOSSystemDataCollector& system_data,
+                               siginfo_t* siginfo,
+                               ucontext_t* context);
+
+  //! \brief Generate an intermediate dump from a mach exception.
+  //!
+  //! \param[in] system_data An object containing various system data points.
+  //! \param[in] behavior
+  //! \param[in] thread
+  //! \param[in] exception
+  //! \param[in] code
+  //! \param[in] code_count
+  //! \param[in,out] flavor
+  //! \param[in] old_state
+  //! \param[in] old_state_count
+  void DumpExceptionFromMachException(const IOSSystemDataCollector& system_data,
+                                      exception_behavior_t behavior,
+                                      thread_t thread,
+                                      exception_type_t exception,
+                                      const mach_exception_data_type_t* code,
+                                      mach_msg_type_number_t code_count,
+                                      thread_state_flavor_t flavor,
+                                      ConstThreadState old_state,
+                                      mach_msg_type_number_t old_state_count);
+
+  //! \brief Generate an intermediate dump from an uncaught NSException.
+  //!
+  //! When the ObjcExceptionPreprocessor does not detect an NSException as it is
+  //! thrown, the last-chance uncaught exception handler passes a list of call
+  //! stack frame addresses.  Record them in the intermediate dump so a minidump
+  //! with a 'fake' call stack is generated.
+  //!
+  //! \param[in] system_data An object containing various system data points.
+  //! \param[in] frames An array of call stack frame addresses.
+  //! \param[in] num_frames The number of frames in |frames|.
+  void DumpExceptionFromNSExceptionFrames(
+      const IOSSystemDataCollector& system_data,
+      const uint64_t* frames,
+      const size_t num_frames);
+
+  //! \brief Requests that the handler convert all intermediate dumps into
+  //!     minidumps and trigger an upload if possible.
+  //!
+  //! \param[in] annotations Process annotations to set in each crash report.
+  void ProcessIntermediateDumps(
+      const std::map<std::string, std::string>& annotations);
+
+  //! \brief Requests that the handler convert a specific intermediate dump into
+  //!     a minidump and trigger an upload if possible.
+  //!
+  //! \param[in] path Path to the specific intermediate dump.
+  //! \param[in] annotations Process annotations to set in each crash report.
+  void ProcessIntermediateDump(
+      const base::FilePath& path,
+      const std::map<std::string, std::string>& annotations = {});
+
+  //! \brief Requests that the handler begin in-process uploading of any
+  //!     pending reports.
+  void StartProcessingPendingReports();
+
+  //! \brief Helper that swaps out the InProcessHandler's |writer_| with an
+  //!     alternate writer so DumpWithContext does not interfere with the
+  //!     |writer_| created on startup. This is useful for -DumpWithoutCrash,
+  //!     which may write to an alternate location.
+  class ScopedAlternateWriter {
+   public:
+    ScopedAlternateWriter(InProcessHandler* handler);
+    ~ScopedAlternateWriter();
+    ScopedAlternateWriter(const ScopedAlternateWriter&) = delete;
+    ScopedAlternateWriter& operator=(const ScopedAlternateWriter&) = delete;
+    //! \brief Open's an alternate dump writer in the same directory as the
+    //!     default InProcessHandler's dump writer, so the file will be
+    //!     processed with -ProcessIntermediateDumps()
+    bool Open();
+
+    //! \brief Open's an alternate dump writer in the client provided |path|.
+    //!     The file will only be processed by calling
+    //!     ProcessIntermediateDump(path)
+    bool OpenAtPath(const base::FilePath& path);
+
+    //! \brief The path of the alternate dump writer.
+    const base::FilePath& path() { return path_; }
+
+   private:
+    InProcessHandler* handler_;
+    std::unique_ptr<IOSIntermediateDumpWriter> original_writer_;
+    base::FilePath path_;
+  };
+
+ private:
+  //! \brief Helper to start and end intermediate reports.
+  class ScopedReport {
+   public:
+    ScopedReport(IOSIntermediateDumpWriter* writer,
+                 const IOSSystemDataCollector& system_data,
+                 const uint64_t* frames = nullptr,
+                 const size_t num_frames = 0);
+    ~ScopedReport() {}
+    ScopedReport(const ScopedReport&) = delete;
+    ScopedReport& operator=(const ScopedReport&) = delete;
+
+   private:
+    IOSIntermediateDumpWriter::ScopedRootMap rootMap_;
+  };
+
+  std::unique_ptr<IOSIntermediateDumpWriter> GetWriter() {
+    return std::move(writer_);
+  }
+
+  void SetWriter(std::unique_ptr<IOSIntermediateDumpWriter> writer) {
+    writer_ = std::move(writer);
+  }
+
+  void SetOpenNewFileAfterReport(bool open_new_file_after_report) {
+    open_new_file_after_report_ = open_new_file_after_report;
+  }
+
+  void ProcessIntermediateDumpWithCompleteAnnotations(
+      const base::FilePath& file,
+      const std::map<std::string, std::string>& annotations);
+  void SaveSnapshot(ProcessSnapshotIOSIntermediateDump& process_snapshot);
+  std::vector<base::FilePath> PendingFiles();
+  bool OpenNewFile();
+  void PostReportCleanup();
+
+  bool upload_thread_started_ = false;
+  bool open_new_file_after_report_ = true;
+  std::map<std::string, std::string> annotations_;
+  base::FilePath base_dir_;
+  base::FilePath current_file_;
+  std::unique_ptr<IOSIntermediateDumpWriter> writer_;
+  std::unique_ptr<IOSIntermediateDumpWriter> alternate_mach_writer_;
+  std::unique_ptr<CrashReportUploadThread> upload_thread_;
+  std::unique_ptr<CrashReportDatabase> database_;
+  InitializationStateDcheck initialized_;
+};
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc
index b7e445f..60f172b 100644
--- a/handler/crash_report_upload_thread.cc
+++ b/handler/crash_report_upload_thread.cc
@@ -68,7 +68,8 @@
 
 void CrashReportUploadThread::ReportPending(const UUID& report_uuid) {
   known_pending_report_uuids_.PushBack(report_uuid);
-  thread_.DoWorkNow();
+  if (thread_.is_running())
+    thread_.DoWorkNow();
 }
 
 void CrashReportUploadThread::Start() {
diff --git a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
index cd60451..5ad0207 100644
--- a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
+++ b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
@@ -200,8 +200,7 @@
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
   DCHECK(exception_data);
 
-  exception_ = EXC_SOFTWARE;
-  exception_info_ = 0xDEADC0DE; /* uncaught NSException */
+  exception_ = kMachExceptionFromNSException;
 
   if (!GetDataValueFromMap(exception_data, Key::kThreadID, &thread_id_)) {
     LOG(ERROR) << "Exceptions require a thread id.";
diff --git a/util/BUILD.gn b/util/BUILD.gn
index affa4e9..3e16b37 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -397,6 +397,7 @@
       "ios/raw_logging.h",
       "ios/scoped_vm_read.cc",
       "ios/scoped_vm_read.h",
+      "net/http_transport_mac.mm",
     ]
   }
 
@@ -659,8 +660,8 @@
 
     deps = [
       ":util",
-      "../third_party/cpp-httplib",
       "$mini_chromium_source_parent:base",
+      "../third_party/cpp-httplib",
       "../third_party/zlib",
       "../tools:tool_support",
     ]
@@ -867,12 +868,12 @@
 
   deps = [
     ":util",
+    "$mini_chromium_source_parent:base",
     "../client",
     "../compat",
     "../test",
     "../third_party/googletest:googlemock",
     "../third_party/googletest:googletest",
-    "$mini_chromium_source_parent:base",
     "../third_party/zlib",
   ]
 
diff --git a/util/mach/mach_extensions.h b/util/mach/mach_extensions.h
index 9247ce9..ebf36d3 100644
--- a/util/mach/mach_extensions.h
+++ b/util/mach/mach_extensions.h
@@ -47,6 +47,9 @@
 //! \brief An exception type to use for simulated exceptions.
 constexpr exception_type_t kMachExceptionSimulated = 'CPsx';
 
+//! \brief An exception type to use for uncaught NSExceptions.
+constexpr exception_type_t kMachExceptionFromNSException = 'CPnx';
+
 //! \brief A const version of `thread_state_t`.
 //!
 //! This is useful as the \a old_state parameter to exception handler functions.