ios: Migrate ios/snapshot to writing intermediate dumps.

This migrates all the logic that used to live in ios/snapshots that
gathers all the various information during an exception.

Everything in InProcessIntermediateDumpHandler is considered
`RUNS-DURING-CRASH`.

Change-Id: I83bf04c946d410a83f55cd895b2bc45a6eb9c4d6
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2920547
Commit-Queue: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
GitOrigin-RevId: 40cd1b72cfd8a6c56092ba5f4840e625fa66bc24
diff --git a/client/BUILD.gn b/client/BUILD.gn
index aa1eaaa..5c18c6b 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_intermediate_dump_handler.cc",
+      "ios_handler/in_process_intermediate_dump_handler.h",
       "simulate_crash_ios.h",
     ]
   }
@@ -165,13 +167,7 @@
     sources += [
       "crashpad_client_ios_test.mm",
       "ios_handler/exception_processor_test.mm",
-    ]
-    sources -= [
-      "annotation_list_test.cc",
-      "annotation_test.cc",
-      "crash_report_database_test.cc",
-      "prune_crash_reports_test.cc",
-      "settings_test.cc",
+      "ios_handler/in_process_intermediate_dump_handler_test.cc",
     ]
   }
 
@@ -181,12 +177,12 @@
 
   deps = [
     ":client",
+    "$mini_chromium_source_parent:base",
     "../compat",
     "../snapshot",
     "../test",
     "../third_party/googletest:googlemock",
     "../third_party/googletest:googletest",
-    "$mini_chromium_source_parent:base",
     "../util",
   ]
 
