diff --git a/minidump/minidump.gyp b/minidump/minidump.gyp index 7874f8ae..2fabeaad 100644 --- a/minidump/minidump.gyp +++ b/minidump/minidump.gyp @@ -44,6 +44,8 @@ 'minidump_misc_info_writer.h', 'minidump_module_writer.cc', 'minidump_module_writer.h', + 'minidump_simple_string_dictionary_writer.cc', + 'minidump_simple_string_dictionary_writer.h', 'minidump_stream_writer.cc', 'minidump_stream_writer.h', 'minidump_string_writer.cc', @@ -83,6 +85,7 @@ 'minidump_memory_writer_test_util.h', 'minidump_misc_info_writer_test.cc', 'minidump_module_writer_test.cc', + 'minidump_simple_string_dictionary_writer_test.cc', 'minidump_string_writer_test.cc', 'minidump_system_info_writer_test.cc', 'minidump_thread_writer_test.cc', diff --git a/minidump/minidump_simple_string_dictionary_writer.cc b/minidump/minidump_simple_string_dictionary_writer.cc new file mode 100644 index 00000000..d9a5c919 --- /dev/null +++ b/minidump/minidump_simple_string_dictionary_writer.cc @@ -0,0 +1,158 @@ +// Copyright 2014 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_simple_string_dictionary_writer.h" + +#include "base/logging.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +MinidumpSimpleStringDictionaryEntryWriter:: + MinidumpSimpleStringDictionaryEntryWriter() + : MinidumpWritable(), entry_(), key_(), value_() { +} + +MinidumpSimpleStringDictionaryEntryWriter:: + ~MinidumpSimpleStringDictionaryEntryWriter() { +} + +const MinidumpSimpleStringDictionaryEntry* +MinidumpSimpleStringDictionaryEntryWriter::MinidumpSimpleStringDictionaryEntry() + const { + DCHECK_EQ(state(), kStateWritable); + + return &entry_; +} + +void MinidumpSimpleStringDictionaryEntryWriter::SetKeyValue( + const std::string& key, + const std::string& value) { + key_.SetUTF8(key); + value_.SetUTF8(value); +} + +bool MinidumpSimpleStringDictionaryEntryWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + key_.RegisterRVA(&entry_.key); + value_.RegisterRVA(&entry_.value); + + return true; +} + +size_t MinidumpSimpleStringDictionaryEntryWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + // This object doesn’t directly write anything itself. Its + // MinidumpSimpleStringDictionaryEntry is written by its parent as part of a + // MinidumpSimpleStringDictionary, and its children are responsible for + // writing themselves. + return 0; +} + +std::vector +MinidumpSimpleStringDictionaryEntryWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + + std::vector children(1, &key_); + children.push_back(&value_); + return children; +} + +bool MinidumpSimpleStringDictionaryEntryWriter::WriteObject( + FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + // This object doesn’t directly write anything itself. Its + // MinidumpSimpleStringDictionaryEntry is written by its parent as part of a + // MinidumpSimpleStringDictionary, and its children are responsible for + // writing themselves. + return true; +} + +MinidumpSimpleStringDictionaryWriter::MinidumpSimpleStringDictionaryWriter() + : MinidumpWritable(), simple_string_dictionary_base_(), entries_() { +} + +MinidumpSimpleStringDictionaryWriter::~MinidumpSimpleStringDictionaryWriter() { +} + +void MinidumpSimpleStringDictionaryWriter::AddEntry( + MinidumpSimpleStringDictionaryEntryWriter* entry) { + DCHECK_GE(state(), kStateMutable); + + entries_[entry->Key()] = entry; +} + +bool MinidumpSimpleStringDictionaryWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + size_t entry_count = entries_.size(); + if (!AssignIfInRange(&simple_string_dictionary_base_.count, entry_count)) { + LOG(ERROR) << "entry_count " << entry_count << " out of range"; + return false; + } + + return true; +} + +size_t MinidumpSimpleStringDictionaryWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + return sizeof(simple_string_dictionary_base_) + + entries_.size() * sizeof(MinidumpSimpleStringDictionaryEntry); +} + +std::vector +MinidumpSimpleStringDictionaryWriter::Children() { + DCHECK_GE(state(), kStateMutable); + + std::vector children; + for (const auto& key_entry : entries_) { + children.push_back(key_entry.second); + } + + return children; +} + +bool MinidumpSimpleStringDictionaryWriter::WriteObject( + FileWriterInterface* file_writer) { + DCHECK_GE(state(), kStateWritable); + + WritableIoVec iov; + iov.iov_base = &simple_string_dictionary_base_; + iov.iov_len = sizeof(simple_string_dictionary_base_); + std::vector iovecs(1, iov); + + if (!entries_.empty()) { + iov.iov_len = sizeof(MinidumpSimpleStringDictionaryEntry); + for (const auto& key_entry : entries_) { + iov.iov_base = key_entry.second->MinidumpSimpleStringDictionaryEntry(); + iovecs.push_back(iov); + } + } + + return file_writer->WriteIoVec(&iovecs); +} + +} // namespace crashpad diff --git a/minidump/minidump_simple_string_dictionary_writer.h b/minidump/minidump_simple_string_dictionary_writer.h new file mode 100644 index 00000000..620444a1 --- /dev/null +++ b/minidump/minidump_simple_string_dictionary_writer.h @@ -0,0 +1,125 @@ +// Copyright 2014 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_SIMPLE_STRING_DICTIONARY_WRITER_H_ +#define CRASHPAD_MINIDUMP_MINIDUMP_SIMPLE_STRING_DICTIONARY_WRITER_H_ + +#include + +#include +#include +#include + +#include "base/basictypes.h" +#include "minidump/minidump_extensions.h" +#include "minidump/minidump_string_writer.h" +#include "minidump/minidump_writable.h" +#include "util/file/file_writer.h" + +namespace crashpad { + +//! \brief The writer for a MinidumpSimpleStringDictionaryEntry object in a +//! minidump file. +//! +//! Because MinidumpSimpleStringDictionaryEntry objects only appear as elements +//! of MinidumpSimpleStringDictionary objects, this class does not write any +//! data on its own. It makes its MinidumpSimpleStringDictionaryEntry data +//! available to its MinidumpSimpleStringDictionaryWriter parent, which writes +//! it as part of a MinidumpSimpleStringDictionary. +class MinidumpSimpleStringDictionaryEntryWriter final + : public internal::MinidumpWritable { + public: + MinidumpSimpleStringDictionaryEntryWriter(); + ~MinidumpSimpleStringDictionaryEntryWriter(); + + //! \brief Returns a MinidumpSimpleStringDictionaryEntry referencing this + //! object’s data. + //! + //! This method is expected to be called by a + //! MinidumpSimpleStringDictionaryWriter in order to obtain a + //! MinidumpSimpleStringDictionaryEntry to include in its list. + //! + //! \note Valid in #kStateWritable. + const MinidumpSimpleStringDictionaryEntry* + MinidumpSimpleStringDictionaryEntry() const; + + //! \brief Sets the strings to be written as the entry object’s key and value. + //! + //! \note Valid in #kStateMutable. + void SetKeyValue(const std::string& key, const std::string& value); + + //! \brief Retrieves the key to be written. + //! + //! \note Valid in any state. + const std::string& Key() const { return key_.UTF8(); } + + protected: + // MinidumpWritable: + + bool Freeze() override; + size_t SizeOfObject() override; + std::vector Children() override; + bool WriteObject(FileWriterInterface* file_writer) override; + + private: + struct MinidumpSimpleStringDictionaryEntry entry_; + internal::MinidumpUTF8StringWriter key_; + internal::MinidumpUTF8StringWriter value_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpSimpleStringDictionaryEntryWriter); +}; + +//! \brief The writer for a MinidumpSimpleStringDictionary object in a minidump +//! file, containing a list of MinidumpSimpleStringDictionaryEntry objects. +//! +//! Because this class writes a representatin of a dictionary, the order of +//! entries is insignificant. Entries may be written in any order. +class MinidumpSimpleStringDictionaryWriter final + : public internal::MinidumpWritable { + public: + MinidumpSimpleStringDictionaryWriter(); + ~MinidumpSimpleStringDictionaryWriter(); + + //! \brief Adds a MinidumpSimpleStringDictionaryEntryWriter to the + //! MinidumpSimpleStringDictionary. + //! + //! \a entry will become a child of this object in the overall tree of + //! internal::MinidumpWritable objects. + //! + //! If the key contained in \a entry duplicates the key of an entry already + //! present in the MinidumpSimpleStringDictionary, the new \a entry will + //! replace the previous one. + //! + //! \note Valid in #kStateMutable. + void AddEntry(MinidumpSimpleStringDictionaryEntryWriter* entry); + + protected: + // MinidumpWritable: + + bool Freeze() override; + size_t SizeOfObject() override; + std::vector Children() override; + bool WriteObject(FileWriterInterface* file_writer) override; + + private: + MinidumpSimpleStringDictionary simple_string_dictionary_base_; + std::map + entries_; // weak + + DISALLOW_COPY_AND_ASSIGN(MinidumpSimpleStringDictionaryWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_MINIDUMP_MINIDUMP_SIMPLE_STRING_DICTIONARY_WRITER_H_ diff --git a/minidump/minidump_simple_string_dictionary_writer_test.cc b/minidump/minidump_simple_string_dictionary_writer_test.cc new file mode 100644 index 00000000..16de38ea --- /dev/null +++ b/minidump/minidump_simple_string_dictionary_writer_test.cc @@ -0,0 +1,224 @@ +// Copyright 2014 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_simple_string_dictionary_writer.h" + +#include + +#include "gtest/gtest.h" +#include "minidump/minidump_extensions.h" +#include "util/file/string_file_writer.h" + +namespace crashpad { +namespace test { +namespace { + +const MinidumpSimpleStringDictionary* MinidumpSimpleStringDictionaryCast( + const StringFileWriter& file_writer) { + return reinterpret_cast( + &file_writer.string()[0]); +} + +TEST(MinidumpSimpleStringDictionaryWriter, EmptySimpleStringDictionary) { + StringFileWriter file_writer; + + MinidumpSimpleStringDictionaryWriter dictionary_writer; + + EXPECT_TRUE(dictionary_writer.WriteEverything(&file_writer)); + ASSERT_EQ(sizeof(MinidumpSimpleStringDictionary), + file_writer.string().size()); + + const MinidumpSimpleStringDictionary* dictionary = + MinidumpSimpleStringDictionaryCast(file_writer); + EXPECT_EQ(0u, dictionary->count); +} + +std::string MinidumpUTF8StringAtRVA(const StringFileWriter& file_writer, + RVA rva) { + const std::string& contents = file_writer.string(); + if (rva == 0) { + return std::string(); + } + + if (rva + sizeof(MinidumpUTF8String) > contents.size()) { + ADD_FAILURE() + << "rva " << rva << " too large for contents " << contents.size(); + return std::string(); + } + + const MinidumpUTF8String* minidump_string = + reinterpret_cast(&contents[rva]); + + // Verify that the file has enough data for the string’s stated length plus + // its required NUL terminator. + if (rva + sizeof(MinidumpUTF8String) + minidump_string->Length + 1 > + contents.size()) { + ADD_FAILURE() + << "rva " << rva << ", length " << minidump_string->Length + << " too large for contents " << contents.size(); + return std::string(); + } + + std::string minidump_string_data( + reinterpret_cast(&minidump_string->Buffer[0]), + minidump_string->Length); + return minidump_string_data; +} + +TEST(MinidumpSimpleStringDictionaryWriter, EmptyKeyValue) { + StringFileWriter file_writer; + + MinidumpSimpleStringDictionaryWriter dictionary_writer; + MinidumpSimpleStringDictionaryEntryWriter entry_writer; + dictionary_writer.AddEntry(&entry_writer); + + EXPECT_TRUE(dictionary_writer.WriteEverything(&file_writer)); + ASSERT_EQ(sizeof(MinidumpSimpleStringDictionary) + + sizeof(MinidumpSimpleStringDictionaryEntry) + + 2 * sizeof(MinidumpUTF8String) + 1 + 3 + 1, // 3 for padding + file_writer.string().size()); + + const MinidumpSimpleStringDictionary* dictionary = + MinidumpSimpleStringDictionaryCast(file_writer); + EXPECT_EQ(1u, dictionary->count); + EXPECT_EQ(12u, dictionary->entries[0].key); + EXPECT_EQ(20u, dictionary->entries[0].value); + EXPECT_EQ("", + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[0].key)); + EXPECT_EQ("", + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[0].value)); +} + +TEST(MinidumpSimpleStringDictionaryWriter, OneKeyValue) { + StringFileWriter file_writer; + + char kKey[] = "key"; + char kValue[] = "value"; + + MinidumpSimpleStringDictionaryWriter dictionary_writer; + MinidumpSimpleStringDictionaryEntryWriter entry_writer; + entry_writer.SetKeyValue(kKey, kValue); + dictionary_writer.AddEntry(&entry_writer); + + EXPECT_TRUE(dictionary_writer.WriteEverything(&file_writer)); + ASSERT_EQ(sizeof(MinidumpSimpleStringDictionary) + + sizeof(MinidumpSimpleStringDictionaryEntry) + + 2 * sizeof(MinidumpUTF8String) + sizeof(kKey) + sizeof(kValue), + file_writer.string().size()); + + const MinidumpSimpleStringDictionary* dictionary = + MinidumpSimpleStringDictionaryCast(file_writer); + EXPECT_EQ(1u, dictionary->count); + EXPECT_EQ(12u, dictionary->entries[0].key); + EXPECT_EQ(20u, dictionary->entries[0].value); + EXPECT_EQ(kKey, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[0].key)); + EXPECT_EQ(kValue, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[0].value)); +} + +TEST(MinidumpSimpleStringDictionaryWriter, ThreeKeysValues) { + StringFileWriter file_writer; + + char kKey0[] = "m0"; + char kValue0[] = "value0"; + char kKey1[] = "zzz1"; + char kValue1[] = "v1"; + char kKey2[] = "aa2"; + char kValue2[] = "val2"; + + MinidumpSimpleStringDictionaryWriter dictionary_writer; + MinidumpSimpleStringDictionaryEntryWriter entry_writer_0; + entry_writer_0.SetKeyValue(kKey0, kValue0); + dictionary_writer.AddEntry(&entry_writer_0); + MinidumpSimpleStringDictionaryEntryWriter entry_writer_1; + entry_writer_1.SetKeyValue(kKey1, kValue1); + dictionary_writer.AddEntry(&entry_writer_1); + MinidumpSimpleStringDictionaryEntryWriter entry_writer_2; + entry_writer_2.SetKeyValue(kKey2, kValue2); + dictionary_writer.AddEntry(&entry_writer_2); + + EXPECT_TRUE(dictionary_writer.WriteEverything(&file_writer)); + ASSERT_EQ(sizeof(MinidumpSimpleStringDictionary) + + 3 * sizeof(MinidumpSimpleStringDictionaryEntry) + + 6 * sizeof(MinidumpUTF8String) + sizeof(kKey2) + + sizeof(kValue2) + 3 + sizeof(kKey0) + 1 + sizeof(kValue0) + 1 + + sizeof(kKey1) + 3 + sizeof(kValue1), + file_writer.string().size()); + + const MinidumpSimpleStringDictionary* dictionary = + MinidumpSimpleStringDictionaryCast(file_writer); + EXPECT_EQ(3u, dictionary->count); + EXPECT_EQ(28u, dictionary->entries[0].key); + EXPECT_EQ(36u, dictionary->entries[0].value); + EXPECT_EQ(48u, dictionary->entries[1].key); + EXPECT_EQ(56u, dictionary->entries[1].value); + EXPECT_EQ(68u, dictionary->entries[2].key); + EXPECT_EQ(80u, dictionary->entries[2].value); + + // The entries don’t appear in the order they were added. The current + // implementation uses a std::map and sorts keys, so the entires appear in + // alphabetical order. However, this is an implementation detail, and it’s OK + // if the writer stops sorting in this order. Testing for a specific order is + // just the easiest way to write this test while the writer will output things + // in a known order. + EXPECT_EQ(kKey2, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[0].key)); + EXPECT_EQ(kValue2, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[0].value)); + EXPECT_EQ(kKey0, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[1].key)); + EXPECT_EQ(kValue0, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[1].value)); + EXPECT_EQ(kKey1, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[2].key)); + EXPECT_EQ(kValue1, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[2].value)); +} + +TEST(MinidumpSimpleStringDictionaryWriter, DuplicateKeyValue) { + StringFileWriter file_writer; + + char kKey[] = "key"; + char kValue0[] = "fake_value"; + char kValue1[] = "value"; + + MinidumpSimpleStringDictionaryWriter dictionary_writer; + MinidumpSimpleStringDictionaryEntryWriter entry_writer_0; + entry_writer_0.SetKeyValue(kKey, kValue0); + dictionary_writer.AddEntry(&entry_writer_0); + MinidumpSimpleStringDictionaryEntryWriter entry_writer_1; + entry_writer_1.SetKeyValue(kKey, kValue1); + dictionary_writer.AddEntry(&entry_writer_1); + + EXPECT_TRUE(dictionary_writer.WriteEverything(&file_writer)); + ASSERT_EQ(sizeof(MinidumpSimpleStringDictionary) + + sizeof(MinidumpSimpleStringDictionaryEntry) + + 2 * sizeof(MinidumpUTF8String) + sizeof(kKey) + sizeof(kValue1), + file_writer.string().size()); + + const MinidumpSimpleStringDictionary* dictionary = + MinidumpSimpleStringDictionaryCast(file_writer); + EXPECT_EQ(1u, dictionary->count); + EXPECT_EQ(12u, dictionary->entries[0].key); + EXPECT_EQ(20u, dictionary->entries[0].value); + EXPECT_EQ(kKey, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[0].key)); + EXPECT_EQ(kValue1, + MinidumpUTF8StringAtRVA(file_writer, dictionary->entries[0].value)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/minidump/minidump_string_writer.h b/minidump/minidump_string_writer.h index 237d0d36..f6c76961 100644 --- a/minidump/minidump_string_writer.h +++ b/minidump/minidump_string_writer.h @@ -69,6 +69,11 @@ class MinidumpStringWriter : public MinidumpWritable { //! \note Valid in #kStateMutable. void set_string(const StringType& string) { string_.assign(string); } + //! \brief Retrieves the string to be written. + //! + //! \note Valid in any state. + const StringType& string() const { return string_; } + private: MinidumpStringType string_base_; StringType string_; @@ -113,6 +118,11 @@ class MinidumpUTF8StringWriter final //! \note Valid in #kStateMutable. void SetUTF8(const std::string& string_utf8) { set_string(string_utf8); } + //! \brief Retrieves the string to be written. + //! + //! \note Valid in any state. + const std::string& UTF8() const { return string(); } + private: DISALLOW_COPY_AND_ASSIGN(MinidumpUTF8StringWriter); }; diff --git a/minidump/minidump_string_writer_test.cc b/minidump/minidump_string_writer_test.cc index c4eccd2d..0bd17717 100644 --- a/minidump/minidump_string_writer_test.cc +++ b/minidump/minidump_string_writer_test.cc @@ -181,8 +181,9 @@ TEST(MinidumpStringWriter, MinidumpUTF8StringWriter) { file_writer.Reset(); crashpad::internal::MinidumpUTF8StringWriter string_writer; - string_writer.SetUTF8( - std::string(kTestData[index].string, kTestData[index].length)); + std::string test_string(kTestData[index].string, kTestData[index].length); + string_writer.SetUTF8(test_string); + EXPECT_EQ(test_string, string_writer.UTF8()); EXPECT_TRUE(string_writer.WriteEverything(&file_writer)); const size_t expected_utf8_bytes_with_nul = kTestData[index].length + 1;