Change MDCVInfoELF into something usable.

This patch changes MDCVInfoELF (which is currently unused, apparently
a vestigal bit of code landed as part of Solaris support) into a supported
CodeView format that simply contains a build id as raw bytes.

Modern ELF toolchains support build ids nicely:
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Developer_Guide/compiling-build-id.html

It would be useful to have the original build ids of loaded modules in
Linux minidumps, since tools like Fedora's darkserver allow querying by build
id and the current Breakpad code truncates the build id to the size of a GUID,
which loses information:
https://darkserver.fedoraproject.org/

A follow-up patch will change the Linux minidump generation code to produce
MDCVInfoELF in minidumps instead of MDCVInfoPDB70. This patch should be landed
first to ensure that crash processors are able to handle this format before
dumps are generated containing it.

The full build id is exposed as the return value of Minidump::code_identifier(),
which currently just returns "id" for modules in Linux dumps. For
backwards-compatibility, Minidump::debug_identifier() continues to treat
the build id as a GUID, so debug identifiers for existing modules will not
change.

BUG=
R=mark@chromium.org

Review URL: https://codereview.chromium.org/1675413002 .
This commit is contained in:
Ted Mielczarek 2016-02-10 09:00:02 -05:00
parent 4becb11709
commit 7a364b1262
4 changed files with 313 additions and 45 deletions

View File

@ -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

View File

@ -75,6 +75,12 @@ class minidump_size<MDCVInfoPDB70> {
static size_t size() { return MDCVInfoPDB70_minsize; }
};
template<>
class minidump_size<MDCVInfoELF> {
public:
static size_t size() { return MDCVInfoELF_minsize; }
};
template<>
class minidump_size<MDImageDebugMisc> {
public:

View File

@ -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;

View File

@ -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");