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: I43a19204935303afd753c8c7090c54099634ccd6
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>
This commit is contained in:
Justin Cohen 2021-05-20 11:10:34 -04:00 committed by Crashpad LUCI CQ
parent 6f8dfc7730
commit b2b65a91cf
16 changed files with 1496 additions and 0 deletions

View File

@ -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",
]

View File

@ -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

View File

@ -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_

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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