aboutsummaryrefslogtreecommitdiffstats
path: root/elff/elf_file.cc
diff options
context:
space:
mode:
Diffstat (limited to 'elff/elf_file.cc')
-rw-r--r--elff/elf_file.cc539
1 files changed, 539 insertions, 0 deletions
diff --git a/elff/elf_file.cc b/elff/elf_file.cc
new file mode 100644
index 0000000..70ed81c
--- /dev/null
+++ b/elff/elf_file.cc
@@ -0,0 +1,539 @@
+/* Copyright (C) 2007-2010 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+
+/*
+ * Contains implementation of ElfFile classes that encapsulate an ELF file.
+ */
+
+#include "string.h"
+#include "elf_file.h"
+#include "elf_alloc.h"
+#include "dwarf_cu.h"
+#include "dwarf_utils.h"
+
+/* Tags to parse when collecting info about routines. */
+static const Dwarf_Tag parse_rt_tags[] = {
+ DW_TAG_compile_unit,
+ DW_TAG_partial_unit,
+ DW_TAG_inlined_subroutine,
+ DW_TAG_subprogram,
+ 0
+};
+static const DwarfParseContext parse_rt_context = { parse_rt_tags };
+
+//=============================================================================
+// Base ElfFile implementation
+//=============================================================================
+
+ElfFile::ElfFile()
+ : sec_table_(NULL),
+ elf_file_path_(NULL),
+ sec_count_(0),
+ cu_count_(0),
+ last_cu_(NULL),
+ allocator_(NULL),
+ fixed_base_address_(0),
+ is_exec_(0),
+ elf_handle_(INVALID_ELF_FILE_HANDLE),
+ sec_entry_size_(0) {
+}
+
+ElfFile::~ElfFile() {
+ DwarfCU* cu_to_del = last_cu_;
+ while (cu_to_del != NULL) {
+ DwarfCU* next_cu_to_del = cu_to_del->prev_cu_;
+ delete cu_to_del;
+ cu_to_del = next_cu_to_del;
+ }
+
+ if (elfhandle_is_valid(elf_handle_)) {
+ close_elf_file_handle(elf_handle_);
+ }
+
+ if (elf_file_path_ != NULL) {
+ delete[] elf_file_path_;
+ }
+
+ if (sec_table_ != NULL) {
+ delete[] reinterpret_cast<Elf_Byte*>(sec_table_);
+ }
+
+ /* Must be deleted last! */
+ if (allocator_ != NULL) {
+ delete allocator_;
+ }
+}
+
+ElfFile* ElfFile::Create(const char* path) {
+ ElfFile* ret = NULL;
+ /* Allocate enough space on the stack to fit the largest ELF file header. */
+ Elf64_FHdr header;
+ const Elf_CommonHdr* elf_hdr = &header.common;
+
+ assert(path != NULL && *path != '\0');
+ if (path == NULL || *path == '\0') {
+ _set_errno(EINVAL);
+ return NULL;
+ }
+
+ /*
+ * Open ELF file, and read its header (the largest one possible).
+ */
+
+#ifdef WIN32
+ HANDLE file_handle = CreateFile(path, GENERIC_READ,
+ FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_FLAG_RANDOM_ACCESS, NULL);
+ assert(file_handle != INVALID_HANDLE_VALUE);
+ if (file_handle == INVALID_HANDLE_VALUE) {
+ _set_errno(GetLastError());
+ return NULL;
+ }
+
+ DWORD read_bytes;
+ BOOL res = ReadFile(file_handle, &header, sizeof(header), &read_bytes, NULL);
+ CloseHandle(file_handle);
+ assert(res && read_bytes == sizeof(header));
+ if (!res || read_bytes != sizeof(header)) {
+ _set_errno(GetLastError());
+ return NULL;
+ }
+#else // WIN32
+ int file_handle = open(path, O_RDONLY | O_BINARY, 0);
+ assert(file_handle >= 0);
+ if (file_handle < 0) {
+ return NULL;
+ }
+ const ssize_t read_bytes = read(file_handle, &header, sizeof(header));
+ close(file_handle);
+ assert(read_bytes != -1 && read_bytes == sizeof(header));
+ if (read_bytes == -1 || read_bytes != sizeof(header)) {
+ if (read_bytes != -1) {
+ _set_errno(EINVAL);
+ }
+ return NULL;
+ }
+#endif // WIN32
+
+ /* Lets see if this is an ELF file at all. */
+ if (memcmp(elf_hdr->e_ident, ELFMAG, SELFMAG) != 0) {
+ /* File is not an ELF file. */
+ _set_errno(ENOEXEC);
+ return NULL;
+ }
+
+ /* Lets check ELF's "bitness". */
+ assert(elf_hdr->ei_info.ei_class == ELFCLASS32 ||
+ elf_hdr->ei_info.ei_class == ELFCLASS64);
+ if (elf_hdr->ei_info.ei_class != ELFCLASS32 &&
+ elf_hdr->ei_info.ei_class != ELFCLASS64) {
+ /* Neither 32, or 64-bit ELF file. Something wrong here. */
+ _set_errno(EBADF);
+ return NULL;
+ }
+
+ /* Lets instantiate appropriate ElfFileImpl object for this ELF. */
+ if (elf_hdr->ei_info.ei_class == ELFCLASS32) {
+ ret = new ElfFileImpl<Elf32_Addr, Elf32_Off>;
+ } else {
+ ret = new ElfFileImpl<Elf64_Addr, Elf64_Off>;
+ }
+ assert(ret != NULL);
+ if (ret != NULL) {
+ if (!ret->initialize(elf_hdr, path)) {
+ delete ret;
+ ret = NULL;
+ }
+ } else {
+ _set_errno(ENOMEM);
+ }
+
+ return ret;
+}
+
+bool ElfFile::initialize(const Elf_CommonHdr* elf_hdr, const char* path) {
+ /* Must be created first! */
+ allocator_ = new ElfAllocator();
+ assert(allocator_ != NULL);
+ if (allocator_ == NULL) {
+ _set_errno(ENOMEM);
+ return false;
+ }
+
+ /* Copy file path. */
+ size_t path_len = strlen(path) + 1;
+ elf_file_path_ = new char[path_len];
+ assert(elf_file_path_ != NULL);
+ if (elf_file_path_ == NULL) {
+ _set_errno(ENOMEM);
+ return false;
+ }
+ memcpy(elf_file_path_, path, path_len);
+
+ /* Cache some basic ELF properties. */
+ is_ELF_64_ = elf_hdr->ei_info.ei_class == ELFCLASS64;
+ is_elf_big_endian_ = elf_hdr->ei_info.ei_data == ELFDATA2MSB;
+ same_endianness_ = is_elf_little_endian() == is_little_endian_cpu();
+ is_exec_ = elf_hdr->e_type == 2;
+
+ /* Reopen file for further reads and mappings. */
+#ifdef WIN32
+ elf_handle_ = CreateFile(elf_file_path_, GENERIC_READ,
+ FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_FLAG_RANDOM_ACCESS, NULL);
+ assert(elf_handle_ != INVALID_HANDLE_VALUE);
+ if (elf_handle_ == INVALID_HANDLE_VALUE) {
+ _set_errno(GetLastError());
+ return false;
+ }
+#else // WIN32
+ elf_handle_ = open(elf_file_path_, O_RDONLY | O_BINARY, 0);
+ assert(elf_handle_ >= 0);
+ if (elf_handle_ < 0) {
+ elf_handle_ = INVALID_ELF_FILE_HANDLE;
+ return false;
+ }
+#endif // WIN32
+
+ return true;
+}
+
+bool ElfFile::get_pc_address_info(Elf_Xword address,
+ Elf_AddressInfo* address_info) {
+ assert(address_info != NULL);
+ if (address_info == NULL) {
+ _set_errno(EINVAL);
+ return false;
+ }
+
+ /* Collect routine information for all CUs in this file. */
+ if (parse_compilation_units(&parse_rt_context) == -1) {
+ return false;
+ }
+
+ /* Iterate through the collected CUs looking for the one that
+ * contains the given address. */
+ address_info->inline_stack = NULL;
+ DwarfCU* cu = last_cu();
+ while (cu != NULL) {
+ /* Find a leaf DIE object in the current CU that contains the address. */
+ Dwarf_AddressInfo info;
+ info.die_obj = cu->get_leaf_die_for_address(address);
+ if (info.die_obj != NULL) {
+ /* Convert the address to a location inside source file. */
+ if (cu->get_pc_address_file_info(address, &info)) {
+ /* Copy location information to the returning structure. */
+ address_info->file_name = info.file_name;
+ address_info->dir_name = info.dir_name;
+ address_info->line_number = info.line_number;
+ } else {
+ address_info->file_name = NULL;
+ address_info->dir_name = NULL;
+ address_info->line_number = 0;
+ }
+
+ /* Lets see if the DIE represents a routine (rather than
+ * a lexical block, for instance). */
+ Dwarf_Tag tag = info.die_obj->get_tag();
+ while (!dwarf_tag_is_routine(tag)) {
+ /* This is not a routine DIE. Lets loop trhough the parents of that
+ * DIE looking for the first routine DIE. */
+ info.die_obj = info.die_obj->parent_die();
+ if (info.die_obj == NULL) {
+ /* Reached compilation unit DIE. Can't go any further. */
+ address_info->routine_name = "<unknown>";
+ return true;
+ }
+ tag = info.die_obj->get_tag();
+ }
+
+ /* Save name of the routine that contains the address. */
+ address_info->routine_name = info.die_obj->get_name();
+ if (address_info->routine_name == NULL) {
+ /* In some cases (minimum debugging info in the file) routine
+ * name may be not avaible. We, however, are obliged by API
+ * considerations to return something in this field. */
+ address_info->routine_name = "<unknown>";
+ }
+
+ /* Lets see if address belongs to an inlined routine. */
+ if (tag != DW_TAG_inlined_subroutine) {
+ address_info->inline_stack = NULL;
+ return true;
+ }
+
+ /*
+ * Address belongs to an inlined routine. Create inline stack.
+ */
+
+ /* Allocate inline stack array big enough to fit all parent entries. */
+ address_info->inline_stack =
+ new Elf_InlineInfo[info.die_obj->get_level() + 1];
+ assert(address_info->inline_stack != NULL);
+ if (address_info->inline_stack == NULL) {
+ _set_errno(ENOMEM);
+ return false;
+ }
+ memset(address_info->inline_stack, 0,
+ sizeof(Elf_InlineInfo) * (info.die_obj->get_level() + 1));
+
+ /* Reverse DIEs filling in inline stack entries for inline
+ * routine tags. */
+ int inl_index = 0;
+ do {
+ /* Save source file information. */
+ DIEAttrib file_desc;
+ if (info.die_obj->get_attrib(DW_AT_call_file, &file_desc)) {
+ const Dwarf_STMTL_FileDesc* desc =
+ cu->get_stmt_file_info(file_desc.value()->u32);
+ if (desc != NULL) {
+ address_info->inline_stack[inl_index].inlined_in_file =
+ desc->file_name;
+ address_info->inline_stack[inl_index].inlined_in_file_dir =
+ cu->get_stmt_dir_name(desc->get_dir_index());
+ }
+ }
+ if (address_info->inline_stack[inl_index].inlined_in_file == NULL) {
+ address_info->inline_stack[inl_index].inlined_in_file = "<unknown>";
+ address_info->inline_stack[inl_index].inlined_in_file_dir = NULL;
+ }
+
+ /* Save source line information. */
+ if (info.die_obj->get_attrib(DW_AT_call_line, &file_desc)) {
+ address_info->inline_stack[inl_index].inlined_at_line = file_desc.value()->u32;
+ }
+
+ /* Advance DIE to the parent routine, and save its name. */
+ info.die_obj = info.die_obj->parent_die();
+ assert(info.die_obj != NULL);
+ if (info.die_obj != NULL) {
+ tag = info.die_obj->get_tag();
+ while (!dwarf_tag_is_routine(tag)) {
+ info.die_obj = info.die_obj->parent_die();
+ if (info.die_obj == NULL) {
+ break;
+ }
+ tag = info.die_obj->get_tag();
+ }
+ if (info.die_obj != NULL) {
+ address_info->inline_stack[inl_index].routine_name =
+ info.die_obj->get_name();
+ }
+ }
+ if (address_info->inline_stack[inl_index].routine_name == NULL) {
+ address_info->inline_stack[inl_index].routine_name = "<unknown>";
+ }
+
+ /* Continue with the parent DIE. */
+ inl_index++;
+ } while (info.die_obj != NULL && tag == DW_TAG_inlined_subroutine);
+
+ return true;
+ }
+ cu = cu->prev_cu();
+ }
+
+ return false;
+}
+
+void ElfFile::free_pc_address_info(Elf_AddressInfo* address_info) const {
+ assert(address_info != NULL);
+ if (address_info != NULL && address_info->inline_stack != NULL) {
+ delete address_info->inline_stack;
+ address_info->inline_stack = NULL;
+ }
+}
+
+//=============================================================================
+// ElfFileImpl
+//=============================================================================
+
+template <typename Elf_Addr, typename Elf_Off>
+bool ElfFileImpl<Elf_Addr, Elf_Off>::initialize(const Elf_CommonHdr* elf_hdr,
+ const char* path) {
+ /* Must be called first! */
+ if (!ElfFile::initialize(elf_hdr, path)) {
+ return false;
+ }
+
+ /* Cache some header data, so later we can discard the header. */
+ const Elf_FHdr<Elf_Addr, Elf_Off>* header =
+ reinterpret_cast<const Elf_FHdr<Elf_Addr, Elf_Off>*>(elf_hdr);
+ sec_count_ = pull_val(header->e_shnum);
+ sec_entry_size_ = pull_val(header->e_shentsize);
+ fixed_base_address_ = pull_val(header->e_entry) & ~0xFFF;
+
+ /* Cache section table (must have one!) */
+ const Elf_Off sec_table_off = pull_val(header->e_shoff);
+ assert(sec_table_off != 0 && sec_count_ != 0);
+ if (sec_table_off == 0 || sec_count_ == 0) {
+ _set_errno(EBADF);
+ return false;
+ }
+ const size_t sec_table_size = sec_count_ * sec_entry_size_;
+ sec_table_ = new Elf_Byte[sec_table_size];
+ assert(sec_table_ != NULL);
+ if (sec_table_ == NULL) {
+ _set_errno(ENOMEM);
+ return false;
+ }
+#ifdef WIN32
+ LARGE_INTEGER convert;
+ convert.QuadPart = sec_table_off;
+ if ((SetFilePointer(elf_handle_, convert.LowPart,
+ &convert.HighPart,
+ FILE_BEGIN) == INVALID_SET_FILE_POINTER) &&
+ (GetLastError() != NO_ERROR)) {
+ _set_errno(GetLastError());
+ return false;
+ }
+ DWORD read_bytes;
+ BOOL res =
+ ReadFile(elf_handle_, sec_table_, sec_table_size, &read_bytes, NULL);
+ assert(res && read_bytes == sec_table_size);
+ if (!res || read_bytes != sec_table_size) {
+ _set_errno(GetLastError());
+ return false;
+ }
+#else // WIN32
+ ssize_t res = lseek(elf_handle_, sec_table_off, SEEK_SET);
+ assert(res != -1);
+ if (res == -1) {
+ return false;
+ }
+ res = read(elf_handle_, sec_table_, sec_table_size);
+ assert(res != -1 && res == sec_table_size);
+ if (res == -1 || res != sec_table_size) {
+ if (res != -1) {
+ _set_errno(EINVAL);
+ }
+ return false;
+ }
+#endif // WIN32
+
+ /* Map ELF's string section (must have one!). */
+ const Elf_Half str_sec_index = pull_val(header->e_shstrndx);
+ assert(str_sec_index != SHN_UNDEF);
+ if (str_sec_index == SHN_UNDEF) {
+ _set_errno(EBADF);
+ return false;
+ }
+ const Elf_SHdr<Elf_Addr, Elf_Off>* str_sec =
+ reinterpret_cast<const Elf_SHdr<Elf_Addr, Elf_Off>*>
+ (get_section_by_index(str_sec_index));
+ assert(str_sec != NULL);
+ if (str_sec == NULL) {
+ _set_errno(EBADF);
+ return false;
+ }
+ if (!string_section_.map(elf_handle_, pull_val(str_sec->sh_offset),
+ pull_val(str_sec->sh_size))) {
+ return false;
+ }
+
+ /* Lets determine DWARF format. According to the docs, DWARF is 64 bit, if
+ * first 4 bytes in the compilation unit header are set to 0xFFFFFFFF.
+ * .debug_info section of the ELF file begins with the first CU header. */
+ if (!map_section_by_name(".debug_info", &debug_info_)) {
+ _set_errno(EBADF);
+ return false;
+ }
+
+ /* Note that we don't care about endianness here, since 0xFFFFFFFF is an
+ * endianness-independent value, so we don't have to pull_val here. */
+ is_DWARF_64_ =
+ *reinterpret_cast<const Elf_Word*>(debug_info_.data()) == 0xFFFFFFFF;
+
+ return true;
+}
+
+template <typename Elf_Addr, typename Elf_Off>
+int ElfFileImpl<Elf_Addr, Elf_Off>::parse_compilation_units(
+ const DwarfParseContext* parse_context) {
+ /* Lets see if we already parsed the file. */
+ if (last_cu() != NULL) {
+ return cu_count_;
+ }
+
+ /* Cache sections required for this parsing. */
+ if (!map_section_by_name(".debug_abbrev", &debug_abbrev_) ||
+ !map_section_by_name(".debug_ranges", &debug_ranges_) ||
+ !map_section_by_name(".debug_line", &debug_line_) ||
+ !map_section_by_name(".debug_str", &debug_str_)) {
+ _set_errno(EBADF);
+ return false;
+ }
+
+ /* .debug_info section opens with the first CU header. */
+ const void* next_cu = debug_info_.data();
+
+ /* Iterate through CUs until we reached the end of .debug_info section, or
+ * advanced to a CU with zero size, indicating the end of CU list for this
+ * file. */
+ while (is_valid_cu(next_cu)) {
+ /* Instatiate CU, depending on DWARF "bitness". */
+ DwarfCU* cu = DwarfCU::create_instance(this, next_cu);
+ if (cu == NULL) {
+ _set_errno(ENOMEM);
+ return -1;
+ }
+
+ if (cu->parse(parse_context, &next_cu)) {
+ cu->set_prev_cu(last_cu_);
+ last_cu_ = cu;
+ cu_count_++;
+ } else {
+ delete cu;
+ return -1;
+ }
+ };
+
+ return cu_count_;
+}
+
+template <typename Elf_Addr, typename Elf_Off>
+bool ElfFileImpl<Elf_Addr, Elf_Off>::get_section_info_by_name(const char* name,
+ Elf_Off* offset,
+ Elf_Word* size) {
+ const Elf_SHdr<Elf_Addr, Elf_Off>* cur_section =
+ reinterpret_cast<const Elf_SHdr<Elf_Addr, Elf_Off>*>(sec_table_);
+
+ for (Elf_Half sec = 0; sec < sec_count_; sec++) {
+ const char* sec_name = get_str_sec_str(pull_val(cur_section->sh_name));
+ if (sec_name != NULL && strcmp(name, sec_name) == 0) {
+ *offset = pull_val(cur_section->sh_offset);
+ *size = pull_val(cur_section->sh_size);
+ return true;
+ }
+ cur_section = reinterpret_cast<const Elf_SHdr<Elf_Addr, Elf_Off>*>
+ (INC_CPTR(cur_section, sec_entry_size_));
+ }
+ _set_errno(EINVAL);
+ return false;
+}
+
+template <typename Elf_Addr, typename Elf_Off>
+bool ElfFileImpl<Elf_Addr, Elf_Off>::map_section_by_name(
+ const char* name,
+ ElfMappedSection* section) {
+ if (section->is_mapped()) {
+ return true;
+ }
+
+ Elf_Off offset;
+ Elf_Word size;
+ if (!get_section_info_by_name(name, &offset, &size)) {
+ return false;
+ }
+
+ return section->map(elf_handle_, offset, size);
+}