| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/debug/elf_reader.h" |
| |
| #include <arpa/inet.h> |
| #include <elf.h> |
| #include <string.h> |
| |
| #include "base/bits.h" |
| #include "base/containers/span.h" |
| #include "base/hash/sha1.h" |
| #include "base/strings/safe_sprintf.h" |
| #include "build/build_config.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| // NOTE: This code may be used in crash handling code, so the implementation |
| // must avoid dynamic memory allocation or using data structures which rely on |
| // dynamic allocation. |
| |
| namespace base { |
| namespace debug { |
| namespace { |
| |
| // See https://refspecs.linuxbase.org/elf/elf.pdf for the ELF specification. |
| |
| #if __SIZEOF_POINTER__ == 4 |
| using Ehdr = Elf32_Ehdr; |
| using Dyn = Elf32_Dyn; |
| using Half = Elf32_Half; |
| using Nhdr = Elf32_Nhdr; |
| using Word = Elf32_Word; |
| using Xword = Elf32_Word; |
| #else |
| using Ehdr = Elf64_Ehdr; |
| using Dyn = Elf64_Dyn; |
| using Half = Elf64_Half; |
| using Nhdr = Elf64_Nhdr; |
| using Word = Elf64_Word; |
| using Xword = Elf64_Xword; |
| #endif |
| |
| constexpr char kGnuNoteName[] = "GNU"; |
| |
| // Returns a pointer to the header of the ELF binary mapped into memory, or a |
| // null pointer if the header is invalid. Here and below |elf_mapped_base| is a |
| // pointer to the start of the ELF image. |
| const Ehdr* GetElfHeader(const void* elf_mapped_base) { |
| if (strncmp(reinterpret_cast<const char*>(elf_mapped_base), ELFMAG, |
| SELFMAG) != 0) |
| return nullptr; |
| |
| return reinterpret_cast<const Ehdr*>(elf_mapped_base); |
| } |
| |
| } // namespace |
| |
| size_t ReadElfBuildId(const void* elf_mapped_base, |
| bool uppercase, |
| ElfBuildIdBuffer build_id) { |
| // NOTE: Function should use async signal safe calls only. |
| |
| const Ehdr* elf_header = GetElfHeader(elf_mapped_base); |
| if (!elf_header) |
| return 0; |
| |
| const size_t relocation_offset = GetRelocationOffset(elf_mapped_base); |
| for (const Phdr& header : GetElfProgramHeaders(elf_mapped_base)) { |
| if (header.p_type != PT_NOTE) |
| continue; |
| |
| // Look for a NT_GNU_BUILD_ID note with name == "GNU". |
| const char* current_section = |
| reinterpret_cast<const char*>(header.p_vaddr + relocation_offset); |
| const char* section_end = current_section + header.p_memsz; |
| const Nhdr* current_note = nullptr; |
| bool found = false; |
| while (current_section < section_end) { |
| current_note = reinterpret_cast<const Nhdr*>(current_section); |
| if (current_note->n_type == NT_GNU_BUILD_ID) { |
| StringPiece note_name(current_section + sizeof(Nhdr), |
| current_note->n_namesz); |
| // Explicit constructor is used to include the '\0' character. |
| if (note_name == StringPiece(kGnuNoteName, sizeof(kGnuNoteName))) { |
| found = true; |
| break; |
| } |
| } |
| |
| size_t section_size = bits::AlignUp(current_note->n_namesz, 4u) + |
| bits::AlignUp(current_note->n_descsz, 4u) + |
| sizeof(Nhdr); |
| if (section_size > static_cast<size_t>(section_end - current_section)) |
| return 0; |
| current_section += section_size; |
| } |
| |
| if (!found) |
| continue; |
| |
| // Validate that the serialized build ID will fit inside |build_id|. |
| size_t note_size = current_note->n_descsz; |
| if ((note_size * 2) > kMaxBuildIdStringLength) |
| continue; |
| |
| // Write out the build ID as a null-terminated hex string. |
| const uint8_t* build_id_raw = |
| reinterpret_cast<const uint8_t*>(current_note) + sizeof(Nhdr) + |
| bits::AlignUp(current_note->n_namesz, 4u); |
| size_t i = 0; |
| for (i = 0; i < current_note->n_descsz; ++i) { |
| strings::SafeSNPrintf(&build_id[i * 2], 3, (uppercase ? "%02X" : "%02x"), |
| build_id_raw[i]); |
| } |
| build_id[i * 2] = '\0'; |
| |
| // Return the length of the string. |
| return i * 2; |
| } |
| |
| return 0; |
| } |
| |
| absl::optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) { |
| // NOTE: Function should use async signal safe calls only. |
| |
| const Ehdr* elf_header = GetElfHeader(elf_mapped_base); |
| if (!elf_header) |
| return {}; |
| |
| const size_t relocation_offset = GetRelocationOffset(elf_mapped_base); |
| for (const Phdr& header : GetElfProgramHeaders(elf_mapped_base)) { |
| if (header.p_type != PT_DYNAMIC) |
| continue; |
| |
| // Read through the ELF dynamic sections to find the string table and |
| // SONAME offsets, which are used to compute the offset of the library |
| // name string. |
| const Dyn* dynamic_start = |
| reinterpret_cast<const Dyn*>(header.p_vaddr + relocation_offset); |
| const Dyn* dynamic_end = reinterpret_cast<const Dyn*>( |
| header.p_vaddr + relocation_offset + header.p_memsz); |
| Xword soname_strtab_offset = 0; |
| const char* strtab_addr = 0; |
| for (const Dyn* dynamic_iter = dynamic_start; dynamic_iter < dynamic_end; |
| ++dynamic_iter) { |
| if (dynamic_iter->d_tag == DT_STRTAB) { |
| #if BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_ANDROID) |
| // Fuchsia and Android do not relocate the symtab pointer on ELF load. |
| strtab_addr = static_cast<size_t>(dynamic_iter->d_un.d_ptr) + |
| reinterpret_cast<const char*>(relocation_offset); |
| #else |
| strtab_addr = reinterpret_cast<const char*>(dynamic_iter->d_un.d_ptr); |
| #endif |
| } else if (dynamic_iter->d_tag == DT_SONAME) { |
| // The Android NDK wrongly defines `d_val` as an Elf32_Sword for 32 bits |
| // and thus needs this cast. |
| soname_strtab_offset = static_cast<Xword>(dynamic_iter->d_un.d_val); |
| } |
| } |
| if (soname_strtab_offset && strtab_addr) |
| return StringPiece(strtab_addr + soname_strtab_offset); |
| } |
| |
| return absl::nullopt; |
| } |
| |
| span<const Phdr> GetElfProgramHeaders(const void* elf_mapped_base) { |
| // NOTE: Function should use async signal safe calls only. |
| |
| const Ehdr* elf_header = GetElfHeader(elf_mapped_base); |
| if (!elf_header) |
| return {}; |
| |
| const char* phdr_start = |
| reinterpret_cast<const char*>(elf_header) + elf_header->e_phoff; |
| return span<const Phdr>(reinterpret_cast<const Phdr*>(phdr_start), |
| elf_header->e_phnum); |
| } |
| |
| // Returns the offset to add to virtual addresses in the image to compute the |
| // mapped virtual address. |
| size_t GetRelocationOffset(const void* elf_mapped_base) { |
| span<const Phdr> headers = GetElfProgramHeaders(elf_mapped_base); |
| for (const Phdr& header : headers) { |
| if (header.p_type == PT_LOAD) { |
| // |elf_mapped_base| + |header.p_offset| is the mapped address of this |
| // segment. |header.p_vaddr| is the specified virtual address within the |
| // ELF image. |
| const char* const mapped_address = |
| reinterpret_cast<const char*>(elf_mapped_base) + header.p_offset; |
| return reinterpret_cast<uintptr_t>(mapped_address) - header.p_vaddr; |
| } |
| } |
| |
| // Assume the virtual addresses in the image start at 0, so the offset is |
| // from 0 to the actual mapped base address. |
| return static_cast<size_t>(reinterpret_cast<uintptr_t>(elf_mapped_base) - |
| reinterpret_cast<uintptr_t>(nullptr)); |
| } |
| |
| } // namespace debug |
| } // namespace base |