/* 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" #include #ifndef O_BINARY #define O_BINARY 0 #endif /* 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() : fixed_base_address_(0), elf_handle_((MapFile*)-1), elf_file_path_(NULL), allocator_(NULL), sec_table_(NULL), sec_count_(0), sec_entry_size_(0), last_cu_(NULL), cu_count_(0), is_exec_(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 (mapfile_is_valid(elf_handle_)) { mapfile_close(elf_handle_); } if (elf_file_path_ != NULL) { delete[] elf_file_path_; } if (sec_table_ != NULL) { delete[] reinterpret_cast(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). */ MapFile* file_handle = mapfile_open(path, O_RDONLY | O_BINARY, 0); if (!mapfile_is_valid(file_handle)) { return NULL; } const ssize_t read_bytes = mapfile_read(file_handle, &header, sizeof(header)); mapfile_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; } /* 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; } else { ret = new ElfFileImpl; } 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. */ elf_handle_ = mapfile_open(elf_file_path_, O_RDONLY | O_BINARY, 0); return mapfile_is_valid(elf_handle_); } 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 = ""; 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 = ""; } /* 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 = ""; 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 = ""; } /* 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 bool ElfFileImpl::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* header = reinterpret_cast*>(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; } if (mapfile_read_at(elf_handle_, sec_table_off, sec_table_, sec_table_size) < 0) { return false; } /* 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* str_sec = reinterpret_cast*> (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(debug_info_.data()) == 0xFFFFFFFF; return true; } template int ElfFileImpl::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 bool ElfFileImpl::get_section_info_by_name(const char* name, Elf_Off* offset, Elf_Word* size) { const Elf_SHdr* cur_section = reinterpret_cast*>(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*> (INC_CPTR(cur_section, sec_entry_size_)); } _set_errno(EINVAL); return false; } template bool ElfFileImpl::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); }