diff --git a/minidump/minidump.gyp b/minidump/minidump.gyp index e36006c9..63e0ba6b 100644 --- a/minidump/minidump.gyp +++ b/minidump/minidump.gyp @@ -33,6 +33,8 @@ '..', ], 'sources': [ + 'minidump_annotation_writer.cc', + 'minidump_annotation_writer.h', 'minidump_byte_array_writer.cc', 'minidump_byte_array_writer.h', 'minidump_context.h', diff --git a/minidump/minidump_annotation_writer.cc b/minidump/minidump_annotation_writer.cc new file mode 100644 index 00000000..54a3ecba --- /dev/null +++ b/minidump/minidump_annotation_writer.cc @@ -0,0 +1,162 @@ +// Copyright 2017 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 "minidump/minidump_annotation_writer.h" + +#include + +#include "base/logging.h" +#include "util/file/file_writer.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +MinidumpAnnotationWriter::MinidumpAnnotationWriter() = default; + +MinidumpAnnotationWriter::~MinidumpAnnotationWriter() = default; + +void MinidumpAnnotationWriter::InitializeFromSnapshot( + const AnnotationSnapshot& snapshot) { + DCHECK_EQ(state(), kStateMutable); + + name_.SetUTF8(snapshot.name); + annotation_.type = snapshot.type; + annotation_.reserved = 0; + value_.set_data(snapshot.value); +} + +void MinidumpAnnotationWriter::InitializeWithData( + const std::string& name, + uint16_t type, + const std::vector& data) { + DCHECK_EQ(state(), kStateMutable); + + name_.SetUTF8(name); + annotation_.type = type; + annotation_.reserved = 0; + value_.set_data(data); +} + +bool MinidumpAnnotationWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + name_.RegisterRVA(&annotation_.name); + value_.RegisterRVA(&annotation_.value); + + return true; +} + +size_t MinidumpAnnotationWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + // This object is written by the MinidumpAnnotationListWriter, and its + // children write themselves. + return 0; +} + +std::vector MinidumpAnnotationWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + + return {&name_, &value_}; +} + +bool MinidumpAnnotationWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + // This object is written by the MinidumpAnnotationListWriter, and its + // children write themselves. + return true; +} + +MinidumpAnnotationListWriter::MinidumpAnnotationListWriter() + : minidump_list_(new MinidumpAnnotationList()) {} + +MinidumpAnnotationListWriter::~MinidumpAnnotationListWriter() = default; + +void MinidumpAnnotationListWriter::InitializeFromList( + const std::vector& list) { + DCHECK_EQ(state(), kStateMutable); + for (const auto& annotation : list) { + auto writer = std::make_unique(); + writer->InitializeFromSnapshot(annotation); + AddObject(std::move(writer)); + } +} + +void MinidumpAnnotationListWriter::AddObject( + std::unique_ptr annotation_writer) { + DCHECK_EQ(state(), kStateMutable); + + objects_.push_back(std::move(annotation_writer)); +} + +bool MinidumpAnnotationListWriter::IsUseful() const { + return !objects_.empty(); +} + +bool MinidumpAnnotationListWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + if (!AssignIfInRange(&minidump_list_->count, objects_.size())) { + LOG(ERROR) << "annotation list size " << objects_.size() + << " is out of range"; + return false; + } + + return true; +} + +size_t MinidumpAnnotationListWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + return sizeof(*minidump_list_) + sizeof(MinidumpAnnotation) * objects_.size(); +} + +std::vector +MinidumpAnnotationListWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + + std::vector children(objects_.size()); + for (size_t i = 0; i < objects_.size(); ++i) { + children[i] = objects_[i].get(); + } + + return children; +} + +bool MinidumpAnnotationListWriter::WriteObject( + FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + std::vector iov(1 + objects_.size()); + iov[0].iov_base = minidump_list_.get(); + iov[0].iov_len = sizeof(*minidump_list_); + + for (const auto& object : objects_) { + iov.emplace_back(WritableIoVec{object->minidump_annotation(), + sizeof(MinidumpAnnotation)}); + } + + return file_writer->WriteIoVec(&iov); +} + +} // namespace crashpad diff --git a/minidump/minidump_annotation_writer.h b/minidump/minidump_annotation_writer.h new file mode 100644 index 00000000..fc30dbc3 --- /dev/null +++ b/minidump/minidump_annotation_writer.h @@ -0,0 +1,109 @@ +// Copyright 2017 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_MINIDUMP_MINIDUMP_ANNOTATION_WRITER_H_ +#define CRASHPAD_MINIDUMP_MINIDUMP_ANNOTATION_WRITER_H_ + +#include +#include + +#include "minidump/minidump_byte_array_writer.h" +#include "minidump/minidump_extensions.h" +#include "minidump/minidump_string_writer.h" +#include "minidump/minidump_writable.h" +#include "snapshot/annotation_snapshot.h" + +namespace crashpad { + +//! \brief The writer for a MinidumpAnnotation object in a minidump file. +//! +//! Because MinidumpAnnotation objects only appear as elements +//! of MinidumpAnnotationList objects, this class does not write any +//! data on its own. It makes its MinidumpAnnotation data available to its +//! MinidumpAnnotationList parent, which writes it as part of a +//! MinidumpAnnotationList. +class MinidumpAnnotationWriter final : public internal::MinidumpWritable { + public: + MinidumpAnnotationWriter(); + ~MinidumpAnnotationWriter(); + + //! \brief Initializes the annotation writer with data from an + //! AnnotationSnapshot. + void InitializeFromSnapshot(const AnnotationSnapshot& snapshot); + + //! \brief Initializes the annotation writer with data values. + void InitializeWithData(const std::string& name, + uint16_t type, + const std::vector& data); + + //! \brief Returns the MinidumpAnnotation referencing this object’s data. + const MinidumpAnnotation* minidump_annotation() const { return &annotation_; } + + protected: + // MinidumpWritable: + + bool Freeze() override; + size_t SizeOfObject() override; + std::vector Children() override; + bool WriteObject(FileWriterInterface* file_writer) override; + + private: + MinidumpAnnotation annotation_; + internal::MinidumpUTF8StringWriter name_; + MinidumpByteArrayWriter value_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpAnnotationWriter); +}; + +//! \brief The writer for a MinidumpAnnotationList object in a minidump file, +//! containing a list of MinidumpAnnotation objects. +class MinidumpAnnotationListWriter final : public internal::MinidumpWritable { + public: + MinidumpAnnotationListWriter(); + ~MinidumpAnnotationListWriter(); + + //! \brief Initializes the annotation list writer with a list of + //! AnnotationSnapshot objects. + void InitializeFromList(const std::vector& list); + + //! \brief Adds a single MinidumpAnnotationWriter to the list to be written. + void AddObject(std::unique_ptr annotation_writer); + + //! \brief Determines whether the object is useful. + //! + //! A useful object is one that carries data that makes a meaningful + //! contribution to a minidump file. An object carrying entries would be + //! considered useful. + //! + //! \return `true` if the object is useful, `false` otherwise. + bool IsUseful() const; + + protected: + // MinidumpWritable: + + bool Freeze() override; + size_t SizeOfObject() override; + std::vector Children() override; + bool WriteObject(FileWriterInterface* file_writer) override; + + private: + std::unique_ptr minidump_list_; + std::vector> objects_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpAnnotationListWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_MINIDUMP_MINIDUMP_ANNOTATION_WRITER_H_ diff --git a/minidump/minidump_annotation_writer_test.cc b/minidump/minidump_annotation_writer_test.cc new file mode 100644 index 00000000..9b4e80de --- /dev/null +++ b/minidump/minidump_annotation_writer_test.cc @@ -0,0 +1,188 @@ +// Copyright 2017 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 "minidump/minidump_annotation_writer.h" + +#include + +#include "base/macros.h" +#include "gtest/gtest.h" +#include "minidump/minidump_extensions.h" +#include "minidump/test/minidump_byte_array_writer_test_util.h" +#include "minidump/test/minidump_string_writer_test_util.h" +#include "minidump/test/minidump_writable_test_util.h" +#include "util/file/string_file.h" + +namespace crashpad { +namespace test { +namespace { + +const MinidumpAnnotationList* MinidumpAnnotationListAtStart( + const std::string& file_contents, + uint32_t count) { + MINIDUMP_LOCATION_DESCRIPTOR location_descriptor; + location_descriptor.DataSize = static_cast( + sizeof(MinidumpAnnotationList) + count * sizeof(MinidumpAnnotation)); + location_descriptor.Rva = 0; + return MinidumpWritableAtLocationDescriptor( + file_contents, location_descriptor); +} + +TEST(MinidumpAnnotationWriter, EmptyList) { + StringFile string_file; + + MinidumpAnnotationListWriter list_writer; + + EXPECT_FALSE(list_writer.IsUseful()); + + EXPECT_TRUE(list_writer.WriteEverything(&string_file)); + + ASSERT_EQ(string_file.string().size(), sizeof(MinidumpAnnotationList)); + + auto* list = MinidumpAnnotationListAtStart(string_file.string(), 0); + ASSERT_TRUE(list); + EXPECT_EQ(0u, list->count); +} + +TEST(MinidumpAnnotationWriter, OneItem) { + StringFile string_file; + + const char kName[] = "name"; + const uint16_t kType = 0xFFFF; + const std::vector kValue{'v', 'a', 'l', 'u', 'e', '\0'}; + + auto annotation_writer = std::make_unique(); + annotation_writer->InitializeWithData(kName, kType, kValue); + + MinidumpAnnotationListWriter list_writer; + list_writer.AddObject(std::move(annotation_writer)); + + EXPECT_TRUE(list_writer.IsUseful()); + + EXPECT_TRUE(list_writer.WriteEverything(&string_file)); + + ASSERT_EQ(string_file.string().size(), + sizeof(MinidumpAnnotationList) + sizeof(MinidumpAnnotation) + + sizeof(MinidumpUTF8String) + sizeof(kName) + + sizeof(MinidumpByteArray) + kValue.size() + + 3); // 3 for padding. + + auto* list = MinidumpAnnotationListAtStart(string_file.string(), 1); + ASSERT_TRUE(list); + EXPECT_EQ(1u, list->count); + EXPECT_EQ(kName, + MinidumpUTF8StringAtRVAAsString(string_file.string(), + list->objects[0].name)); + EXPECT_EQ(kType, list->objects[0].type); + EXPECT_EQ(0u, list->objects[0].reserved); + EXPECT_EQ( + kValue, + MinidumpByteArrayAtRVA(string_file.string(), list->objects[0].value)); +} + +TEST(MinidumpAnnotationWriter, ThreeItems) { + StringFile string_file; + + const char* kNames[] = { + "~~FIRST~~", " second + ", "3", + }; + const uint16_t kTypes[] = { + 0x1, 0xABCD, 0x42, + }; + const std::vector kValues[] = { + {'\0'}, {0xB0, 0xA0, 0xD0, 0xD0, 0xD0}, {'T'}, + }; + + MinidumpAnnotationListWriter list_writer; + + for (size_t i = 0; i < arraysize(kNames); ++i) { + auto annotation = std::make_unique(); + annotation->InitializeWithData(kNames[i], kTypes[i], kValues[i]); + list_writer.AddObject(std::move(annotation)); + } + + EXPECT_TRUE(list_writer.WriteEverything(&string_file)); + + ASSERT_EQ(string_file.string().size(), + sizeof(MinidumpAnnotationList) + 3 * sizeof(MinidumpAnnotation) + + 3 * sizeof(MinidumpUTF8String) + 3 * sizeof(MinidumpByteArray) + + strlen(kNames[0]) + 1 + kValues[0].size() + 2 + + strlen(kNames[1]) + 1 + 3 + kValues[1].size() + 1 + + strlen(kNames[2]) + 1 + 3 + kValues[2].size() + 2); + + auto* list = MinidumpAnnotationListAtStart(string_file.string(), 3); + ASSERT_TRUE(list); + EXPECT_EQ(3u, list->count); + + for (size_t i = 0; i < 3; ++i) { + EXPECT_EQ(kNames[i], + MinidumpUTF8StringAtRVAAsString(string_file.string(), + list->objects[i].name)); + EXPECT_EQ(kTypes[i], list->objects[i].type); + EXPECT_EQ(0u, list->objects[i].reserved); + EXPECT_EQ( + kValues[i], + MinidumpByteArrayAtRVA(string_file.string(), list->objects[i].value)); + } +} + +TEST(MinidumpAnnotationWriter, DuplicateNames) { + StringFile string_file; + + const char kName[] = "@@name!"; + const uint16_t kType = 0x1; + const std::vector kValue1{'r', 'e', 'd', '\0'}; + const std::vector kValue2{'m', 'a', 'g', 'e', 'n', 't', 'a', '\0'}; + + MinidumpAnnotationListWriter list_writer; + + auto annotation = std::make_unique(); + annotation->InitializeWithData(kName, kType, kValue1); + list_writer.AddObject(std::move(annotation)); + + annotation = std::make_unique(); + annotation->InitializeWithData(kName, kType, kValue2); + list_writer.AddObject(std::move(annotation)); + + EXPECT_TRUE(list_writer.WriteEverything(&string_file)); + + ASSERT_EQ(string_file.string().size(), + sizeof(MinidumpAnnotationList) + 2 * sizeof(MinidumpAnnotation) + + 2 * sizeof(MinidumpUTF8String) + 2 * sizeof(MinidumpByteArray) + + 2 * sizeof(kName) + kValue1.size() + kValue2.size()); + + auto* list = MinidumpAnnotationListAtStart(string_file.string(), 2); + ASSERT_TRUE(list); + EXPECT_EQ(2u, list->count); + + EXPECT_EQ(kName, + MinidumpUTF8StringAtRVAAsString(string_file.string(), + list->objects[0].name)); + EXPECT_EQ(kType, list->objects[0].type); + EXPECT_EQ( + kValue1, + MinidumpByteArrayAtRVA(string_file.string(), list->objects[0].value)); + + EXPECT_EQ(kName, + MinidumpUTF8StringAtRVAAsString(string_file.string(), + list->objects[1].name)); + EXPECT_EQ(kType, list->objects[1].type); + EXPECT_EQ( + kValue2, + MinidumpByteArrayAtRVA(string_file.string(), list->objects[1].value)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/minidump/minidump_extensions.h b/minidump/minidump_extensions.h index ad69aecb..3be5cea1 100644 --- a/minidump/minidump_extensions.h +++ b/minidump/minidump_extensions.h @@ -282,6 +282,32 @@ struct ALIGNAS(4) PACKED MinidumpSimpleStringDictionary { MinidumpSimpleStringDictionaryEntry entries[0]; }; +//! \brief A typed annotation object. +struct ALIGNAS(4) PACKED MinidumpAnnotation { + //! \brief ::RVA of a MinidumpUTF8String containing the name of the + //! annotation. + RVA name; + + //! \brief The type of data stored in the \a value of the annotation. This + //! may correspond to an \a Annotation::Type or it may be user-defined. + uint16_t type; + + //! \brief This field is always `0`. + uint16_t reserved; + + //! \brief ::RVA of a MinidumpByteArray to the data for the annotation. + RVA value; +}; + +//! \brief A list of annotation objects. +struct ALIGNAS(4) PACKED MinidumpAnnotationList { + //! \brief The number of annotation objects present. + uint32_t count; + + //! \brief A list of MinidumpAnnotation objects. + MinidumpAnnotation objects[0]; +}; + //! \brief Additional Crashpad-specific information about a module carried //! within a minidump file. //! diff --git a/minidump/minidump_test.gyp b/minidump/minidump_test.gyp index beb41519..cfc0606d 100644 --- a/minidump/minidump_test.gyp +++ b/minidump/minidump_test.gyp @@ -64,6 +64,7 @@ '..', ], 'sources': [ + 'minidump_annotation_writer_test.cc', 'minidump_byte_array_writer_test.cc', 'minidump_context_writer_test.cc', 'minidump_crashpad_info_writer_test.cc', diff --git a/minidump/test/minidump_writable_test_util.cc b/minidump/test/minidump_writable_test_util.cc index 1841243a..1a832bf3 100644 --- a/minidump/test/minidump_writable_test_util.cc +++ b/minidump/test/minidump_writable_test_util.cc @@ -223,6 +223,12 @@ struct MinidumpSimpleStringDictionaryListTraits { } }; +struct MinidumpAnnotationListObjectsTraits { + using ListType = MinidumpAnnotationList; + enum : size_t { kElementSize = sizeof(MinidumpAnnotation) }; + static size_t ElementCount(const ListType* list) { return list->count; } +}; + template const typename T::ListType* MinidumpListAtLocationDescriptor( const std::string& file_contents, @@ -313,6 +319,15 @@ MinidumpWritableAtLocationDescriptor( MinidumpSimpleStringDictionaryListTraits>(file_contents, location); } +template <> +const MinidumpAnnotationList* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { + return MinidumpListAtLocationDescriptor( + file_contents, location); +} + namespace { template diff --git a/minidump/test/minidump_writable_test_util.h b/minidump/test/minidump_writable_test_util.h index dd8f5531..5b176d2b 100644 --- a/minidump/test/minidump_writable_test_util.h +++ b/minidump/test/minidump_writable_test_util.h @@ -96,6 +96,7 @@ MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_MEMORY_INFO_LIST); MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpModuleCrashpadInfoList); MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpRVAList); MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpSimpleStringDictionary); +MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpAnnotationList); // These types have final fields carrying variable-sized data (typically string // data). @@ -141,10 +142,10 @@ const T* TMinidumpWritableAtLocationDescriptor( //! - With a MINIDUMP_HEADER template parameter, a template specialization //! ensures that the structure’s magic number and version fields are correct. //! - With a MINIDUMP_MEMORY_LIST, MINIDUMP_THREAD_LIST, MINIDUMP_MODULE_LIST, -//! MINIDUMP_MEMORY_INFO_LIST, or MinidumpSimpleStringDictionary template -//! parameter, template specializations ensure that the size given by \a -//! location matches the size expected of a stream containing the number of -//! elements it claims to have. +//! MINIDUMP_MEMORY_INFO_LIST, MinidumpSimpleStringDictionary, or +//! MinidumpAnnotationList template parameter, template specializations +//! ensure that the size given by \a location matches the size expected of a +//! stream containing the number of elements it claims to have. //! - With an IMAGE_DEBUG_MISC, CodeViewRecordPDB20, or CodeViewRecordPDB70 //! template parameter, template specializations ensure that the structure //! has the expected format including any magic number and the `NUL`- @@ -230,6 +231,12 @@ MinidumpWritableAtLocationDescriptor( const std::string& file_contents, const MINIDUMP_LOCATION_DESCRIPTOR& location); +template <> +const MinidumpAnnotationList* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); + //! \brief Returns a typed minidump object located within a minidump file’s //! contents, where the offset of the object is known. //!