| // 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 <stddef.h> |
| #include <string.h> |
| |
| #include <utility> |
| |
| #include "base/format_macros.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "gtest/gtest.h" |
| #include "minidump/minidump_file_writer.h" |
| #include "minidump/test/minidump_file_writer_test_util.h" |
| #include "minidump/test/minidump_string_writer_test_util.h" |
| #include "minidump/test/minidump_writable_test_util.h" |
| #include "snapshot/test/test_module_snapshot.h" |
| #include "test/gtest_death.h" |
| #include "util/file/string_file.h" |
| #include "util/misc/implicit_cast.h" |
| #include "util/misc/uuid.h" |
| |
| namespace crashpad { |
| namespace test { |
| namespace { |
| |
| void GetModuleListStream(const std::string& file_contents, |
| const MINIDUMP_MODULE_LIST** module_list) { |
| constexpr size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER); |
| constexpr size_t kModuleListStreamOffset = |
| kDirectoryOffset + sizeof(MINIDUMP_DIRECTORY); |
| constexpr size_t kModulesOffset = |
| kModuleListStreamOffset + sizeof(MINIDUMP_MODULE_LIST); |
| |
| ASSERT_GE(file_contents.size(), kModulesOffset); |
| |
| const MINIDUMP_DIRECTORY* directory; |
| const MINIDUMP_HEADER* header = |
| MinidumpHeaderAtStart(file_contents, &directory); |
| ASSERT_NO_FATAL_FAILURE(VerifyMinidumpHeader(header, 1, 0)); |
| ASSERT_TRUE(directory); |
| |
| ASSERT_EQ(directory[0].StreamType, kMinidumpStreamTypeModuleList); |
| EXPECT_EQ(directory[0].Location.Rva, kModuleListStreamOffset); |
| |
| *module_list = MinidumpWritableAtLocationDescriptor<MINIDUMP_MODULE_LIST>( |
| file_contents, directory[0].Location); |
| ASSERT_TRUE(module_list); |
| } |
| |
| TEST(MinidumpModuleWriter, EmptyModuleList) { |
| MinidumpFileWriter minidump_file_writer; |
| auto module_list_writer = std::make_unique<MinidumpModuleListWriter>(); |
| |
| ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); |
| |
| StringFile string_file; |
| ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); |
| |
| ASSERT_EQ(string_file.string().size(), |
| sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + |
| sizeof(MINIDUMP_MODULE_LIST)); |
| |
| const MINIDUMP_MODULE_LIST* module_list = nullptr; |
| ASSERT_NO_FATAL_FAILURE( |
| GetModuleListStream(string_file.string(), &module_list)); |
| |
| EXPECT_EQ(module_list->NumberOfModules, 0u); |
| } |
| |
| // If |expected_pdb_name| is not nullptr, |codeview_record| is used to locate a |
| // CodeView record in |file_contents|, and its fields are compared against 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 nullptr, |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(codeview_record->Rva, 0u); |
| |
| std::string observed_pdb_name; |
| if (expected_pdb_uuid) { |
| // The CodeView record should be a PDB 7.0 link. |
| const CodeViewRecordPDB70* codeview_pdb70_record = |
| MinidumpWritableAtLocationDescriptor<CodeViewRecordPDB70>( |
| file_contents, *codeview_record); |
| ASSERT_TRUE(codeview_pdb70_record); |
| EXPECT_EQ(memcmp(expected_pdb_uuid, |
| &codeview_pdb70_record->uuid, |
| sizeof(codeview_pdb70_record->uuid)), |
| 0); |
| EXPECT_EQ(codeview_pdb70_record->age, expected_pdb_age); |
| |
| observed_pdb_name.assign( |
| reinterpret_cast<const char*>(&codeview_pdb70_record->pdb_name[0]), |
| codeview_record->DataSize - offsetof(CodeViewRecordPDB70, pdb_name)); |
| } else { |
| // The CodeView record should be a PDB 2.0 link. |
| const CodeViewRecordPDB20* codeview_pdb20_record = |
| MinidumpWritableAtLocationDescriptor<CodeViewRecordPDB20>( |
| file_contents, *codeview_record); |
| ASSERT_TRUE(codeview_pdb20_record); |
| EXPECT_EQ(codeview_pdb20_record->timestamp, |
| static_cast<uint32_t>(expected_pdb_timestamp)); |
| EXPECT_EQ(codeview_pdb20_record->age, expected_pdb_age); |
| |
| observed_pdb_name.assign( |
| reinterpret_cast<const char*>(&codeview_pdb20_record->pdb_name[0]), |
| codeview_record->DataSize - offsetof(CodeViewRecordPDB20, pdb_name)); |
| } |
| |
| // Check for, and then remove, the NUL terminator. |
| EXPECT_EQ(observed_pdb_name[observed_pdb_name.size() - 1], '\0'); |
| observed_pdb_name.resize(observed_pdb_name.size() - 1); |
| |
| EXPECT_EQ(observed_pdb_name, expected_pdb_name); |
| } else { |
| // There should be no CodeView record. |
| EXPECT_EQ(codeview_record->DataSize, 0u); |
| EXPECT_EQ(codeview_record->Rva, 0u); |
| } |
| } |
| |
| // If |expected_debug_name| is not nullptr, |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 |
| // nullptr, |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_NE(misc_record->Rva, 0u); |
| const IMAGE_DEBUG_MISC* misc_debug_record = |
| MinidumpWritableAtLocationDescriptor<IMAGE_DEBUG_MISC>(file_contents, |
| *misc_record); |
| ASSERT_TRUE(misc_debug_record); |
| EXPECT_EQ(misc_debug_record->DataType, expected_debug_type); |
| EXPECT_EQ(misc_debug_record->Unicode != 0, expected_debug_utf16); |
| EXPECT_EQ(misc_debug_record->Reserved[0], 0u); |
| EXPECT_EQ(misc_debug_record->Reserved[1], 0u); |
| EXPECT_EQ(misc_debug_record->Reserved[2], 0u); |
| |
| // Check for the NUL terminator. |
| size_t bytes_available = |
| misc_debug_record->Length - offsetof(IMAGE_DEBUG_MISC, Data); |
| EXPECT_EQ(misc_debug_record->Data[bytes_available - 1], '\0'); |
| std::string observed_data( |
| reinterpret_cast<const char*>(misc_debug_record->Data)); |
| |
| size_t bytes_used; |
| if (misc_debug_record->Unicode) { |
| base::string16 observed_data_utf16( |
| reinterpret_cast<const base::char16*>(misc_debug_record->Data)); |
| bytes_used = (observed_data_utf16.size() + 1) * sizeof(base::char16); |
| observed_data = base::UTF16ToUTF8(observed_data_utf16); |
| } else { |
| observed_data = reinterpret_cast<const char*>(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(misc_debug_record->Data[index], '\0'); |
| } |
| |
| EXPECT_EQ(observed_data, expected_debug_name); |
| } else { |
| // There should be no miscellaneous debugging record. |
| EXPECT_EQ(misc_record->DataSize, 0u); |
| EXPECT_EQ(misc_record->Rva, 0u); |
| } |
| } |
| |
| // 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(observed->BaseOfImage, expected->BaseOfImage); |
| EXPECT_EQ(observed->SizeOfImage, expected->SizeOfImage); |
| EXPECT_EQ(observed->CheckSum, expected->CheckSum); |
| EXPECT_EQ(observed->TimeDateStamp, expected->TimeDateStamp); |
| EXPECT_EQ(observed->VersionInfo.dwSignature, |
| implicit_cast<uint32_t>(VS_FFI_SIGNATURE)); |
| EXPECT_EQ(observed->VersionInfo.dwStrucVersion, |
| implicit_cast<uint32_t>(VS_FFI_STRUCVERSION)); |
| EXPECT_EQ(observed->VersionInfo.dwFileVersionMS, |
| expected->VersionInfo.dwFileVersionMS); |
| EXPECT_EQ(observed->VersionInfo.dwFileVersionLS, |
| expected->VersionInfo.dwFileVersionLS); |
| EXPECT_EQ(observed->VersionInfo.dwProductVersionMS, |
| expected->VersionInfo.dwProductVersionMS); |
| EXPECT_EQ(observed->VersionInfo.dwProductVersionLS, |
| expected->VersionInfo.dwProductVersionLS); |
| EXPECT_EQ(observed->VersionInfo.dwFileFlagsMask, |
| expected->VersionInfo.dwFileFlagsMask); |
| EXPECT_EQ(observed->VersionInfo.dwFileFlags, |
| expected->VersionInfo.dwFileFlags); |
| EXPECT_EQ(observed->VersionInfo.dwFileOS, expected->VersionInfo.dwFileOS); |
| EXPECT_EQ(observed->VersionInfo.dwFileType, expected->VersionInfo.dwFileType); |
| EXPECT_EQ(observed->VersionInfo.dwFileSubtype, |
| expected->VersionInfo.dwFileSubtype); |
| EXPECT_EQ(observed->VersionInfo.dwFileDateMS, |
| expected->VersionInfo.dwFileDateMS); |
| EXPECT_EQ(observed->VersionInfo.dwFileDateLS, |
| expected->VersionInfo.dwFileDateLS); |
| EXPECT_EQ(observed->Reserved0, 0u); |
| EXPECT_EQ(observed->Reserved1, 0u); |
| |
| EXPECT_NE(observed->ModuleNameRva, 0u); |
| base::string16 observed_module_name_utf16 = |
| MinidumpStringAtRVAAsString(file_contents, observed->ModuleNameRva); |
| base::string16 expected_module_name_utf16 = |
| base::UTF8ToUTF16(expected_module_name); |
| EXPECT_EQ(observed_module_name_utf16, expected_module_name_utf16); |
| |
| ASSERT_NO_FATAL_FAILURE(ExpectCodeViewRecord(&observed->CvRecord, |
| file_contents, |
| expected_pdb_name, |
| expected_pdb_uuid, |
| expected_pdb_timestamp, |
| expected_pdb_age)); |
| |
| ASSERT_NO_FATAL_FAILURE(ExpectMiscellaneousDebugRecord(&observed->MiscRecord, |
| file_contents, |
| expected_debug_name, |
| expected_debug_type, |
| expected_debug_utf16)); |
| } |
| |
| // ExpectModuleWithBuildIDCv() is like ExpectModule( but expects the module to |
| // have a BuildID CodeView Record. |
| void ExpectModuleWithBuildIDCv(const MINIDUMP_MODULE* expected, |
| const MINIDUMP_MODULE* observed, |
| const std::string& file_contents, |
| const std::string& expected_module_name, |
| const std::vector<uint8_t>& expected_build_id) { |
| EXPECT_EQ(observed->BaseOfImage, expected->BaseOfImage); |
| EXPECT_EQ(observed->SizeOfImage, expected->SizeOfImage); |
| EXPECT_EQ(observed->CheckSum, expected->CheckSum); |
| EXPECT_EQ(observed->TimeDateStamp, expected->TimeDateStamp); |
| EXPECT_EQ(observed->VersionInfo.dwSignature, |
| implicit_cast<uint32_t>(VS_FFI_SIGNATURE)); |
| EXPECT_EQ(observed->VersionInfo.dwStrucVersion, |
| implicit_cast<uint32_t>(VS_FFI_STRUCVERSION)); |
| EXPECT_EQ(observed->VersionInfo.dwFileVersionMS, |
| expected->VersionInfo.dwFileVersionMS); |
| EXPECT_EQ(observed->VersionInfo.dwFileVersionLS, |
| expected->VersionInfo.dwFileVersionLS); |
| EXPECT_EQ(observed->VersionInfo.dwProductVersionMS, |
| expected->VersionInfo.dwProductVersionMS); |
| EXPECT_EQ(observed->VersionInfo.dwProductVersionLS, |
| expected->VersionInfo.dwProductVersionLS); |
| EXPECT_EQ(observed->VersionInfo.dwFileFlagsMask, |
| expected->VersionInfo.dwFileFlagsMask); |
| EXPECT_EQ(observed->VersionInfo.dwFileFlags, |
| expected->VersionInfo.dwFileFlags); |
| EXPECT_EQ(observed->VersionInfo.dwFileOS, expected->VersionInfo.dwFileOS); |
| EXPECT_EQ(observed->VersionInfo.dwFileType, expected->VersionInfo.dwFileType); |
| EXPECT_EQ(observed->VersionInfo.dwFileSubtype, |
| expected->VersionInfo.dwFileSubtype); |
| EXPECT_EQ(observed->VersionInfo.dwFileDateMS, |
| expected->VersionInfo.dwFileDateMS); |
| EXPECT_EQ(observed->VersionInfo.dwFileDateLS, |
| expected->VersionInfo.dwFileDateLS); |
| EXPECT_EQ(observed->Reserved0, 0u); |
| EXPECT_EQ(observed->Reserved1, 0u); |
| |
| EXPECT_NE(observed->ModuleNameRva, 0u); |
| base::string16 observed_module_name_utf16 = |
| MinidumpStringAtRVAAsString(file_contents, observed->ModuleNameRva); |
| base::string16 expected_module_name_utf16 = |
| base::UTF8ToUTF16(expected_module_name); |
| EXPECT_EQ(observed_module_name_utf16, expected_module_name_utf16); |
| |
| const CodeViewRecordBuildID* codeview_build_id_record = |
| MinidumpWritableAtLocationDescriptor<CodeViewRecordBuildID>( |
| file_contents, observed->CvRecord); |
| ASSERT_TRUE(codeview_build_id_record); |
| EXPECT_EQ(memcmp(expected_build_id.data(), |
| &codeview_build_id_record->build_id, |
| expected_build_id.size()), |
| 0); |
| } |
| |
| TEST(MinidumpModuleWriter, EmptyModule) { |
| MinidumpFileWriter minidump_file_writer; |
| auto module_list_writer = std::make_unique<MinidumpModuleListWriter>(); |
| |
| static constexpr char kModuleName[] = "test_executable"; |
| |
| auto module_writer = std::make_unique<MinidumpModuleWriter>(); |
| module_writer->SetName(kModuleName); |
| |
| module_list_writer->AddModule(std::move(module_writer)); |
| ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); |
| |
| StringFile string_file; |
| ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); |
| |
| ASSERT_GT(string_file.string().size(), |
| sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + |
| sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); |
| |
| const MINIDUMP_MODULE_LIST* module_list = nullptr; |
| ASSERT_NO_FATAL_FAILURE( |
| GetModuleListStream(string_file.string(), &module_list)); |
| |
| EXPECT_EQ(module_list->NumberOfModules, 1u); |
| |
| MINIDUMP_MODULE expected = {}; |
| ASSERT_NO_FATAL_FAILURE(ExpectModule(&expected, |
| &module_list->Modules[0], |
| string_file.string(), |
| kModuleName, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| nullptr, |
| 0, |
| false)); |
| } |
| |
| TEST(MinidumpModuleWriter, OneModule) { |
| MinidumpFileWriter minidump_file_writer; |
| auto module_list_writer = std::make_unique<MinidumpModuleListWriter>(); |
| |
| static constexpr char kModuleName[] = "statically_linked"; |
| constexpr uint64_t kModuleBase = 0x10da69000; |
| constexpr uint32_t kModuleSize = 0x1000; |
| constexpr uint32_t kChecksum = 0x76543210; |
| constexpr time_t kTimestamp = 0x386d4380; |
| constexpr uint32_t kFileVersionMS = 0x00010002; |
| constexpr uint32_t kFileVersionLS = 0x00030004; |
| constexpr uint32_t kProductVersionMS = 0x00050006; |
| constexpr uint32_t kProductVersionLS = 0x00070008; |
| constexpr uint32_t kFileFlagsMask = VS_FF_DEBUG | VS_FF_PRERELEASE | |
| VS_FF_PATCHED | VS_FF_PRIVATEBUILD | |
| VS_FF_INFOINFERRED | VS_FF_SPECIALBUILD; |
| constexpr uint32_t kFileFlags = VS_FF_PRIVATEBUILD | VS_FF_SPECIALBUILD; |
| constexpr uint32_t kFileOS = VOS_DOS; |
| constexpr uint32_t kFileType = VFT_DRV; |
| constexpr uint32_t kFileSubtype = VFT2_DRV_KEYBOARD; |
| static constexpr char kPDBName[] = "statical.pdb"; |
| static constexpr 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); |
| constexpr uint32_t kPDBAge = 1; |
| constexpr uint32_t kDebugType = IMAGE_DEBUG_MISC_EXENAME; |
| static constexpr char kDebugName[] = "statical.dbg"; |
| constexpr bool kDebugUTF16 = false; |
| |
| auto module_writer = std::make_unique<MinidumpModuleWriter>(); |
| 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); |
| |
| auto codeview_pdb70_writer = |
| std::make_unique<MinidumpModuleCodeViewRecordPDB70Writer>(); |
| codeview_pdb70_writer->SetPDBName(kPDBName); |
| codeview_pdb70_writer->SetUUIDAndAge(pdb_uuid, kPDBAge); |
| module_writer->SetCodeViewRecord(std::move(codeview_pdb70_writer)); |
| |
| auto misc_debug_writer = |
| std::make_unique<MinidumpModuleMiscDebugRecordWriter>(); |
| misc_debug_writer->SetDataType(kDebugType); |
| misc_debug_writer->SetData(kDebugName, kDebugUTF16); |
| module_writer->SetMiscDebugRecord(std::move(misc_debug_writer)); |
| |
| module_list_writer->AddModule(std::move(module_writer)); |
| ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); |
| |
| StringFile string_file; |
| ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); |
| |
| ASSERT_GT(string_file.string().size(), |
| sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + |
| sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); |
| |
| const MINIDUMP_MODULE_LIST* module_list = nullptr; |
| ASSERT_NO_FATAL_FAILURE( |
| GetModuleListStream(string_file.string(), &module_list)); |
| |
| EXPECT_EQ(module_list->NumberOfModules, 1u); |
| |
| 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; |
| |
| ASSERT_NO_FATAL_FAILURE(ExpectModule(&expected, |
| &module_list->Modules[0], |
| string_file.string(), |
| kModuleName, |
| kPDBName, |
| &pdb_uuid, |
| 0, |
| kPDBAge, |
| kDebugName, |
| kDebugType, |
| kDebugUTF16)); |
| } |
| |
| 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; |
| auto module_list_writer = std::make_unique<MinidumpModuleListWriter>(); |
| |
| static constexpr char kModuleName[] = "dinosaur"; |
| static constexpr char kPDBName[] = "d1n05.pdb"; |
| constexpr time_t kPDBTimestamp = 0x386d4380; |
| constexpr uint32_t kPDBAge = 1; |
| constexpr uint32_t kDebugType = IMAGE_DEBUG_MISC_EXENAME; |
| static constexpr char kDebugName[] = "d1n05.dbg"; |
| constexpr bool kDebugUTF16 = true; |
| |
| auto module_writer = std::make_unique<MinidumpModuleWriter>(); |
| module_writer->SetName(kModuleName); |
| |
| auto codeview_pdb20_writer = |
| std::make_unique<MinidumpModuleCodeViewRecordPDB20Writer>(); |
| codeview_pdb20_writer->SetPDBName(kPDBName); |
| codeview_pdb20_writer->SetTimestampAndAge(kPDBTimestamp, kPDBAge); |
| module_writer->SetCodeViewRecord(std::move(codeview_pdb20_writer)); |
| |
| auto misc_debug_writer = |
| std::make_unique<MinidumpModuleMiscDebugRecordWriter>(); |
| misc_debug_writer->SetDataType(kDebugType); |
| misc_debug_writer->SetData(kDebugName, kDebugUTF16); |
| module_writer->SetMiscDebugRecord(std::move(misc_debug_writer)); |
| |
| module_list_writer->AddModule(std::move(module_writer)); |
| ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); |
| |
| StringFile string_file; |
| ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); |
| |
| ASSERT_GT(string_file.string().size(), |
| sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + |
| sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); |
| |
| const MINIDUMP_MODULE_LIST* module_list = nullptr; |
| ASSERT_NO_FATAL_FAILURE( |
| GetModuleListStream(string_file.string(), &module_list)); |
| |
| EXPECT_EQ(module_list->NumberOfModules, 1u); |
| |
| MINIDUMP_MODULE expected = {}; |
| |
| ASSERT_NO_FATAL_FAILURE(ExpectModule(&expected, |
| &module_list->Modules[0], |
| string_file.string(), |
| kModuleName, |
| kPDBName, |
| nullptr, |
| kPDBTimestamp, |
| kPDBAge, |
| kDebugName, |
| kDebugType, |
| kDebugUTF16)); |
| } |
| |
| TEST(MinidumpModuleWriter, OneModule_CodeViewBuildID) { |
| // MinidumpModuleWriter.OneModule tested with a BuildID CodeView |
| MinidumpFileWriter minidump_file_writer; |
| auto module_list_writer = std::make_unique<MinidumpModuleListWriter>(); |
| |
| static constexpr char kModuleName[] = "dinosaur"; |
| static constexpr char kBuildID[] = |
| "averylonghashcodeormaybeitsjustrandomnumbershardtosay"; |
| |
| std::vector<uint8_t> build_id_data(kBuildID, kBuildID + 53); |
| |
| auto module_writer = std::make_unique<MinidumpModuleWriter>(); |
| module_writer->SetName(kModuleName); |
| |
| auto codeview_build_id_writer = |
| std::make_unique<MinidumpModuleCodeViewRecordBuildIDWriter>(); |
| codeview_build_id_writer->SetBuildID(build_id_data); |
| module_writer->SetCodeViewRecord(std::move(codeview_build_id_writer)); |
| |
| module_list_writer->AddModule(std::move(module_writer)); |
| ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); |
| |
| StringFile string_file; |
| ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); |
| |
| ASSERT_GT(string_file.string().size(), |
| sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + |
| sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); |
| |
| const MINIDUMP_MODULE_LIST* module_list = nullptr; |
| ASSERT_NO_FATAL_FAILURE( |
| GetModuleListStream(string_file.string(), &module_list)); |
| |
| EXPECT_EQ(module_list->NumberOfModules, 1u); |
| |
| MINIDUMP_MODULE expected = {}; |
| |
| ASSERT_NO_FATAL_FAILURE(ExpectModuleWithBuildIDCv(&expected, |
| &module_list->Modules[0], |
| string_file.string(), |
| kModuleName, |
| build_id_data)); |
| } |
| |
| 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; |
| auto module_list_writer = std::make_unique<MinidumpModuleListWriter>(); |
| |
| static constexpr char kModuleName0[] = "main"; |
| constexpr uint64_t kModuleBase0 = 0x100101000; |
| constexpr uint32_t kModuleSize0 = 0xf000; |
| static constexpr char kPDBName0[] = "main"; |
| static constexpr uint8_t kPDBUUIDBytes0[16] = {0xaa, |
| 0xbb, |
| 0xcc, |
| 0xdd, |
| 0xee, |
| 0xff, |
| 0x00, |
| 0x11, |
| 0x22, |
| 0x33, |
| 0x44, |
| 0x55, |
| 0x66, |
| 0x77, |
| 0x88, |
| 0x99}; |
| UUID pdb_uuid_0; |
| pdb_uuid_0.InitializeFromBytes(kPDBUUIDBytes0); |
| constexpr uint32_t kPDBAge0 = 0; |
| |
| static constexpr char kModuleName1[] = "ld.so"; |
| constexpr uint64_t kModuleBase1 = 0x200202000; |
| constexpr uint32_t kModuleSize1 = 0x1e000; |
| |
| static constexpr char kModuleName2[] = "libc.so"; |
| constexpr uint64_t kModuleBase2 = 0x300303000; |
| constexpr uint32_t kModuleSize2 = 0x2d000; |
| static constexpr char kPDBName2[] = "libc.so"; |
| constexpr time_t kPDBTimestamp2 = 0x386d4380; |
| constexpr uint32_t kPDBAge2 = 2; |
| |
| auto module_writer_0 = std::make_unique<MinidumpModuleWriter>(); |
| module_writer_0->SetName(kModuleName0); |
| module_writer_0->SetImageBaseAddress(kModuleBase0); |
| module_writer_0->SetImageSize(kModuleSize0); |
| |
| auto codeview_pdb70_writer_0 = |
| std::make_unique<MinidumpModuleCodeViewRecordPDB70Writer>(); |
| codeview_pdb70_writer_0->SetPDBName(kPDBName0); |
| codeview_pdb70_writer_0->SetUUIDAndAge(pdb_uuid_0, kPDBAge0); |
| module_writer_0->SetCodeViewRecord(std::move(codeview_pdb70_writer_0)); |
| |
| module_list_writer->AddModule(std::move(module_writer_0)); |
| |
| auto module_writer_1 = std::make_unique<MinidumpModuleWriter>(); |
| module_writer_1->SetName(kModuleName1); |
| module_writer_1->SetImageBaseAddress(kModuleBase1); |
| module_writer_1->SetImageSize(kModuleSize1); |
| |
| module_list_writer->AddModule(std::move(module_writer_1)); |
| |
| auto module_writer_2 = std::make_unique<MinidumpModuleWriter>(); |
| module_writer_2->SetName(kModuleName2); |
| module_writer_2->SetImageBaseAddress(kModuleBase2); |
| module_writer_2->SetImageSize(kModuleSize2); |
| |
| auto codeview_pdb70_writer_2 = |
| std::make_unique<MinidumpModuleCodeViewRecordPDB20Writer>(); |
| codeview_pdb70_writer_2->SetPDBName(kPDBName2); |
| codeview_pdb70_writer_2->SetTimestampAndAge(kPDBTimestamp2, kPDBAge2); |
| module_writer_2->SetCodeViewRecord(std::move(codeview_pdb70_writer_2)); |
| |
| module_list_writer->AddModule(std::move(module_writer_2)); |
| |
| ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); |
| |
| StringFile string_file; |
| ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); |
| |
| ASSERT_GT(string_file.string().size(), |
| sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + |
| sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE)); |
| |
| const MINIDUMP_MODULE_LIST* module_list = nullptr; |
| ASSERT_NO_FATAL_FAILURE( |
| GetModuleListStream(string_file.string(), &module_list)); |
| |
| EXPECT_EQ(module_list->NumberOfModules, 3u); |
| |
| MINIDUMP_MODULE expected = {}; |
| |
| { |
| SCOPED_TRACE("module 0"); |
| |
| expected.BaseOfImage = kModuleBase0; |
| expected.SizeOfImage = kModuleSize0; |
| |
| ASSERT_NO_FATAL_FAILURE(ExpectModule(&expected, |
| &module_list->Modules[0], |
| string_file.string(), |
| kModuleName0, |
| kPDBName0, |
| &pdb_uuid_0, |
| 0, |
| kPDBAge0, |
| nullptr, |
| 0, |
| false)); |
| } |
| |
| { |
| SCOPED_TRACE("module 1"); |
| |
| expected.BaseOfImage = kModuleBase1; |
| expected.SizeOfImage = kModuleSize1; |
| |
| ASSERT_NO_FATAL_FAILURE(ExpectModule(&expected, |
| &module_list->Modules[1], |
| string_file.string(), |
| kModuleName1, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| nullptr, |
| 0, |
| false)); |
| } |
| |
| { |
| SCOPED_TRACE("module 2"); |
| |
| expected.BaseOfImage = kModuleBase2; |
| expected.SizeOfImage = kModuleSize2; |
| |
| ASSERT_NO_FATAL_FAILURE(ExpectModule(&expected, |
| &module_list->Modules[2], |
| string_file.string(), |
| kModuleName2, |
| kPDBName2, |
| nullptr, |
| kPDBTimestamp2, |
| kPDBAge2, |
| nullptr, |
| 0, |
| false)); |
| } |
| } |
| |
| void InitializeTestModuleSnapshotFromMinidumpModule( |
| TestModuleSnapshot* module_snapshot, |
| const MINIDUMP_MODULE& minidump_module, |
| const std::string& name, |
| const std::string& pdb_name, |
| const crashpad::UUID& uuid, |
| uint32_t age) { |
| module_snapshot->SetName(name); |
| |
| module_snapshot->SetAddressAndSize(minidump_module.BaseOfImage, |
| minidump_module.SizeOfImage); |
| module_snapshot->SetTimestamp(minidump_module.TimeDateStamp); |
| module_snapshot->SetFileVersion( |
| minidump_module.VersionInfo.dwFileVersionMS >> 16, |
| minidump_module.VersionInfo.dwFileVersionMS & 0xffff, |
| minidump_module.VersionInfo.dwFileVersionLS >> 16, |
| minidump_module.VersionInfo.dwFileVersionLS & 0xffff); |
| module_snapshot->SetSourceVersion( |
| minidump_module.VersionInfo.dwProductVersionMS >> 16, |
| minidump_module.VersionInfo.dwProductVersionMS & 0xffff, |
| minidump_module.VersionInfo.dwProductVersionLS >> 16, |
| minidump_module.VersionInfo.dwProductVersionLS & 0xffff); |
| |
| ModuleSnapshot::ModuleType module_type; |
| switch (minidump_module.VersionInfo.dwFileType) { |
| case VFT_APP: |
| module_type = ModuleSnapshot::kModuleTypeExecutable; |
| break; |
| case VFT_DLL: |
| module_type = ModuleSnapshot::kModuleTypeSharedLibrary; |
| break; |
| default: |
| module_type = ModuleSnapshot::kModuleTypeUnknown; |
| break; |
| } |
| module_snapshot->SetModuleType(module_type); |
| |
| module_snapshot->SetUUIDAndAge(uuid, age); |
| module_snapshot->SetDebugFileName(pdb_name); |
| } |
| |
| TEST(MinidumpModuleWriter, InitializeFromSnapshot) { |
| MINIDUMP_MODULE expect_modules[3] = {}; |
| const char* module_paths[base::size(expect_modules)] = {}; |
| const char* module_pdbs[base::size(expect_modules)] = {}; |
| UUID uuids[base::size(expect_modules)] = {}; |
| uint32_t ages[base::size(expect_modules)] = {}; |
| |
| expect_modules[0].BaseOfImage = 0x100101000; |
| expect_modules[0].SizeOfImage = 0xf000; |
| expect_modules[0].TimeDateStamp = 0x01234567; |
| expect_modules[0].VersionInfo.dwFileVersionMS = 0x00010002; |
| expect_modules[0].VersionInfo.dwFileVersionLS = 0x00030004; |
| expect_modules[0].VersionInfo.dwProductVersionMS = 0x00050006; |
| expect_modules[0].VersionInfo.dwProductVersionLS = 0x00070008; |
| expect_modules[0].VersionInfo.dwFileType = VFT_APP; |
| module_paths[0] = "/usr/bin/true"; |
| module_pdbs[0] = "true"; |
| static constexpr uint8_t kUUIDBytes0[16] = {0x00, |
| 0x11, |
| 0x22, |
| 0x33, |
| 0x44, |
| 0x55, |
| 0x66, |
| 0x77, |
| 0x88, |
| 0x99, |
| 0xaa, |
| 0xbb, |
| 0xcc, |
| 0xdd, |
| 0xee, |
| 0xff}; |
| uuids[0].InitializeFromBytes(kUUIDBytes0); |
| ages[0] = 10; |
| |
| expect_modules[1].BaseOfImage = 0x200202000; |
| expect_modules[1].SizeOfImage = 0x1e1000; |
| expect_modules[1].TimeDateStamp = 0x89abcdef; |
| expect_modules[1].VersionInfo.dwFileVersionMS = 0x0009000a; |
| expect_modules[1].VersionInfo.dwFileVersionLS = 0x000b000c; |
| expect_modules[1].VersionInfo.dwProductVersionMS = 0x000d000e; |
| expect_modules[1].VersionInfo.dwProductVersionLS = 0x000f0000; |
| expect_modules[1].VersionInfo.dwFileType = VFT_DLL; |
| module_paths[1] = "/usr/lib/libSystem.B.dylib"; |
| module_pdbs[1] = "libSystem.B.dylib.pdb"; |
| static constexpr uint8_t kUUIDBytes1[16] = {0x00, |
| 0x01, |
| 0x02, |
| 0x03, |
| 0x04, |
| 0x05, |
| 0x06, |
| 0x07, |
| 0x08, |
| 0x09, |
| 0x0a, |
| 0x0b, |
| 0x0c, |
| 0x0d, |
| 0x0e, |
| 0x0f}; |
| uuids[1].InitializeFromBytes(kUUIDBytes1); |
| ages[1] = 20; |
| |
| expect_modules[2].BaseOfImage = 0x300303000; |
| expect_modules[2].SizeOfImage = 0x2d000; |
| expect_modules[2].TimeDateStamp = 0x76543210; |
| expect_modules[2].VersionInfo.dwFileVersionMS = 0x11112222; |
| expect_modules[2].VersionInfo.dwFileVersionLS = 0x33334444; |
| expect_modules[2].VersionInfo.dwProductVersionMS = 0x9999aaaa; |
| expect_modules[2].VersionInfo.dwProductVersionLS = 0xbbbbcccc; |
| expect_modules[2].VersionInfo.dwFileType = VFT_UNKNOWN; |
| module_paths[2] = "/usr/lib/dyld"; |
| module_pdbs[2] = "/usr/lib/dyld.pdb"; |
| static constexpr uint8_t kUUIDBytes2[16] = {0xff, |
| 0xfe, |
| 0xfd, |
| 0xfc, |
| 0xfb, |
| 0xfa, |
| 0xf9, |
| 0xf8, |
| 0xf7, |
| 0xf6, |
| 0xf5, |
| 0xf4, |
| 0xf3, |
| 0xf2, |
| 0xf1, |
| 0xf0}; |
| uuids[2].InitializeFromBytes(kUUIDBytes2); |
| ages[2] = 30; |
| |
| std::vector<std::unique_ptr<TestModuleSnapshot>> module_snapshots_owner; |
| std::vector<const ModuleSnapshot*> module_snapshots; |
| for (size_t index = 0; index < base::size(expect_modules); ++index) { |
| module_snapshots_owner.push_back(std::make_unique<TestModuleSnapshot>()); |
| TestModuleSnapshot* module_snapshot = module_snapshots_owner.back().get(); |
| InitializeTestModuleSnapshotFromMinidumpModule(module_snapshot, |
| expect_modules[index], |
| module_paths[index], |
| module_pdbs[index], |
| uuids[index], |
| ages[index]); |
| module_snapshots.push_back(module_snapshot); |
| } |
| |
| auto module_list_writer = std::make_unique<MinidumpModuleListWriter>(); |
| module_list_writer->InitializeFromSnapshot(module_snapshots); |
| |
| MinidumpFileWriter minidump_file_writer; |
| ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); |
| |
| StringFile string_file; |
| ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); |
| |
| const MINIDUMP_MODULE_LIST* module_list = nullptr; |
| ASSERT_NO_FATAL_FAILURE( |
| GetModuleListStream(string_file.string(), &module_list)); |
| |
| ASSERT_EQ(module_list->NumberOfModules, 3u); |
| |
| for (size_t index = 0; index < module_list->NumberOfModules; ++index) { |
| SCOPED_TRACE(base::StringPrintf("index %" PRIuS, index)); |
| ASSERT_NO_FATAL_FAILURE(ExpectModule(&expect_modules[index], |
| &module_list->Modules[index], |
| string_file.string(), |
| module_paths[index], |
| module_pdbs[index], |
| &uuids[index], |
| 0, |
| ages[index], |
| nullptr, |
| 0, |
| false)); |
| } |
| } |
| |
| TEST(MinidumpModuleWriterDeathTest, NoModuleName) { |
| MinidumpFileWriter minidump_file_writer; |
| auto module_list_writer = std::make_unique<MinidumpModuleListWriter>(); |
| auto module_writer = std::make_unique<MinidumpModuleWriter>(); |
| module_list_writer->AddModule(std::move(module_writer)); |
| ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer))); |
| |
| StringFile string_file; |
| ASSERT_DEATH_CHECK(minidump_file_writer.WriteEverything(&string_file), |
| "name_"); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace crashpad |