// Copyright 2014 The Crashpad Authors // // 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 #include "base/format_macros.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( file_contents, directory[0].Location); ASSERT_TRUE(module_list); } TEST(MinidumpModuleWriter, EmptyModuleList) { MinidumpFileWriter minidump_file_writer; auto module_list_writer = std::make_unique(); 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( 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(&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( file_contents, *codeview_record); ASSERT_TRUE(codeview_pdb20_record); EXPECT_EQ(codeview_pdb20_record->timestamp, static_cast(expected_pdb_timestamp)); EXPECT_EQ(codeview_pdb20_record->age, expected_pdb_age); observed_pdb_name.assign( reinterpret_cast(&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(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(misc_debug_record->Data)); size_t bytes_used; if (misc_debug_record->Unicode) { std::u16string observed_data_utf16( reinterpret_cast(misc_debug_record->Data)); bytes_used = (observed_data_utf16.size() + 1) * sizeof(char16_t); 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(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) { MINIDUMP_MODULE expected_module, observed_module; memcpy(&expected_module, expected, sizeof(expected_module)); memcpy(&observed_module, observed, sizeof(observed_module)); EXPECT_EQ(observed_module.BaseOfImage, expected_module.BaseOfImage); EXPECT_EQ(observed_module.SizeOfImage, expected_module.SizeOfImage); EXPECT_EQ(observed_module.CheckSum, expected_module.CheckSum); EXPECT_EQ(observed_module.TimeDateStamp, expected_module.TimeDateStamp); EXPECT_EQ(observed_module.VersionInfo.dwSignature, implicit_cast(VS_FFI_SIGNATURE)); EXPECT_EQ(observed_module.VersionInfo.dwStrucVersion, implicit_cast(VS_FFI_STRUCVERSION)); EXPECT_EQ(observed_module.VersionInfo.dwFileVersionMS, expected_module.VersionInfo.dwFileVersionMS); EXPECT_EQ(observed_module.VersionInfo.dwFileVersionLS, expected_module.VersionInfo.dwFileVersionLS); EXPECT_EQ(observed_module.VersionInfo.dwProductVersionMS, expected_module.VersionInfo.dwProductVersionMS); EXPECT_EQ(observed_module.VersionInfo.dwProductVersionLS, expected_module.VersionInfo.dwProductVersionLS); EXPECT_EQ(observed_module.VersionInfo.dwFileFlagsMask, expected_module.VersionInfo.dwFileFlagsMask); EXPECT_EQ(observed_module.VersionInfo.dwFileFlags, expected_module.VersionInfo.dwFileFlags); EXPECT_EQ(observed_module.VersionInfo.dwFileOS, expected_module.VersionInfo.dwFileOS); EXPECT_EQ(observed_module.VersionInfo.dwFileType, expected_module.VersionInfo.dwFileType); EXPECT_EQ(observed_module.VersionInfo.dwFileSubtype, expected_module.VersionInfo.dwFileSubtype); EXPECT_EQ(observed_module.VersionInfo.dwFileDateMS, expected_module.VersionInfo.dwFileDateMS); EXPECT_EQ(observed_module.VersionInfo.dwFileDateLS, expected_module.VersionInfo.dwFileDateLS); uint64_t reserved0, reserved1; memcpy(&reserved0, &observed_module.Reserved0, sizeof(reserved0)); memcpy(&reserved1, &observed_module.Reserved1, sizeof(reserved1)); EXPECT_EQ(reserved0, 0u); EXPECT_EQ(reserved1, 0u); EXPECT_NE(observed_module.ModuleNameRva, 0u); std::u16string observed_module_name_utf16 = MinidumpStringAtRVAAsString(file_contents, observed_module.ModuleNameRva); std::u16string 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_module.CvRecord, file_contents, expected_pdb_name, expected_pdb_uuid, expected_pdb_timestamp, expected_pdb_age)); ASSERT_NO_FATAL_FAILURE( ExpectMiscellaneousDebugRecord(&observed_module.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& 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(VS_FFI_SIGNATURE)); EXPECT_EQ(observed->VersionInfo.dwStrucVersion, implicit_cast(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); uint64_t reserved0, reserved1; memcpy(&reserved0, &observed->Reserved0, sizeof(reserved0)); memcpy(&reserved1, &observed->Reserved1, sizeof(reserved1)); EXPECT_EQ(reserved0, 0u); EXPECT_EQ(reserved1, 0u); EXPECT_NE(observed->ModuleNameRva, 0u); std::u16string observed_module_name_utf16 = MinidumpStringAtRVAAsString(file_contents, observed->ModuleNameRva); std::u16string 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( 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(); static constexpr char kModuleName[] = "test_executable"; auto module_writer = std::make_unique(); 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(); 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(); 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(); 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(); 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(); 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(); module_writer->SetName(kModuleName); auto codeview_pdb20_writer = std::make_unique(); 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(); 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(); static constexpr char kModuleName[] = "dinosaur"; static constexpr char kBuildID[] = "averylonghashcodeormaybeitsjustrandomnumbershardtosay"; std::vector build_id_data(kBuildID, kBuildID + 53); auto module_writer = std::make_unique(); module_writer->SetName(kModuleName); auto codeview_build_id_writer = std::make_unique(); 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(); 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(); module_writer_0->SetName(kModuleName0); module_writer_0->SetImageBaseAddress(kModuleBase0); module_writer_0->SetImageSize(kModuleSize0); auto codeview_pdb70_writer_0 = std::make_unique(); 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(); 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(); module_writer_2->SetName(kModuleName2); module_writer_2->SetImageBaseAddress(kModuleBase2); module_writer_2->SetImageSize(kModuleSize2); auto codeview_pdb70_writer_2 = std::make_unique(); 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[std::size(expect_modules)] = {}; const char* module_pdbs[std::size(expect_modules)] = {}; UUID uuids[std::size(expect_modules)] = {}; uint32_t ages[std::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> module_snapshots_owner; std::vector module_snapshots; for (size_t index = 0; index < std::size(expect_modules); ++index) { module_snapshots_owner.push_back(std::make_unique()); 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(); 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(); auto module_writer = std::make_unique(); 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