ios: Add support for intermediate dump reader and writer.

Due to the limitations of in-process handling, an intermediate dump file
is written during exceptions. The data is streamed to a file using only
in-process safe methods. The file format is similar to binary JSON,
supporting keyed properties, maps and arrays.
 - Property [key:int, length:int, value:intarray]
 - StartMap [key:int], followed by repeating Properties until EndMap
 - StartArray [key:int], followed by repeating Maps until EndArray
 - EndMap, EndArray, EndDocument

Similar to JSON, maps can contain other maps, arrays and properties.

Once loaded, the binary file is read into a set of data structures that
expose the data, maps and arrays.

Bug: crashpad: 31
Change-Id: I7eefa1d38e7633adaffd55800eae4c2703fe4267
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2870807
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Justin Cohen <justincohen@chromium.org>
GitOrigin-RevId: b2b65a91cf8b5c33b50422fef87325f54dae0146
diff --git a/util/BUILD.gn b/util/BUILD.gn
index 71bf1ce..f4d88a3 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -382,6 +382,19 @@
     sources += [
       "ios/exception_processor.h",
       "ios/exception_processor.mm",
+      "ios/ios_intermediate_dump_data.cc",
+      "ios/ios_intermediate_dump_data.h",
+      "ios/ios_intermediate_dump_format.h",
+      "ios/ios_intermediate_dump_list.cc",
+      "ios/ios_intermediate_dump_list.h",
+      "ios/ios_intermediate_dump_map.cc",
+      "ios/ios_intermediate_dump_map.h",
+      "ios/ios_intermediate_dump_object.cc",
+      "ios/ios_intermediate_dump_object.h",
+      "ios/ios_intermediate_dump_reader.cc",
+      "ios/ios_intermediate_dump_reader.h",
+      "ios/ios_intermediate_dump_writer.cc",
+      "ios/ios_intermediate_dump_writer.h",
       "ios/ios_system_data_collector.h",
       "ios/ios_system_data_collector.mm",
       "ios/raw_logging.cc",
@@ -793,6 +806,8 @@
   if (crashpad_is_ios) {
     sources += [
       "ios/exception_processor_test.mm",
+      "ios/ios_intermediate_dump_reader_test.cc",
+      "ios/ios_intermediate_dump_writer_test.cc",
       "ios/scoped_vm_read_test.cc",
     ]
 
diff --git a/util/ios/ios_intermediate_dump_data.cc b/util/ios/ios_intermediate_dump_data.cc
new file mode 100644
index 0000000..4879ccf
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_data.cc
@@ -0,0 +1,42 @@
+// 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 "util/ios/ios_intermediate_dump_data.h"
+
+namespace crashpad {
+namespace internal {
+
+IOSIntermediateDumpData::IOSIntermediateDumpData() : data_() {}
+
+IOSIntermediateDumpData::~IOSIntermediateDumpData() {}
+
+IOSIntermediateDumpObject::Type IOSIntermediateDumpData::GetType() const {
+  return Type::kData;
+}
+
+std::string IOSIntermediateDumpData::GetString() const {
+  return std::string(reinterpret_cast<const char*>(data_.data()), data_.size());
+}
+
+bool IOSIntermediateDumpData::GetValueInternal(void* value,
+                                               size_t value_size) const {
+  if (value_size == data_.size()) {
+    memcpy(value, data_.data(), data_.size());
+    return true;
+  }
+  return false;
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/util/ios/ios_intermediate_dump_data.h b/util/ios/ios_intermediate_dump_data.h
new file mode 100644
index 0000000..def5ab9
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_data.h
@@ -0,0 +1,68 @@
+// 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_UTIL_IOS_IOS_INTERMEDIATE_DUMP_DATA_H_
+#define CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_DATA_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "util/ios/ios_intermediate_dump_object.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A data object, consisting of a std::vector<uint8_t>.
+class IOSIntermediateDumpData : public IOSIntermediateDumpObject {
+ public:
+  IOSIntermediateDumpData();
+  ~IOSIntermediateDumpData() override;
+
+  //! \brief Constructs a new data object which owns a std::vector<uint8_t>.
+  //!
+  //! \param[in] data An array of uint8_t.
+  //! \param[in] length The length of \a data.
+  IOSIntermediateDumpData(std::vector<uint8_t> data) : data_(std::move(data)) {}
+
+  // IOSIntermediateDumpObject:
+  Type GetType() const override;
+
+  //! \brief Returns data as a string.
+  std::string GetString() const;
+
+  //! \brief Copies the data into \a value if sizeof(T) matches data_.size().
+  //!
+  //! \param[out] value The data to populate.
+  //!
+  //! \return On success, returns `true`, otherwise returns `false`.
+  template <typename T>
+  bool GetValue(T* value) const {
+    return GetValueInternal(reinterpret_cast<void*>(value), sizeof(*value));
+  }
+
+  const std::vector<uint8_t>& bytes() const { return data_; }
+
+ private:
+  bool GetValueInternal(void* value, size_t value_size) const;
+
+  std::vector<uint8_t> data_;
+
+  DISALLOW_COPY_AND_ASSIGN(IOSIntermediateDumpData);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_DATA_H_
diff --git a/util/ios/ios_intermediate_dump_format.h b/util/ios/ios_intermediate_dump_format.h
new file mode 100644
index 0000000..1d7cad6
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_format.h
@@ -0,0 +1,120 @@
+// 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_UTIL_IOS_IOS_INTERMEDIATE_DUMP_FORMAT_H_
+#define CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_FORMAT_H_
+
+namespace crashpad {
+namespace internal {
+
+// Define values for intermediate dump enum class IntermediateDumpKey. Use
+// |INTERMEDIATE_DUMP_KEYS| so it is easier to print human readable keys in
+// logs.
+// clang-format off
+#define INTERMEDIATE_DUMP_KEYS(TD) \
+  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(kSignalException, 2000) \
+    TD(kSignalNumber, 2001) \
+    TD(kSignalCode, 2002) \
+    TD(kSignalAddress, 2003) \
+  TD(kNSException, 2500) \
+  TD(kModules, 3000) \
+    TD(kAddress, 3001) \
+    TD(kFileType, 3002) \
+    TD(kName, 3003) \
+    TD(kSize, 3004) \
+    TD(kDylibCurrentVersion, 3005) \
+    TD(kSourceVersion, 3006) \
+    TD(kTimestamp, 3007) \
+    TD(kUUID, 3008) \
+    TD(kAnnotationObjects, 3009) \
+    TD(kAnnotationsSimpleMap, 3010) \
+    TD(kAnnotationsVector, 3011) \
+    TD(kAnnotationType, 3012) \
+    TD(kAnnotationName, 3013) \
+    TD(kAnnotationValue, 3014) \
+    TD(kAnnotationsCrashInfo, 3015) \
+    TD(kAnnotationsCrashInfoMessage1, 3016) \
+    TD(kAnnotationsCrashInfoMessage2, 3017) \
+    TD(kAnnotationsDyldErrorString, 3018) \
+  TD(kProcessInfo, 4000) \
+    TD(kParentPID, 4001) \
+    TD(kPID, 4002) \
+    TD(kStartTime, 4003) \
+    TD(kSnapshotTime, 4004) \
+    TD(kTaskBasicInfo, 4005) \
+    TD(kTaskThreadTimes, 4006) \
+    TD(kSystemTime, 4007) \
+    TD(kUserTime, 4008) \
+  TD(kSystemInfo, 5000) \
+    TD(kCpuCount, 5001) \
+    TD(kCpuVendor, 5002) \
+    TD(kDaylightName, 5003) \
+    TD(kDaylightOffsetSeconds, 5004) \
+    TD(kHasDaylightSavingTime, 5005) \
+    TD(kIsDaylightSavingTime, 5006) \
+    TD(kMachineDescription, 5007) \
+    TD(kOSVersionBugfix, 5008) \
+    TD(kOSVersionBuild, 5009) \
+    TD(kOSVersionMajor, 5010) \
+    TD(kOSVersionMinor, 5011) \
+    TD(kPageSize, 5012) \
+    TD(kStandardName, 5013) \
+    TD(kStandardOffsetSeconds, 5014) \
+    TD(kVMStat, 5015) \
+    TD(kActive, 5016) \
+    TD(kFree, 5017) \
+    TD(kInactive, 5018) \
+    TD(kWired, 5019) \
+  TD(kThreads, 6000) \
+    TD(kDebugState, 6001) \
+    TD(kFloatState, 6002) \
+    TD(kThreadState, 6003) \
+    TD(kPriority, 6004) \
+    TD(kStackRegionAddress, 6005) \
+    TD(kStackRegionData, 6006) \
+    TD(kSuspendCount, 6007) \
+    TD(kThreadID, 6008) \
+    TD(kThreadDataAddress, 6009) \
+    TD(kThreadUncaughtNSExceptionFrames, 6010) \
+    TD(kThreadContextMemoryRegions, 6011) \
+    TD(kThreadContextMemoryRegionAddress, 6012) \
+    TD(kThreadContextMemoryRegionData, 6013) \
+  TD(kMaxValue, 65535) \
+// clang-format on
+
+
+//! \brief They key for items in the intermediate dump file.
+//!
+//! These values are persisted to the intermediate crash dump file. Entries
+//! should not be renumbered and numeric values should never be reused.
+enum class IntermediateDumpKey : uint16_t {
+#define X(NAME, VALUE) NAME = VALUE,
+  INTERMEDIATE_DUMP_KEYS(X)
+#undef X
+};
+
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_FORMAT_H_
diff --git a/util/ios/ios_intermediate_dump_list.cc b/util/ios/ios_intermediate_dump_list.cc
new file mode 100644
index 0000000..17f2b89
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_list.cc
@@ -0,0 +1,29 @@
+// 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 "util/ios/ios_intermediate_dump_list.h"
+
+namespace crashpad {
+namespace internal {
+
+IOSIntermediateDumpList::IOSIntermediateDumpList() : list_() {}
+
+IOSIntermediateDumpList::~IOSIntermediateDumpList() {}
+
+IOSIntermediateDumpObject::Type IOSIntermediateDumpList::GetType() const {
+  return Type::kList;
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/util/ios/ios_intermediate_dump_list.h b/util/ios/ios_intermediate_dump_list.h
new file mode 100644
index 0000000..42d1ef2
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_list.h
@@ -0,0 +1,55 @@
+// 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_UTIL_IOS_IOS_INTERMEDIATE_DUMP_LIST_H_
+#define CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_LIST_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "util/ios/ios_intermediate_dump_map.h"
+#include "util/ios/ios_intermediate_dump_object.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A list object, consisting of a vector of IOSIntermediateDumpMap.
+//!
+//! Provides a wrapper around an internal std::vector.
+class IOSIntermediateDumpList : public IOSIntermediateDumpObject {
+ public:
+  IOSIntermediateDumpList();
+  ~IOSIntermediateDumpList() override;
+
+  // IOSIntermediateDumpObject:
+  Type GetType() const override;
+
+  using VectorType = std::vector<std::unique_ptr<const IOSIntermediateDumpMap>>;
+  VectorType::const_iterator begin() const { return list_.begin(); }
+  VectorType::const_iterator end() const { return list_.end(); }
+  VectorType::size_type size() const { return list_.size(); }
+  void push_back(std::unique_ptr<const IOSIntermediateDumpMap> val) {
+    list_.push_back(std::move(val));
+  }
+
+ private:
+  VectorType list_;
+
+  DISALLOW_COPY_AND_ASSIGN(IOSIntermediateDumpList);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_LIST_H_
diff --git a/util/ios/ios_intermediate_dump_map.cc b/util/ios/ios_intermediate_dump_map.cc
new file mode 100644
index 0000000..7ec6103
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_map.cc
@@ -0,0 +1,68 @@
+// 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 "util/ios/ios_intermediate_dump_map.h"
+
+#include "util/ios/ios_intermediate_dump_data.h"
+#include "util/ios/ios_intermediate_dump_list.h"
+#include "util/ios/ios_intermediate_dump_object.h"
+
+using crashpad::internal::IntermediateDumpKey;
+
+namespace crashpad {
+namespace internal {
+
+IOSIntermediateDumpMap::IOSIntermediateDumpMap() : map_() {}
+
+IOSIntermediateDumpMap::~IOSIntermediateDumpMap() {}
+
+IOSIntermediateDumpMap::Type IOSIntermediateDumpMap::GetType() const {
+  return Type::kMap;
+}
+
+const IOSIntermediateDumpData* IOSIntermediateDumpMap::GetAsData(
+    const IntermediateDumpKey& key) const {
+  auto object_it = map_.find(key);
+  if (object_it != map_.end()) {
+    IOSIntermediateDumpObject* object = object_it->second.get();
+    if (object->GetType() == Type::kData)
+      return static_cast<IOSIntermediateDumpData*>(object);
+  }
+  return nullptr;
+}
+
+const IOSIntermediateDumpList* IOSIntermediateDumpMap::GetAsList(
+    const IntermediateDumpKey& key) const {
+  auto object_it = map_.find(key);
+  if (object_it != map_.end()) {
+    IOSIntermediateDumpObject* object = object_it->second.get();
+    if (object->GetType() == Type::kList)
+      return static_cast<IOSIntermediateDumpList*>(object);
+  }
+  return nullptr;
+}
+
+const IOSIntermediateDumpMap* IOSIntermediateDumpMap::GetAsMap(
+    const IntermediateDumpKey& key) const {
+  auto object_it = map_.find(key);
+  if (object_it != map_.end()) {
+    IOSIntermediateDumpObject* object = object_it->second.get();
+    if (object->GetType() == Type::kMap)
+      return static_cast<IOSIntermediateDumpMap*>(object);
+  }
+  return nullptr;
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/util/ios/ios_intermediate_dump_map.h b/util/ios/ios_intermediate_dump_map.h
new file mode 100644
index 0000000..a11d6a6
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_map.h
@@ -0,0 +1,70 @@
+// 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_UTIL_IOS_PACK_IOS_MAP_H_
+#define CRASHPAD_UTIL_IOS_PACK_IOS_MAP_H_
+
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "util/ios/ios_intermediate_dump_format.h"
+#include "util/ios/ios_intermediate_dump_object.h"
+
+namespace crashpad {
+namespace internal {
+
+class IOSIntermediateDumpList;
+class IOSIntermediateDumpData;
+
+//! \brief A map object containing a IntermediateDump Key-Object pair.
+//!
+//! Also provides an element access helper.
+class IOSIntermediateDumpMap : public IOSIntermediateDumpObject {
+ public:
+  IOSIntermediateDumpMap();
+  ~IOSIntermediateDumpMap() override;
+
+  // IOSIntermediateDumpObject:
+  Type GetType() const override;
+
+  //! \brief Returns an IOSIntermediateDumpData. If the type is not kData,
+  //!     returns nullptr
+  const IOSIntermediateDumpData* GetAsData(
+      const IntermediateDumpKey& key) const;
+
+  //! \brief Returns an IOSIntermediateDumpList. If the type is not kList,
+  //!     returns nullptr
+  const IOSIntermediateDumpList* GetAsList(
+      const IntermediateDumpKey& key) const;
+
+  //! \brief Returns an IOSIntermediateDumpMap.  If the type is not kMap,
+  //!     returns nullptr
+  const IOSIntermediateDumpMap* GetAsMap(const IntermediateDumpKey& key) const;
+
+  //! \brief Returns `true` if the map is empty.
+  bool empty() const { return map_.empty(); }
+
+ private:
+  friend class IOSIntermediateDumpReader;
+  std::map<IntermediateDumpKey, std::unique_ptr<IOSIntermediateDumpObject>>
+      map_;
+
+  DISALLOW_COPY_AND_ASSIGN(IOSIntermediateDumpMap);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_PACK_IOS_MAP_H_
diff --git a/util/ios/ios_intermediate_dump_object.cc b/util/ios/ios_intermediate_dump_object.cc
new file mode 100644
index 0000000..9909935
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_object.cc
@@ -0,0 +1,25 @@
+// 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 "util/ios/ios_intermediate_dump_object.h"
+
+namespace crashpad {
+namespace internal {
+
+IOSIntermediateDumpObject::IOSIntermediateDumpObject() = default;
+
+IOSIntermediateDumpObject::~IOSIntermediateDumpObject() {}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/util/ios/ios_intermediate_dump_object.h b/util/ios/ios_intermediate_dump_object.h
new file mode 100644
index 0000000..21ccd29
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_object.h
@@ -0,0 +1,51 @@
+// 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_UTIL_IOS_IOS_INTERMEDIATE_DUMP_OBJECT_H_
+#define CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_OBJECT_H_
+
+#include "base/macros.h"
+#include "util/ios/ios_intermediate_dump_writer.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Base class for intermediate dump object types.
+class IOSIntermediateDumpObject {
+ public:
+  IOSIntermediateDumpObject();
+  virtual ~IOSIntermediateDumpObject();
+
+  //! \brief The type of object stored in the intermediate dump.  .
+  enum class Type {
+    //! \brief A data object, containing array of bytes.
+    kData,
+
+    //! \brief A map object, containing other lists, maps and data objects.
+    kMap,
+
+    //! \brief A list object, containing a list of map objects.
+    kList,
+  };
+
+  //! \brief Returns a type.
+  virtual Type GetType() const = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(IOSIntermediateDumpObject);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_OBJECT_H_
diff --git a/util/ios/ios_intermediate_dump_reader.cc b/util/ios/ios_intermediate_dump_reader.cc
new file mode 100644
index 0000000..b571662
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_reader.cc
@@ -0,0 +1,192 @@
+// 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 "util/ios/ios_intermediate_dump_reader.h"
+
+#include <memory>
+#include <stack>
+#include <vector>
+
+#include "base/logging.h"
+#include "util/file/filesystem.h"
+#include "util/ios/ios_intermediate_dump_data.h"
+#include "util/ios/ios_intermediate_dump_format.h"
+#include "util/ios/ios_intermediate_dump_list.h"
+#include "util/ios/ios_intermediate_dump_object.h"
+#include "util/ios/ios_intermediate_dump_writer.h"
+
+namespace crashpad {
+namespace internal {
+
+bool IOSIntermediateDumpReader::Initialize(const base::FilePath& path) {
+  ScopedFileHandle handle(LoggingOpenFileForRead(path));
+  auto reader = std::make_unique<WeakFileHandleFileReader>(handle.get());
+
+  // In the event a crash is introduced by this intermediate dump, don't ever
+  // read a file twice.  To ensure this doesn't happen, immediately unlink.
+  LoggingRemoveFile(path);
+
+  // Don't initialize invalid or empty files.
+  FileOffset size = LoggingFileSizeByHandle(handle.get());
+  if (!handle.is_valid() || size == 0) {
+    return false;
+  }
+
+  if (!Parse(reader.get(), size)) {
+    LOG(ERROR) << "Intermediate dump parsing failed";
+  }
+
+  return true;
+}
+
+bool IOSIntermediateDumpReader::Parse(FileReaderInterface* reader,
+                                      FileOffset file_size) {
+  std::stack<IOSIntermediateDumpObject*> stack;
+  stack.push(&minidump_);
+  using Command = IOSIntermediateDumpWriter::CommandType;
+  using Type = IOSIntermediateDumpObject::Type;
+
+  Command command;
+  if (!reader->ReadExactly(&command, sizeof(Command)) ||
+      command != Command::kRootMapStart) {
+    LOG(ERROR) << "Unexpected start to root map.";
+    return false;
+  }
+
+  while (reader->ReadExactly(&command, sizeof(Command))) {
+    IOSIntermediateDumpObject* parent = stack.top();
+    switch (command) {
+      case Command::kMapStart: {
+        std::unique_ptr<IOSIntermediateDumpMap> new_map(
+            new IOSIntermediateDumpMap());
+        if (parent->GetType() == Type::kMap) {
+          const auto parent_map = static_cast<IOSIntermediateDumpMap*>(parent);
+          stack.push(new_map.get());
+          IntermediateDumpKey key;
+          if (!reader->ReadExactly(&key, sizeof(key)))
+            return false;
+          if (key == IntermediateDumpKey::kInvalid)
+            return false;
+          parent_map->map_[key] = std::move(new_map);
+        } else if (parent->GetType() == Type::kList) {
+          const auto parent_list =
+              static_cast<IOSIntermediateDumpList*>(parent);
+          stack.push(new_map.get());
+          parent_list->push_back(std::move(new_map));
+        } else {
+          LOG(ERROR) << "Unexpected parent (not a map or list).";
+          return false;
+        }
+        break;
+      }
+      case Command::kArrayStart: {
+        auto new_list = std::make_unique<IOSIntermediateDumpList>();
+        if (parent->GetType() != Type::kMap) {
+          LOG(ERROR) << "Attempting to push an array not in a map.";
+          return false;
+        }
+
+        IntermediateDumpKey key;
+        if (!reader->ReadExactly(&key, sizeof(key)))
+          return false;
+        if (key == IntermediateDumpKey::kInvalid)
+          return false;
+
+        auto parent_map = static_cast<IOSIntermediateDumpMap*>(parent);
+        stack.push(new_list.get());
+        parent_map->map_[key] = std::move(new_list);
+        break;
+      }
+      case Command::kMapEnd:
+        if (stack.size() < 2) {
+          LOG(ERROR) << "Attempting to pop off main map.";
+          return false;
+        }
+
+        if (parent->GetType() != Type::kMap) {
+          LOG(ERROR) << "Unexpected map end not in a map.";
+          return false;
+        }
+        stack.pop();
+        break;
+      case Command::kArrayEnd:
+        if (stack.size() < 2) {
+          LOG(ERROR) << "Attempting to pop off main map.";
+          return false;
+        }
+        if (parent->GetType() != Type::kList) {
+          LOG(ERROR) << "Unexpected list end not in a list.";
+          return false;
+        }
+        stack.pop();
+        break;
+      case Command::kProperty: {
+        if (parent->GetType() != Type::kMap) {
+          LOG(ERROR) << "Attempting to add a property not in a map.";
+          return false;
+        }
+        IntermediateDumpKey key;
+        if (!reader->ReadExactly(&key, sizeof(key)))
+          return false;
+        if (key == IntermediateDumpKey::kInvalid)
+          return false;
+
+        off_t value_length;
+        if (!reader->ReadExactly(&value_length, sizeof(value_length))) {
+          return false;
+        }
+
+        constexpr int kMaximumPropertyLength = 64 * 1024 * 1024;  // 64MB.
+        if (value_length > kMaximumPropertyLength) {
+          LOG(ERROR) << "Attempting to read a property that's too big: "
+                     << value_length;
+          return false;
+        }
+
+        std::vector<uint8_t> data(value_length);
+        if (!reader->ReadExactly(data.data(), value_length)) {
+          return false;
+        }
+        auto parent_map = static_cast<IOSIntermediateDumpMap*>(parent);
+        if (parent_map->map_.find(key) != parent_map->map_.end()) {
+          LOG(ERROR) << "Inserting duplicate key";
+        }
+        parent_map->map_[key] =
+            std::make_unique<IOSIntermediateDumpData>(std::move(data));
+        break;
+      }
+      case Command::kRootMapEnd: {
+        if (stack.size() != 1) {
+          LOG(ERROR) << "Unexpected end of root map.";
+          return false;
+        }
+
+        if (reader->Seek(0, SEEK_CUR) != file_size) {
+          LOG(ERROR) << "Root map ended before end of file.";
+          return false;
+        }
+        return true;
+      }
+      default:
+        LOG(ERROR) << "Failed to parse serialized intermediate minidump.";
+        return false;
+    }
+  }
+
+  LOG(ERROR) << "Unexpected end of root map.";
+  return false;
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/util/ios/ios_intermediate_dump_reader.h b/util/ios/ios_intermediate_dump_reader.h
new file mode 100644
index 0000000..2966e7c
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_reader.h
@@ -0,0 +1,58 @@
+// 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_UTIL_IOS_IOS_INTERMEDIATE_DUMP_READER_H_
+#define CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_READER_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "util/file/file_reader.h"
+#include "util/ios/ios_intermediate_dump_map.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Open and parse iOS intermediate dumps.
+class IOSIntermediateDumpReader {
+ public:
+  IOSIntermediateDumpReader() {}
+
+  //! \brief Open and parses \a path, ignoring empty files.
+  //!
+  //! Will attempt to parse the binary file, similar to a JSON file, using the
+  //! same format used by IOSIntermediateDumpWriter, resulting in an
+  //! IOSIntermediateDumpMap
+  //!
+  //! \param[in] path The intermediate dump to read.
+  //!
+  //! \return On success, returns `true`, otherwise returns `false`. Clients may
+  //!     still attempt to parse RootMap, as partial minidumps may still be
+  //!     usable.
+  bool Initialize(const base::FilePath& path);
+
+  //! \brief Returns an IOSIntermediateDumpMap corresponding to the root of the
+  //!     intermediate dump.
+  const IOSIntermediateDumpMap* RootMap() { return &minidump_; }
+
+ private:
+  bool Parse(FileReaderInterface* reader, FileOffset file_size);
+  IOSIntermediateDumpMap minidump_;
+
+  DISALLOW_COPY_AND_ASSIGN(IOSIntermediateDumpReader);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_READER_H_
diff --git a/util/ios/ios_intermediate_dump_reader_test.cc b/util/ios/ios_intermediate_dump_reader_test.cc
new file mode 100644
index 0000000..ae27f7d
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_reader_test.cc
@@ -0,0 +1,238 @@
+// 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 "util/ios/ios_intermediate_dump_reader.h"
+
+#include <fcntl.h>
+#include <mach/vm_map.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/errors.h"
+#include "test/scoped_temp_dir.h"
+#include "util/file/filesystem.h"
+#include "util/ios/ios_intermediate_dump_data.h"
+#include "util/ios/ios_intermediate_dump_format.h"
+#include "util/ios/ios_intermediate_dump_list.h"
+#include "util/ios/ios_intermediate_dump_writer.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+using Key = internal::IntermediateDumpKey;
+using internal::IOSIntermediateDumpWriter;
+
+class IOSIntermediateDumpReaderTest : public testing::Test {
+ protected:
+  // testing::Test:
+
+  void SetUp() override {
+    path_ = temp_dir_.path().Append("dump_file");
+    fd_ = base::ScopedFD(HANDLE_EINTR(
+        ::open(path_.value().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644)));
+    ASSERT_GE(fd_.get(), 0) << ErrnoMessage("open");
+
+    writer_ = std::make_unique<IOSIntermediateDumpWriter>();
+    ASSERT_TRUE(writer_->Open(path_));
+    ASSERT_TRUE(IsRegularFile(path_));
+  }
+
+  void TearDown() override {
+    fd_.reset();
+    writer_.reset();
+    EXPECT_FALSE(IsRegularFile(path_));
+  }
+
+  int fd() { return fd_.get(); }
+
+  const base::FilePath& path() const { return path_; }
+
+  std::unique_ptr<IOSIntermediateDumpWriter> writer_;
+
+ private:
+  base::ScopedFD fd_;
+  ScopedTempDir temp_dir_;
+  base::FilePath path_;
+};
+
+TEST_F(IOSIntermediateDumpReaderTest, ReadNoFile) {
+  internal::IOSIntermediateDumpReader reader;
+  EXPECT_FALSE(reader.Initialize(base::FilePath()));
+  EXPECT_TRUE(LoggingRemoveFile(path()));
+  EXPECT_FALSE(IsRegularFile(path()));
+}
+
+TEST_F(IOSIntermediateDumpReaderTest, ReadEmptyFile) {
+  internal::IOSIntermediateDumpReader reader;
+  EXPECT_FALSE(reader.Initialize(path()));
+  EXPECT_FALSE(IsRegularFile(path()));
+
+  const auto root_map = reader.RootMap();
+  EXPECT_TRUE(root_map->empty());
+}
+
+TEST_F(IOSIntermediateDumpReaderTest, ReadHelloWorld) {
+  std::string hello_world("hello world.");
+  EXPECT_TRUE(
+      LoggingWriteFile(fd(), hello_world.c_str(), hello_world.length()));
+  internal::IOSIntermediateDumpReader reader;
+  EXPECT_TRUE(reader.Initialize(path()));
+  EXPECT_FALSE(IsRegularFile(path()));
+
+  const auto root_map = reader.RootMap();
+  EXPECT_TRUE(root_map->empty());
+}
+
+TEST_F(IOSIntermediateDumpReaderTest, WriteBadPropertyDataLength) {
+  internal::IOSIntermediateDumpReader reader;
+  IOSIntermediateDumpWriter::CommandType command_type =
+      IOSIntermediateDumpWriter::CommandType::kRootMapStart;
+  EXPECT_TRUE(LoggingWriteFile(fd(), &command_type, sizeof(command_type)));
+
+  command_type = IOSIntermediateDumpWriter::CommandType::kProperty;
+  EXPECT_TRUE(LoggingWriteFile(fd(), &command_type, sizeof(command_type)));
+  Key key = Key::kVersion;
+  EXPECT_TRUE(LoggingWriteFile(fd(), &key, sizeof(key)));
+  uint8_t value = 1;
+  size_t value_length = 999999;
+  EXPECT_TRUE(LoggingWriteFile(fd(), &value_length, sizeof(size_t)));
+  EXPECT_TRUE(LoggingWriteFile(fd(), &value, sizeof(value)));
+  EXPECT_TRUE(reader.Initialize(path()));
+  EXPECT_FALSE(IsRegularFile(path()));
+
+  const auto root_map = reader.RootMap();
+  EXPECT_TRUE(root_map->empty());
+  const auto version_data = root_map->GetAsData(Key::kVersion);
+  EXPECT_EQ(version_data, nullptr);
+}
+
+TEST_F(IOSIntermediateDumpReaderTest, InvalidArrayInArray) {
+  internal::IOSIntermediateDumpReader reader;
+  {
+    IOSIntermediateDumpWriter::ScopedRootMap scopedRoot(writer_.get());
+    IOSIntermediateDumpWriter::ScopedArray threadArray(writer_.get(),
+                                                       Key::kThreads);
+    IOSIntermediateDumpWriter::ScopedArray innerThreadArray(writer_.get(),
+                                                            Key::kModules);
+
+    // Write version last, so it's not parsed.
+    int8_t version = 1;
+    writer_->AddProperty(Key::kVersion, &version);
+  }
+  EXPECT_TRUE(writer_->Close());
+  EXPECT_TRUE(reader.Initialize(path()));
+  EXPECT_FALSE(IsRegularFile(path()));
+
+  const auto root_map = reader.RootMap();
+  EXPECT_FALSE(root_map->empty());
+  const auto version_data = root_map->GetAsData(Key::kVersion);
+  EXPECT_EQ(version_data, nullptr);
+}
+
+TEST_F(IOSIntermediateDumpReaderTest, InvalidPropertyInArray) {
+  internal::IOSIntermediateDumpReader reader;
+
+  {
+    IOSIntermediateDumpWriter::ScopedRootMap scopedRoot(writer_.get());
+    IOSIntermediateDumpWriter::ScopedArray threadArray(writer_.get(),
+                                                       Key::kThreads);
+
+    // Write version last, so it's not parsed.
+    int8_t version = 1;
+    writer_->AddProperty(Key::kVersion, &version);
+  }
+  EXPECT_TRUE(writer_->Close());
+  EXPECT_TRUE(reader.Initialize(path()));
+  EXPECT_FALSE(IsRegularFile(path()));
+
+  const auto root_map = reader.RootMap();
+  EXPECT_FALSE(root_map->empty());
+  const auto version_data = root_map->GetAsData(Key::kVersion);
+  EXPECT_EQ(version_data, nullptr);
+}
+
+TEST_F(IOSIntermediateDumpReaderTest, ReadValidData) {
+  internal::IOSIntermediateDumpReader reader;
+  uint8_t version = 1;
+  {
+    IOSIntermediateDumpWriter::ScopedRootMap scopedRoot(writer_.get());
+    EXPECT_TRUE(writer_->AddProperty(Key::kVersion, &version));
+    {
+      IOSIntermediateDumpWriter::ScopedArray threadArray(
+          writer_.get(), Key::kThreadContextMemoryRegions);
+      IOSIntermediateDumpWriter::ScopedArrayMap threadMap(writer_.get());
+
+      std::string random_data("random_data");
+      EXPECT_TRUE(writer_->AddProperty(Key::kThreadContextMemoryRegionAddress,
+                                       &version));
+      EXPECT_TRUE(writer_->AddProperty(Key::kThreadContextMemoryRegionData,
+                                       random_data.c_str(),
+                                       random_data.length()));
+    }
+
+    {
+      IOSIntermediateDumpWriter::ScopedMap map(writer_.get(),
+                                               Key::kProcessInfo);
+      pid_t p_pid = getpid();
+      EXPECT_TRUE(writer_->AddProperty(Key::kPID, &p_pid));
+    }
+  }
+
+  EXPECT_TRUE(writer_->Close());
+  EXPECT_TRUE(reader.Initialize(path()));
+  EXPECT_FALSE(IsRegularFile(path()));
+
+  auto root_map = reader.RootMap();
+  EXPECT_FALSE(root_map->empty());
+  version = -1;
+  const auto version_data = root_map->GetAsData(Key::kVersion);
+  ASSERT_NE(version_data, nullptr);
+  EXPECT_TRUE(version_data->GetValue<uint8_t>(&version));
+  EXPECT_EQ(version, 1);
+
+  const auto process_info = root_map->GetAsMap(Key::kProcessInfo);
+  ASSERT_NE(process_info, nullptr);
+  const auto pid_data = process_info->GetAsData(Key::kPID);
+  ASSERT_NE(pid_data, nullptr);
+  pid_t p_pid = -1;
+  EXPECT_TRUE(pid_data->GetValue<pid_t>(&p_pid));
+  ASSERT_EQ(p_pid, getpid());
+
+  const auto thread_context_memory_regions =
+      root_map->GetAsList(Key::kThreadContextMemoryRegions);
+  EXPECT_EQ(thread_context_memory_regions->size(), 1UL);
+  for (const auto& region : *thread_context_memory_regions) {
+    const auto data = region->GetAsData(Key::kThreadContextMemoryRegionData);
+    ASSERT_NE(data, nullptr);
+    // Load as string.
+    EXPECT_EQ(data->GetString(), "random_data");
+
+    // Load as bytes.
+    auto bytes = data->bytes();
+    vm_size_t data_size = bytes.size();
+    EXPECT_EQ(data_size, 11UL);
+
+    const char* data_bytes = reinterpret_cast<const char*>(bytes.data());
+    EXPECT_EQ(std::string(data_bytes, data_size), "random_data");
+  }
+
+  const auto system_info = root_map->GetAsMap(Key::kSystemInfo);
+  EXPECT_EQ(system_info, nullptr);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/ios/ios_intermediate_dump_writer.cc b/util/ios/ios_intermediate_dump_writer.cc
new file mode 100644
index 0000000..eca33d3
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_writer.cc
@@ -0,0 +1,137 @@
+// 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 "util/ios/ios_intermediate_dump_writer.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "build/build_config.h"
+#include "util/ios/raw_logging.h"
+#include "util/ios/scoped_vm_read.h"
+
+namespace crashpad {
+namespace internal {
+
+// Similar to LoggingWriteFile but with CRASHPAD_RAW_LOG.
+bool RawLoggingWriteFile(int fd, const void* buffer, size_t size) {
+  uintptr_t buffer_int = reinterpret_cast<uintptr_t>(buffer);
+  while (size > 0) {
+    ssize_t bytes_written = HANDLE_EINTR(
+        write(fd, reinterpret_cast<const char*>(buffer_int), size));
+    if (bytes_written < 0 || bytes_written == 0) {
+      CRASHPAD_RAW_LOG_ERROR(bytes_written, "RawLoggingWriteFile");
+      return false;
+    }
+    buffer_int += bytes_written;
+    size -= bytes_written;
+  }
+  return true;
+}
+
+// Similar to LoggingCloseFile but with CRASHPAD_RAW_LOG.
+bool RawLoggingCloseFile(int fd) {
+  int rv = IGNORE_EINTR(close(fd));
+  if (rv != 0) {
+    CRASHPAD_RAW_LOG_ERROR(rv, "RawLoggingCloseFile");
+  }
+  return rv == 0;
+}
+
+// Similar to LoggingLockFile but with CRASHPAD_RAW_LOG.
+bool RawLoggingLockFileExclusiveNonBlocking(int fd) {
+  int rv = HANDLE_EINTR(flock(fd, LOCK_EX | LOCK_NB));
+  if (rv != 0) {
+    CRASHPAD_RAW_LOG_ERROR(rv, "RawLoggingLockFileExclusiveNonBlocking");
+  }
+  return rv == 0;
+}
+
+bool IOSIntermediateDumpWriter::Open(const base::FilePath& path) {
+  // Set data protection class D (No protection). A file with this type of
+  // protection can be read from or written to at any time.
+  // See:
+  // https://support.apple.com/guide/security/data-protection-classes-secb010e978a/web
+  constexpr int PROTECTION_CLASS_D = 4;
+  fd_ = HANDLE_EINTR(open_dprotected_np(path.value().c_str(),
+                                        O_WRONLY | O_CREAT | O_TRUNC,
+                                        PROTECTION_CLASS_D,
+                                        0 /* dpflags */,
+                                        0644 /* mode */));
+  if (fd_ < 0) {
+    CRASHPAD_RAW_LOG_ERROR(fd_, "open intermediate dump");
+    CRASHPAD_RAW_LOG(path.value().c_str());
+    return false;
+  }
+
+  return RawLoggingLockFileExclusiveNonBlocking(fd_);
+}
+
+bool IOSIntermediateDumpWriter::Close() {
+  return RawLoggingCloseFile(fd_);
+}
+
+bool IOSIntermediateDumpWriter::ArrayMapStart() {
+  const CommandType command_type = CommandType::kMapStart;
+  return RawLoggingWriteFile(fd_, &command_type, sizeof(command_type));
+}
+
+bool IOSIntermediateDumpWriter::MapStart(IntermediateDumpKey key) {
+  const CommandType command_type = CommandType::kMapStart;
+  return RawLoggingWriteFile(fd_, &command_type, sizeof(command_type)) &&
+         RawLoggingWriteFile(fd_, &key, sizeof(key));
+}
+
+bool IOSIntermediateDumpWriter::ArrayStart(IntermediateDumpKey key) {
+  const CommandType command_type = CommandType::kArrayStart;
+  return RawLoggingWriteFile(fd_, &command_type, sizeof(command_type)) &&
+         RawLoggingWriteFile(fd_, &key, sizeof(key));
+}
+
+bool IOSIntermediateDumpWriter::MapEnd() {
+  const CommandType command_type = CommandType::kMapEnd;
+  return RawLoggingWriteFile(fd_, &command_type, sizeof(command_type));
+}
+
+bool IOSIntermediateDumpWriter::ArrayEnd() {
+  const CommandType command_type = CommandType::kArrayEnd;
+  return RawLoggingWriteFile(fd_, &command_type, sizeof(command_type));
+}
+
+bool IOSIntermediateDumpWriter::RootMapStart() {
+  const CommandType command_type = CommandType::kRootMapStart;
+  return RawLoggingWriteFile(fd_, &command_type, sizeof(command_type));
+}
+
+bool IOSIntermediateDumpWriter::RootMapEnd() {
+  const CommandType command_type = CommandType::kRootMapEnd;
+  return RawLoggingWriteFile(fd_, &command_type, sizeof(command_type));
+}
+
+bool IOSIntermediateDumpWriter::AddPropertyInternal(IntermediateDumpKey key,
+                                                    const char* value,
+                                                    size_t value_length) {
+  ScopedVMRead<char> vmread;
+  if (!vmread.Read(value, value_length))
+    return false;
+  const CommandType command_type = CommandType::kProperty;
+  return RawLoggingWriteFile(fd_, &command_type, sizeof(command_type)) &&
+         RawLoggingWriteFile(fd_, &key, sizeof(key)) &&
+         RawLoggingWriteFile(fd_, &value_length, sizeof(size_t)) &&
+         RawLoggingWriteFile(fd_, vmread.get(), value_length);
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/util/ios/ios_intermediate_dump_writer.h b/util/ios/ios_intermediate_dump_writer.h
new file mode 100644
index 0000000..0f8b1b7
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_writer.h
@@ -0,0 +1,197 @@
+// 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_UTIL_IOS_IOS_INTERMEDIATE_DUMP_WRITER_H_
+#define CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_WRITER_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "util/ios/ios_intermediate_dump_format.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Wrapper class for writing intermediate dump file.
+//!
+//! Due to the limitations of in-process handling, an intermediate dump file is
+//! written during exceptions. The data is streamed to a file using only
+//! in-process safe methods.
+//!
+//! The file format is similar to binary JSON, supporting keyed properties, maps
+//! and arrays.
+//!  - Property [key:int, length:int, value:intarray]
+//!  - StartMap [key:int], followed by repeating Properties until EndMap
+//!  - StartArray [key:int], followed by repeating Maps until EndArray
+//!  - EndMap, EndArray, EndDocument
+//!
+//!  Similar to JSON, maps can contain other maps, arrays and properties.
+//!
+//! Note: All methods are `RUNS-DURING-CRASH`.
+class IOSIntermediateDumpWriter final {
+ public:
+  IOSIntermediateDumpWriter() = default;
+
+  //! \brief Command instructions for the intermediate dump reader.
+  enum class CommandType : uint8_t {
+    //! \brief Indicates a new map, followed by associated key.
+    kMapStart = 0x01,
+
+    //! \brief Indicates map is complete.
+    kMapEnd = 0x02,
+
+    //! \brief Indicates a new array, followed by associated key.
+    kArrayStart = 0x03,
+
+    //! \brief Indicates array is complete.
+    kArrayEnd = 0x04,
+
+    //! \brief Indicates a new property, followed by a key, length and value.
+    kProperty = 0x05,
+
+    //! \brief Indicates the start of the root map.
+    kRootMapStart = 0x06,
+
+    //! \brief Indicates the end of the root map, and that there is nothing left
+    //!     to parse.
+    kRootMapEnd = 0x07,
+  };
+
+  //! \brief Open and lock an intermediate dump file. This is the only method
+  //!     in the writer class that is generally run outside of a crash.
+  //!
+  //! \param[in] path The path to the intermediate dump.
+  //!
+  //! \return On success, returns `true`, otherwise returns `false`.
+  bool Open(const base::FilePath& path);
+
+  //! \brief Completes writing the intermediate dump file and releases the
+  //!     file handle.
+  //!
+  //! \return On success, returns `true`, otherwise returns `false`.
+  bool Close();
+
+  //! \brief A scoped wrapper for calls to RootMapStart and RootMapEnd.
+  class ScopedRootMap {
+   public:
+    explicit ScopedRootMap(IOSIntermediateDumpWriter* writer)
+        : writer_(writer) {
+      writer->RootMapStart();
+    }
+    ~ScopedRootMap() { writer_->RootMapEnd(); }
+
+   private:
+    IOSIntermediateDumpWriter* writer_;
+    DISALLOW_COPY_AND_ASSIGN(ScopedRootMap);
+  };
+
+  //! \brief A scoped wrapper for calls to MapStart and MapEnd.
+  class ScopedMap {
+   public:
+    explicit ScopedMap(IOSIntermediateDumpWriter* writer,
+                       IntermediateDumpKey key)
+        : writer_(writer) {
+      writer->MapStart(key);
+    }
+    ~ScopedMap() { writer_->MapEnd(); }
+
+   private:
+    IOSIntermediateDumpWriter* writer_;
+    DISALLOW_COPY_AND_ASSIGN(ScopedMap);
+  };
+
+  //! \brief A scoped wrapper for calls to ArrayMapStart and MapEnd.
+  class ScopedArrayMap {
+   public:
+    explicit ScopedArrayMap(IOSIntermediateDumpWriter* writer)
+        : writer_(writer) {
+      writer->ArrayMapStart();
+    }
+    ~ScopedArrayMap() { writer_->MapEnd(); }
+
+   private:
+    IOSIntermediateDumpWriter* writer_;
+    DISALLOW_COPY_AND_ASSIGN(ScopedArrayMap);
+  };
+
+  //! \brief A scoped wrapper for calls to ArrayStart and ArrayEnd.
+  class ScopedArray {
+   public:
+    explicit ScopedArray(IOSIntermediateDumpWriter* writer,
+                         IntermediateDumpKey key)
+        : writer_(writer) {
+      writer->ArrayStart(key);
+    }
+    ~ScopedArray() { writer_->ArrayEnd(); }
+
+   private:
+    IOSIntermediateDumpWriter* writer_;
+    DISALLOW_COPY_AND_ASSIGN(ScopedArray);
+  };
+
+  //! \return The `true` if able to AddPropertyInternal the \a key \a value
+  //!     \a count tuple.
+  template <typename T>
+  bool AddProperty(IntermediateDumpKey key, const T* value, size_t count = 1) {
+    return AddPropertyInternal(
+        key, reinterpret_cast<const char*>(value), count * sizeof(T));
+  }
+
+  //! \return The `true` if able to AddPropertyInternal the \a key \a value
+  //!     \a count tuple.
+  bool AddPropertyBytes(IntermediateDumpKey key,
+                        const void* value,
+                        size_t value_length) {
+    return AddPropertyInternal(
+        key, reinterpret_cast<const char*>(value), value_length);
+  }
+
+ private:
+  //! \return Returns `true` if able to write a kProperty command  with the
+  //!     \a key \a value \a count tuple.
+  bool AddPropertyInternal(IntermediateDumpKey key,
+                           const char* value,
+                           size_t value_length);
+
+  //! \return Returns `true` if able to write a kArrayStart command  with the
+  //!     \a key.
+  bool ArrayStart(IntermediateDumpKey key);
+
+  //! \return Returns `true` if able to write a kMapStart command with the
+  //!     \a key.
+  bool MapStart(IntermediateDumpKey key);
+
+  //! \return Returns `true` if able to write a kMapStart command.
+  bool ArrayMapStart();
+
+  //! \return Returns `true` if able to write a kArrayEnd command.
+  bool ArrayEnd();
+
+  //! \return Returns `true` if able to write a kMapEnd command.
+  bool MapEnd();
+
+  //! \return Returns `true` if able to write a kRootMapStart command.
+  bool RootMapStart();
+
+  //! \return Returns `true` if able to write a kRootMapEnd command.
+  bool RootMapEnd();
+
+  int fd_;
+
+  DISALLOW_COPY_AND_ASSIGN(IOSIntermediateDumpWriter);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_IOS_INTERMEDIATE_DUMP_WRITER_H_
diff --git a/util/ios/ios_intermediate_dump_writer_test.cc b/util/ios/ios_intermediate_dump_writer_test.cc
new file mode 100644
index 0000000..6c6bcad
--- /dev/null
+++ b/util/ios/ios_intermediate_dump_writer_test.cc
@@ -0,0 +1,131 @@
+// 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 "util/ios/ios_intermediate_dump_writer.h"
+
+#include <fcntl.h>
+
+#include "base/files/scoped_file.h"
+#include "base/posix/eintr_wrapper.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/errors.h"
+#include "test/scoped_temp_dir.h"
+#include "util/file/file_io.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+using Key = internal::IntermediateDumpKey;
+using internal::IOSIntermediateDumpWriter;
+
+class IOSIntermediateDumpWriterTest : public testing::Test {
+ protected:
+  // testing::Test:
+
+  void SetUp() override {
+    path_ = temp_dir_.path().Append("dump_file");
+    writer_ = std::make_unique<IOSIntermediateDumpWriter>();
+  }
+
+  void TearDown() override {
+    writer_.reset();
+    EXPECT_EQ(unlink(path_.value().c_str()), 0) << ErrnoMessage("unlink");
+  }
+
+  const base::FilePath& path() const { return path_; }
+
+  std::unique_ptr<IOSIntermediateDumpWriter> writer_;
+
+ private:
+  ScopedTempDir temp_dir_;
+  base::FilePath path_;
+};
+
+// Test file is locked.
+TEST_F(IOSIntermediateDumpWriterTest, OpenLocked) {
+  EXPECT_TRUE(writer_->Open(path()));
+
+  ScopedFileHandle handle(LoggingOpenFileForRead(path()));
+  EXPECT_TRUE(handle.is_valid());
+  EXPECT_EQ(LoggingLockFile(handle.get(),
+                            FileLocking::kExclusive,
+                            FileLockingBlocking::kNonBlocking),
+            FileLockingResult::kWouldBlock);
+}
+
+TEST_F(IOSIntermediateDumpWriterTest, Close) {
+  EXPECT_TRUE(writer_->Open(path()));
+  EXPECT_TRUE(writer_->Close());
+
+  std::string contents;
+  ASSERT_TRUE(LoggingReadEntireFile(path(), &contents));
+  ASSERT_EQ(contents, "");
+}
+
+TEST_F(IOSIntermediateDumpWriterTest, ScopedArray) {
+  EXPECT_TRUE(writer_->Open(path()));
+  {
+    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer_.get());
+    IOSIntermediateDumpWriter::ScopedArray threadArray(writer_.get(),
+                                                       Key::kThreads);
+    IOSIntermediateDumpWriter::ScopedArrayMap threadMap(writer_.get());
+  }
+  std::string contents;
+  ASSERT_TRUE(LoggingReadEntireFile(path(), &contents));
+  std::string result("\6\x3p\x17\1\2\4\a", 8);
+  ASSERT_EQ(contents, result);
+}
+
+TEST_F(IOSIntermediateDumpWriterTest, ScopedMap) {
+  EXPECT_TRUE(writer_->Open(path()));
+  {
+    IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer_.get());
+    IOSIntermediateDumpWriter::ScopedMap map(writer_.get(),
+                                             Key::kMachException);
+  }
+
+  std::string contents;
+  ASSERT_TRUE(LoggingReadEntireFile(path(), &contents));
+  std::string result("\6\1\xe8\3\2\a", 6);
+  ASSERT_EQ(contents, result);
+}
+
+TEST_F(IOSIntermediateDumpWriterTest, Property) {
+  EXPECT_TRUE(writer_->Open(path()));
+  EXPECT_TRUE(writer_->AddProperty(Key::kVersion, "version", 7));
+
+  std::string contents;
+  ASSERT_TRUE(LoggingReadEntireFile(path(), &contents));
+  std::string result("\5\1\0\a\0\0\0\0\0\0\0version", 18);
+  ASSERT_EQ(contents, result);
+}
+
+TEST_F(IOSIntermediateDumpWriterTest, BadProperty) {
+  EXPECT_TRUE(writer_->Open(path()));
+  ASSERT_FALSE(writer_->AddProperty(Key::kVersion, "version", -1));
+
+  std::string contents;
+  ASSERT_TRUE(LoggingReadEntireFile(path(), &contents));
+
+  // path() is now invalid, as type, key and value were written, but the
+  // value itself is not.
+  std::string results("\5\1\0\xff\xff\xff\xff\xff\xff\xff\xff", 11);
+  ASSERT_EQ(contents, results);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad