diff --git a/util/BUILD.gn b/util/BUILD.gn index 71bf1ce6..f4d88a3a 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -382,6 +382,19 @@ crashpad_static_library("util") { 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 @@ source_set("util_test") { 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 00000000..4879ccf2 --- /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(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 00000000..def5ab9c --- /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 +#include + +#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. +class IOSIntermediateDumpData : public IOSIntermediateDumpObject { + public: + IOSIntermediateDumpData(); + ~IOSIntermediateDumpData() override; + + //! \brief Constructs a new data object which owns a std::vector. + //! + //! \param[in] data An array of uint8_t. + //! \param[in] length The length of \a data. + IOSIntermediateDumpData(std::vector 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 + bool GetValue(T* value) const { + return GetValueInternal(reinterpret_cast(value), sizeof(*value)); + } + + const std::vector& bytes() const { return data_; } + + private: + bool GetValueInternal(void* value, size_t value_size) const; + + std::vector 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 00000000..1d7cad65 --- /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 00000000..17f2b89d --- /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 00000000..42d1ef29 --- /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 + +#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>; + 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 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 00000000..7ec61038 --- /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(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(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(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 00000000..a11d6a63 --- /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 +#include + +#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> + 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 00000000..99099350 --- /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 00000000..21ccd297 --- /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 00000000..b571662d --- /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 +#include +#include + +#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(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 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 new_map( + new IOSIntermediateDumpMap()); + if (parent->GetType() == Type::kMap) { + const auto parent_map = static_cast(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(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(); + 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(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 data(value_length); + if (!reader->ReadExactly(data.data(), value_length)) { + return false; + } + auto parent_map = static_cast(parent); + if (parent_map->map_.find(key) != parent_map->map_.end()) { + LOG(ERROR) << "Inserting duplicate key"; + } + parent_map->map_[key] = + std::make_unique(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 00000000..2966e7c9 --- /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 00000000..ae27f7d8 --- /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 +#include + +#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(); + 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 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(&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(&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(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 00000000..eca33d38 --- /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 +#include + +#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(buffer); + while (size > 0) { + ssize_t bytes_written = HANDLE_EINTR( + write(fd, reinterpret_cast(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 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 00000000..0f8b1b7d --- /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 + bool AddProperty(IntermediateDumpKey key, const T* value, size_t count = 1) { + return AddPropertyInternal( + key, reinterpret_cast(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(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 00000000..6c6bcadf --- /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 + +#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(); + } + + 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 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