diff --git a/client/annotation.h b/client/annotation.h
index ddfe8f6..edb963c 100644
--- a/client/annotation.h
+++ b/client/annotation.h
@@ -29,7 +29,11 @@
 #include "build/build_config.h"
 
 namespace crashpad {
-
+#if defined(OS_IOS)
+namespace internal {
+class InProcessIntermediateDumpHandler;
+}  // namespace internal
+#endif
 class AnnotationList;
 
 //! \brief Base class for an annotation, which records a name-value pair of
@@ -167,6 +171,9 @@
 
  protected:
   friend class AnnotationList;
+#if defined(OS_IOS)
+  friend class internal::InProcessIntermediateDumpHandler;
+#endif
 
   std::atomic<Annotation*>& link_node() { return link_node_; }
 
diff --git a/client/annotation_list.h b/client/annotation_list.h
index 9485c46..0b80768 100644
--- a/client/annotation_list.h
+++ b/client/annotation_list.h
@@ -16,9 +16,15 @@
 #define CRASHPAD_CLIENT_ANNOTATION_LIST_H_
 
 #include "base/macros.h"
+#include "build/build_config.h"
 #include "client/annotation.h"
 
 namespace crashpad {
+#if defined(OS_IOS)
+namespace internal {
+class InProcessIntermediateDumpHandler;
+}  // namespace internal
+#endif
 
 //! \brief A list that contains all the currently set annotations.
 //!
@@ -77,6 +83,17 @@
   //! \brief Returns an iterator past the last element of the annotation list.
   Iterator end();
 
+ protected:
+#if defined(OS_IOS)
+  friend class internal::InProcessIntermediateDumpHandler;
+#endif
+
+  //! \brief Returns a pointer to the tail node.
+  const Annotation* tail_pointer() const { return tail_pointer_; }
+
+  //! \brief Returns a pointer to the head element.
+  const Annotation* head() const { return &head_; }
+
  private:
   // To make it easier for the handler to locate the dummy tail node, store the
   // pointer. Placed first for packing.
diff --git a/client/crashpad_info.h b/client/crashpad_info.h
index ed7b9c1..62efc10 100644
--- a/client/crashpad_info.h
+++ b/client/crashpad_info.h
@@ -32,6 +32,10 @@
 
 namespace internal {
 
+#if defined(OS_IOS)
+class InProcessIntermediateDumpHandler;
+#endif
+
 //! \brief A linked list of blocks representing custom streams in the minidump,
 //!     with addresses (and size) stored as uint64_t to simplify reading from
 //!     the handler process.
@@ -223,6 +227,15 @@
     kSignature = 'CPad',
   };
 
+ protected:
+#if defined(OS_IOS)
+  friend class internal::InProcessIntermediateDumpHandler;
+#endif
+
+  uint32_t signature() const { return signature_; }
+  uint32_t version() const { return version_; }
+  uint32_t size() const { return size_; }
+
  private:
   // The compiler won’t necessarily see anyone using these fields, but it
   // shouldn’t warn about that. These fields aren’t intended for use by the
diff --git a/client/ios_handler/in_process_intermediate_dump_handler.cc b/client/ios_handler/in_process_intermediate_dump_handler.cc
new file mode 100644
index 0000000..43552a7
--- /dev/null
+++ b/client/ios_handler/in_process_intermediate_dump_handler.cc
@@ -0,0 +1,1258 @@
+// 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_intermediate_dump_handler.h"
+
+#include <mach-o/dyld_images.h>
+#include <mach-o/nlist.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/sysctl.h>
+#include <time.h>
+
+#include "base/cxx17_backports.h"
+#include "build/build_config.h"
+#include "snapshot/snapshot_constants.h"
+#include "util/ios/ios_intermediate_dump_writer.h"
+#include "util/ios/raw_logging.h"
+#include "util/ios/scoped_vm_read.h"
+
+namespace crashpad {
+namespace internal {
+
+namespace {
+
+#if defined(ARCH_CPU_X86_64)
+const thread_state_flavor_t kThreadStateFlavor = x86_THREAD_STATE64;
+const thread_state_flavor_t kFloatStateFlavor = x86_FLOAT_STATE64;
+const thread_state_flavor_t kDebugStateFlavor = x86_DEBUG_STATE64;
+using thread_state_type = x86_thread_state64_t;
+#elif defined(ARCH_CPU_ARM64)
+const thread_state_flavor_t kThreadStateFlavor = ARM_THREAD_STATE64;
+const thread_state_flavor_t kFloatStateFlavor = ARM_NEON_STATE64;
+const thread_state_flavor_t kDebugStateFlavor = ARM_DEBUG_STATE64;
+using thread_state_type = arm_thread_state64_t;
+#endif
+
+// From snapshot/mac/process_types/crashreporterclient.proctype
+struct crashreporter_annotations_t {
+  uint64_t version;
+  uint64_t message;
+  uint64_t signature_string;
+  uint64_t backtrace;
+  uint64_t message2;
+  uint64_t thread;
+  uint64_t dialog_mode;
+  uint64_t abort_cause;
+};
+
+//! \brief Manage memory and ports after calling `task_threads`.
+class ScopedTaskThreads {
+ public:
+  explicit ScopedTaskThreads(thread_act_array_t threads,
+                             mach_msg_type_number_t thread_count)
+      : threads_(threads), thread_count_(thread_count) {}
+
+  ~ScopedTaskThreads() {
+    for (uint32_t thread_index = 0; thread_index < thread_count_;
+         ++thread_index) {
+      mach_port_deallocate(mach_task_self(), threads_[thread_index]);
+    }
+    vm_deallocate(mach_task_self(),
+                  reinterpret_cast<vm_address_t>(threads_),
+                  sizeof(thread_t) * thread_count_);
+  }
+
+ private:
+  thread_act_array_t threads_;
+  mach_msg_type_number_t thread_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedTaskThreads);
+};
+
+//! \brief Log \a key as a string.
+void WriteError(IntermediateDumpKey key) {
+  CRASHPAD_RAW_LOG("Unable to write key");
+  switch (key) {
+// clang-format off
+#define CASE_KEY(Name, Value)       \
+    case IntermediateDumpKey::Name: \
+      CRASHPAD_RAW_LOG(#Name);      \
+      break;
+    INTERMEDIATE_DUMP_KEYS(CASE_KEY)
+#undef CASE_KEY
+    // clang-format on
+  }
+}
+
+//! \brief Call AddProperty with raw error log.
+//!
+//! \param[in] writer The dump writer
+//! \param[in] key The key to write.
+//! \param[in] value Memory to be written.
+//! \param[in] count Length of \a value.
+template <typename T>
+void WriteProperty(IOSIntermediateDumpWriter* writer,
+                   IntermediateDumpKey key,
+                   const T* value,
+                   size_t count = 1) {
+  if (!writer->AddProperty(key, value, count))
+    WriteError(key);
+}
+
+//! \brief Call AddPropertyBytes with raw error log.
+//!
+//! \param[in] writer The dump writer
+//! \param[in] key The key to write.
+//! \param[in] value Memory to be written.
+//! \param[in] count Length of \a data.
+void WritePropertyBytes(IOSIntermediateDumpWriter* writer,
+                        IntermediateDumpKey key,
+                        const void* value,
+                        size_t value_length) {
+  if (!writer->AddPropertyBytes(key, value, value_length))
+    WriteError(key);
+}
+
+kern_return_t MachVMRegionRecurseDeepest(task_t task,
+                                         vm_address_t* address,
+                                         vm_size_t* size,
+                                         natural_t* depth,
+                                         vm_prot_t* protection,
+                                         unsigned int* user_tag) {
+  vm_region_submap_short_info_64 submap_info;
+  mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
+  while (true) {
+    // Note: vm_region_recurse() would be fine here, but it does not provide
+    // VM_REGION_SUBMAP_SHORT_INFO_COUNT.
+    kern_return_t kr = vm_region_recurse_64(
+        task,
+        address,
+        size,
+        depth,
+        reinterpret_cast<vm_region_recurse_info_t>(&submap_info),
+        &count);
+    if (kr != KERN_SUCCESS) {
+      CRASHPAD_RAW_LOG_ERROR(kr, "vm_region_recurse_64");
+      return kr;
+    }
+
+    if (!submap_info.is_submap) {
+      *protection = submap_info.protection;
+      *user_tag = submap_info.user_tag;
+      return KERN_SUCCESS;
+    }
+
+    ++*depth;
+  }
+}
+
+//! \brief Adjusts the region for the red zone, if the ABI requires one.
+//!
+//! This method performs red zone calculation for CalculateStackRegion(). Its
+//! parameters are local variables used within that method, and may be
+//! modified as needed.
+//!
+//! Where a red zone is required, the region of memory captured for a thread’s
+//! stack will be extended to include the red zone below the stack pointer,
+//! provided that such memory is mapped, readable, and has the correct user
+//! tag value. If these conditions cannot be met fully, as much of the red
+//! zone will be captured as is possible while meeting these conditions.
+//!
+//! \param[in,out] start_address The base address of the region to begin
+//!     capturing stack memory from. On entry, \a start_address is the stack
+//!     pointer. On return, \a start_address may be decreased to encompass a
+//!     red zone.
+//! \param[in,out] region_base The base address of the region that contains
+//!     stack memory. This is distinct from \a start_address in that \a
+//!     region_base will be page-aligned. On entry, \a region_base is the
+//!     base address of a region that contains \a start_address. On return,
+//!     if \a start_address is decremented and is outside of the region
+//!     originally described by \a region_base, \a region_base will also be
+//!     decremented appropriately.
+//! \param[in,out] region_size The size of the region that contains stack
+//!     memory. This region begins at \a region_base. On return, if \a
+//!     region_base is decremented, \a region_size will be incremented
+//!     appropriately.
+//! \param[in] user_tag The Mach VM system’s user tag for the region described
+//!     by the initial values of \a region_base and \a region_size. The red
+//!     zone will only be allowed to extend out of the region described by
+//!     these initial values if the user tag is appropriate for stack memory
+//!     and the expanded region has the same user tag value.
+void LocateRedZone(vm_address_t* const start_address,
+                   vm_address_t* const region_base,
+                   vm_address_t* const region_size,
+                   const unsigned int user_tag) {
+  // x86_64 has a red zone. See AMD64 ABI 0.99.8,
+  // https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/uploads/01de35b2c8adc7545de52604cc45d942/x86-64-psABI-2021-05-20.pdf#page=23.
+  // section 3.2.2, “The Stack Frame”.
+  // So does ARM64,
+  // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Respect-the-Stacks-Red-Zone
+  // section "Respect the Stack’s Red Zone".
+  constexpr vm_size_t kRedZoneSize = 128;
+  vm_address_t red_zone_base =
+      *start_address >= kRedZoneSize ? *start_address - kRedZoneSize : 0;
+  bool red_zone_ok = false;
+  if (red_zone_base >= *region_base) {
+    // The red zone is within the region already discovered.
+    red_zone_ok = true;
+  } else if (red_zone_base < *region_base && user_tag == VM_MEMORY_STACK) {
+    // Probe to see if there’s a region immediately below the one already
+    // discovered.
+    vm_address_t red_zone_region_base = red_zone_base;
+    vm_size_t red_zone_region_size;
+    natural_t red_zone_depth = 0;
+    vm_prot_t red_zone_protection;
+    unsigned int red_zone_user_tag;
+    kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(),
+                                                  &red_zone_region_base,
+                                                  &red_zone_region_size,
+                                                  &red_zone_depth,
+                                                  &red_zone_protection,
+                                                  &red_zone_user_tag);
+    if (kr != KERN_SUCCESS) {
+      *start_address = *region_base;
+    } else if (red_zone_region_base + red_zone_region_size == *region_base &&
+               (red_zone_protection & VM_PROT_READ) != 0 &&
+               red_zone_user_tag == user_tag) {
+      // The region containing the red zone is immediately below the region
+      // already found, it’s readable (not the guard region), and it has the
+      // same user tag as the region already found, so merge them.
+      red_zone_ok = true;
+      *region_base -= red_zone_region_size;
+      *region_size += red_zone_region_size;
+    }
+  }
+
+  if (red_zone_ok) {
+    // Begin capturing from the base of the red zone (but not the entire
+    // region that encompasses the red zone).
+    *start_address = red_zone_base;
+  } else {
+    // The red zone would go lower into another region in memory, but no
+    // region was found. Memory can only be captured to an address as low as
+    // the base address of the region already found.
+    *start_address = *region_base;
+  }
+}
+
+//! \brief Calculates the base address and size of the region used as a
+//!     thread’s stack.
+//!
+//! The region returned by this method may be formed by merging multiple
+//! adjacent regions in a process’ memory map if appropriate. The base address
+//! of the returned region may be lower than the \a stack_pointer passed in
+//! when the ABI mandates a red zone below the stack pointer.
+//!
+//! \param[in] stack_pointer The stack pointer, referring to the top (lowest
+//!     address) of a thread’s stack.
+//! \param[out] stack_region_size The size of the memory region used as the
+//!     thread’s stack.
+//!
+//! \return The base address (lowest address) of the memory region used as the
+//!     thread’s stack.
+vm_address_t CalculateStackRegion(vm_address_t stack_pointer,
+                                  vm_size_t* stack_region_size) {
+  // For pthreads, it may be possible to compute the stack region based on the
+  // internal _pthread::stackaddr and _pthread::stacksize. The _pthread struct
+  // for a thread can be located at TSD slot 0, or the known offsets of
+  // stackaddr and stacksize from the TSD area could be used.
+  vm_address_t region_base = stack_pointer;
+  vm_size_t region_size;
+  natural_t depth = 0;
+  vm_prot_t protection;
+  unsigned int user_tag;
+  kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(),
+                                                &region_base,
+                                                &region_size,
+                                                &depth,
+                                                &protection,
+                                                &user_tag);
+  if (kr != KERN_SUCCESS) {
+    CRASHPAD_RAW_LOG_ERROR(kr, "MachVMRegionRecurseDeepest");
+    *stack_region_size = 0;
+    return 0;
+  }
+
+  if (region_base > stack_pointer) {
+    // There’s nothing mapped at the stack pointer’s address. Something may have
+    // trashed the stack pointer. Note that this shouldn’t happen for a normal
+    // stack guard region violation because the guard region is mapped but has
+    // VM_PROT_NONE protection.
+    *stack_region_size = 0;
+    return 0;
+  }
+
+  vm_address_t start_address = stack_pointer;
+
+  if ((protection & VM_PROT_READ) == 0) {
+    // If the region isn’t readable, the stack pointer probably points to the
+    // guard region. Don’t include it as part of the stack, and don’t include
+    // anything at any lower memory address. The code below may still possibly
+    // find the real stack region at a memory address higher than this region.
+    start_address = region_base + region_size;
+  } else {
+    // If the ABI requires a red zone, adjust the region to include it if
+    // possible.
+    LocateRedZone(&start_address, &region_base, &region_size, user_tag);
+
+    // Regardless of whether the ABI requires a red zone, capture up to
+    // kExtraCaptureSize additional bytes of stack, but only if present in the
+    // region that was already found.
+    constexpr vm_size_t kExtraCaptureSize = 128;
+    start_address = std::max(start_address >= kExtraCaptureSize
+                                 ? start_address - kExtraCaptureSize
+                                 : start_address,
+                             region_base);
+
+    // Align start_address to a 16-byte boundary, which can help readers by
+    // ensuring that data is aligned properly. This could page-align instead,
+    // but that might be wasteful.
+    constexpr vm_size_t kDesiredAlignment = 16;
+    start_address &= ~(kDesiredAlignment - 1);
+    DCHECK_GE(start_address, region_base);
+  }
+
+  region_size -= (start_address - region_base);
+  region_base = start_address;
+
+  vm_size_t total_region_size = region_size;
+
+  // The stack region may have gotten split up into multiple abutting regions.
+  // Try to coalesce them. This frequently happens for the main thread’s stack
+  // when setrlimit(RLIMIT_STACK, …) is called. It may also happen if a region
+  // is split up due to an mprotect() or vm_protect() call.
+  //
+  // Stack regions created by the kernel and the pthreads library will be marked
+  // with the VM_MEMORY_STACK user tag. Scanning for multiple adjacent regions
+  // with the same tag should find an entire stack region. Checking that the
+  // protection on individual regions is not VM_PROT_NONE should guarantee that
+  // this algorithm doesn’t collect map entries belonging to another thread’s
+  // stack: well-behaved stacks (such as those created by the kernel and the
+  // pthreads library) have VM_PROT_NONE guard regions at their low-address
+  // ends.
+  //
+  // Other stack regions may not be so well-behaved and thus if user_tag is not
+  // VM_MEMORY_STACK, the single region that was found is used as-is without
+  // trying to merge it with other adjacent regions.
+  if (user_tag == VM_MEMORY_STACK) {
+    vm_address_t try_address = region_base;
+    vm_address_t original_try_address;
+
+    while (try_address += region_size,
+           original_try_address = try_address,
+           (kr = MachVMRegionRecurseDeepest(mach_task_self(),
+                                            &try_address,
+                                            &region_size,
+                                            &depth,
+                                            &protection,
+                                            &user_tag) == KERN_SUCCESS) &&
+               try_address == original_try_address &&
+               (protection & VM_PROT_READ) != 0 &&
+               user_tag == VM_MEMORY_STACK) {
+      total_region_size += region_size;
+    }
+
+    if (kr != KERN_SUCCESS && kr != KERN_INVALID_ADDRESS) {
+      // Tolerate KERN_INVALID_ADDRESS because it will be returned when there
+      // are no more regions in the map at or above the specified |try_address|.
+      CRASHPAD_RAW_LOG_ERROR(kr, "MachVMRegionRecurseDeepest");
+    }
+  }
+
+  *stack_region_size = total_region_size;
+  return region_base;
+}
+
+//! \brief Write data around \a address to intermediate dump. Must be called
+//!    from within a ScopedArray.
+void MaybeCaptureMemoryAround(IOSIntermediateDumpWriter* writer,
+                              uint64_t address) {
+  constexpr uint64_t non_address_offset = 0x10000;
+  if (address < non_address_offset)
+    return;
+
+  constexpr uint64_t max_address = std::numeric_limits<uint64_t>::max();
+
+  if (address > max_address - non_address_offset)
+    return;
+
+  constexpr uint64_t kRegisterByteOffset = 128;
+  const uint64_t target = address - kRegisterByteOffset;
+  constexpr uint64_t size = 512;
+  static_assert(kRegisterByteOffset <= size / 2, "negative offset too large");
+
+  IOSIntermediateDumpWriter::ScopedArrayMap memory_region(writer);
+  WriteProperty(
+      writer, IntermediateDumpKey::kThreadContextMemoryRegionAddress, &address);
+  // Don't use WritePropertyBytes, this one will fail regularly if |target|
+  // cannot be read.
+  writer->AddPropertyBytes(IntermediateDumpKey::kThreadContextMemoryRegionData,
+                           reinterpret_cast<const void*>(target),
+                           size);
+}
+
+void CaptureMemoryPointedToByThreadState(IOSIntermediateDumpWriter* writer,
+                                         thread_state_type thread_state) {
+  IOSIntermediateDumpWriter::ScopedArray memory_regions(
+      writer, IntermediateDumpKey::kThreadContextMemoryRegions);
+
+#if defined(ARCH_CPU_X86_64)
+  MaybeCaptureMemoryAround(writer, thread_state.__rax);
+  MaybeCaptureMemoryAround(writer, thread_state.__rbx);
+  MaybeCaptureMemoryAround(writer, thread_state.__rcx);
+  MaybeCaptureMemoryAround(writer, thread_state.__rdx);
+  MaybeCaptureMemoryAround(writer, thread_state.__rdi);
+  MaybeCaptureMemoryAround(writer, thread_state.__rsi);
+  MaybeCaptureMemoryAround(writer, thread_state.__rbp);
+  MaybeCaptureMemoryAround(writer, thread_state.__r8);
+  MaybeCaptureMemoryAround(writer, thread_state.__r9);
+  MaybeCaptureMemoryAround(writer, thread_state.__r10);
+  MaybeCaptureMemoryAround(writer, thread_state.__r11);
+  MaybeCaptureMemoryAround(writer, thread_state.__r12);
+  MaybeCaptureMemoryAround(writer, thread_state.__r13);
+  MaybeCaptureMemoryAround(writer, thread_state.__r14);
+  MaybeCaptureMemoryAround(writer, thread_state.__r15);
+  MaybeCaptureMemoryAround(writer, thread_state.__rip);
+#elif defined(ARCH_CPU_ARM_FAMILY)
+  MaybeCaptureMemoryAround(writer, thread_state.__pc);
+  for (size_t i = 0; i < base::size(thread_state.__x); ++i) {
+    MaybeCaptureMemoryAround(writer, thread_state.__x[i]);
+  }
+#endif
+}
+
+void WriteCrashpadSimpleAnnotationsDictionary(IOSIntermediateDumpWriter* writer,
+                                              CrashpadInfo* crashpad_info) {
+  if (!crashpad_info->simple_annotations())
+    return;
+
+  ScopedVMRead<SimpleStringDictionary> simple_annotations;
+  if (!simple_annotations.Read(crashpad_info->simple_annotations())) {
+    CRASHPAD_RAW_LOG("Unable to read simple annotations.");
+    return;
+  }
+
+  const size_t count = simple_annotations->GetCount();
+  if (!count)
+    return;
+
+  IOSIntermediateDumpWriter::ScopedArray annotations_array(
+      writer, IntermediateDumpKey::kAnnotationsSimpleMap);
+
+  SimpleStringDictionary::Entry* entries =
+      reinterpret_cast<SimpleStringDictionary::Entry*>(
+          simple_annotations.get());
+  for (size_t index = 0; index < count; index++) {
+    IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer);
+    const auto& entry = entries[index];
+    size_t key_length = strnlen(entry.key, sizeof(entry.key));
+    WritePropertyBytes(writer,
+                       IntermediateDumpKey::kAnnotationName,
+                       reinterpret_cast<const void*>(entry.key),
+                       key_length);
+    size_t value_length = strnlen(entry.value, sizeof(entry.value));
+    WritePropertyBytes(writer,
+                       IntermediateDumpKey::kAnnotationValue,
+                       reinterpret_cast<const void*>(entry.value),
+                       value_length);
+  }
+}
+
+void WriteAppleCrashReporterAnnotations(
+    IOSIntermediateDumpWriter* writer,
+    crashreporter_annotations_t* crash_info) {
+  // This number was totally made up out of nowhere, but it seems prudent to
+  // enforce some limit.
+  constexpr size_t kMaxMessageSize = 1024;
+  IOSIntermediateDumpWriter::ScopedMap annotation_map(
+      writer, IntermediateDumpKey::kAnnotationsCrashInfo);
+  if (crash_info->message) {
+    const size_t message_len = strnlen(
+        reinterpret_cast<const char*>(crash_info->message), kMaxMessageSize);
+    WritePropertyBytes(writer,
+                       IntermediateDumpKey::kAnnotationsCrashInfoMessage1,
+                       reinterpret_cast<const void*>(crash_info->message),
+                       message_len);
+  }
+  if (crash_info->message2) {
+    const size_t message_len = strnlen(
+        reinterpret_cast<const char*>(crash_info->message2), kMaxMessageSize);
+    WritePropertyBytes(writer,
+                       IntermediateDumpKey::kAnnotationsCrashInfoMessage2,
+                       reinterpret_cast<const void*>(crash_info->message2),
+                       message_len);
+  }
+}
+
+void WriteDyldErrorStringAnnotation(
+    IOSIntermediateDumpWriter* writer,
+    const uint64_t address,
+    const symtab_command* symtab_command_ptr,
+    const dysymtab_command* dysymtab_command_ptr,
+    const segment_command_64* text_seg_ptr,
+    const segment_command_64* linkedit_seg_ptr,
+    vm_size_t slide) {
+  if (text_seg_ptr == nullptr || linkedit_seg_ptr == nullptr ||
+      symtab_command_ptr == nullptr) {
+    return;
+  }
+
+  ScopedVMRead<symtab_command> symtab_command;
+  ScopedVMRead<dysymtab_command> dysymtab_command;
+  ScopedVMRead<segment_command_64> text_seg;
+  ScopedVMRead<segment_command_64> linkedit_seg;
+  if (!symtab_command.Read(symtab_command_ptr) ||
+      !text_seg.Read(text_seg_ptr) || !linkedit_seg.Read(linkedit_seg_ptr) ||
+      (dysymtab_command_ptr && !dysymtab_command.Read(dysymtab_command_ptr))) {
+    CRASHPAD_RAW_LOG("Unable to load dyld symbol table.");
+  }
+
+  uint64_t file_slide =
+      (linkedit_seg->vmaddr - text_seg->vmaddr) - linkedit_seg->fileoff;
+  uint64_t strings = address + (symtab_command->stroff + file_slide);
+  nlist_64* symbol_ptr = reinterpret_cast<nlist_64*>(
+      address + (symtab_command->symoff + file_slide));
+
+  // If a dysymtab is present, use it to filter the symtab for just the
+  // portion used for extdefsym. If no dysymtab is present, the entire symtab
+  // will need to be consulted.
+  uint32_t symbol_count = symtab_command->nsyms;
+  if (dysymtab_command_ptr) {
+    symbol_ptr += dysymtab_command->iextdefsym;
+    symbol_count = dysymtab_command->nextdefsym;
+  }
+
+  for (uint32_t i = 0; i < symbol_count; i++, symbol_ptr++) {
+    ScopedVMRead<nlist_64> symbol;
+    if (!symbol.Read(symbol_ptr)) {
+      CRASHPAD_RAW_LOG("Unable to load dyld symbol table symbol.");
+      return;
+    }
+
+    if (!symbol->n_value)
+      continue;
+
+    ScopedVMRead<const char> symbol_name;
+    if (!symbol_name.Read(strings + symbol->n_un.n_strx)) {
+      CRASHPAD_RAW_LOG("Unable to load dyld symbol name.");
+    }
+
+    if (strcmp(symbol_name.get(), "_error_string") == 0) {
+      ScopedVMRead<const char> symbol_value;
+      if (!symbol_value.Read(symbol->n_value + slide)) {
+        CRASHPAD_RAW_LOG("Unable to load dyld symbol value.");
+      }
+      // 1024 here is distinct from kMaxMessageSize above, because it refers to
+      // a precisely-sized buffer inside dyld.
+      const size_t value_len = strnlen(symbol_value.get(), 1024);
+      if (value_len) {
+        WriteProperty(writer,
+                      IntermediateDumpKey::kAnnotationsDyldErrorString,
+                      symbol_value.get(),
+                      value_len);
+      }
+      return;
+    }
+
+    continue;
+  }
+}
+
+}  // namespace
+
+// static
+void InProcessIntermediateDumpHandler::WriteHeader(
+    IOSIntermediateDumpWriter* writer) {
+  static constexpr uint8_t version = 1;
+  WriteProperty(writer, IntermediateDumpKey::kVersion, &version);
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteProcessInfo(
+    IOSIntermediateDumpWriter* writer) {
+  IOSIntermediateDumpWriter::ScopedMap process_map(
+      writer, IntermediateDumpKey::kProcessInfo);
+
+  timeval snapshot_time;
+  if (gettimeofday(&snapshot_time, nullptr) == 0) {
+    WriteProperty(writer, IntermediateDumpKey::kSnapshotTime, &snapshot_time);
+  } else {
+    CRASHPAD_RAW_LOG("gettimeofday");
+  }
+
+  // Used by pid, parent pid and snapshot time.
+  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) {
+    WriteProperty(
+        writer, IntermediateDumpKey::kPID, &kern_proc_info.kp_proc.p_pid);
+    WriteProperty(writer,
+                  IntermediateDumpKey::kParentPID,
+                  &kern_proc_info.kp_eproc.e_ppid);
+    WriteProperty(writer,
+                  IntermediateDumpKey::kStartTime,
+                  &kern_proc_info.kp_proc.p_starttime);
+  } else {
+    CRASHPAD_RAW_LOG("sysctl kern_proc_info");
+  }
+
+  // Used by user time and system time.
+  mach_task_basic_info task_basic_info;
+  mach_msg_type_number_t task_basic_info_count = MACH_TASK_BASIC_INFO_COUNT;
+  kern_return_t kr = task_info(mach_task_self(),
+                               MACH_TASK_BASIC_INFO,
+                               reinterpret_cast<task_info_t>(&task_basic_info),
+                               &task_basic_info_count);
+  if (kr == KERN_SUCCESS) {
+    IOSIntermediateDumpWriter::ScopedMap task_info(
+        writer, IntermediateDumpKey::kTaskBasicInfo);
+
+    WriteProperty(
+        writer, IntermediateDumpKey::kUserTime, &task_basic_info.user_time);
+    WriteProperty(
+        writer, IntermediateDumpKey::kSystemTime, &task_basic_info.system_time);
+  } else {
+    CRASHPAD_RAW_LOG("task_info task_basic_info");
+  }
+
+  task_thread_times_info_data_t task_thread_times;
+  mach_msg_type_number_t task_thread_times_count = TASK_THREAD_TIMES_INFO_COUNT;
+  kr = task_info(mach_task_self(),
+                 TASK_THREAD_TIMES_INFO,
+                 reinterpret_cast<task_info_t>(&task_thread_times),
+                 &task_thread_times_count);
+  if (kr == KERN_SUCCESS) {
+    IOSIntermediateDumpWriter::ScopedMap task_thread_times_map(
+        writer, IntermediateDumpKey::kTaskThreadTimes);
+
+    WriteProperty(
+        writer, IntermediateDumpKey::kUserTime, &task_thread_times.user_time);
+    WriteProperty(writer,
+                  IntermediateDumpKey::kSystemTime,
+                  &task_thread_times.system_time);
+  } else {
+    CRASHPAD_RAW_LOG("task_info task_basic_info");
+  }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteSystemInfo(
+    IOSIntermediateDumpWriter* writer,
+    const IOSSystemDataCollector& system_data) {
+  IOSIntermediateDumpWriter::ScopedMap system_map(
+      writer, IntermediateDumpKey::kSystemInfo);
+
+  const std::string& machine_description = system_data.MachineDescription();
+  WriteProperty(writer,
+                IntermediateDumpKey::kMachineDescription,
+                machine_description.c_str(),
+                machine_description.length());
+  int os_version_major;
+  int os_version_minor;
+  int os_version_bugfix;
+  system_data.OSVersion(
+      &os_version_major, &os_version_minor, &os_version_bugfix);
+  WriteProperty(
+      writer, IntermediateDumpKey::kOSVersionMajor, &os_version_major);
+  WriteProperty(
+      writer, IntermediateDumpKey::kOSVersionMinor, &os_version_minor);
+  WriteProperty(
+      writer, IntermediateDumpKey::kOSVersionBugfix, &os_version_bugfix);
+  const std::string& os_version_build = system_data.Build();
+  WriteProperty(writer,
+                IntermediateDumpKey::kOSVersionBuild,
+                os_version_build.c_str(),
+                os_version_build.length());
+
+  int cpu_count = system_data.ProcessorCount();
+  WriteProperty(writer, IntermediateDumpKey::kCpuCount, &cpu_count);
+  const std::string& cpu_vendor = system_data.CPUVendor();
+  WriteProperty(writer,
+                IntermediateDumpKey::kCpuVendor,
+                cpu_vendor.c_str(),
+                cpu_vendor.length());
+
+  bool has_daylight_saving_time = system_data.HasDaylightSavingTime();
+  WriteProperty(writer,
+                IntermediateDumpKey::kHasDaylightSavingTime,
+                &has_daylight_saving_time);
+  bool is_daylight_saving_time = system_data.IsDaylightSavingTime();
+  WriteProperty(writer,
+                IntermediateDumpKey::kIsDaylightSavingTime,
+                &is_daylight_saving_time);
+  int standard_offset_seconds = system_data.StandardOffsetSeconds();
+  WriteProperty(writer,
+                IntermediateDumpKey::kStandardOffsetSeconds,
+                &standard_offset_seconds);
+  int daylight_offset_seconds = system_data.DaylightOffsetSeconds();
+  WriteProperty(writer,
+                IntermediateDumpKey::kDaylightOffsetSeconds,
+                &daylight_offset_seconds);
+  const std::string& standard_name = system_data.StandardName();
+  WriteProperty(writer,
+                IntermediateDumpKey::kStandardName,
+                standard_name.c_str(),
+                standard_name.length());
+  const std::string& daylight_name = system_data.DaylightName();
+  WriteProperty(writer,
+                IntermediateDumpKey::kDaylightName,
+                daylight_name.c_str(),
+                daylight_name.length());
+
+  vm_size_t page_size;
+  host_page_size(mach_host_self(), &page_size);
+  WriteProperty(writer, IntermediateDumpKey::kPageSize, &page_size);
+
+  mach_msg_type_number_t host_size =
+      sizeof(vm_statistics_data_t) / sizeof(integer_t);
+  vm_statistics_data_t vm_stat;
+  kern_return_t kr = host_statistics(mach_host_self(),
+                                     HOST_VM_INFO,
+                                     reinterpret_cast<host_info_t>(&vm_stat),
+                                     &host_size);
+  if (kr == KERN_SUCCESS) {
+    IOSIntermediateDumpWriter::ScopedMap vm_stat_map(
+        writer, IntermediateDumpKey::kVMStat);
+
+    WriteProperty(writer, IntermediateDumpKey::kActive, &vm_stat.active_count);
+    WriteProperty(
+        writer, IntermediateDumpKey::kInactive, &vm_stat.inactive_count);
+    WriteProperty(writer, IntermediateDumpKey::kWired, &vm_stat.wire_count);
+    WriteProperty(writer, IntermediateDumpKey::kFree, &vm_stat.free_count);
+  } else {
+    CRASHPAD_RAW_LOG("host_statistics");
+  }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteThreadInfo(
+    IOSIntermediateDumpWriter* writer,
+    const uint64_t* frames,
+    const size_t num_frames) {
+  IOSIntermediateDumpWriter::ScopedArray thread_array(
+      writer, IntermediateDumpKey::kThreads);
+
+  // Exception thread ID.
+  uint64_t exception_thread_id = 0;
+  thread_identifier_info identifier_info;
+  mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+  kern_return_t kr =
+      thread_info(mach_thread_self(),
+                  THREAD_IDENTIFIER_INFO,
+                  reinterpret_cast<thread_info_t>(&identifier_info),
+                  &count);
+  if (kr == KERN_SUCCESS) {
+    exception_thread_id = identifier_info.thread_id;
+  } else {
+    CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_IDENTIFIER_INFO");
+  }
+
+  mach_msg_type_number_t thread_count = 0;
+  thread_act_array_t threads;
+  kr = task_threads(mach_task_self(), &threads, &thread_count);
+  if (kr != KERN_SUCCESS) {
+    CRASHPAD_RAW_LOG_ERROR(kr, "task_threads");
+  }
+  ScopedTaskThreads threads_vm_owner(threads, thread_count);
+
+  for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) {
+    IOSIntermediateDumpWriter::ScopedArrayMap thread_map(writer);
+    thread_t thread = threads[thread_index];
+
+    thread_basic_info basic_info;
+    mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
+    kr = thread_info(thread,
+                     THREAD_BASIC_INFO,
+                     reinterpret_cast<thread_info_t>(&basic_info),
+                     &count);
+    if (kr == KERN_SUCCESS) {
+      WriteProperty(writer,
+                    IntermediateDumpKey::kSuspendCount,
+                    &basic_info.suspend_count);
+    } else {
+      CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_BASIC_INFO");
+    }
+
+    thread_precedence_policy precedence;
+    count = THREAD_PRECEDENCE_POLICY_COUNT;
+    boolean_t get_default = FALSE;
+    kr = thread_policy_get(thread,
+                           THREAD_PRECEDENCE_POLICY,
+                           reinterpret_cast<thread_policy_t>(&precedence),
+                           &count,
+                           &get_default);
+    if (kr == KERN_SUCCESS) {
+      WriteProperty(
+          writer, IntermediateDumpKey::kPriority, &precedence.importance);
+    } else {
+      CRASHPAD_RAW_LOG_ERROR(kr, "thread_policy_get");
+    }
+
+    // Thread ID.
+    uint64_t thread_id;
+    thread_identifier_info identifier_info;
+    count = THREAD_IDENTIFIER_INFO_COUNT;
+    kr = thread_info(thread,
+                     THREAD_IDENTIFIER_INFO,
+                     reinterpret_cast<thread_info_t>(&identifier_info),
+                     &count);
+    if (kr == KERN_SUCCESS) {
+      thread_id = identifier_info.thread_id;
+      WriteProperty(
+          writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id);
+      WriteProperty(writer,
+                    IntermediateDumpKey::kThreadDataAddress,
+                    &identifier_info.thread_handle);
+    } else {
+      CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_IDENTIFIER_INFO");
+    }
+
+    // thread_snapshot_ios_intermediate_dump::GenerateStackMemoryFromFrames is
+    // only implemented for arm64, so no x86_64 block here.
+#if defined(ARCH_CPU_ARM64)
+    // For uncaught NSExceptions, use the frames passed from the system rather
+    // than the current thread state.
+    if (num_frames > 0 && exception_thread_id == thread_id) {
+      WriteProperty(writer,
+                    IntermediateDumpKey::kThreadUncaughtNSExceptionFrames,
+                    frames,
+                    num_frames);
+      continue;
+    }
+#endif
+
+#if defined(ARCH_CPU_X86_64)
+    x86_thread_state64_t thread_state;
+    x86_float_state64_t float_state;
+    x86_debug_state64_t debug_state;
+    mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT;
+    mach_msg_type_number_t float_state_count = x86_FLOAT_STATE64_COUNT;
+    mach_msg_type_number_t debug_state_count = x86_DEBUG_STATE64_COUNT;
+#elif defined(ARCH_CPU_ARM64)
+    arm_thread_state64_t thread_state;
+    arm_neon_state64_t float_state;
+    arm_debug_state64_t debug_state;
+    mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT;
+    mach_msg_type_number_t float_state_count = ARM_NEON_STATE64_COUNT;
+    mach_msg_type_number_t debug_state_count = ARM_DEBUG_STATE64_COUNT;
+#endif
+
+    kern_return_t kr =
+        thread_get_state(thread,
+                         kThreadStateFlavor,
+                         reinterpret_cast<thread_state_t>(&thread_state),
+                         &thread_state_count);
+    if (kr != KERN_SUCCESS) {
+      CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kThreadStateFlavor");
+    }
+    WriteProperty(writer, IntermediateDumpKey::kThreadState, &thread_state);
+
+    kr = thread_get_state(thread,
+                          kFloatStateFlavor,
+                          reinterpret_cast<thread_state_t>(&float_state),
+                          &float_state_count);
+    if (kr != KERN_SUCCESS) {
+      CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kFloatStateFlavor");
+    }
+    WriteProperty(writer, IntermediateDumpKey::kFloatState, &float_state);
+
+    kr = thread_get_state(thread,
+                          kDebugStateFlavor,
+                          reinterpret_cast<thread_state_t>(&debug_state),
+                          &debug_state_count);
+    if (kr != KERN_SUCCESS) {
+      CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kDebugStateFlavor");
+    }
+    WriteProperty(writer, IntermediateDumpKey::kDebugState, &debug_state);
+
+#if defined(ARCH_CPU_X86_64)
+    vm_address_t stack_pointer = thread_state.__rsp;
+#elif defined(ARCH_CPU_ARM64)
+    vm_address_t stack_pointer = thread_state.__sp;
+#endif
+
+    vm_size_t stack_region_size;
+    const vm_address_t stack_region_address =
+        CalculateStackRegion(stack_pointer, &stack_region_size);
+    WriteProperty(writer,
+                  IntermediateDumpKey::kStackRegionAddress,
+                  &stack_region_address);
+    WritePropertyBytes(writer,
+                       IntermediateDumpKey::kStackRegionData,
+                       reinterpret_cast<const void*>(stack_region_address),
+                       stack_region_size);
+
+    // Grab extra memory from context.
+    CaptureMemoryPointedToByThreadState(writer, thread_state);
+  }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteModuleInfo(
+    IOSIntermediateDumpWriter* writer) {
+#ifndef ARCH_CPU_64_BITS
+#error Only 64-bit Mach-O is supported
+#endif
+
+  IOSIntermediateDumpWriter::ScopedArray module_array(
+      writer, IntermediateDumpKey::kModules);
+
+  task_dyld_info_data_t dyld_info;
+  mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
+  kern_return_t kr = task_info(mach_task_self(),
+                               TASK_DYLD_INFO,
+                               reinterpret_cast<task_info_t>(&dyld_info),
+                               &count);
+  if (kr != KERN_SUCCESS) {
+    CRASHPAD_RAW_LOG_ERROR(kr, "task_info");
+  }
+
+  ScopedVMRead<dyld_all_image_infos> image_infos;
+  if (!image_infos.Read(dyld_info.all_image_info_addr)) {
+    CRASHPAD_RAW_LOG("Unable to dyld_info.all_image_info_addr");
+    return;
+  }
+
+  uint32_t image_count = image_infos->infoArrayCount;
+  const dyld_image_info* image_array = image_infos->infoArray;
+  for (uint32_t image_index = 0; image_index < image_count; ++image_index) {
+    IOSIntermediateDumpWriter::ScopedArrayMap modules(writer);
+    ScopedVMRead<dyld_image_info> image;
+    if (!image.Read(&image_array[image_index])) {
+      CRASHPAD_RAW_LOG("Unable to dyld_image_info");
+      return;
+    }
+
+    WriteProperty(writer,
+                  IntermediateDumpKey::kName,
+                  image->imageFilePath,
+                  strlen(image->imageFilePath));
+    uint64_t address = FromPointerCast<uint64_t>(image->imageLoadAddress);
+    WriteProperty(writer, IntermediateDumpKey::kAddress, &address);
+    WriteProperty(
+        writer, IntermediateDumpKey::kTimestamp, &image->imageFileModDate);
+    WriteModuleInfoAtAddress(writer, address, false /*is_dyld=false*/);
+  }
+
+  {
+    IOSIntermediateDumpWriter::ScopedArrayMap modules(writer);
+    WriteProperty(writer, IntermediateDumpKey::kName, image_infos->dyldPath);
+    uint64_t address =
+        FromPointerCast<uint64_t>(image_infos->dyldImageLoadAddress);
+    WriteProperty(writer, IntermediateDumpKey::kAddress, &address);
+    WriteModuleInfoAtAddress(writer, address, true /*is_dyld=true*/);
+  }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteExceptionFromSignal(
+    IOSIntermediateDumpWriter* writer,
+    const IOSSystemDataCollector& system_data,
+    siginfo_t* siginfo,
+    ucontext_t* context) {
+  IOSIntermediateDumpWriter::ScopedMap signal_exception_map(
+      writer, IntermediateDumpKey::kSignalException);
+
+  WriteProperty(writer, IntermediateDumpKey::kSignalNumber, &siginfo->si_signo);
+  WriteProperty(writer, IntermediateDumpKey::kSignalCode, &siginfo->si_code);
+  WriteProperty(writer, IntermediateDumpKey::kSignalAddress, &siginfo->si_addr);
+#if defined(ARCH_CPU_X86_64)
+  WriteProperty(
+      writer, IntermediateDumpKey::kThreadState, &context->uc_mcontext->__ss);
+  WriteProperty(
+      writer, IntermediateDumpKey::kFloatState, &context->uc_mcontext->__fs);
+#elif defined(ARCH_CPU_ARM64)
+  WriteProperty(
+      writer, IntermediateDumpKey::kThreadState, &context->uc_mcontext->__ss);
+  WriteProperty(
+      writer, IntermediateDumpKey::kFloatState, &context->uc_mcontext->__ns);
+#else
+#error Port to your CPU architecture
+#endif
+
+  // Thread ID.
+  thread_identifier_info identifier_info;
+  mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+  kern_return_t kr =
+      thread_info(mach_thread_self(),
+                  THREAD_IDENTIFIER_INFO,
+                  reinterpret_cast<thread_info_t>(&identifier_info),
+                  &count);
+  if (kr == KERN_SUCCESS) {
+    WriteProperty(
+        writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id);
+  } else {
+    CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::self");
+  }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteExceptionFromMachException(
+    IOSIntermediateDumpWriter* writer,
+    exception_behavior_t behavior,
+    thread_t exception_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 state,
+    mach_msg_type_number_t state_count) {
+  IOSIntermediateDumpWriter::ScopedMap mach_exception_map(
+      writer, IntermediateDumpKey::kMachException);
+
+  WriteProperty(writer, IntermediateDumpKey::kException, &exception);
+  WriteProperty(writer, IntermediateDumpKey::kCodes, code, code_count);
+  WriteProperty(writer, IntermediateDumpKey::kFlavor, &flavor);
+  WritePropertyBytes(writer,
+                     IntermediateDumpKey::kState,
+                     state,
+                     state_count * sizeof(uint32_t));
+
+  thread_identifier_info identifier_info;
+  mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+  kern_return_t kr =
+      thread_info(exception_thread,
+                  THREAD_IDENTIFIER_INFO,
+                  reinterpret_cast<thread_info_t>(&identifier_info),
+                  &count);
+  if (kr == KERN_SUCCESS) {
+    WriteProperty(
+        writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id);
+  } else {
+    CRASHPAD_RAW_LOG_ERROR(kr, "thread_info");
+  }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteExceptionFromNSException(
+    IOSIntermediateDumpWriter* writer) {
+  IOSIntermediateDumpWriter::ScopedMap nsexception_map(
+      writer, IntermediateDumpKey::kNSException);
+
+  thread_identifier_info identifier_info;
+  mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+  kern_return_t kr =
+      thread_info(mach_thread_self(),
+                  THREAD_IDENTIFIER_INFO,
+                  reinterpret_cast<thread_info_t>(&identifier_info),
+                  &count);
+  if (kr == KERN_SUCCESS) {
+    WriteProperty(
+        writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id);
+  } else {
+    CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::self");
+  }
+}
+
+void InProcessIntermediateDumpHandler::WriteModuleInfoAtAddress(
+    IOSIntermediateDumpWriter* writer,
+    uint64_t address,
+    bool is_dyld) {
+  ScopedVMRead<mach_header_64> header;
+  if (!header.Read(address) || header->magic != MH_MAGIC_64) {
+    CRASHPAD_RAW_LOG("Invalid module header");
+    return;
+  }
+
+  const load_command* command_ptr = reinterpret_cast<const load_command*>(
+      reinterpret_cast<const mach_header_64*>(address) + 1);
+
+  ScopedVMRead<load_command> command;
+  if (!command.Read(command_ptr)) {
+    CRASHPAD_RAW_LOG("Invalid module command");
+    return;
+  }
+
+  // Make sure that the basic load command structure doesn’t overflow the
+  // space allotted for load commands, as well as iterating through ncmds.
+  vm_size_t slide = 0;
+  const symtab_command* symtab_command = nullptr;
+  const dysymtab_command* dysymtab_command = nullptr;
+  const segment_command_64* linkedit_seg = nullptr;
+  const segment_command_64* text_seg = nullptr;
+  for (uint32_t cmd_index = 0, cumulative_cmd_size = 0;
+       cmd_index <= header->ncmds && cumulative_cmd_size < header->sizeofcmds;
+       ++cmd_index, cumulative_cmd_size += command->cmdsize) {
+    if (command->cmd == LC_SEGMENT_64) {
+      ScopedVMRead<segment_command_64> segment;
+      if (!segment.Read(command_ptr)) {
+        CRASHPAD_RAW_LOG("Invalid LC_SEGMENT_64 segment");
+        return;
+      }
+      const segment_command_64* segment_ptr =
+          reinterpret_cast<const segment_command_64*>(command_ptr);
+      if (strcmp(segment->segname, SEG_TEXT) == 0) {
+        text_seg = segment_ptr;
+        WriteProperty(writer, IntermediateDumpKey::kSize, &segment->vmsize);
+        slide = address - segment->vmaddr;
+      } else if (strcmp(segment->segname, SEG_DATA) == 0) {
+        WriteDataSegmentAnnotations(writer, segment_ptr, slide);
+      } else if (strcmp(segment->segname, SEG_LINKEDIT) == 0) {
+        linkedit_seg = segment_ptr;
+      }
+    } else if (command->cmd == LC_SYMTAB) {
+      symtab_command =
+          reinterpret_cast<const struct symtab_command*>(command_ptr);
+    } else if (command->cmd == LC_DYSYMTAB) {
+      dysymtab_command =
+          reinterpret_cast<const struct dysymtab_command*>(command_ptr);
+    } else if (command->cmd == LC_ID_DYLIB) {
+      ScopedVMRead<dylib_command> dylib;
+      if (!dylib.Read(command_ptr)) {
+        CRASHPAD_RAW_LOG("Invalid LC_ID_DYLIB segment");
+        return;
+      }
+      WriteProperty(writer,
+                    IntermediateDumpKey::kDylibCurrentVersion,
+                    &dylib->dylib.current_version);
+    } else if (command->cmd == LC_SOURCE_VERSION) {
+      ScopedVMRead<source_version_command> source_version;
+      if (!source_version.Read(command_ptr)) {
+        CRASHPAD_RAW_LOG("Invalid LC_SOURCE_VERSION segment");
+        return;
+      }
+      WriteProperty(writer,
+                    IntermediateDumpKey::kSourceVersion,
+                    &source_version->version);
+    } else if (command->cmd == LC_UUID) {
+      ScopedVMRead<uuid_command> uuid;
+      if (!uuid.Read(command_ptr)) {
+        CRASHPAD_RAW_LOG("Invalid LC_UUID segment");
+        return;
+      }
+      WriteProperty(writer, IntermediateDumpKey::kUUID, &uuid->uuid);
+    }
+
+    command_ptr = reinterpret_cast<const load_command*>(
+        reinterpret_cast<const uint8_t*>(command_ptr) + command->cmdsize);
+    if (!command.Read(command_ptr)) {
+      CRASHPAD_RAW_LOG("Invalid module command");
+      return;
+    }
+  }
+
+  WriteProperty(writer, IntermediateDumpKey::kFileType, &header->filetype);
+
+  if (is_dyld && header->filetype == MH_DYLINKER) {
+    WriteDyldErrorStringAnnotation(writer,
+                                   address,
+                                   symtab_command,
+                                   dysymtab_command,
+                                   text_seg,
+                                   linkedit_seg,
+                                   slide);
+  }
+}
+
+void InProcessIntermediateDumpHandler::WriteDataSegmentAnnotations(
+    IOSIntermediateDumpWriter* writer,
+    const segment_command_64* segment_ptr,
+    vm_size_t slide) {
+  ScopedVMRead<segment_command_64> segment;
+  if (!segment.Read(segment_ptr)) {
+    CRASHPAD_RAW_LOG("Unable to read SEG_DATA.");
+    return;
+  }
+  const section_64* section_ptr = reinterpret_cast<const section_64*>(
+      reinterpret_cast<uint64_t>(segment_ptr) + sizeof(segment_command_64));
+  for (uint32_t sect_index = 0; sect_index <= segment->nsects; ++sect_index) {
+    ScopedVMRead<section_64> section;
+    if (!section.Read(section_ptr)) {
+      CRASHPAD_RAW_LOG("Unable to read SEG_DATA section.");
+      return;
+    }
+    if (strcmp(section->sectname, "crashpad_info") == 0) {
+      ScopedVMRead<CrashpadInfo> crashpad_info;
+      if (crashpad_info.Read(section->addr + slide) &&
+          crashpad_info->size() == sizeof(CrashpadInfo) &&
+          crashpad_info->signature() == CrashpadInfo::kSignature &&
+          crashpad_info->version() == 1) {
+        WriteCrashpadAnnotationsList(writer, crashpad_info.get());
+        WriteCrashpadSimpleAnnotationsDictionary(writer, crashpad_info.get());
+      }
+    } else if (strcmp(section->sectname, "__crash_info") == 0) {
+      ScopedVMRead<crashreporter_annotations_t> crash_info;
+      if (!crash_info.Read(section->addr + slide) ||
+          (crash_info->version != 4 && crash_info->version != 5)) {
+        continue;
+      }
+      WriteAppleCrashReporterAnnotations(writer, crash_info.get());
+    }
+    section_ptr = reinterpret_cast<const section_64*>(
+        reinterpret_cast<uint64_t>(section_ptr) + sizeof(section_64));
+  }
+}
+
+void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList(
+    IOSIntermediateDumpWriter* writer,
+    CrashpadInfo* crashpad_info) {
+  if (!crashpad_info->annotations_list()) {
+    return;
+  }
+  ScopedVMRead<AnnotationList> annotation_list;
+  if (!annotation_list.Read(crashpad_info->annotations_list())) {
+    CRASHPAD_RAW_LOG("Unable to read annotations list object");
+    return;
+  }
+
+  IOSIntermediateDumpWriter::ScopedArray annotations_array(
+      writer, IntermediateDumpKey::kAnnotationObjects);
+  ScopedVMRead<Annotation> current;
+  if (!current.Read(annotation_list->head())) {
+    CRASHPAD_RAW_LOG("Unable to read annotation");
+    return;
+  }
+
+  for (size_t index = 0;
+       current->link_node() != annotation_list.get()->tail_pointer() &&
+       index < kMaxNumberOfAnnotations;
+       ++index) {
+    ScopedVMRead<Annotation> node;
+    if (!node.Read(current->link_node())) {
+      CRASHPAD_RAW_LOG("Unable to read annotation");
+      return;
+    }
+    current.Read(current->link_node());
+
+    if (node->size() == 0)
+      continue;
+
+    if (node->size() > Annotation::kValueMaxSize) {
+      CRASHPAD_RAW_LOG("Incorrect annotation length");
+      continue;
+    }
+
+    IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer);
+    const size_t name_len = strnlen(reinterpret_cast<const char*>(node->name()),
+                                    Annotation::kNameMaxLength);
+    WritePropertyBytes(writer,
+                       IntermediateDumpKey::kAnnotationName,
+                       reinterpret_cast<const void*>(node->name()),
+                       name_len);
+    WritePropertyBytes(writer,
+                       IntermediateDumpKey::kAnnotationValue,
+                       reinterpret_cast<const void*>(node->value()),
+                       node->size());
+    Annotation::Type type = node->type();
+    WritePropertyBytes(writer,
+                       IntermediateDumpKey::kAnnotationType,
+                       reinterpret_cast<const void*>(&type),
+                       sizeof(type));
+  }
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/client/ios_handler/in_process_intermediate_dump_handler.h b/client/ios_handler/in_process_intermediate_dump_handler.h
new file mode 100644
index 0000000..9a33ff4
--- /dev/null
+++ b/client/ios_handler/in_process_intermediate_dump_handler.h
@@ -0,0 +1,146 @@
+// 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.
+
+#ifndef CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_
+#define CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_
+
+#include <mach-o/loader.h>
+#include <mach/mach.h>
+#include <signal.h>
+#include <sys/types.h>
+
+#include "client/crashpad_info.h"
+#include "util/ios/ios_intermediate_dump_writer.h"
+#include "util/ios/ios_system_data_collector.h"
+#include "util/mach/mach_extensions.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Dump all in-process data to iOS intermediate dump.
+//! Note: All methods are `RUNS-DURING-CRASH`.
+class InProcessIntermediateDumpHandler final {
+ public:
+  //! \brief Set kVersion to 1.
+  //!
+  //! \param[in] writer The dump writer
+  static void WriteHeader(IOSIntermediateDumpWriter* writer);
+
+  //! \brief Write ProcessSnapshot data to the intermediate dump.
+  //!
+  //! \param[in] writer The dump writer
+  static void WriteProcessInfo(IOSIntermediateDumpWriter* writer);
+
+  //! \brief Write SystemSnapshot data to the intermediate dump.
+  //!
+  //! \param[in] writer The dump writer
+  static void WriteSystemInfo(IOSIntermediateDumpWriter* writer,
+                              const IOSSystemDataCollector& system_data);
+
+  //! \brief Write ThreadSnapshot data to the intermediate dump.
+  //!
+  //! For uncaught NSExceptions, \a frames and \a num_frames will be added to
+  //! the intermediate dump for the exception thread. Otherwise, or for the
+  //! remaining threads, use `thread_get_state`.
+  //!
+  //! \param[in] writer The dump writer
+  //! \param[in] frames An array of callstack return addresses.
+  //! \param[in] num_frames The number of callstack return address in \a frames.
+  static void WriteThreadInfo(IOSIntermediateDumpWriter* writer,
+                              const uint64_t* frames,
+                              const size_t num_frames);
+
+  //! \brief Write ModuleSnapshot data to the intermediate dump.
+  //!
+  //! This includes both modules and annotations.
+  //!
+  //! \param[in] writer The dump writer
+  static void WriteModuleInfo(IOSIntermediateDumpWriter* writer);
+
+  //! \brief Write an ExceptionSnapshot from a signal to the intermediate dump.
+  //!
+  //!  Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException
+  //!  and WriteExceptionFromNSException should be called per intermediate dump.
+  //!
+  //! \param[in] writer The dump writer
+  //! \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.
+  static void WriteExceptionFromSignal(
+      IOSIntermediateDumpWriter* writer,
+      const IOSSystemDataCollector& system_data,
+      siginfo_t* siginfo,
+      ucontext_t* context);
+
+  //! \brief Write an ExceptionSnapshot from a mach exception to the
+  //!     intermediate dump.
+  //!
+  //!  Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException
+  //!  and WriteExceptionFromNSException should be called per intermediate dump.
+  //!
+  //! \param[in] writer The dump writer
+  //! \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] flavor
+  //! \param[in] old_state
+  //! \param[in] old_state_count
+  static void WriteExceptionFromMachException(
+      IOSIntermediateDumpWriter* writer,
+      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 Write an ExceptionSnapshot from an NSException to the
+  //!     intermediate dump.
+  //!
+  //!  Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException
+  //!  and WriteExceptionFromNSException should be called per intermediate dump.
+  //!
+  //! \param[in] writer The dump writer
+  static void WriteExceptionFromNSException(IOSIntermediateDumpWriter* writer);
+
+ private:
+  //! \brief Parse and extract module and annotation information from header.
+  static void WriteModuleInfoAtAddress(IOSIntermediateDumpWriter* writer,
+                                       uint64_t address,
+                                       bool is_dyld);
+
+  //! \brief Extract and write Apple crashreporter_annotations_t data and
+  //!     Crashpad annotations.
+  static void WriteDataSegmentAnnotations(IOSIntermediateDumpWriter* writer,
+                                          const segment_command_64* segment_ptr,
+                                          vm_size_t slide);
+
+  //! \brief Write Crashpad annotations list.
+  static void WriteCrashpadAnnotationsList(IOSIntermediateDumpWriter* writer,
+                                           CrashpadInfo* crashpad_info);
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(InProcessIntermediateDumpHandler);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_
diff --git a/client/ios_handler/in_process_intermediate_dump_handler_test.cc b/client/ios_handler/in_process_intermediate_dump_handler_test.cc
new file mode 100644
index 0000000..9616ce7
--- /dev/null
+++ b/client/ios_handler/in_process_intermediate_dump_handler_test.cc
@@ -0,0 +1,242 @@
+// 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_intermediate_dump_handler.h"
+
+#include <sys/utsname.h>
+
+#include "base/cxx17_backports.h"
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "client/annotation.h"
+#include "client/annotation_list.h"
+#include "client/crashpad_info.h"
+#include "client/simple_string_dictionary.h"
+#include "gtest/gtest.h"
+#include "snapshot/ios/process_snapshot_ios_intermediate_dump.h"
+#include "test/scoped_temp_dir.h"
+#include "test/test_paths.h"
+#include "util/file/filesystem.h"
+#include "util/misc/capture_context.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+using internal::InProcessIntermediateDumpHandler;
+
+class InProcessIntermediateDumpHandlerTest : public testing::Test {
+ protected:
+  // testing::Test:
+
+  void SetUp() override {
+    path_ = temp_dir_.path().Append("dump_file");
+    writer_ = std::make_unique<internal::IOSIntermediateDumpWriter>();
+    EXPECT_TRUE(writer_->Open(path_));
+    ASSERT_TRUE(IsRegularFile(path_));
+  }
+
+  void TearDown() override {
+    writer_.reset();
+    EXPECT_FALSE(IsRegularFile(path_));
+  }
+
+  void WriteReport() {
+    internal::IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer_.get());
+    InProcessIntermediateDumpHandler::WriteHeader(writer_.get());
+    InProcessIntermediateDumpHandler::WriteProcessInfo(writer_.get());
+    InProcessIntermediateDumpHandler::WriteSystemInfo(writer_.get(),
+                                                      system_data_);
+    InProcessIntermediateDumpHandler::WriteThreadInfo(writer_.get(), 0, 0);
+    InProcessIntermediateDumpHandler::WriteModuleInfo(writer_.get());
+  }
+
+  void WriteMachException() {
+    crashpad::NativeCPUContext cpu_context;
+    crashpad::CaptureContext(&cpu_context);
+    const mach_exception_data_type_t code[2] = {};
+    static constexpr int kSimulatedException = -1;
+    InProcessIntermediateDumpHandler::WriteExceptionFromMachException(
+        writer_.get(),
+        MACH_EXCEPTION_CODES,
+        mach_thread_self(),
+        kSimulatedException,
+        code,
+        base::size(code),
+        MACHINE_THREAD_STATE,
+        reinterpret_cast<ConstThreadState>(&cpu_context),
+        MACHINE_THREAD_STATE_COUNT);
+  }
+
+  const auto& path() const { return path_; }
+  auto writer() const { return writer_.get(); }
+
+ private:
+  std::unique_ptr<internal::IOSIntermediateDumpWriter> writer_;
+  internal::IOSSystemDataCollector system_data_;
+  ScopedTempDir temp_dir_;
+  base::FilePath path_;
+};
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestSystem) {
+  WriteReport();
+  internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+
+  // Snpahot
+  const SystemSnapshot* system = process_snapshot.System();
+  ASSERT_NE(system, nullptr);
+#if defined(ARCH_CPU_X86_64)
+  EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureX86_64);
+  EXPECT_STREQ(system->CPUVendor().c_str(), "GenuineIntel");
+#elif defined(ARCH_CPU_ARM64)
+  EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureARM64);
+  utsname uts;
+  ASSERT_EQ(uname(&uts), 0);
+  EXPECT_STREQ(system->MachineDescription().c_str(), uts.machine);
+#else
+#error Port to your CPU architecture
+#endif
+  EXPECT_EQ(system->GetOperatingSystem(), SystemSnapshot::kOperatingSystemIOS);
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestAnnotations) {
+  // This is “leaked” to crashpad_info.
+  crashpad::SimpleStringDictionary* simple_annotations =
+      new crashpad::SimpleStringDictionary();
+  simple_annotations->SetKeyValue("#TEST# pad", "break");
+  simple_annotations->SetKeyValue("#TEST# key", "value");
+  simple_annotations->SetKeyValue("#TEST# pad", "crash");
+  simple_annotations->SetKeyValue("#TEST# x", "y");
+  simple_annotations->SetKeyValue("#TEST# longer", "shorter");
+  simple_annotations->SetKeyValue("#TEST# empty_value", "");
+
+  crashpad::CrashpadInfo* crashpad_info =
+      crashpad::CrashpadInfo::GetCrashpadInfo();
+
+  crashpad_info->set_simple_annotations(simple_annotations);
+
+  crashpad::AnnotationList::Register();  // This is “leaked” to crashpad_info.
+
+  static crashpad::StringAnnotation<32> test_annotation_one{"#TEST# one"};
+  static crashpad::StringAnnotation<32> test_annotation_two{"#TEST# two"};
+  static crashpad::StringAnnotation<32> test_annotation_three{
+      "#TEST# same-name"};
+  static crashpad::StringAnnotation<32> test_annotation_four{
+      "#TEST# same-name"};
+
+  test_annotation_one.Set("moocow");
+  test_annotation_two.Set("this will be cleared");
+  test_annotation_three.Set("same-name 3");
+  test_annotation_four.Set("same-name 4");
+  test_annotation_two.Clear();
+
+  WriteReport();
+  internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(path(), {{"after_dump", "post"}}));
+
+  auto process_map = process_snapshot.AnnotationsSimpleMap();
+  EXPECT_EQ(process_map.size(), 1u);
+  EXPECT_EQ(process_map["after_dump"], "post");
+
+  std::map<std::string, std::string> all_annotations_simple_map;
+  std::vector<AnnotationSnapshot> all_annotations;
+  for (const auto* module : process_snapshot.Modules()) {
+    std::map<std::string, std::string> module_annotations_simple_map =
+        module->AnnotationsSimpleMap();
+    all_annotations_simple_map.insert(module_annotations_simple_map.begin(),
+                                      module_annotations_simple_map.end());
+
+    std::vector<AnnotationSnapshot> annotations = module->AnnotationObjects();
+    all_annotations.insert(
+        all_annotations.end(), annotations.begin(), annotations.end());
+  }
+
+  EXPECT_EQ(all_annotations_simple_map.size(), 5u);
+  EXPECT_EQ(all_annotations_simple_map["#TEST# pad"], "crash");
+  EXPECT_EQ(all_annotations_simple_map["#TEST# key"], "value");
+  EXPECT_EQ(all_annotations_simple_map["#TEST# x"], "y");
+  EXPECT_EQ(all_annotations_simple_map["#TEST# longer"], "shorter");
+  EXPECT_EQ(all_annotations_simple_map["#TEST# empty_value"], "");
+
+  bool saw_same_name_3 = false, saw_same_name_4 = false;
+  for (const auto& annotation : all_annotations) {
+    EXPECT_EQ(annotation.type,
+              static_cast<uint16_t>(Annotation::Type::kString));
+    std::string value(reinterpret_cast<const char*>(annotation.value.data()),
+                      annotation.value.size());
+    if (annotation.name == "#TEST# one") {
+      EXPECT_EQ(value, "moocow");
+    } else if (annotation.name == "#TEST# same-name") {
+      if (value == "same-name 3") {
+        EXPECT_FALSE(saw_same_name_3);
+        saw_same_name_3 = true;
+      } else if (value == "same-name 4") {
+        EXPECT_FALSE(saw_same_name_4);
+        saw_same_name_4 = true;
+      } else {
+        ADD_FAILURE() << "unexpected annotation value " << value;
+      }
+    } else {
+      ADD_FAILURE() << "unexpected annotation " << annotation.name;
+    }
+  }
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) {
+  WriteReport();
+  internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+
+  const auto& threads = process_snapshot.Threads();
+  ASSERT_GT(threads.size(), 0u);
+
+  thread_identifier_info identifier_info;
+  mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+  ASSERT_EQ(thread_info(mach_thread_self(),
+                        THREAD_IDENTIFIER_INFO,
+                        reinterpret_cast<thread_info_t>(&identifier_info),
+                        &count),
+            0);
+  EXPECT_EQ(threads[0]->ThreadID(), identifier_info.thread_id);
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestProcess) {
+  WriteReport();
+  internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+  EXPECT_EQ(process_snapshot.ProcessID(), getpid());
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestMachException) {
+  WriteReport();
+  internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestSignalException) {
+  WriteReport();
+  internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestNSException) {
+  WriteReport();
+  internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
index 0d24029..cd60451 100644
--- a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
+++ b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
@@ -156,20 +156,18 @@
     exception_ = exception;
   }
 
-  mach_msg_type_number_t code_count;
-  GetDataValueFromMap(exception_data, Key::kCodeCount, &code_count);
-
   const IOSIntermediateDumpData* code_dump =
-      GetDataFromMap(exception_data, Key::kCode);
+      GetDataFromMap(exception_data, Key::kCodes);
   if (code_dump) {
     const std::vector<uint8_t>& bytes = code_dump->bytes();
     const mach_exception_data_type_t* code =
         reinterpret_cast<const mach_exception_data_type_t*>(bytes.data());
-    if (!code ||
-        bytes.size() != sizeof(mach_exception_data_type_t) * code_count) {
+    if (bytes.size() == 0 || !code) {
       LOG(ERROR) << "Invalid mach exception code.";
     } else {
       // TODO: rationalize with the macOS implementation.
+      mach_msg_type_number_t code_count =
+          bytes.size() / sizeof(mach_exception_data_type_t);
       for (mach_msg_type_number_t code_index = 0; code_index < code_count;
            ++code_index) {
         codes_.push_back(code[code_index]);
@@ -288,22 +286,24 @@
   float_state_type float_state;
   debug_state_type debug_state;
 
-  mach_msg_type_number_t state_count = 0;
   thread_state_flavor_t flavor = THREAD_STATE_NONE;
-  if (GetDataValueFromMap(exception_data, Key::kStateCount, &state_count) &&
-      GetDataValueFromMap(exception_data, Key::kFlavor, &flavor) &&
+  if (GetDataValueFromMap(exception_data, Key::kFlavor, &flavor) &&
       GetDataValueFromMap(other_thread, Key::kThreadState, &thread_state) &&
       GetDataValueFromMap(other_thread, Key::kFloatState, &float_state) &&
       GetDataValueFromMap(other_thread, Key::kDebugState, &debug_state)) {
-    size_t expected_length = ThreadStateLengthForFlavor(flavor);
     const IOSIntermediateDumpData* state_dump =
         GetDataFromMap(exception_data, Key::kState);
     if (state_dump) {
       const std::vector<uint8_t>& bytes = state_dump->bytes();
       size_t actual_length = bytes.size();
+      size_t expected_length = ThreadStateLengthForFlavor(flavor);
+      // TODO(justincohen): Consider zero-ing out bytes if actual_length is
+      // shorter than expected_length, and tolerating actual_length longer than
+      // expected_length.
       if (expected_length == actual_length) {
         const ConstThreadState state =
             reinterpret_cast<const ConstThreadState>(bytes.data());
+        mach_msg_type_number_t state_count = bytes.size() / sizeof(uint32_t);
 #if defined(ARCH_CPU_X86_64)
         InitializeCPUContextX86_64(&context_x86_64_,
                                    flavor,
diff --git a/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc b/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
index 9710439..0bc9247 100644
--- a/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
+++ b/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
@@ -281,7 +281,6 @@
 
 #if defined(ARCH_CPU_X86_64)
     thread_state_flavor_t flavor = x86_THREAD_STATE;
-    mach_msg_type_number_t state_count = x86_THREAD_STATE_COUNT;
     x86_thread_state_t state = {};
     state.tsh.flavor = x86_THREAD_STATE64;
     state.tsh.count = x86_THREAD_STATE64_COUNT;
@@ -289,7 +288,6 @@
     size_t state_length = sizeof(x86_thread_state_t);
 #elif defined(ARCH_CPU_ARM64)
     thread_state_flavor_t flavor = ARM_UNIFIED_THREAD_STATE;
-    mach_msg_type_number_t state_count = ARM_UNIFIED_THREAD_STATE_COUNT;
     arm_unified_thread_state_t state = {};
     state.ash.flavor = ARM_THREAD_STATE64;
     state.ash.count = ARM_THREAD_STATE64_COUNT;
@@ -297,12 +295,10 @@
     size_t state_length = sizeof(arm_unified_thread_state_t);
 #endif
     EXPECT_TRUE(writer->AddProperty(Key::kException, &exception));
-    EXPECT_TRUE(writer->AddProperty(Key::kCode, code, code_count));
-    EXPECT_TRUE(writer->AddProperty(Key::kCodeCount, &code_count));
+    EXPECT_TRUE(writer->AddProperty(Key::kCodes, code, code_count));
     EXPECT_TRUE(writer->AddProperty(Key::kFlavor, &flavor));
     EXPECT_TRUE(writer->AddPropertyBytes(
         Key::kState, reinterpret_cast<const void*>(&state), state_length));
-    EXPECT_TRUE(writer->AddProperty(Key::kStateCount, &state_count));
     uint64_t thread_id = 1;
     EXPECT_TRUE(writer->AddProperty(Key::kThreadID, &thread_id));
   }
diff --git a/snapshot/mac/process_reader_mac.cc b/snapshot/mac/process_reader_mac.cc
index 821fe3c..9b2a235 100644
--- a/snapshot/mac/process_reader_mac.cc
+++ b/snapshot/mac/process_reader_mac.cc
@@ -701,7 +701,7 @@
 #if defined(ARCH_CPU_X86_FAMILY)
   if (Is64Bit()) {
     // x86_64 has a red zone. See AMD64 ABI 0.99.8,
-    // https://raw.githubusercontent.com/wiki/hjl-tools/x86-psABI/x86-64-psABI-r252.pdf#page=19,
+    // https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/uploads/01de35b2c8adc7545de52604cc45d942/x86-64-psABI-2021-05-20.pdf#page=23.
     // section 3.2.2, “The Stack Frame”.
     constexpr mach_vm_size_t kRedZoneSize = 128;
     mach_vm_address_t red_zone_base =
diff --git a/snapshot/mac/process_types/crashreporterclient.proctype b/snapshot/mac/process_types/crashreporterclient.proctype
index 398bd63..226eaa2 100644
--- a/snapshot/mac/process_types/crashreporterclient.proctype
+++ b/snapshot/mac/process_types/crashreporterclient.proctype
@@ -23,7 +23,8 @@
 //
 // This file is included by snapshot/mac/process_types.h and
 // snapshot/mac/process_types.cc to produce process type struct definitions and
-// accessors.
+// accessors. This file is also used by the iOS in process handler to read both
+// messages in client/ios_handler/in_process_intermediate_dump_handler.cc.
 
 // Client Mach-O images will contain a __DATA,__crash_info section formatted
 // according to this structure.
diff --git a/util/ios/ios_intermediate_dump_format.h b/util/ios/ios_intermediate_dump_format.h
index 1d7cad6..46d3f46 100644
--- a/util/ios/ios_intermediate_dump_format.h
+++ b/util/ios/ios_intermediate_dump_format.h
@@ -26,12 +26,10 @@
   TD(kInvalid, 0) \
   TD(kVersion, 1) \
   TD(kMachException, 1000) \
-    TD(kCode, 1001) \
-    TD(kCodeCount, 1002) \
-    TD(kException, 1003) \
-    TD(kFlavor, 1004) \
-    TD(kState, 1005) \
-    TD(kStateCount, 1006) \
+    TD(kCodes, 1001) \
+    TD(kException, 1002) \
+    TD(kFlavor, 1003) \
+    TD(kState, 1004) \
   TD(kSignalException, 2000) \
     TD(kSignalNumber, 2001) \
     TD(kSignalCode, 2002) \
diff --git a/util/ios/ios_system_data_collector.h b/util/ios/ios_system_data_collector.h
index 556d724..cac0c86 100644
--- a/util/ios/ios_system_data_collector.h
+++ b/util/ios/ios_system_data_collector.h
@@ -29,16 +29,16 @@
   ~IOSSystemDataCollector();
 
   void OSVersion(int* major, int* minor, int* bugfix) const;
-  std::string MachineDescription() const { return machine_description_; }
+  const std::string& MachineDescription() const { return machine_description_; }
   int ProcessorCount() const { return processor_count_; }
-  std::string Build() const { return build_; }
-  std::string CPUVendor() const { return cpu_vendor_; }
+  const std::string& Build() const { return build_; }
+  const std::string& CPUVendor() const { return cpu_vendor_; }
   bool HasDaylightSavingTime() const { return has_next_daylight_saving_time_; }
   bool IsDaylightSavingTime() const { return is_daylight_saving_time_; }
   int StandardOffsetSeconds() const { return standard_offset_seconds_; }
   int DaylightOffsetSeconds() const { return daylight_offset_seconds_; }
-  std::string StandardName() const { return standard_name_; }
-  std::string DaylightName() const { return daylight_name_; }
+  const std::string& StandardName() const { return standard_name_; }
+  const std::string& DaylightName() const { return daylight_name_; }
 
   // Currently unused by minidump.
   int Orientation() const { return orientation_; }
diff --git a/util/ios/scoped_vm_read.cc b/util/ios/scoped_vm_read.cc
index 3bbc596..06cf434 100644
--- a/util/ios/scoped_vm_read.cc
+++ b/util/ios/scoped_vm_read.cc
@@ -53,7 +53,7 @@
     data_ = vm_read_data_ + (data_address - page_region_address);
     return true;
   } else {
-    CRASHPAD_RAW_LOG_ERROR(kr, "vm_read");
+    // It's expected that this will sometimes fail. Don't log here.
     return false;
   }
 }
diff --git a/util/ios/scoped_vm_read.h b/util/ios/scoped_vm_read.h
index 86d8039..3f396d4 100644
--- a/util/ios/scoped_vm_read.h
+++ b/util/ios/scoped_vm_read.h
@@ -70,13 +70,22 @@
   //! \param[in] data Memory to be read by vm_read.
   //! \param[in] count Length of \a data.
   //!
-  //! \return `true` if all the data was read. Logs an error and returns false
-  //!   on failure
+  //! \return `true` if all \a data was read. Returns false on failure.
   bool Read(const void* data, size_t count = 1) {
     size_t data_length = count * sizeof(T);
     return internal_.Read(data, data_length);
   }
 
+  //! \brief Releases any previously read data and vm_reads address.
+  //!
+  //! \param[in] address Address of memory to be read by vm_read.
+  //! \param[in] count Length of \a data.
+  //!
+  //! \return `true` if all of \a address was read. Returns false on failure.
+  bool Read(vm_address_t address, size_t count = 1) {
+    return Read(reinterpret_cast<T*>(address), count);
+  }
+
   //! \brief Returns the pointer to memory safe to read during the in-process
   //!   crash handler.
   T* operator->() const { return get(); }