/* 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_((MapFile*)-1),
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 (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<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).
*/
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<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. */
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 = "<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;
}
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<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);
}