diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/google_breakpad/common/minidump_format.h | 23 | ||||
-rw-r--r-- | src/google_breakpad/common/minidump_size.h | 6 | ||||
-rw-r--r-- | src/processor/minidump.cc | 106 | ||||
-rw-r--r-- | src/processor/minidump_unittest.cc | 223 |
4 files changed, 313 insertions, 45 deletions
diff --git a/src/google_breakpad/common/minidump_format.h b/src/google_breakpad/common/minidump_format.h index 18f8268b..da20f459 100644 --- a/src/google_breakpad/common/minidump_format.h +++ b/src/google_breakpad/common/minidump_format.h @@ -449,15 +449,26 @@ static const size_t MDCVInfoPDB70_minsize = offsetof(MDCVInfoPDB70, #define MD_CVINFOPDB70_SIGNATURE 0x53445352 /* cvSignature = 'SDSR' */ +/* + * Modern ELF toolchains insert a "build id" into the ELF headers that + * usually contains a hash of some ELF headers + sections to uniquely + * identify a binary. + * + * https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Developer_Guide/compiling-build-id.html + * https://sourceware.org/binutils/docs-2.26/ld/Options.html#index-g_t_002d_002dbuild_002did-292 + */ typedef struct { - uint32_t data1[2]; - uint32_t data2; - uint32_t data3; - uint32_t data4; - uint32_t data5[3]; - uint8_t extra[2]; + uint32_t cv_signature; + uint8_t build_id[1]; /* Bytes of build id from GNU_BUILD_ID ELF note. + * This is variable-length, but usually 20 bytes + * as the binutils ld default is a SHA-1 hash. */ } MDCVInfoELF; +static const size_t MDCVInfoELF_minsize = offsetof(MDCVInfoELF, + build_id[0]); + +#define MD_CVINFOELF_SIGNATURE 0x4270454c /* cvSignature = 'BpEL' */ + /* In addition to the two CodeView record formats above, used for linking * to external pdb files, it is possible for debugging data to be carried * directly in the CodeView record itself. These signature values will diff --git a/src/google_breakpad/common/minidump_size.h b/src/google_breakpad/common/minidump_size.h index 918544b6..fae57923 100644 --- a/src/google_breakpad/common/minidump_size.h +++ b/src/google_breakpad/common/minidump_size.h @@ -76,6 +76,12 @@ class minidump_size<MDCVInfoPDB70> { }; template<> +class minidump_size<MDCVInfoELF> { + public: + static size_t size() { return MDCVInfoELF_minsize; } +}; + +template<> class minidump_size<MDImageDebugMisc> { public: static size_t size() { return MDImageDebugMisc_minsize; } diff --git a/src/processor/minidump.cc b/src/processor/minidump.cc index 753c6df7..a9b56a50 100644 --- a/src/processor/minidump.cc +++ b/src/processor/minidump.cc @@ -1853,11 +1853,30 @@ string MinidumpModule::code_identifier() const { break; } + case MD_OS_ANDROID: + case MD_OS_LINUX: { + // If ELF CodeView data is present, return the debug id. + if (cv_record_ && cv_record_signature_ == MD_CVINFOELF_SIGNATURE) { + const MDCVInfoELF* cv_record_elf = + reinterpret_cast<const MDCVInfoELF*>(&(*cv_record_)[0]); + assert(cv_record_elf->cv_signature == MD_CVINFOELF_SIGNATURE); + + for (unsigned int build_id_index = 0; + build_id_index < (cv_record_->size() - MDCVInfoELF_minsize); + ++build_id_index) { + char hexbyte[3]; + snprintf(hexbyte, sizeof(hexbyte), "%02x", + cv_record_elf->build_id[build_id_index]); + identifier += hexbyte; + } + break; + } + // Otherwise fall through to the case below. + } + case MD_OS_MAC_OS_X: case MD_OS_IOS: case MD_OS_SOLARIS: - case MD_OS_ANDROID: - case MD_OS_LINUX: case MD_OS_NACL: case MD_OS_PS3: { // TODO(mmentovai): support uuid extension if present, otherwise fall @@ -1908,6 +1927,14 @@ string MinidumpModule::debug_file() const { // GetCVRecord guarantees pdb_file_name is null-terminated. file = reinterpret_cast<const char*>(cv_record_20->pdb_file_name); + } else if (cv_record_signature_ == MD_CVINFOELF_SIGNATURE) { + // It's actually an MDCVInfoELF structure. + const MDCVInfoELF* cv_record_elf = + reinterpret_cast<const MDCVInfoELF*>(&(*cv_record_)[0]); + assert(cv_record_elf->cv_signature == MD_CVINFOELF_SIGNATURE); + + // For MDCVInfoELF, the debug file is the code file. + file = *name_; } // If there's a CodeView record but it doesn't match a known signature, @@ -1959,6 +1986,25 @@ string MinidumpModule::debug_file() const { return file; } +static string guid_and_age_to_debug_id(const MDGUID& guid, + uint32_t age) { + char identifier_string[41]; + snprintf(identifier_string, sizeof(identifier_string), + "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x", + guid.data1, + guid.data2, + guid.data3, + guid.data4[0], + guid.data4[1], + guid.data4[2], + guid.data4[3], + guid.data4[4], + guid.data4[5], + guid.data4[6], + guid.data4[7], + age); + return identifier_string; +} string MinidumpModule::debug_identifier() const { if (!valid_) { @@ -1981,22 +2027,8 @@ string MinidumpModule::debug_identifier() const { // Use the same format that the MS symbol server uses in filesystem // hierarchies. - char identifier_string[41]; - snprintf(identifier_string, sizeof(identifier_string), - "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x", - cv_record_70->signature.data1, - cv_record_70->signature.data2, - cv_record_70->signature.data3, - cv_record_70->signature.data4[0], - cv_record_70->signature.data4[1], - cv_record_70->signature.data4[2], - cv_record_70->signature.data4[3], - cv_record_70->signature.data4[4], - cv_record_70->signature.data4[5], - cv_record_70->signature.data4[6], - cv_record_70->signature.data4[7], - cv_record_70->age); - identifier = identifier_string; + identifier = guid_and_age_to_debug_id(cv_record_70->signature, + cv_record_70->age); } else if (cv_record_signature_ == MD_CVINFOPDB20_SIGNATURE) { // It's actually an MDCVInfoPDB20 structure. const MDCVInfoPDB20* cv_record_20 = @@ -2009,6 +2041,21 @@ string MinidumpModule::debug_identifier() const { snprintf(identifier_string, sizeof(identifier_string), "%08X%x", cv_record_20->signature, cv_record_20->age); identifier = identifier_string; + } else if (cv_record_signature_ == MD_CVINFOELF_SIGNATURE) { + // It's actually an MDCVInfoELF structure. + const MDCVInfoELF* cv_record_elf = + reinterpret_cast<const MDCVInfoELF*>(&(*cv_record_)[0]); + assert(cv_record_elf->cv_signature == MD_CVINFOELF_SIGNATURE); + + // For backwards-compatibility, stuff as many bytes as will fit into + // a MDGUID and use the MS symbol server format as MDCVInfoPDB70 does + // with age = 0. Historically Breakpad would do this during dump + // writing to fit the build id data into a MDCVInfoPDB70 struct. + // The full build id is available by calling code_identifier. + MDGUID guid = {0}; + memcpy(&guid, &cv_record_elf->build_id, + cv_record_->size() - MDCVInfoELF_minsize); + identifier = guid_and_age_to_debug_id(guid, 0); } } @@ -2167,6 +2214,15 @@ const uint8_t* MinidumpModule::GetCVRecord(uint32_t* size) { "0-terminated"; return NULL; } + } else if (signature == MD_CVINFOELF_SIGNATURE) { + // Now that the structure type is known, recheck the size. + if (MDCVInfoELF_minsize > module_.cv_record.data_size) { + BPLOG(ERROR) << "MinidumpModule CodeViewELF record size mismatch, " << + MDCVInfoELF_minsize << " > " << + module_.cv_record.data_size; + return NULL; + } + // There's nothing to swap in CVInfoELF, it's just raw bytes. } // If the signature doesn't match something above, it's not something @@ -2367,6 +2423,20 @@ void MinidumpModule::Print() { cv_record_20->age); printf(" (cv_record).pdb_file_name = \"%s\"\n", cv_record_20->pdb_file_name); + } else if (cv_record_signature_ == MD_CVINFOELF_SIGNATURE) { + const MDCVInfoELF* cv_record_elf = + reinterpret_cast<const MDCVInfoELF*>(cv_record); + assert(cv_record_elf->cv_signature == MD_CVINFOELF_SIGNATURE); + + printf(" (cv_record).cv_signature = 0x%x\n", + cv_record_elf->cv_signature); + printf(" (cv_record).build_id = "); + for (unsigned int build_id_index = 0; + build_id_index < (cv_record_size - MDCVInfoELF_minsize); + ++build_id_index) { + printf("%02x", cv_record_elf->build_id[build_id_index]); + } + printf("\n"); } else { printf(" (cv_record) = "); for (unsigned int cv_byte_index = 0; diff --git a/src/processor/minidump_unittest.cc b/src/processor/minidump_unittest.cc index bb7dac64..4c5a7b2f 100644 --- a/src/processor/minidump_unittest.cc +++ b/src/processor/minidump_unittest.cc @@ -63,6 +63,7 @@ using google_breakpad::SynthMinidump::Dump; using google_breakpad::SynthMinidump::Exception; using google_breakpad::SynthMinidump::Memory; using google_breakpad::SynthMinidump::Module; +using google_breakpad::SynthMinidump::Section; using google_breakpad::SynthMinidump::Stream; using google_breakpad::SynthMinidump::String; using google_breakpad::SynthMinidump::SystemInfo; @@ -90,7 +91,15 @@ TEST_F(MinidumpTest, TestMinidumpFromFile) { const MDRawHeader* header = minidump.header(); ASSERT_NE(header, (MDRawHeader*)NULL); ASSERT_EQ(header->signature, uint32_t(MD_HEADER_SIGNATURE)); - //TODO: add more checks here + + MinidumpModuleList *md_module_list = minidump.GetModuleList(); + ASSERT_TRUE(md_module_list != NULL); + const MinidumpModule *md_module = md_module_list->GetModuleAtIndex(0); + ASSERT_TRUE(md_module != NULL); + ASSERT_EQ("c:\\test_app.exe", md_module->code_file()); + ASSERT_EQ("c:\\test_app.pdb", md_module->debug_file()); + ASSERT_EQ("45D35F6C2d000", md_module->code_identifier()); + ASSERT_EQ("5A9832E5287241C1838ED98914E9B7FF1", md_module->debug_identifier()); } TEST_F(MinidumpTest, TestMinidumpFromStream) { @@ -385,44 +394,61 @@ TEST(Dump, ThreadMissingContext) { ASSERT_EQ(reinterpret_cast<MinidumpContext*>(NULL), md_context); } -TEST(Dump, OneModule) { - static const MDVSFixedFileInfo fixed_file_info = { - 0xb2fba33a, // signature - 0x33d7a728, // struct_version - 0x31afcb20, // file_version_hi - 0xe51cdab1, // file_version_lo - 0xd1ea6907, // product_version_hi - 0x03032857, // product_version_lo - 0x11bf71d7, // file_flags_mask - 0x5fb8cdbf, // file_flags - 0xe45d0d5d, // file_os - 0x107d9562, // file_type - 0x5a8844d4, // file_subtype - 0xa8d30b20, // file_date_hi - 0x651c3e4e // file_date_lo - }; +static const MDVSFixedFileInfo fixed_file_info = { + 0xb2fba33a, // signature + 0x33d7a728, // struct_version + 0x31afcb20, // file_version_hi + 0xe51cdab1, // file_version_lo + 0xd1ea6907, // product_version_hi + 0x03032857, // product_version_lo + 0x11bf71d7, // file_flags_mask + 0x5fb8cdbf, // file_flags + 0xe45d0d5d, // file_os + 0x107d9562, // file_type + 0x5a8844d4, // file_subtype + 0xa8d30b20, // file_date_hi + 0x651c3e4e // file_date_lo +}; +TEST(Dump, OneModule) { Dump dump(0, kBigEndian); String module_name(dump, "single module"); + Section cv_info(dump); + cv_info + .D32(MD_CVINFOPDB70_SIGNATURE) // signature + // signature, a MDGUID + .D32(0xabcd1234) + .D16(0xf00d) + .D16(0xbeef) + .Append("\x01\x02\x03\x04\x05\x06\x07\x08") + .D32(1) // age + .AppendCString("c:\\foo\\file.pdb"); // pdb_file_name + + String csd_version(dump, "Windows 9000"); + SystemInfo system_info(dump, SystemInfo::windows_x86, csd_version); + Module module(dump, 0xa90206ca83eb2852ULL, 0xada542bd, module_name, 0xb1054d2a, 0x34571371, fixed_file_info, // from synth_minidump_unittest_data.h - NULL, NULL); + &cv_info, nullptr); dump.Add(&module); dump.Add(&module_name); + dump.Add(&cv_info); + dump.Add(&system_info); + dump.Add(&csd_version); dump.Finish(); - + string contents; ASSERT_TRUE(dump.GetContents(&contents)); istringstream minidump_stream(contents); Minidump minidump(minidump_stream); ASSERT_TRUE(minidump.Read()); - ASSERT_EQ(1U, minidump.GetDirectoryEntryCount()); + ASSERT_EQ(2U, minidump.GetDirectoryEntryCount()); - const MDRawDirectory *dir = minidump.GetDirectoryEntryAtIndex(0); + const MDRawDirectory *dir = minidump.GetDirectoryEntryAtIndex(1); ASSERT_TRUE(dir != NULL); EXPECT_EQ((uint32_t) MD_MODULE_LIST_STREAM, dir->stream_type); @@ -435,6 +461,10 @@ TEST(Dump, OneModule) { ASSERT_EQ(0xa90206ca83eb2852ULL, md_module->base_address()); ASSERT_EQ(0xada542bd, md_module->size()); ASSERT_EQ("single module", md_module->code_file()); + ASSERT_EQ("c:\\foo\\file.pdb", md_module->debug_file()); + // time_date_stamp and size_of_image concatenated + ASSERT_EQ("B1054D2Aada542bd", md_module->code_identifier()); + ASSERT_EQ("ABCD1234F00DBEEF01020304050607081", md_module->debug_identifier()); const MDRawModule *md_raw_module = md_module->module(); ASSERT_TRUE(md_raw_module != NULL); @@ -444,6 +474,157 @@ TEST(Dump, OneModule) { sizeof(fixed_file_info)) == 0); } +// Test that a module with a MDCVInfoELF CV record is handled properly. +TEST(Dump, OneModuleCVELF) { + Dump dump(0, kLittleEndian); + String module_name(dump, "elf module"); + Section cv_info(dump); + cv_info + .D32(MD_CVINFOELF_SIGNATURE) // signature + // build_id + .Append("\x5f\xa9\xcd\xb4\x10\x53\xdf\x1b\x86\xfa\xb7\x33\xb4\xdf" + "\x37\x38\xce\xa3\x4a\x87"); + + const MDRawSystemInfo linux_x86 = { + MD_CPU_ARCHITECTURE_X86, // processor_architecture + 6, // processor_level + 0xd08, // processor_revision + 1, // number_of_processors + 0, // product_type + 0, // major_version + 0, // minor_version + 0, // build_number + MD_OS_LINUX, // platform_id + 0xdeadbeef, // csd_version_rva + 0x100, // suite_mask + 0, // reserved2 + { // cpu + { // x86_cpu_info + { 0x756e6547, 0x49656e69, 0x6c65746e }, // vendor_id + 0x6d8, // version_information + 0xafe9fbff, // feature_information + 0xffffffff // amd_extended_cpu_features + } + } + }; + String csd_version(dump, "Literally Linux"); + SystemInfo system_info(dump, linux_x86, csd_version); + + Module module(dump, 0xa90206ca83eb2852ULL, 0xada542bd, + module_name, + 0xb1054d2a, + 0x34571371, + fixed_file_info, // from synth_minidump_unittest_data.h + &cv_info, nullptr); + + dump.Add(&module); + dump.Add(&module_name); + dump.Add(&cv_info); + dump.Add(&system_info); + dump.Add(&csd_version); + dump.Finish(); + + string contents; + ASSERT_TRUE(dump.GetContents(&contents)); + istringstream minidump_stream(contents); + Minidump minidump(minidump_stream); + ASSERT_TRUE(minidump.Read()); + + MinidumpModuleList *md_module_list = minidump.GetModuleList(); + ASSERT_TRUE(md_module_list != NULL); + ASSERT_EQ(1U, md_module_list->module_count()); + + const MinidumpModule *md_module = md_module_list->GetModuleAtIndex(0); + ASSERT_TRUE(md_module != NULL); + ASSERT_EQ(0xa90206ca83eb2852ULL, md_module->base_address()); + ASSERT_EQ(0xada542bd, md_module->size()); + ASSERT_EQ("elf module", md_module->code_file()); + // debug_file == code_file + ASSERT_EQ("elf module", md_module->debug_file()); + // just the build_id, directly + ASSERT_EQ("5fa9cdb41053df1b86fab733b4df3738cea34a87", + md_module->code_identifier()); + // build_id truncted to GUID length and treated as such, with zero + // age appended + ASSERT_EQ("B4CDA95F53101BDF86FAB733B4DF37380", md_module->debug_identifier()); + + const MDRawModule *md_raw_module = md_module->module(); + ASSERT_TRUE(md_raw_module != NULL); + ASSERT_EQ(0xb1054d2aU, md_raw_module->time_date_stamp); + ASSERT_EQ(0x34571371U, md_raw_module->checksum); + ASSERT_TRUE(memcmp(&md_raw_module->version_info, &fixed_file_info, + sizeof(fixed_file_info)) == 0); +} + +// Test that a build_id that's shorter than a GUID is handled properly. +TEST(Dump, CVELFShort) { + Dump dump(0, kLittleEndian); + String module_name(dump, "elf module"); + Section cv_info(dump); + cv_info + .D32(MD_CVINFOELF_SIGNATURE) // signature + // build_id, shorter than a GUID + .Append("\x5f\xa9\xcd\xb4"); + + const MDRawSystemInfo linux_x86 = { + MD_CPU_ARCHITECTURE_X86, // processor_architecture + 6, // processor_level + 0xd08, // processor_revision + 1, // number_of_processors + 0, // product_type + 0, // major_version + 0, // minor_version + 0, // build_number + MD_OS_LINUX, // platform_id + 0xdeadbeef, // csd_version_rva + 0x100, // suite_mask + 0, // reserved2 + { // cpu + { // x86_cpu_info + { 0x756e6547, 0x49656e69, 0x6c65746e }, // vendor_id + 0x6d8, // version_information + 0xafe9fbff, // feature_information + 0xffffffff // amd_extended_cpu_features + } + } + }; + String csd_version(dump, "Literally Linux"); + SystemInfo system_info(dump, linux_x86, csd_version); + + Module module(dump, 0xa90206ca83eb2852ULL, 0xada542bd, + module_name, + 0xb1054d2a, + 0x34571371, + fixed_file_info, // from synth_minidump_unittest_data.h + &cv_info, nullptr); + + dump.Add(&module); + dump.Add(&module_name); + dump.Add(&cv_info); + dump.Add(&system_info); + dump.Add(&csd_version); + dump.Finish(); + + string contents; + ASSERT_TRUE(dump.GetContents(&contents)); + istringstream minidump_stream(contents); + Minidump minidump(minidump_stream); + ASSERT_TRUE(minidump.Read()); + ASSERT_EQ(2U, minidump.GetDirectoryEntryCount()); + + MinidumpModuleList *md_module_list = minidump.GetModuleList(); + ASSERT_TRUE(md_module_list != NULL); + ASSERT_EQ(1U, md_module_list->module_count()); + + const MinidumpModule *md_module = md_module_list->GetModuleAtIndex(0); + ASSERT_TRUE(md_module != NULL); + // just the build_id, directly + ASSERT_EQ("5fa9cdb4", md_module->code_identifier()); + // build_id expanded to GUID length and treated as such, with zero + // age appended + ASSERT_EQ("B4CDA95F0000000000000000000000000", md_module->debug_identifier()); +} + TEST(Dump, OneSystemInfo) { Dump dump(0, kLittleEndian); String csd_version(dump, "Petulant Pierogi"); |