From 8ade75f9558e31d25aec862a552fe78f9ba98c82 Mon Sep 17 00:00:00 2001 From: "ted.mielczarek" Date: Tue, 30 Aug 2011 15:21:07 +0000 Subject: issue 243 - Linux dumper should use build id produced by ld --build-id if available R=thestig at http://breakpad.appspot.com/185001 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@830 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/common/linux/file_id.cc | 160 ++++++++++++++++++++++++++--------- src/common/linux/file_id.h | 5 +- src/common/linux/file_id_unittest.cc | 150 ++++++++++++++++++++------------ src/common/linux/synth_elf.cc | 32 ++++++- src/common/linux/synth_elf.h | 9 ++ 5 files changed, 260 insertions(+), 96 deletions(-) (limited to 'src/common/linux') diff --git a/src/common/linux/file_id.cc b/src/common/linux/file_id.cc index 2227c83b..acd448a9 100644 --- a/src/common/linux/file_id.cc +++ b/src/common/linux/file_id.cc @@ -56,39 +56,46 @@ namespace google_breakpad { +#ifndef NT_GNU_BUILD_ID +#define NT_GNU_BUILD_ID 3 +#endif + FileID::FileID(const char* path) { strncpy(path_, path, sizeof(path_)); } struct ElfClass32 { typedef Elf32_Ehdr Ehdr; + typedef Elf32_Nhdr Nhdr; typedef Elf32_Shdr Shdr; static const int kClass = ELFCLASS32; }; struct ElfClass64 { typedef Elf64_Ehdr Ehdr; + typedef Elf64_Nhdr Nhdr; typedef Elf64_Shdr Shdr; static const int kClass = ELFCLASS64; }; -// These three functions are also used inside the crashed process, so be safe +// These six functions are also used inside the crashed process, so be safe // and use the syscall/libc wrappers instead of direct syscalls or libc. template -static void FindElfClassTextSection(const char *elf_base, - const void **text_start, - int *text_size) { +static void FindElfClassSection(const char *elf_base, + const char *section_name, + uint32_t section_type, + const void **section_start, + int *section_size) { typedef typename ElfClass::Ehdr Ehdr; typedef typename ElfClass::Shdr Shdr; assert(elf_base); - assert(text_start); - assert(text_size); + assert(section_start); + assert(section_size); assert(my_strncmp(elf_base, ELFMAG, SELFMAG) == 0); - const char* text_section_name = ".text"; - int name_len = my_strlen(text_section_name); + int name_len = my_strlen(section_name); const Ehdr* elf_header = reinterpret_cast(elf_base); assert(elf_header->e_ident[EI_CLASS] == ElfClass::kClass); @@ -97,30 +104,41 @@ static void FindElfClassTextSection(const char *elf_base, reinterpret_cast(elf_base + elf_header->e_shoff); const Shdr* string_section = sections + elf_header->e_shstrndx; - const Shdr* text_section = NULL; + const Shdr* section = NULL; for (int i = 0; i < elf_header->e_shnum; ++i) { - if (sections[i].sh_type == SHT_PROGBITS) { + if (sections[i].sh_type == section_type) { const char* section_name = (char*)(elf_base + string_section->sh_offset + sections[i].sh_name); - if (!my_strncmp(section_name, text_section_name, name_len)) { - text_section = §ions[i]; + if (!my_strncmp(section_name, section_name, name_len)) { + section = §ions[i]; break; } } } - if (text_section != NULL && text_section->sh_size > 0) { - *text_start = elf_base + text_section->sh_offset; - *text_size = text_section->sh_size; + if (section != NULL && section->sh_size > 0) { + *section_start = elf_base + section->sh_offset; + *section_size = section->sh_size; } } -static bool FindElfTextSection(const void *elf_mapped_base, - const void **text_start, - int *text_size) { +// Attempt to find a section named |section_name| of type |section_type| +// in the ELF binary data at |elf_mapped_base|. On success, returns true +// and sets |*section_start| to point to the start of the section data, +// and |*section_size| to the size of the section's data. If |elfclass| +// is not NULL, set |*elfclass| to the ELF file class. +static bool FindElfSection(const void *elf_mapped_base, + const char *section_name, + uint32_t section_type, + const void **section_start, + int *section_size, + int *elfclass) { assert(elf_mapped_base); - assert(text_start); - assert(text_size); + assert(section_start); + assert(section_size); + + *section_start = NULL; + *section_size = 0; const char* elf_base = static_cast(elf_mapped_base); @@ -129,14 +147,89 @@ static bool FindElfTextSection(const void *elf_mapped_base, if (my_strncmp(elf_base, ELFMAG, SELFMAG) != 0) return false; + if (elfclass) { + *elfclass = elf_header->e_ident[EI_CLASS]; + } + if (elf_header->e_ident[EI_CLASS] == ELFCLASS32) { - FindElfClassTextSection(elf_base, text_start, text_size); + FindElfClassSection(elf_base, section_name, section_type, + section_start, section_size); + return *section_start != NULL; } else if (elf_header->e_ident[EI_CLASS] == ELFCLASS64) { - FindElfClassTextSection(elf_base, text_start, text_size); - } else { + FindElfClassSection(elf_base, section_name, section_type, + section_start, section_size); + return *section_start != NULL; + } + + return false; +} + +template +static bool ElfClassBuildIDNoteIdentifier(const void *section, + uint8_t identifier[kMDGUIDSize]) +{ + typedef typename ElfClass::Nhdr Nhdr; + + const Nhdr* note_header = reinterpret_cast(section); + if (note_header->n_type != NT_GNU_BUILD_ID || + note_header->n_descsz == 0) { return false; } + const char* build_id = reinterpret_cast(section) + + sizeof(Nhdr) + note_header->n_namesz; + // Copy as many bits of the build ID as will fit + // into the GUID space. + my_memset(identifier, 0, kMDGUIDSize); + memcpy(identifier, build_id, + std::min(kMDGUIDSize, (size_t)note_header->n_descsz)); + + return true; +} + +// Attempt to locate a .note.gnu.build-id section in an ELF binary +// and copy as many bytes of it as will fit into |identifier|. +static bool FindElfBuildIDNote(const void *elf_mapped_base, + uint8_t identifier[kMDGUIDSize]) +{ + void* note_section; + int note_size, elfclass; + if (!FindElfSection(elf_mapped_base, ".note.gnu.build-id", SHT_NOTE, + (const void**)¬e_section, ¬e_size, &elfclass) || + note_size == 0) { + return false; + } + + if (elfclass == ELFCLASS32) { + return ElfClassBuildIDNoteIdentifier(note_section, identifier); + } else if (elfclass == ELFCLASS64) { + return ElfClassBuildIDNoteIdentifier(note_section, identifier); + } + + return false; +} + +// Attempt to locate the .text section of an ELF binary and generate +// a simple hash by XORing the first page worth of bytes into |identifier|. +static bool HashElfTextSection(const void *elf_mapped_base, + uint8_t identifier[kMDGUIDSize]) { + + void* text_section; + int text_size; + if (!FindElfSection(elf_mapped_base, ".text", SHT_PROGBITS, + (const void**)&text_section, &text_size, NULL) || + text_size == 0) { + return false; + } + + my_memset(identifier, 0, kMDGUIDSize); + const uint8_t* ptr = reinterpret_cast(text_section); + const uint8_t* ptr_end = ptr + std::min(text_size, 4096); + while (ptr < ptr_end) { + for (unsigned i = 0; i < kMDGUIDSize; i++) + identifier[i] ^= ptr[i]; + ptr += kMDGUIDSize; + } return true; } @@ -144,21 +237,12 @@ static bool FindElfTextSection(const void *elf_mapped_base, bool FileID::ElfFileIdentifierFromMappedFile(void* base, uint8_t identifier[kMDGUIDSize]) { - const void* text_section = NULL; - int text_size = 0; - bool success = false; - if (FindElfTextSection(base, &text_section, &text_size) && (text_size > 0)) { - my_memset(identifier, 0, kMDGUIDSize); - const uint8_t* ptr = reinterpret_cast(text_section); - const uint8_t* ptr_end = ptr + std::min(text_size, 4096); - while (ptr < ptr_end) { - for (unsigned i = 0; i < kMDGUIDSize; i++) - identifier[i] ^= ptr[i]; - ptr += kMDGUIDSize; - } - success = true; - } - return success; + // Look for a build id note first. + if (FindElfBuildIDNote(base, identifier)) + return true; + + // Fall back on hashing the first page of the text section. + return HashElfTextSection(base, identifier); } bool FileID::ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]) { diff --git a/src/common/linux/file_id.h b/src/common/linux/file_id.h index 2cd4953e..4d5b52dc 100644 --- a/src/common/linux/file_id.h +++ b/src/common/linux/file_id.h @@ -49,8 +49,9 @@ class FileID { // Load the identifier for the elf file path specified in the constructor into // |identifier|. Return false if the identifier could not be created for the // file. - // The current implementation will XOR the first 4096 bytes of the - // .text section to generate an identifier. + // The current implementation will look for a .note.gnu.build-id + // section and use that as the file id, otherwise it falls back to + // XORing the first 4096 bytes of the .text section to generate an identifier. bool ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]); // Load the identifier for the elf file mapped into memory at |base| into diff --git a/src/common/linux/file_id_unittest.cc b/src/common/linux/file_id_unittest.cc index f5298b12..24208983 100644 --- a/src/common/linux/file_id_unittest.cc +++ b/src/common/linux/file_id_unittest.cc @@ -33,18 +33,20 @@ #include #include "common/linux/file_id.h" +#include "common/linux/synth_elf.h" +#include "common/test_assembler.h" #include "breakpad_googletest_includes.h" using namespace google_breakpad; - -namespace { -typedef testing::Test FileIDTest; -} - -TEST(FileIDTest, FileIDStrip) { - // Calculate the File ID of our binary using - // FileID::ElfFileIdentifier, then make a copy of our binary, - // strip it, and ensure that we still get the same result. +using google_breakpad::synth_elf::BuildIDNote; +using google_breakpad::synth_elf::ELF; +using google_breakpad::test_assembler::kLittleEndian; +using google_breakpad::test_assembler::Section; + +TEST(FileIDStripTest, StripSelf) { + // Calculate the File ID of this binary using + // FileID::ElfFileIdentifier, then make a copy of this binary, + // strip it, and ensure that the result is the same. char exe_name[PATH_MAX]; ssize_t len = readlink("/proc/self/exe", exe_name, PATH_MAX - 1); ASSERT_NE(len, -1); @@ -75,58 +77,40 @@ TEST(FileIDTest, FileIDStrip) { unlink(templ); } -struct ElfClass32 { - typedef Elf32_Ehdr Ehdr; - typedef Elf32_Shdr Shdr; - static const int kClass = ELFCLASS32; -}; +class FileIDTest : public testing::Test { +public: + void GetElfContents(ELF& elf) { + string contents; + ASSERT_TRUE(elf.GetContents(&contents)); + ASSERT_LT(0, contents.size()); -struct ElfClass64 { - typedef Elf64_Ehdr Ehdr; - typedef Elf64_Shdr Shdr; - static const int kClass = ELFCLASS64; -}; - -template -struct ElfishElf { - static const size_t kTextSectionSize = 128; - typedef typename ElfClass::Ehdr Ehdr; - typedef typename ElfClass::Shdr Shdr; - - Ehdr elf_header; - Shdr text_header; - Shdr string_header; - char text_section[kTextSectionSize]; - char string_section[8]; - - static void Populate(ElfishElf* elf) { - memset(elf, 0, sizeof(ElfishElf)); - memcpy(elf, ELFMAG, SELFMAG); - elf->elf_header.e_ident[EI_CLASS] = ElfClass::kClass; - elf->elf_header.e_shoff = offsetof(ElfishElf, text_header); - elf->elf_header.e_shnum = 2; - elf->elf_header.e_shstrndx = 1; - elf->text_header.sh_name = 0; - elf->text_header.sh_type = SHT_PROGBITS; - elf->text_header.sh_offset = offsetof(ElfishElf, text_section); - elf->text_header.sh_size = kTextSectionSize; - for (size_t i = 0; i < kTextSectionSize; ++i) { - elf->text_section[i] = i * 3; - } - elf->string_header.sh_offset = offsetof(ElfishElf, string_section); - strcpy(elf->string_section, ".text"); + elfdata_v.clear(); + elfdata_v.insert(elfdata_v.begin(), contents.begin(), contents.end()); + elfdata = &elfdata_v[0]; } + + vector elfdata_v; + uint8_t* elfdata; }; -TEST(FileIDTest, ElfClass) { +TEST_F(FileIDTest, ElfClass) { uint8_t identifier[sizeof(MDGUID)]; const char expected_identifier_string[] = "80808080-8080-0000-0000-008080808080"; char identifier_string[sizeof(expected_identifier_string)]; + const size_t kTextSectionSize = 128; + + ELF elf32(EM_386, ELFCLASS32, kLittleEndian); + Section text32(kLittleEndian); + for (size_t i = 0; i < kTextSectionSize; ++i) { + text32.D8(i * 3); + } + elf32.AddSection(".text", text32, SHT_PROGBITS); + elf32.Finish(); + GetElfContents(elf32); + + EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(elfdata, identifier)); - ElfishElf elf32; - ElfishElf::Populate(&elf32); - EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(&elf32, identifier)); FileID::ConvertIdentifierToString(identifier, identifier_string, sizeof(identifier_string)); EXPECT_STREQ(expected_identifier_string, identifier_string); @@ -134,9 +118,65 @@ TEST(FileIDTest, ElfClass) { memset(identifier, 0, sizeof(identifier)); memset(identifier_string, 0, sizeof(identifier_string)); - ElfishElf elf64; - ElfishElf::Populate(&elf64); - EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(&elf64, identifier)); + ELF elf64(EM_X86_64, ELFCLASS64, kLittleEndian); + Section text64(kLittleEndian); + for (size_t i = 0; i < kTextSectionSize; ++i) { + text64.D8(i * 3); + } + elf64.AddSection(".text", text64, SHT_PROGBITS); + elf64.Finish(); + GetElfContents(elf64); + + EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(elfdata, identifier)); + + FileID::ConvertIdentifierToString(identifier, identifier_string, + sizeof(identifier_string)); + EXPECT_STREQ(expected_identifier_string, identifier_string); +} + +TEST_F(FileIDTest, BuildID) { + const uint8_t kExpectedIdentifier[sizeof(MDGUID)] = + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; + char expected_identifier_string[] = + "00000000-0000-0000-0000-000000000000"; + FileID::ConvertIdentifierToString(kExpectedIdentifier, + expected_identifier_string, + sizeof(expected_identifier_string)); + + uint8_t identifier[sizeof(MDGUID)]; + char identifier_string[sizeof(expected_identifier_string)]; + + ELF elf32(EM_386, ELFCLASS32, kLittleEndian); + Section text(kLittleEndian); + text.Append(4096, 0); + elf32.AddSection(".text", text, SHT_PROGBITS); + BuildIDNote::AppendSection(elf32, + kExpectedIdentifier, + sizeof(kExpectedIdentifier)); + elf32.Finish(); + GetElfContents(elf32); + + EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(elfdata, identifier)); + + FileID::ConvertIdentifierToString(identifier, identifier_string, + sizeof(identifier_string)); + EXPECT_STREQ(expected_identifier_string, identifier_string); + + memset(identifier, 0, sizeof(identifier)); + memset(identifier_string, 0, sizeof(identifier_string)); + + ELF elf64(EM_X86_64, ELFCLASS64, kLittleEndian); + // Re-use empty text section from previous test + elf64.AddSection(".text", text, SHT_PROGBITS); + BuildIDNote::AppendSection(elf64, + kExpectedIdentifier, + sizeof(kExpectedIdentifier)); + elf64.Finish(); + GetElfContents(elf64); + + EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(elfdata, identifier)); + FileID::ConvertIdentifierToString(identifier, identifier_string, sizeof(identifier_string)); EXPECT_STREQ(expected_identifier_string, identifier_string); diff --git a/src/common/linux/synth_elf.cc b/src/common/linux/synth_elf.cc index 8f8aad32..3e10b691 100644 --- a/src/common/linux/synth_elf.cc +++ b/src/common/linux/synth_elf.cc @@ -3,10 +3,15 @@ #include #include #include +#include namespace google_breakpad { namespace synth_elf { +#ifndef NT_GNU_BUILD_ID +#define NT_GNU_BUILD_ID 3 +#endif + ELF::ELF(uint16_t machine, uint8_t file_class, Endianness endianness) @@ -169,6 +174,31 @@ void SymbolTable::AddSymbol(const string& name, uint64_t value, D64(size); } +BuildIDNote::BuildIDNote(const uint8_t* id_bytes, + size_t id_size, + Endianness endianness) : Section(endianness) { + const char kNoteName[] = "GNU"; + // Elf32_Nhdr and Elf64_Nhdr are exactly the same. + Elf32_Nhdr note_header; + memset(¬e_header, 0, sizeof(note_header)); + note_header.n_namesz = sizeof(kNoteName); + note_header.n_descsz = id_size; + note_header.n_type = NT_GNU_BUILD_ID; + + Append(reinterpret_cast(¬e_header), + sizeof(note_header)); + AppendCString(kNoteName); + Append(id_bytes, id_size); +} + +// static +void BuildIDNote::AppendSection(ELF& elf, + const uint8_t* id_bytes, + size_t id_size) { + const char kBuildIDSectionName[] = ".note.gnu.build-id"; + BuildIDNote note(id_bytes, id_size, elf.endianness()); + elf.AddSection(kBuildIDSectionName, note, SHT_NOTE); +} + } // namespace synth_elf } // namespace google_breakpad - diff --git a/src/common/linux/synth_elf.h b/src/common/linux/synth_elf.h index 215fd997..90bd8341 100644 --- a/src/common/linux/synth_elf.h +++ b/src/common/linux/synth_elf.h @@ -149,6 +149,15 @@ class SymbolTable : public Section { StringTable& table_; }; +// A class to build GNU Build ID note sections +class BuildIDNote : public Section { +public: + BuildIDNote(const uint8_t* id_bytes, size_t id_size, Endianness endianness); + + // Append a new Build ID note section to |elf|. + static void AppendSection(ELF& elf, const uint8_t* id_bytes, size_t id_size); +}; + } // namespace synth_elf } // namespace google_breakpad -- cgit v1.2.1