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