diff --git a/minidump/minidump.gyp b/minidump/minidump.gyp index d3dcde94..9dbfd56f 100644 --- a/minidump/minidump.gyp +++ b/minidump/minidump.gyp @@ -29,6 +29,7 @@ '..', ], 'sources': [ + 'minidump_extensions.cc', 'minidump_extensions.h', 'minidump_file_writer.cc', 'minidump_file_writer.h', @@ -36,6 +37,8 @@ 'minidump_memory_writer.h', 'minidump_misc_info_writer.cc', 'minidump_misc_info_writer.h', + 'minidump_module_writer.cc', + 'minidump_module_writer.h', 'minidump_stream_writer.cc', 'minidump_stream_writer.h', 'minidump_string_writer.cc', @@ -64,6 +67,7 @@ 'minidump_file_writer_test.cc', 'minidump_memory_writer_test.cc', 'minidump_misc_info_writer_test.cc', + 'minidump_module_writer_test.cc', 'minidump_string_writer_test.cc', 'minidump_system_info_writer_test.cc', 'minidump_test_util.cc', diff --git a/minidump/minidump_extensions.cc b/minidump/minidump_extensions.cc new file mode 100644 index 00000000..39b210dc --- /dev/null +++ b/minidump/minidump_extensions.cc @@ -0,0 +1,22 @@ +// 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_extensions.h" + +namespace crashpad { + +const uint32_t MinidumpModuleCodeViewRecordPDB20::kSignature; +const uint32_t MinidumpModuleCodeViewRecordPDB70::kSignature; + +} // namespace crashpad diff --git a/minidump/minidump_memory_writer_test.cc b/minidump/minidump_memory_writer_test.cc index 3015bbe6..f275d8ad 100644 --- a/minidump/minidump_memory_writer_test.cc +++ b/minidump/minidump_memory_writer_test.cc @@ -19,6 +19,7 @@ #include "base/basictypes.h" #include "gtest/gtest.h" +#include "minidump/minidump_extensions.h" #include "minidump/minidump_file_writer.h" #include "minidump/minidump_stream_writer.h" #include "minidump/minidump_test_util.h" @@ -248,27 +249,36 @@ TEST(MinidumpMemoryWriter, TwoMemoryRegions) { EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges); MINIDUMP_MEMORY_DESCRIPTOR expected; - expected.StartOfMemoryRange = kBaseAddress1; - expected.Memory.DataSize = kSize1; - expected.Memory.Rva = - sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + - sizeof(MINIDUMP_MEMORY_LIST) + - memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); - ExpectMemoryDescriptorAndContents(&expected, - &memory_list->MemoryRanges[0], - file_writer.string(), - kValue1, - false); - expected.StartOfMemoryRange = kBaseAddress2; - expected.Memory.DataSize = kSize2; - expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva + - memory_list->MemoryRanges[0].Memory.DataSize; - ExpectMemoryDescriptorAndContents(&expected, - &memory_list->MemoryRanges[1], - file_writer.string(), - kValue2, - true); + { + SCOPED_TRACE("region 0"); + + expected.StartOfMemoryRange = kBaseAddress1; + expected.Memory.DataSize = kSize1; + expected.Memory.Rva = + sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MEMORY_LIST) + + memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); + ExpectMemoryDescriptorAndContents(&expected, + &memory_list->MemoryRanges[0], + file_writer.string(), + kValue1, + false); + } + + { + SCOPED_TRACE("region 1"); + + expected.StartOfMemoryRange = kBaseAddress2; + expected.Memory.DataSize = kSize2; + expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva + + memory_list->MemoryRanges[0].Memory.DataSize; + ExpectMemoryDescriptorAndContents(&expected, + &memory_list->MemoryRanges[1], + file_writer.string(), + kValue2, + true); + } } class TestMemoryStream final : public internal::MinidumpStreamWriter { @@ -346,27 +356,36 @@ TEST(MinidumpMemoryWriter, ExtraMemory) { EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges); MINIDUMP_MEMORY_DESCRIPTOR expected; - expected.StartOfMemoryRange = kBaseAddress1; - expected.Memory.DataSize = kSize1; - expected.Memory.Rva = - sizeof(MINIDUMP_HEADER) + 2 * sizeof(MINIDUMP_DIRECTORY) + - sizeof(MINIDUMP_MEMORY_LIST) + - memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); - ExpectMemoryDescriptorAndContents(&expected, - &memory_list->MemoryRanges[0], - file_writer.string(), - kValue1, - false); - expected.StartOfMemoryRange = kBaseAddress2; - expected.Memory.DataSize = kSize2; - expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva + - memory_list->MemoryRanges[0].Memory.DataSize; - ExpectMemoryDescriptorAndContents(&expected, - &memory_list->MemoryRanges[1], - file_writer.string(), - kValue2, - true); + { + SCOPED_TRACE("region 0"); + + expected.StartOfMemoryRange = kBaseAddress1; + expected.Memory.DataSize = kSize1; + expected.Memory.Rva = + sizeof(MINIDUMP_HEADER) + 2 * sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MEMORY_LIST) + + memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); + ExpectMemoryDescriptorAndContents(&expected, + &memory_list->MemoryRanges[0], + file_writer.string(), + kValue1, + false); + } + + { + SCOPED_TRACE("region 1"); + + expected.StartOfMemoryRange = kBaseAddress2; + expected.Memory.DataSize = kSize2; + expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva + + memory_list->MemoryRanges[0].Memory.DataSize; + ExpectMemoryDescriptorAndContents(&expected, + &memory_list->MemoryRanges[1], + file_writer.string(), + kValue2, + true); + } } } // namespace diff --git a/minidump/minidump_module_writer.cc b/minidump/minidump_module_writer.cc new file mode 100644 index 00000000..66ad8d5a --- /dev/null +++ b/minidump/minidump_module_writer.cc @@ -0,0 +1,375 @@ +// 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_module_writer.h" + +#include "base/logging.h" +#include "minidump/minidump_string_writer.h" +#include "minidump/minidump_writer_util.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +MinidumpModuleCodeViewRecordWriter::~MinidumpModuleCodeViewRecordWriter() { +} + +namespace internal { + +template +MinidumpModuleCodeViewRecordPDBLinkWriter< + CodeViewRecordType>::MinidumpModuleCodeViewRecordPDBLinkWriter() + : MinidumpModuleCodeViewRecordWriter(), codeview_record_(), pdb_name_() { + codeview_record_.signature = CodeViewRecordType::kSignature; +} + +template +MinidumpModuleCodeViewRecordPDBLinkWriter< + CodeViewRecordType>::~MinidumpModuleCodeViewRecordPDBLinkWriter() { +} + +template +size_t +MinidumpModuleCodeViewRecordPDBLinkWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + // NUL-terminate. + return offsetof(typeof(codeview_record_), pdb_name) + + (pdb_name_.size() + 1) * sizeof(pdb_name_[0]); +} + +template +bool MinidumpModuleCodeViewRecordPDBLinkWriter::WriteObject( + FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + WritableIoVec iov; + iov.iov_base = &codeview_record_; + iov.iov_len = offsetof(typeof(codeview_record_), pdb_name); + std::vector iovecs(1, iov); + + // NUL-terminate. + iov.iov_base = &pdb_name_[0]; + iov.iov_len = (pdb_name_.size() + 1) * sizeof(pdb_name_[0]); + iovecs.push_back(iov); + + return file_writer->WriteIoVec(&iovecs); +} + +} // namespace internal + +template class internal::MinidumpModuleCodeViewRecordPDBLinkWriter< + MinidumpModuleCodeViewRecordPDB20>; + +MinidumpModuleCodeViewRecordPDB20Writer:: + ~MinidumpModuleCodeViewRecordPDB20Writer() { +} + +void MinidumpModuleCodeViewRecordPDB20Writer::SetTimestampAndAge( + time_t timestamp, + uint32_t age) { + DCHECK_EQ(state(), kStateMutable); + + internal::MinidumpWriterUtil::AssignTimeT(&codeview_record()->timestamp, + timestamp); + + codeview_record()->age = age; +} + +template class internal::MinidumpModuleCodeViewRecordPDBLinkWriter< + MinidumpModuleCodeViewRecordPDB70>; + +MinidumpModuleCodeViewRecordPDB70Writer:: + ~MinidumpModuleCodeViewRecordPDB70Writer() { +} + +MinidumpModuleMiscDebugRecordWriter::MinidumpModuleMiscDebugRecordWriter() + : internal::MinidumpWritable(), + image_debug_misc_(), + data_(), + data_utf16_() { +} + +void MinidumpModuleMiscDebugRecordWriter::SetData(const std::string& data, + bool utf16) { + DCHECK_EQ(state(), kStateMutable); + + if (!utf16) { + data_utf16_.clear(); + image_debug_misc_.Unicode = 0; + data_ = data; + } else { + data_.clear(); + image_debug_misc_.Unicode = 1; + data_utf16_ = internal::MinidumpWriterUtil::ConvertUTF8ToUTF16(data); + } +} + +bool MinidumpModuleMiscDebugRecordWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + // NUL-terminate. + if (!image_debug_misc_.Unicode) { + DCHECK(data_utf16_.empty()); + image_debug_misc_.Length = offsetof(typeof(image_debug_misc_), Data) + + (data_.size() + 1) * sizeof(data_[0]); + } else { + DCHECK(data_.empty()); + image_debug_misc_.Length = + offsetof(typeof(image_debug_misc_), Data) + + (data_utf16_.size() + 1) * sizeof(data_utf16_[0]); + } + + return true; +} + +size_t MinidumpModuleMiscDebugRecordWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + return image_debug_misc_.Length; +} + +bool MinidumpModuleMiscDebugRecordWriter::WriteObject( + FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + const size_t base_length = offsetof(typeof(image_debug_misc_), Data); + + WritableIoVec iov; + iov.iov_base = &image_debug_misc_; + iov.iov_len = base_length; + std::vector iovecs(1, iov); + + iov.iov_len = image_debug_misc_.Length - base_length; + if (!image_debug_misc_.Unicode) { + DCHECK(data_utf16_.empty()); + iov.iov_base = &data_[0]; + } else { + DCHECK(data_.empty()); + iov.iov_base = &data_utf16_[0]; + } + iovecs.push_back(iov); + + return file_writer->WriteIoVec(&iovecs); +} + +MinidumpModuleWriter::MinidumpModuleWriter() + : MinidumpWritable(), + module_(), + name_(), + codeview_record_(NULL), + misc_debug_record_(NULL) { + module_.VersionInfo.dwSignature = VS_FFI_SIGNATURE; + module_.VersionInfo.dwStrucVersion = VS_FFI_STRUCVERSION; +} + +MinidumpModuleWriter::~MinidumpModuleWriter() { +} + +const MINIDUMP_MODULE* MinidumpModuleWriter::MinidumpModule() const { + DCHECK_EQ(state(), kStateWritable); + + return &module_; +} + +void MinidumpModuleWriter::SetName(const std::string& name) { + DCHECK_EQ(state(), kStateMutable); + + if (!name_) { + name_.reset(new internal::MinidumpUTF16StringWriter()); + } + name_->SetUTF8(name); +} + +void MinidumpModuleWriter::SetCodeViewRecord( + MinidumpModuleCodeViewRecordWriter* codeview_record) { + DCHECK_EQ(state(), kStateMutable); + + codeview_record_ = codeview_record; +} + +void MinidumpModuleWriter::SetMiscDebugRecord( + MinidumpModuleMiscDebugRecordWriter* misc_debug_record) { + DCHECK_EQ(state(), kStateMutable); + + misc_debug_record_ = misc_debug_record; +} + +void MinidumpModuleWriter::SetTimestamp(time_t timestamp) { + DCHECK_EQ(state(), kStateMutable); + + internal::MinidumpWriterUtil::AssignTimeT(&module_.TimeDateStamp, timestamp); +} + +void MinidumpModuleWriter::SetFileVersion(uint16_t version_0, + uint16_t version_1, + uint16_t version_2, + uint16_t version_3) { + DCHECK_EQ(state(), kStateMutable); + + module_.VersionInfo.dwFileVersionMS = + (static_cast(version_0) << 16) | version_1; + module_.VersionInfo.dwFileVersionLS = + (static_cast(version_2) << 16) | version_3; +} + +void MinidumpModuleWriter::SetProductVersion(uint16_t version_0, + uint16_t version_1, + uint16_t version_2, + uint16_t version_3) { + DCHECK_EQ(state(), kStateMutable); + + module_.VersionInfo.dwProductVersionMS = + (static_cast(version_0) << 16) | version_1; + module_.VersionInfo.dwProductVersionLS = + (static_cast(version_2) << 16) | version_3; +} + +void MinidumpModuleWriter::SetFileFlagsAndMask(uint32_t file_flags, + uint32_t file_flags_mask) { + DCHECK_EQ(state(), kStateMutable); + DCHECK_EQ(file_flags & ~file_flags_mask, 0u); + + module_.VersionInfo.dwFileFlags = file_flags; + module_.VersionInfo.dwFileFlagsMask = file_flags_mask; +} + +bool MinidumpModuleWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + CHECK(name_); + + name_->RegisterRVA(&module_.ModuleNameRva); + + if (codeview_record_) { + codeview_record_->RegisterLocationDescriptor(&module_.CvRecord); + } + + if (misc_debug_record_) { + misc_debug_record_->RegisterLocationDescriptor(&module_.MiscRecord); + } + + return true; +} + +size_t MinidumpModuleWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + // This object doesn’t directly write anything itself. Its MINIDUMP_MODULE is + // written by its parent as part of a MINIDUMP_MODULE_LIST, and its children + // are responsible for writing themselves. + return 0; +} + +std::vector MinidumpModuleWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + + std::vector children; + if (name_) { + children.push_back(name_.get()); + } + if (codeview_record_) { + children.push_back(codeview_record_); + } + if (misc_debug_record_) { + children.push_back(misc_debug_record_); + } + + return children; +} + +bool MinidumpModuleWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + // This object doesn’t directly write anything itself. Its MINIDUMP_MODULE is + // written by its parent as part of a MINIDUMP_MODULE_LIST, and its children + // are responsible for writing themselves. + return true; +} + +MinidumpModuleListWriter::MinidumpModuleListWriter() + : MinidumpStreamWriter(), module_list_base_(), modules_() { +} + +MinidumpModuleListWriter::~MinidumpModuleListWriter() { +} + +void MinidumpModuleListWriter::AddModule(MinidumpModuleWriter* module) { + DCHECK_EQ(state(), kStateMutable); + + modules_.push_back(module); +} + +bool MinidumpModuleListWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpStreamWriter::Freeze()) { + return false; + } + + size_t module_count = modules_.size(); + if (!AssignIfInRange(&module_list_base_.NumberOfModules, module_count)) { + LOG(ERROR) << "module_count " << module_count << " out of range"; + return false; + } + + return true; +} + +size_t MinidumpModuleListWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + return sizeof(module_list_base_) + modules_.size() * sizeof(MINIDUMP_MODULE); +} + +std::vector MinidumpModuleListWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + + std::vector children; + for (MinidumpModuleWriter* module : modules_) { + children.push_back(module); + } + + return children; +} + +bool MinidumpModuleListWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + WritableIoVec iov; + iov.iov_base = &module_list_base_; + iov.iov_len = sizeof(module_list_base_); + std::vector iovecs(1, iov); + + for (const MinidumpModuleWriter* module : modules_) { + iov.iov_len = sizeof(MINIDUMP_MODULE); + iov.iov_base = module->MinidumpModule(); + iovecs.push_back(iov); + } + + return file_writer->WriteIoVec(&iovecs); +} + +MinidumpStreamType MinidumpModuleListWriter::StreamType() const { + return kMinidumpStreamTypeModuleList; +} + +} // namespace crashpad diff --git a/minidump/minidump_module_writer.h b/minidump/minidump_module_writer.h new file mode 100644 index 00000000..401a5bdc --- /dev/null +++ b/minidump/minidump_module_writer.h @@ -0,0 +1,324 @@ +// 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_MODULE_WRITER_H_ +#define CRASHPAD_MINIDUMP_MINIDUMP_MODULE_WRITER_H_ + +#include +#include +#include +#include + +#include +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "minidump/minidump_extensions.h" +#include "minidump/minidump_stream_writer.h" +#include "minidump/minidump_writable.h" +#include "util/file/file_writer.h" + +namespace crashpad { + +namespace internal { +class MinidumpUTF16StringWriter; +} // namespace internal + +//! \brief The base class for writers of CodeView records referenced by +//! MINIDUMP_MODULE::CvRecord in minidump files. +class MinidumpModuleCodeViewRecordWriter : public internal::MinidumpWritable { + public: + virtual ~MinidumpModuleCodeViewRecordWriter(); + + protected: + MinidumpModuleCodeViewRecordWriter() : MinidumpWritable() {} + + private: + DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCodeViewRecordWriter); +}; + +namespace internal { + +//! \brief The base class for writers of CodeView records that serve as links to +//! `.pdb` (program database) files. +template +class MinidumpModuleCodeViewRecordPDBLinkWriter + : public MinidumpModuleCodeViewRecordWriter { + public: + //! \brief Sets the name of the `.pdb` file being linked to. + void SetPDBName(const std::string& pdb_name) { pdb_name_ = pdb_name; } + + protected: + MinidumpModuleCodeViewRecordPDBLinkWriter(); + virtual ~MinidumpModuleCodeViewRecordPDBLinkWriter(); + + // MinidumpWritable: + virtual size_t SizeOfObject() override; + virtual bool WriteObject(FileWriterInterface* file_writer) override; + + //! \brief Returns a pointer to the raw CodeView record’s data. + //! + //! Subclasses can use this to set fields in their codeview records other than + //! the `pdb_name` field. + CodeViewRecordType* codeview_record() { return &codeview_record_; } + + private: + CodeViewRecordType codeview_record_; + std::string pdb_name_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCodeViewRecordPDBLinkWriter); +}; + +} // namespace internal + +//! \brief The writer for a MinidumpModuleCodeViewRecordPDB20 object in a +//! minidump file. +//! +//! Most users will want MinidumpModuleCodeViewRecordPDB70Writer instead. +class MinidumpModuleCodeViewRecordPDB20Writer final + : public internal::MinidumpModuleCodeViewRecordPDBLinkWriter< + MinidumpModuleCodeViewRecordPDB20> { + public: + MinidumpModuleCodeViewRecordPDB20Writer() + : internal::MinidumpModuleCodeViewRecordPDBLinkWriter< + MinidumpModuleCodeViewRecordPDB20>() {} + + virtual ~MinidumpModuleCodeViewRecordPDB20Writer(); + + //! \brief Sets MinidumpModuleCodeViewRecordPDB20::timestamp and + //! MinidumpModuleCodeViewRecordPDB20::age. + void SetTimestampAndAge(time_t timestamp, uint32_t age); + + private: + DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCodeViewRecordPDB20Writer); +}; + +//! \brief The writer for a MinidumpModuleCodeViewRecordPDB70 object in a +//! minidump file. +class MinidumpModuleCodeViewRecordPDB70Writer final + : public internal::MinidumpModuleCodeViewRecordPDBLinkWriter< + MinidumpModuleCodeViewRecordPDB70> { + public: + MinidumpModuleCodeViewRecordPDB70Writer() + : internal::MinidumpModuleCodeViewRecordPDBLinkWriter< + MinidumpModuleCodeViewRecordPDB70>() {} + + virtual ~MinidumpModuleCodeViewRecordPDB70Writer(); + + //! \brief Sets MinidumpModuleCodeViewRecordPDB70::uuid and + //! MinidumpModuleCodeViewRecordPDB70::age. + void SetUUIDAndAge(const UUID& uuid, uint32_t age) { + codeview_record()->uuid = uuid; + codeview_record()->age = age; + } + + private: + DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCodeViewRecordPDB70Writer); +}; + +//! \brief The writer for an IMAGE_DEBUG_MISC object in a minidump file. +//! +//! Most users will want MinidumpModuleCodeViewRecordPDB70Writer instead. +class MinidumpModuleMiscDebugRecordWriter final + : public internal::MinidumpWritable { + public: + MinidumpModuleMiscDebugRecordWriter(); + ~MinidumpModuleMiscDebugRecordWriter() {} + + //! \brief Sets IMAGE_DEBUG_MISC::DataType. + void SetDataType(uint32_t data_type) { + image_debug_misc_.DataType = data_type; + } + + //! \brief Sets IMAGE_DEBUG_MISC::Data, IMAGE_DEBUG_MISC::Length, and + //! IMAGE_DEBUG_MISC::Unicode. + //! + //! If \a utf16 is `true`, \a data will be treated as UTF-8 data and will be + //! converted to UTF-16, and IMAGE_DEBUG_MISC::Unicode will be set to `1`. + //! Otherwise, \a data will be used as-is and IMAGE_DEBUG_MISC::Unicode will + //! be set to `0`. + void SetData(const std::string& data, bool utf16); + + protected: + // MinidumpWritable: + virtual bool Freeze() override; + virtual size_t SizeOfObject() override; + virtual bool WriteObject(FileWriterInterface* file_writer) override; + + private: + IMAGE_DEBUG_MISC image_debug_misc_; + std::string data_; + string16 data_utf16_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpModuleMiscDebugRecordWriter); +}; + +//! \brief The writer for a MINIDUMP_MODULE object in a minidump file. +//! +//! Because MINIDUMP_MODULE objects only appear as elements of +//! MINIDUMP_MODULE_LIST objects, this class does not write any data on its own. +//! It makes its MINIDUMP_MODULE data available to its MinidumpModuleListWriter +//! parent, which writes it as part of a MINIDUMP_MODULE_LIST. +class MinidumpModuleWriter final : public internal::MinidumpWritable { + public: + MinidumpModuleWriter(); + ~MinidumpModuleWriter(); + + //! \brief Returns a MINIDUMP_MODULE referencing this object’s data. + //! + //! This method is expected to be called by a MinidumpModuleListWriter in + //! order to obtain a MINIDUMP_MODULE to include in its list. + //! + //! \note Valid in #kStateWritable. + const MINIDUMP_MODULE* MinidumpModule() const; + + //! \brief Arranges for MINIDUMP_MODULE::ModuleNameRva to point to a + //! MINIDUMP_STRING containing \a name. + //! + //! A name is required in all MINIDUMP_MODULE objects. + //! + //! \note Valid in #kStateMutable. + void SetName(const std::string& name); + + //! \brief Arranges for MINIDUMP_MODULE::CvRecord to point to a CodeView + //! record to be written by \a codeview_record. + //! + //! \a codeview_record will become a child of this object in the overall tree + //! of internal::MinidumpWritable objects. + //! + //! \note Valid in #kStateMutable. + void SetCodeViewRecord(MinidumpModuleCodeViewRecordWriter* codeview_record); + + //! \brief Arranges for MINIDUMP_MODULE::MiscRecord to point to an + //! IMAGE_DEBUG_MISC object to be written by \a misc_debug_record. + //! + //! \a misc_debug_record will become a child of this object in the overall + //! tree of internal::MinidumpWritable objects. + //! + //! \note Valid in #kStateMutable. + void SetMiscDebugRecord( + MinidumpModuleMiscDebugRecordWriter* misc_debug_record); + + //! \brief Sets IMAGE_DEBUG_MISC::BaseOfImage. + void SetImageBaseAddress(uint64_t image_base_address) { + module_.BaseOfImage = image_base_address; + } + + //! \brief Sets IMAGE_DEBUG_MISC::SizeOfImage. + void SetImageSize(uint32_t image_size) { module_.SizeOfImage = image_size; } + + //! \brief Sets IMAGE_DEBUG_MISC::CheckSum. + void SetChecksum(uint32_t checksum) { module_.CheckSum = checksum; } + + //! \brief Sets IMAGE_DEBUG_MISC::TimeDateStamp. + //! + //! \note Valid in #kStateMutable. + void SetTimestamp(time_t timestamp); + + //! \brief Sets \ref VS_FIXEDFILEINFO::dwFileVersionMS + //! "IMAGE_DEBUG_MISC::VersionInfo::dwFileVersionMS" and \ref + //! VS_FIXEDFILEINFO::dwFileVersionLS + //! "IMAGE_DEBUG_MISC::VersionInfo::dwFileVersionLS". + //! + //! \note Valid in #kStateMutable. + void SetFileVersion(uint16_t version_0, + uint16_t version_1, + uint16_t version_2, + uint16_t version_3); + + //! \brief Sets \ref VS_FIXEDFILEINFO::dwProductVersionMS + //! "IMAGE_DEBUG_MISC::VersionInfo::dwProductVersionMS" and \ref + //! VS_FIXEDFILEINFO::dwProductVersionLS + //! "IMAGE_DEBUG_MISC::VersionInfo::dwProductVersionLS". + //! + //! \note Valid in #kStateMutable. + void SetProductVersion(uint16_t version_0, + uint16_t version_1, + uint16_t version_2, + uint16_t version_3); + + //! \brief Sets \ref VS_FIXEDFILEINFO::dwFileFlags + //! "IMAGE_DEBUG_MISC::VersionInfo::dwFileFlags" and \ref + //! VS_FIXEDFILEINFO::dwFileFlagsMask + //! "IMAGE_DEBUG_MISC::VersionInfo::dwFileFlagsMask". + //! + //! \note Valid in #kStateMutable. + void SetFileFlagsAndMask(uint32_t file_flags, uint32_t file_flags_mask); + + //! \brief Sets \ref VS_FIXEDFILEINFO::dwFileOS + //! "IMAGE_DEBUG_MISC::VersionInfo::dwFileOS". + void SetFileOS(uint32_t file_os) { module_.VersionInfo.dwFileOS = file_os; } + + //! \brief Sets \ref VS_FIXEDFILEINFO::dwFileType + //! "IMAGE_DEBUG_MISC::VersionInfo::dwFileType" and \ref + //! VS_FIXEDFILEINFO::dwFileSubtype + //! "IMAGE_DEBUG_MISC::VersionInfo::dwFileSubtype". + void SetFileTypeAndSubtype(uint32_t file_type, uint32_t file_subtype) { + module_.VersionInfo.dwFileType = file_type; + module_.VersionInfo.dwFileSubtype = file_subtype; + } + + protected: + // MinidumpWritable: + virtual bool Freeze() override; + virtual size_t SizeOfObject() override; + virtual std::vector Children() override; + virtual bool WriteObject(FileWriterInterface* file_writer) override; + + private: + MINIDUMP_MODULE module_; + scoped_ptr name_; + MinidumpModuleCodeViewRecordWriter* codeview_record_; // weak + MinidumpModuleMiscDebugRecordWriter* misc_debug_record_; // weak + + DISALLOW_COPY_AND_ASSIGN(MinidumpModuleWriter); +}; + +//! \brief The writer for a MINIDUMP_MODULE_LIST stream in a minidump file, +//! containing a list of MINIDUMP_MODULE objects. +class MinidumpModuleListWriter final : public internal::MinidumpStreamWriter { + public: + MinidumpModuleListWriter(); + ~MinidumpModuleListWriter(); + + //! \brief Adds a MinidumpModuleWriter to the MINIDUMP_MODULE_LIST. + //! + //! \a module will become a child of this object in the overall tree of + //! internal::MinidumpWritable objects. + //! + //! \note Valid in #kStateMutable. + void AddModule(MinidumpModuleWriter* module); + + protected: + // MinidumpWritable: + virtual bool Freeze() override; + virtual size_t SizeOfObject() override; + virtual std::vector Children() override; + virtual bool WriteObject(FileWriterInterface* file_writer) override; + + // MinidumpStreamWriter: + virtual MinidumpStreamType StreamType() const override; + + private: + MINIDUMP_MODULE_LIST module_list_base_; + std::vector modules_; // weak + + DISALLOW_COPY_AND_ASSIGN(MinidumpModuleListWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_MINIDUMP_MINIDUMP_MODULE_WRITER_H_ diff --git a/minidump/minidump_module_writer_test.cc b/minidump/minidump_module_writer_test.cc new file mode 100644 index 00000000..5b47e017 --- /dev/null +++ b/minidump/minidump_module_writer_test.cc @@ -0,0 +1,673 @@ +// 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_module_writer.h" + +#include +#include +#include + +#include "base/strings/utf_string_conversions.h" +#include "gtest/gtest.h" +#include "minidump/minidump_extensions.h" +#include "minidump/minidump_file_writer.h" +#include "minidump/minidump_test_util.h" +#include "util/file/string_file_writer.h" +#include "util/misc/uuid.h" + +namespace { + +using namespace crashpad; +using namespace crashpad::test; + +void GetModuleListStream(const std::string& file_contents, + const MINIDUMP_MODULE_LIST** module_list) { + const size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER); + const size_t kModuleListStreamOffset = + kDirectoryOffset + sizeof(MINIDUMP_DIRECTORY); + const size_t kModulesOffset = + kModuleListStreamOffset + sizeof(MINIDUMP_MODULE_LIST); + + ASSERT_GE(file_contents.size(), kModulesOffset); + + const MINIDUMP_HEADER* header = + reinterpret_cast(&file_contents[0]); + + VerifyMinidumpHeader(header, 1, 0); + if (testing::Test::HasFatalFailure()) { + return; + } + + const MINIDUMP_DIRECTORY* directory = + reinterpret_cast( + &file_contents[kDirectoryOffset]); + + ASSERT_EQ(kMinidumpStreamTypeModuleList, directory->StreamType); + ASSERT_GE(directory->Location.DataSize, sizeof(MINIDUMP_MODULE_LIST)); + ASSERT_EQ(kModuleListStreamOffset, directory->Location.Rva); + + *module_list = reinterpret_cast( + &file_contents[kModuleListStreamOffset]); + + ASSERT_EQ(sizeof(MINIDUMP_MODULE_LIST) + + (*module_list)->NumberOfModules * sizeof(MINIDUMP_MODULE), + directory->Location.DataSize); +} + +TEST(MinidumpModuleWriter, EmptyModuleList) { + MinidumpFileWriter minidump_file_writer; + MinidumpModuleListWriter module_list_writer; + + minidump_file_writer.AddStream(&module_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_EQ(sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MODULE_LIST), + file_writer.string().size()); + + const MINIDUMP_MODULE_LIST* module_list; + GetModuleListStream(file_writer.string(), &module_list); + if (Test::HasFatalFailure()) { + return; + } + + EXPECT_EQ(0u, module_list->NumberOfModules); +} + +// If |expected_pdb_name| is non-NULL, |codeview_record| is used to locate a +// CodeView record in |file_contents|, and its fields are compared against the +// the |expected_pdb_*| values. If |expected_pdb_uuid| is supplied, the CodeView +// record must be a PDB 7.0 link, otherwise, it must be a PDB 2.0 link. If +// |expected_pdb_name| is NULL, |codeview_record| must not point to anything. +void ExpectCodeViewRecord(const MINIDUMP_LOCATION_DESCRIPTOR* codeview_record, + const std::string& file_contents, + const char* expected_pdb_name, + const UUID* expected_pdb_uuid, + time_t expected_pdb_timestamp, + uint32_t expected_pdb_age) { + if (expected_pdb_name) { + EXPECT_NE(0u, codeview_record->Rva); + ASSERT_LE(codeview_record->Rva + codeview_record->DataSize, + file_contents.size()); + + std::string observed_pdb_name; + if (expected_pdb_uuid) { + // The CodeView record should be a PDB 7.0 link. + EXPECT_GE(codeview_record->DataSize, + sizeof(MinidumpModuleCodeViewRecordPDB70)); + const MinidumpModuleCodeViewRecordPDB70* codeview_pdb70_record = + reinterpret_cast( + &file_contents[codeview_record->Rva]); + EXPECT_EQ(MinidumpModuleCodeViewRecordPDB70::kSignature, + codeview_pdb70_record->signature); + EXPECT_EQ(0, + memcmp(expected_pdb_uuid, + &codeview_pdb70_record->uuid, + sizeof(codeview_pdb70_record->uuid))); + EXPECT_EQ(expected_pdb_age, codeview_pdb70_record->age); + + observed_pdb_name.assign( + reinterpret_cast(&codeview_pdb70_record->pdb_name[0]), + codeview_record->DataSize - + offsetof(MinidumpModuleCodeViewRecordPDB70, pdb_name)); + } else { + // The CodeView record should be a PDB 2.0 link. + EXPECT_GE(codeview_record->DataSize, + sizeof(MinidumpModuleCodeViewRecordPDB20)); + const MinidumpModuleCodeViewRecordPDB20* codeview_pdb20_record = + reinterpret_cast( + &file_contents[codeview_record->Rva]); + EXPECT_EQ(MinidumpModuleCodeViewRecordPDB20::kSignature, + codeview_pdb20_record->signature); + EXPECT_EQ(expected_pdb_timestamp, codeview_pdb20_record->timestamp); + EXPECT_EQ(expected_pdb_age, codeview_pdb20_record->age); + + observed_pdb_name.assign( + reinterpret_cast(&codeview_pdb20_record->pdb_name[0]), + codeview_record->DataSize - + offsetof(MinidumpModuleCodeViewRecordPDB20, pdb_name)); + } + + // Check for, and then remove, the NUL terminator. + EXPECT_EQ('\0', observed_pdb_name[observed_pdb_name.size() - 1]); + observed_pdb_name.resize(observed_pdb_name.size() - 1); + + EXPECT_EQ(expected_pdb_name, observed_pdb_name); + } else { + // There should be no CodeView record. + EXPECT_EQ(0u, codeview_record->DataSize); + EXPECT_EQ(0u, codeview_record->Rva); + } +} + +// If |expected_debug_name| is non-NULL, |misc_record| is used to locate a +// miscellanous debugging record in |file_contents|, and its fields are compared +// against the the |expected_debug_*| values. If |expected_debug_name| is NULL, +// |misc_record| must not point to anything. +void ExpectMiscellaneousDebugRecord( + const MINIDUMP_LOCATION_DESCRIPTOR* misc_record, + const std::string& file_contents, + const char* expected_debug_name, + uint32_t expected_debug_type, + bool expected_debug_utf16) { + if (expected_debug_name) { + EXPECT_GE(misc_record->DataSize, sizeof(IMAGE_DEBUG_MISC)); + EXPECT_NE(0u, misc_record->Rva); + ASSERT_LE(misc_record->Rva + misc_record->DataSize, file_contents.size()); + const IMAGE_DEBUG_MISC* misc_debug_record = + reinterpret_cast( + &file_contents[misc_record->Rva]); + EXPECT_EQ(expected_debug_type, misc_debug_record->DataType); + EXPECT_EQ(misc_record->DataSize, misc_debug_record->Length); + EXPECT_EQ(expected_debug_utf16, misc_debug_record->Unicode); + EXPECT_EQ(0u, misc_debug_record->Reserved[0]); + EXPECT_EQ(0u, misc_debug_record->Reserved[1]); + EXPECT_EQ(0u, misc_debug_record->Reserved[2]); + + // Check for the NUL terminator. + size_t bytes_available = + misc_debug_record->Length - offsetof(IMAGE_DEBUG_MISC, Data); + EXPECT_EQ('\0', misc_debug_record->Data[bytes_available - 1]); + std::string observed_data( + reinterpret_cast(misc_debug_record->Data)); + + size_t bytes_used; + if (misc_debug_record->Unicode) { + string16 observed_data_utf16( + reinterpret_cast(misc_debug_record->Data)); + bytes_used = (observed_data_utf16.size() + 1) * sizeof(char16); + observed_data = base::UTF16ToUTF8(observed_data_utf16); + } else { + observed_data = reinterpret_cast(misc_debug_record->Data); + bytes_used = (observed_data.size() + 1) * sizeof(char); + } + EXPECT_LE(bytes_used, bytes_available); + + // Make sure that any padding bytes after the first NUL are also NUL. + for (size_t index = bytes_used; index < bytes_available; ++index) { + EXPECT_EQ('\0', misc_debug_record->Data[index]); + } + + EXPECT_EQ(expected_debug_name, observed_data); + } else { + // There should be no miscellaneous debugging record. + EXPECT_EQ(0u, misc_record->DataSize); + EXPECT_EQ(0u, misc_record->Rva); + } +} + +// ExpectModule() verifies that |expected| matches |observed|. Fields that are +// supposed to contain constant magic numbers are verified against the expected +// constants instead of |expected|. Reserved fields are verified to be 0. RVA +// and MINIDUMP_LOCATION_DESCRIPTOR fields are not verified against |expected|. +// Instead, |ModuleNameRva| is used to locate the module name, which is compared +// against |expected_module_name|. ExpectCodeViewRecord() and +// ExpectMiscellaneousDebugRecord() are used to verify the |CvRecord| and +// |MiscRecord| fields against |expected_pdb_*| and |expected_debug_*| +// parameters, respectively. +void ExpectModule(const MINIDUMP_MODULE* expected, + const MINIDUMP_MODULE* observed, + const std::string& file_contents, + const std::string& expected_module_name, + const char* expected_pdb_name, + const UUID* expected_pdb_uuid, + time_t expected_pdb_timestamp, + uint32_t expected_pdb_age, + const char* expected_debug_name, + uint32_t expected_debug_type, + bool expected_debug_utf16) { + EXPECT_EQ(expected->BaseOfImage, observed->BaseOfImage); + EXPECT_EQ(expected->SizeOfImage, observed->SizeOfImage); + EXPECT_EQ(expected->CheckSum, observed->CheckSum); + EXPECT_EQ(expected->TimeDateStamp, observed->TimeDateStamp); + EXPECT_EQ(static_cast(VS_FFI_SIGNATURE), + observed->VersionInfo.dwSignature); + EXPECT_EQ(static_cast(VS_FFI_STRUCVERSION), + observed->VersionInfo.dwStrucVersion); + EXPECT_EQ(expected->VersionInfo.dwFileVersionMS, + observed->VersionInfo.dwFileVersionMS); + EXPECT_EQ(expected->VersionInfo.dwFileVersionLS, + observed->VersionInfo.dwFileVersionLS); + EXPECT_EQ(expected->VersionInfo.dwProductVersionMS, + observed->VersionInfo.dwProductVersionMS); + EXPECT_EQ(expected->VersionInfo.dwProductVersionLS, + observed->VersionInfo.dwProductVersionLS); + EXPECT_EQ(expected->VersionInfo.dwFileFlagsMask, + observed->VersionInfo.dwFileFlagsMask); + EXPECT_EQ(expected->VersionInfo.dwFileFlags, + observed->VersionInfo.dwFileFlags); + EXPECT_EQ(expected->VersionInfo.dwFileOS, observed->VersionInfo.dwFileOS); + EXPECT_EQ(expected->VersionInfo.dwFileType, observed->VersionInfo.dwFileType); + EXPECT_EQ(expected->VersionInfo.dwFileSubtype, + observed->VersionInfo.dwFileSubtype); + EXPECT_EQ(expected->VersionInfo.dwFileDateMS, + observed->VersionInfo.dwFileDateMS); + EXPECT_EQ(expected->VersionInfo.dwFileDateLS, + observed->VersionInfo.dwFileDateLS); + EXPECT_EQ(0u, observed->Reserved0); + EXPECT_EQ(0u, observed->Reserved1); + + EXPECT_NE(0u, observed->ModuleNameRva); + ASSERT_LE(observed->ModuleNameRva, + file_contents.size() - sizeof(MINIDUMP_STRING)); + const MINIDUMP_STRING* module_name = reinterpret_cast( + &file_contents[observed->ModuleNameRva]); + ASSERT_LE(observed->ModuleNameRva + sizeof(MINIDUMP_STRING) + + (module_name->Length + 1), + file_contents.size()); + ASSERT_EQ(0u, module_name->Length % 2); + string16 observed_module_name_utf16( + reinterpret_cast( + &file_contents[observed->ModuleNameRva + sizeof(MINIDUMP_STRING)]), + module_name->Length / 2); + string16 expected_module_name_utf16 = base::UTF8ToUTF16(expected_module_name); + EXPECT_EQ(expected_module_name_utf16, observed_module_name_utf16); + + ExpectCodeViewRecord(&observed->CvRecord, + file_contents, + expected_pdb_name, + expected_pdb_uuid, + expected_pdb_timestamp, + expected_pdb_age); + if (testing::Test::HasFatalFailure()) { + return; + } + + ExpectMiscellaneousDebugRecord(&observed->MiscRecord, + file_contents, + expected_debug_name, + expected_debug_type, + expected_debug_utf16); + if (testing::Test::HasFatalFailure()) { + return; + } +} + +TEST(MinidumpModuleWriter, EmptyModule) { + MinidumpFileWriter minidump_file_writer; + MinidumpModuleListWriter module_list_writer; + + const char kModuleName[] = "test_executable"; + + MinidumpModuleWriter module_writer; + module_writer.SetName(kModuleName); + + module_list_writer.AddModule(&module_writer); + minidump_file_writer.AddStream(&module_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_GT(file_writer.string().size(), + sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); + + const MINIDUMP_MODULE_LIST* module_list; + GetModuleListStream(file_writer.string(), &module_list); + if (Test::HasFatalFailure()) { + return; + } + + EXPECT_EQ(1u, module_list->NumberOfModules); + + MINIDUMP_MODULE expected = {}; + ExpectModule(&expected, + &module_list->Modules[0], + file_writer.string(), + kModuleName, + NULL, + NULL, + 0, + 0, + NULL, + 0, + false); + if (Test::HasFatalFailure()) { + return; + } +} + +TEST(MinidumpModuleWriter, OneModule) { + MinidumpFileWriter minidump_file_writer; + MinidumpModuleListWriter module_list_writer; + + const char kModuleName[] = "statically_linked"; + const uint64_t kModuleBase = 0x10da69000; + const uint32_t kModuleSize = 0x1000; + const uint32_t kChecksum = 0x76543210; + const time_t kTimestamp = 0x386d4380; + const uint32_t kFileVersionMS = 0x00010002; + const uint32_t kFileVersionLS = 0x00030004; + const uint32_t kProductVersionMS = 0x00050006; + const uint32_t kProductVersionLS = 0x00070008; + const uint32_t kFileFlagsMask = VS_FF_DEBUG | VS_FF_PRERELEASE | + VS_FF_PATCHED | VS_FF_PRIVATEBUILD | + VS_FF_INFOINFERRED | VS_FF_SPECIALBUILD; + const uint32_t kFileFlags = VS_FF_PRIVATEBUILD | VS_FF_SPECIALBUILD; + const uint32_t kFileOS = VOS_DOS; + const uint32_t kFileType = VFT_DRV; + const uint32_t kFileSubtype = VFT2_DRV_KEYBOARD; + const char kPDBName[] = "statical.pdb"; + const uint8_t kPDBUUIDBytes[16] = + {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, + 0x08, 0x19, 0x2a, 0x3b, 0x4c, 0x5d, 0x6e, 0x7f}; + UUID pdb_uuid; + pdb_uuid.InitializeFromBytes(kPDBUUIDBytes); + const uint32_t kPDBAge = 1; + const uint32_t kDebugType = IMAGE_DEBUG_MISC_EXENAME; + const char kDebugName[] = "statical.dbg"; + const bool kDebugUTF16 = false; + + MinidumpModuleWriter module_writer; + module_writer.SetName(kModuleName); + module_writer.SetImageBaseAddress(kModuleBase); + module_writer.SetImageSize(kModuleSize); + module_writer.SetChecksum(kChecksum); + module_writer.SetTimestamp(kTimestamp); + module_writer.SetFileVersion(kFileVersionMS >> 16, + kFileVersionMS & 0xffff, + kFileVersionLS >> 16, + kFileVersionLS & 0xffff); + module_writer.SetProductVersion(kProductVersionMS >> 16, + kProductVersionMS & 0xffff, + kProductVersionLS >> 16, + kProductVersionLS & 0xffff); + module_writer.SetFileFlagsAndMask(kFileFlags, kFileFlagsMask); + module_writer.SetFileOS(kFileOS); + module_writer.SetFileTypeAndSubtype(kFileType, kFileSubtype); + + MinidumpModuleCodeViewRecordPDB70Writer codeview_pdb70_writer; + codeview_pdb70_writer.SetPDBName(kPDBName); + codeview_pdb70_writer.SetUUIDAndAge(pdb_uuid, kPDBAge); + module_writer.SetCodeViewRecord(&codeview_pdb70_writer); + + MinidumpModuleMiscDebugRecordWriter misc_debug_writer; + misc_debug_writer.SetDataType(kDebugType); + misc_debug_writer.SetData(kDebugName, kDebugUTF16); + module_writer.SetMiscDebugRecord(&misc_debug_writer); + + module_list_writer.AddModule(&module_writer); + minidump_file_writer.AddStream(&module_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_GT(file_writer.string().size(), + sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); + + const MINIDUMP_MODULE_LIST* module_list; + GetModuleListStream(file_writer.string(), &module_list); + if (Test::HasFatalFailure()) { + return; + } + + EXPECT_EQ(1u, module_list->NumberOfModules); + + MINIDUMP_MODULE expected = {}; + expected.BaseOfImage = kModuleBase; + expected.SizeOfImage = kModuleSize; + expected.CheckSum = kChecksum; + expected.TimeDateStamp = kTimestamp; + expected.VersionInfo.dwFileVersionMS = kFileVersionMS; + expected.VersionInfo.dwFileVersionLS = kFileVersionLS; + expected.VersionInfo.dwProductVersionMS = kProductVersionMS; + expected.VersionInfo.dwProductVersionLS = kProductVersionLS; + expected.VersionInfo.dwFileFlagsMask = kFileFlagsMask; + expected.VersionInfo.dwFileFlags = kFileFlags; + expected.VersionInfo.dwFileOS = kFileOS; + expected.VersionInfo.dwFileType = kFileType; + expected.VersionInfo.dwFileSubtype = kFileSubtype; + + ExpectModule(&expected, + &module_list->Modules[0], + file_writer.string(), + kModuleName, + kPDBName, + &pdb_uuid, + 0, + kPDBAge, + kDebugName, + kDebugType, + kDebugUTF16); + if (Test::HasFatalFailure()) { + return; + } +} + +TEST(MinidumpModuleWriter, OneModule_CodeViewUsesPDB20_MiscUsesUTF16) { + // MinidumpModuleWriter.OneModule tested with a PDB 7.0 link as the CodeView + // record and an IMAGE_DEBUG_MISC record in UTF-8. This test exercises the + // alternatives, a PDB 2.0 link as the CodeView record and an IMAGE_DEBUG_MISC + // record with UTF-16 data. + MinidumpFileWriter minidump_file_writer; + MinidumpModuleListWriter module_list_writer; + + const char kModuleName[] = "dinosaur"; + const char kPDBName[] = "d1n05.pdb"; + const time_t kPDBTimestamp = 0x386d4380; + const uint32_t kPDBAge = 1; + const uint32_t kDebugType = IMAGE_DEBUG_MISC_EXENAME; + const char kDebugName[] = "d1n05.dbg"; + const bool kDebugUTF16 = true; + + MinidumpModuleWriter module_writer; + module_writer.SetName(kModuleName); + + MinidumpModuleCodeViewRecordPDB20Writer codeview_pdb20_writer; + codeview_pdb20_writer.SetPDBName(kPDBName); + codeview_pdb20_writer.SetTimestampAndAge(kPDBTimestamp, kPDBAge); + module_writer.SetCodeViewRecord(&codeview_pdb20_writer); + + MinidumpModuleMiscDebugRecordWriter misc_debug_writer; + misc_debug_writer.SetDataType(kDebugType); + misc_debug_writer.SetData(kDebugName, kDebugUTF16); + module_writer.SetMiscDebugRecord(&misc_debug_writer); + + module_list_writer.AddModule(&module_writer); + minidump_file_writer.AddStream(&module_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_GT(file_writer.string().size(), + sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); + + const MINIDUMP_MODULE_LIST* module_list; + GetModuleListStream(file_writer.string(), &module_list); + if (Test::HasFatalFailure()) { + return; + } + + EXPECT_EQ(1u, module_list->NumberOfModules); + + MINIDUMP_MODULE expected = {}; + + ExpectModule(&expected, + &module_list->Modules[0], + file_writer.string(), + kModuleName, + kPDBName, + NULL, + kPDBTimestamp, + kPDBAge, + kDebugName, + kDebugType, + kDebugUTF16); + if (Test::HasFatalFailure()) { + return; + } +} + +TEST(MinidumpModuleWriter, ThreeModules) { + // As good exercise, this test uses three modules, one with a PDB 7.0 link as + // its CodeView record, one with no CodeView record, and one with a PDB 2.0 + // link as its CodeView record. + MinidumpFileWriter minidump_file_writer; + MinidumpModuleListWriter module_list_writer; + + const char kModuleName1[] = "main"; + const uint64_t kModuleBase1 = 0x100101000; + const uint32_t kModuleSize1 = 0xf000; + const char kPDBName1[] = "main"; + const uint8_t kPDBUUIDBytes1[16] = + {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}; + UUID pdb_uuid_1; + pdb_uuid_1.InitializeFromBytes(kPDBUUIDBytes1); + const uint32_t kPDBAge1 = 0; + + const char kModuleName2[] = "ld.so"; + const uint64_t kModuleBase2 = 0x200202000; + const uint32_t kModuleSize2 = 0x1e000; + + const char kModuleName3[] = "libc.so"; + const uint64_t kModuleBase3 = 0x300303000; + const uint32_t kModuleSize3 = 0x2d000; + const char kPDBName3[] = "libc.so"; + const time_t kPDBTimestamp3 = 0x386d4380; + const uint32_t kPDBAge3 = 2; + + MinidumpModuleWriter module_writer_1; + module_writer_1.SetName(kModuleName1); + module_writer_1.SetImageBaseAddress(kModuleBase1); + module_writer_1.SetImageSize(kModuleSize1); + + MinidumpModuleCodeViewRecordPDB70Writer codeview_pdb70_writer_1; + codeview_pdb70_writer_1.SetPDBName(kPDBName1); + codeview_pdb70_writer_1.SetUUIDAndAge(pdb_uuid_1, kPDBAge1); + module_writer_1.SetCodeViewRecord(&codeview_pdb70_writer_1); + + module_list_writer.AddModule(&module_writer_1); + + MinidumpModuleWriter module_writer_2; + module_writer_2.SetName(kModuleName2); + module_writer_2.SetImageBaseAddress(kModuleBase2); + module_writer_2.SetImageSize(kModuleSize2); + + module_list_writer.AddModule(&module_writer_2); + + MinidumpModuleWriter module_writer_3; + module_writer_3.SetName(kModuleName3); + module_writer_3.SetImageBaseAddress(kModuleBase3); + module_writer_3.SetImageSize(kModuleSize3); + + MinidumpModuleCodeViewRecordPDB20Writer codeview_pdb70_writer_3; + codeview_pdb70_writer_3.SetPDBName(kPDBName3); + codeview_pdb70_writer_3.SetTimestampAndAge(kPDBTimestamp3, kPDBAge3); + module_writer_3.SetCodeViewRecord(&codeview_pdb70_writer_3); + + module_list_writer.AddModule(&module_writer_3); + + minidump_file_writer.AddStream(&module_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_GT(file_writer.string().size(), + sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); + + const MINIDUMP_MODULE_LIST* module_list; + GetModuleListStream(file_writer.string(), &module_list); + if (Test::HasFatalFailure()) { + return; + } + + EXPECT_EQ(3u, module_list->NumberOfModules); + + MINIDUMP_MODULE expected = {}; + + { + SCOPED_TRACE("module 0"); + + expected.BaseOfImage = kModuleBase1; + expected.SizeOfImage = kModuleSize1; + + ExpectModule(&expected, + &module_list->Modules[0], + file_writer.string(), + kModuleName1, + kPDBName1, + &pdb_uuid_1, + 0, + kPDBAge1, + NULL, + 0, + false); + if (Test::HasFatalFailure()) { + return; + } + } + + { + SCOPED_TRACE("module 1"); + + expected.BaseOfImage = kModuleBase2; + expected.SizeOfImage = kModuleSize2; + + ExpectModule(&expected, + &module_list->Modules[1], + file_writer.string(), + kModuleName2, + NULL, + NULL, + 0, + 0, + NULL, + 0, + false); + if (Test::HasFatalFailure()) { + return; + } + } + + { + SCOPED_TRACE("module 2"); + + expected.BaseOfImage = kModuleBase3; + expected.SizeOfImage = kModuleSize3; + + ExpectModule(&expected, + &module_list->Modules[2], + file_writer.string(), + kModuleName3, + kPDBName3, + NULL, + kPDBTimestamp3, + kPDBAge3, + NULL, + 0, + false); + if (Test::HasFatalFailure()) { + return; + } + } +} + +TEST(MinidumpSystemInfoWriterDeathTest, NoModuleName) { + MinidumpFileWriter minidump_file_writer; + MinidumpModuleListWriter module_list_writer; + MinidumpModuleWriter module_writer; + module_list_writer.AddModule(&module_writer); + minidump_file_writer.AddStream(&module_list_writer); + + StringFileWriter file_writer; + ASSERT_DEATH(minidump_file_writer.WriteEverything(&file_writer), "name_"); +} + +} // namespace