/* * Copyright (C) 2018 The Android Open Source Project * * 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 "elf_reader.h" #include #include #include #include #include #include #include #include #include #include #include #include "string_offset_table.h" #include "string_table.h" namespace nogrod { using berberis::bit_cast; using berberis::StringPrintf; namespace { [[nodiscard]] constexpr uint8_t ElfStType(uint32_t info) { return info & 0xf; } class Elf32 { public: using Off = Elf32_Off; using Word = Elf32_Word; using Ehdr = Elf32_Ehdr; using Shdr = Elf32_Shdr; using Sym = Elf32_Sym; Elf32() = delete; Elf32(const Elf32&) = delete; const Elf32& operator=(const Elf32&) = delete; }; class Elf64 { public: using Off = Elf64_Off; using Word = Elf64_Word; using Ehdr = Elf64_Ehdr; using Shdr = Elf64_Shdr; using Sym = Elf64_Sym; Elf64() = delete; Elf64(const Elf64&) = delete; const Elf64& operator=(const Elf64&) = delete; }; template class ElfFileImpl : public ElfFile { public: ~ElfFileImpl() override; static std::unique_ptr> Create(const char* path, int fd, std::string* error_msg); [[nodiscard]] bool ReadExportedSymbols(std::vector* symbols, std::string* error_msg) override; [[nodiscard]] std::unique_ptr ReadDwarfInfo(std::string* error_msg) override; private: explicit ElfFileImpl(const char* path, int fd); [[nodiscard]] bool Init(std::string* error_msg); [[nodiscard]] bool ValidateShdrTable(std::string* error_msg); const typename ElfT::Shdr* FindSectionHeaderByType(typename ElfT::Word sh_type); const typename ElfT::Shdr* FindSectionHeaderByName(const char* name); template [[nodiscard]] const T* OffsetToAddr(typename ElfT::Off offset) const; template [[nodiscard]] const T* ShdrOffsetToAddr(const typename ElfT::Shdr* shdr) const; std::string path_; int fd_; MappedFileFragment mapped_file_; const typename ElfT::Ehdr* header_; const typename ElfT::Shdr* shdr_table_; size_t shdr_num_; StringTable strtab_; }; template ElfFileImpl::ElfFileImpl(const char* path, int fd) : path_(path), fd_(fd), header_(nullptr), shdr_table_(nullptr), shdr_num_(0) {} template ElfFileImpl::~ElfFileImpl() { close(fd_); } template bool ElfFileImpl::ValidateShdrTable(std::string* error_msg) { size_t file_size = mapped_file_.size(); for (size_t i = 0; i < shdr_num_; ++i) { const typename ElfT::Shdr* shdr = shdr_table_ + i; if (shdr->sh_link >= shdr_num_) { *error_msg = StringPrintf( "section %zd: sh_link (%d) is out of bounds (shnum=%zd)", i, shdr->sh_link, shdr_num_); return false; } // Skip boundary checks for SHT_NOBIT section headers. if (shdr->sh_type == SHT_NOBITS) { continue; } if (shdr->sh_offset >= file_size) { *error_msg = StringPrintf("section %zd: offset (%zd) is out of bounds (file_size=%zd)", i, static_cast(shdr->sh_offset), file_size); return false; } size_t section_end = shdr->sh_offset + shdr->sh_size; if (section_end > file_size) { *error_msg = StringPrintf("section %zd: offset+size (%zd) is out of bounds (file_size=%zd)", i, section_end, file_size); return false; } } return true; } template template const T* ElfFileImpl::OffsetToAddr(typename ElfT::Off offset) const { auto start = bit_cast(mapped_file_.data()); return bit_cast(start + offset); } template template const T* ElfFileImpl::ShdrOffsetToAddr(const typename ElfT::Shdr* shdr) const { CHECK(shdr->sh_type != SHT_NOBITS); return OffsetToAddr(shdr->sh_offset); } template bool ElfFileImpl::Init(std::string* error_msg) { struct stat st {}; if (fstat(fd_, &st) == -1) { *error_msg = StringPrintf("unable to stat \"%s\": %s", path_.c_str(), strerror(errno)); return false; } size_t size = st.st_size; if (!mapped_file_.Map(fd_, 0, 0, size)) { *error_msg = StringPrintf("unable to map the file \"%s\"", path_.c_str()); return false; } if (size < sizeof(typename ElfT::Ehdr)) { *error_msg = StringPrintf( "file \"%s\" is too small(%zd), there is not enough space for an ELF header(%zd)", path_.c_str(), size, sizeof(typename ElfT::Ehdr)); return false; } header_ = OffsetToAddr(0); uintptr_t shdr_offset = header_->e_shoff; size_t shdr_num = header_->e_shnum; if (header_->e_shentsize != sizeof(typename ElfT::Shdr)) { *error_msg = StringPrintf("invalid e_shentsize: %d, expected: %zd", header_->e_shentsize, sizeof(typename ElfT::Shdr)); return false; } if (shdr_offset >= size) { *error_msg = StringPrintf("file \"%s\" is too small, e_shoff(%zd) is out of bounds (%zd)", path_.c_str(), shdr_offset, size); return false; } if (shdr_offset + (shdr_num * sizeof(typename ElfT::Shdr)) > size) { *error_msg = StringPrintf("file \"%s\" is too small, e_shoff + shdr_size (%zd) is out of bounds (%zd)", path_.c_str(), shdr_offset + (shdr_num * sizeof(typename ElfT::Shdr)), size); return false; } shdr_table_ = OffsetToAddr(shdr_offset); shdr_num_ = shdr_num; if (!ValidateShdrTable(error_msg)) { return false; } if (header_->e_shstrndx == SHN_UNDEF) { *error_msg = StringPrintf( "\"%s\": e_shstrndx is not defined, this is not good because " "section names are needed to extract dwarf_info", path_.c_str()); return false; } if (header_->e_shstrndx >= shdr_num) { *error_msg = StringPrintf("\"%s\" invalid e_shstrndx (%d) - out of bounds (e_shnum=%zd)", path_.c_str(), header_->e_shstrndx, shdr_num); return false; } const typename ElfT::Shdr* strtab_shdr = &shdr_table_[header_->e_shstrndx]; strtab_ = StringTable(ShdrOffsetToAddr(strtab_shdr), strtab_shdr->sh_size); return true; } template std::unique_ptr> ElfFileImpl::Create(const char* path, int fd, std::string* error_msg) { std::unique_ptr> result(new ElfFileImpl(path, fd)); if (!result->Init(error_msg)) { return nullptr; } return result; } template const typename ElfT::Shdr* ElfFileImpl::FindSectionHeaderByType(typename ElfT::Word sh_type) { for (size_t i = 0; i < shdr_num_; ++i) { if (shdr_table_[i].sh_type == sh_type) { return shdr_table_ + i; } } return nullptr; } template const typename ElfT::Shdr* ElfFileImpl::FindSectionHeaderByName(const char* name) { for (size_t i = 0; i < shdr_num_; ++i) { if (strcmp(name, strtab_.GetString(shdr_table_[i].sh_name)) == 0) { return shdr_table_ + i; } } return nullptr; } template bool ElfFileImpl::ReadExportedSymbols(std::vector* symbols, std::string* error_msg) { const typename ElfT::Shdr* dynsym_shdr = FindSectionHeaderByType(SHT_DYNSYM); if (dynsym_shdr == nullptr) { *error_msg = "dynamic symbol section was not found"; return false; } if (dynsym_shdr->sh_size % sizeof(typename ElfT::Sym) != 0) { *error_msg = StringPrintf("invalid SHT_DYNSYM section size(%zd): should be divisible by %zd", static_cast(dynsym_shdr->sh_size), sizeof(typename ElfT::Sym)); return false; } size_t dynsym_num = dynsym_shdr->sh_size / sizeof(typename ElfT::Sym); const auto* dynsyms = ShdrOffsetToAddr(dynsym_shdr); const typename ElfT::Shdr* strtab_shdr = shdr_table_ + dynsym_shdr->sh_link; const StringTable strtab(ShdrOffsetToAddr(strtab_shdr), strtab_shdr->sh_size); for (size_t i = 0; i < dynsym_num; ++i) { const typename ElfT::Sym* sym = dynsyms + i; // skip undefined symbols if (sym->st_shndx == SHN_UNDEF) { continue; } // We are interested only in functions and variables. // This is a bit strange but the fact of the matter is that ld.gold generates OBJECT // of size 0 for version labels - we need to skip them as well. uint8_t st_type = ElfStType(sym->st_info); if (st_type == STT_FUNC || (st_type == STT_OBJECT && sym->st_size != 0)) { symbols->push_back(strtab.GetString(sym->st_name)); } } return true; } template std::unique_ptr ElfFileImpl::ReadDwarfInfo(std::string* error_msg) { const typename ElfT::Shdr* dwarf_abbrev_shdr = FindSectionHeaderByName(".debug_abbrev"); if (dwarf_abbrev_shdr == nullptr) { *error_msg = "couldn't find .debug_abbrev section"; return nullptr; } const typename ElfT::Shdr* dwarf_info_shdr = FindSectionHeaderByName(".debug_info"); if (dwarf_info_shdr == nullptr) { *error_msg = "couldn't find .debug_info section"; return nullptr; } const typename ElfT::Shdr* dwarf_str_shdr = FindSectionHeaderByName(".debug_str"); if (dwarf_str_shdr == nullptr) { *error_msg = "couldn't find .debug_str section"; return nullptr; } StringTable string_table{ShdrOffsetToAddr(dwarf_str_shdr), dwarf_str_shdr->sh_size}; // This section is optional (at least as of now) const typename ElfT::Shdr* debug_str_offsets_shdr = FindSectionHeaderByName(".debug_str_offsets"); std::optional string_offsets_table; if (debug_str_offsets_shdr != nullptr) { string_offsets_table.emplace(ShdrOffsetToAddr(debug_str_offsets_shdr), debug_str_offsets_shdr->sh_size); } std::unique_ptr dwarf_info( new DwarfInfo(ShdrOffsetToAddr(dwarf_abbrev_shdr), dwarf_abbrev_shdr->sh_size, ShdrOffsetToAddr(dwarf_info_shdr), dwarf_info_shdr->sh_size, string_table, string_offsets_table)); if (!dwarf_info->Parse(error_msg)) { return nullptr; } return dwarf_info; } } // namespace std::unique_ptr ElfFile::Load(const char* path, std::string* error_msg) { int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC)); if (fd == -1) { *error_msg = strerror(errno); return nullptr; } // Read header in order verify the file and detect bitness uint8_t e_ident[EI_NIDENT]; ssize_t res = TEMP_FAILURE_RETRY(pread64(fd, e_ident, sizeof(e_ident), 0)); if (res < 0) { *error_msg = strerror(errno); return nullptr; } if (res != sizeof(e_ident)) { *error_msg = "file is too small for an ELF file"; return nullptr; } if (memcmp(e_ident, ELFMAG, SELFMAG) != 0) { *error_msg = "bad ELF magic"; return nullptr; } std::unique_ptr result; if (e_ident[EI_CLASS] == ELFCLASS32) { result = ElfFileImpl::Create(path, fd, error_msg); } else if (e_ident[EI_CLASS] == ELFCLASS64) { result = ElfFileImpl::Create(path, fd, error_msg); } else { *error_msg = StringPrintf("bad EI_CLASS: %d", e_ident[EI_CLASS]); } return result; } } // namespace nogrod