blob: 7ce24d1a22973d7cd6d25615371a1ca0a5a42af6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2010 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
/*
* This module implements reading and caching of ELF files.
*/
#include <config.h>
#if ENABLE_ELF
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <framework/myalloc.h>
#include <framework/exceptions.h>
#include <framework/events.h>
#include <framework/trace.h>
#include <services/tcf_elf.h>
#include <services/memorymap.h>
#include <services/dwarfcache.h>
#include <services/pathmap.h>
#if defined(_WRS_KERNEL)
#elif defined(WIN32)
#else
# include <sys/mman.h>
# define USE_MMAP
#endif
#define MIN_FILE_AGE 3
#define MAX_FILE_AGE 60
typedef struct FileINode {
struct FileINode * next;
char * name;
ino_t ino;
} FileINode;
static ELF_File * files = NULL;
static FileINode * inodes = NULL;
static ELFCloseListener * listeners = NULL;
static U4_T listeners_cnt = 0;
static U4_T listeners_max = 0;
static int elf_cleanup_posted = 0;
static ino_t elf_ino_cnt = 0;
static Context * elf_list_ctx;
static unsigned elf_list_pos;
static MemoryMap elf_list;
static MemoryMap elf_map;
void elf_add_close_listener(ELFCloseListener listener) {
if (listeners_cnt >= listeners_max) {
listeners_max = listeners_max == 0 ? 16 : listeners_max * 2;
listeners = (ELFCloseListener *)loc_realloc(listeners, sizeof(ELFCloseListener) * listeners_max);
}
listeners[listeners_cnt++] = listener;
}
static void elf_dispose(ELF_File * file) {
U4_T n;
trace(LOG_ELF, "Dispose ELF file cache %s", file->name);
for (n = 0; n < listeners_cnt; n++) {
listeners[n](file);
}
if (file->fd >= 0) close(file->fd);
if (file->sections != NULL) {
for (n = 0; n < file->section_cnt; n++) {
ELF_Section * s = file->sections + n;
#ifdef USE_MMAP
if (s->mmap_addr != NULL) {
s->data = NULL;
munmap(s->mmap_addr, s->mmap_size);
}
#endif
loc_free(s->data);
loc_free(s->symbols);
}
loc_free(file->sections);
}
release_error_report(file->error);
loc_free(file->pheaders);
loc_free(file->str_pool);
loc_free(file->debug_info_file_name);
loc_free(file->name);
loc_free(file);
}
static void elf_cleanup_event(void * arg) {
ELF_File * prev = NULL;
ELF_File * file = files;
assert(elf_cleanup_posted);
elf_cleanup_posted = 0;
while (file != NULL) {
file->age++;
if (file->age > MAX_FILE_AGE || (file->age > MIN_FILE_AGE && list_is_empty(&context_root))) {
ELF_File * next = file->next;
elf_dispose(file);
file = next;
if (prev != NULL) prev->next = file;
else files = file;
}
else {
prev = file;
file = file->next;
}
}
file = files;
while (file != NULL) {
struct stat st;
if (!file->mtime_changed && stat(file->name, &st) == 0) {
if (st.st_ino == 0) st.st_ino = file->ino;
if (file->dev == st.st_dev && file->ino == st.st_ino && file->mtime != st.st_mtime) {
file->mtime_changed = 1;
}
}
file = file->next;
}
if (files != NULL) {
post_event_with_delay(elf_cleanup_event, NULL, 1000000);
elf_cleanup_posted = 1;
}
else if (list_is_empty(&context_root)) {
while (inodes != NULL) {
FileINode * n = inodes;
inodes = n->next;
loc_free(n->name);
loc_free(n);
}
}
}
static ino_t add_ino(const char * fnm, ino_t ino) {
FileINode * n = (FileINode *)loc_alloc_zero(sizeof(*n));
n->next = inodes;
n->name = loc_strdup(fnm);
n->ino = ino;
inodes = n;
return ino;
}
static ino_t elf_ino(const char * fnm) {
/*
* Number of the information node (the inode) for the file is used as file ID.
* Since some file systems don't support inodes, this function is used in such cases
* to generate virtual inode numbers to be used as file IDs.
*/
char * abs = NULL;
FileINode * n = inodes;
while (n != NULL) {
if (strcmp(n->name, fnm) == 0) return n->ino;
n = n->next;
}
abs = canonicalize_file_name(fnm);
if (abs == NULL) return add_ino(fnm, 0);
n = inodes;
while (n != NULL) {
if (strcmp(n->name, abs) == 0) {
free(abs);
return add_ino(fnm, n->ino);
}
n = n->next;
}
if (elf_ino_cnt == 0) elf_ino_cnt++;
add_ino(fnm, elf_ino_cnt);
if (strcmp(abs, fnm) != 0) add_ino(abs, elf_ino_cnt);
free(abs);
return elf_ino_cnt++;
}
static ELF_File * find_open_file_by_inode(dev_t dev, ino_t ino, int64_t mtime) {
ELF_File * prev = NULL;
ELF_File * file = files;
while (file != NULL) {
if (file->dev == dev && file->ino == ino &&
(mtime ? file->mtime == mtime : !file->mtime_changed)) {
if (prev != NULL) {
prev->next = file->next;
file->next = files;
files = file;
}
file->age = 0;
return file;
}
prev = file;
file = file->next;
}
return NULL;
}
static ELF_File * find_open_file_by_name(const char * name) {
ELF_File * prev = NULL;
ELF_File * file = files;
while (file != NULL) {
if (strcmp(name, file->name) == 0) {
if (prev != NULL) {
prev->next = file->next;
file->next = files;
files = file;
}
file->age = 0;
return file;
}
prev = file;
file = file->next;
}
return NULL;
}
void swap_bytes(void * buf, size_t size) {
size_t i, j, n;
char * p = (char *)buf;
n = size >> 1;
for (i = 0, j = size - 1; i < n; i++, j--) {
char x = p[i];
p[i] = p[j];
p[j] = x;
}
}
static char * get_debug_info_file_name(ELF_File * file, int * error) {
unsigned idx;
for (idx = 1; idx < file->section_cnt; idx++) {
ELF_Section * sec = file->sections + idx;
if (sec->size == 0) continue;
if (sec->type == SHT_NOTE && (sec->flags & SHF_ALLOC)) {
unsigned offs = 0;
if (elf_load(sec) < 0) {
*error = errno;
return NULL;
}
while (offs < sec->size) {
U4_T name_sz = *(U4_T *)((U1_T *)sec->data + offs);
U4_T desc_sz = *(U4_T *)((U1_T *)sec->data + offs + 4);
U4_T type = *(U4_T *)((U1_T *)sec->data + offs + 8);
char * name = NULL;
offs += 12;
if (file->byte_swap) {
SWAP(name_sz);
SWAP(desc_sz);
SWAP(type);
}
name = (char *)((U1_T *)sec->data + offs);
offs += name_sz;
while (offs % 4 != 0) offs++;
if (type == 3 && strcmp(name, "GNU") == 0) {
char fnm[FILE_PATH_SIZE];
struct stat buf;
char id[64];
size_t id_size = 0;
U1_T * desc = (U1_T *)sec->data + offs;
U4_T i = 0;
while (i < desc_sz) {
U1_T j = (desc[i] >> 4) & 0xf;
U1_T k = desc[i++] & 0xf;
id[id_size++] = j < 10 ? '0' + j : 'a' + j - 10;
id[id_size++] = k < 10 ? '0' + k : 'a' + k - 10;
}
id[id_size++] = 0;
trace(LOG_ELF, "Found GNU build ID %s", id);
snprintf(fnm, sizeof(fnm), "/usr/lib/debug/.build-id/%.2s/%s.debug", id, id + 2);
if (stat(fnm, &buf) == 0) return loc_strdup(fnm);
#if SERVICE_PathMap
{
char * lnm = path_map_to_local(NULL, fnm);
if (lnm != NULL) return loc_strdup(lnm);
}
#endif
return NULL;
}
offs += desc_sz;
while (offs % 4 != 0) offs++;
}
}
else if (sec->name != NULL && strcmp(sec->name, ".gnu_debuglink") == 0) {
if (elf_load(sec) < 0) {
*error = errno;
return NULL;
}
else {
/* TODO: check debug info CRC */
char fnm[FILE_PATH_SIZE];
struct stat buf;
char * name = (char *)sec->data;
int l = strlen(file->name);
while (l > 0 && file->name[l - 1] != '/' && file->name[l - 1] != '\\') l--;
if (strcmp(file->name + l, name) != 0) {
snprintf(fnm, sizeof(fnm), "%.*s%s", l, file->name, name);
if (stat(fnm, &buf) == 0) return loc_strdup(fnm);
}
snprintf(fnm, sizeof(fnm), "%.*s.debug/%s", l, file->name, name);
if (stat(fnm, &buf) == 0) return loc_strdup(fnm);
snprintf(fnm, sizeof(fnm), "/usr/lib/debug%.*s%s", l, file->name, name);
if (stat(fnm, &buf) == 0) return loc_strdup(fnm);
#if SERVICE_PathMap
{
char * lnm = path_map_to_local(NULL, fnm);
if (lnm != NULL) return loc_strdup(lnm);
}
#endif
return NULL;
}
}
}
return NULL;
}
static ELF_File * create_elf_cache(const char * file_name) {
struct stat st;
int error = 0;
ELF_File * file = NULL;
unsigned str_index = 0;
trace(LOG_ELF, "Create ELF file cache %s", file_name);
file = (ELF_File *)loc_alloc_zero(sizeof(ELF_File));
file->name = loc_strdup(file_name);
file->fd = -1;
if (stat(file_name, &st) < 0) {
error = errno;
memset(&st, 0, sizeof(st));
}
else if (st.st_ino == 0) {
st.st_ino = elf_ino(file_name);
}
file->dev = st.st_dev;
file->ino = st.st_ino;
file->mtime = st.st_mtime;
if (error == 0 && (file->fd = open(file->name, O_RDONLY | O_BINARY, 0)) < 0) error = errno;
if (error == 0) {
Elf32_Ehdr hdr;
memset(&hdr, 0, sizeof(hdr));
if (read(file->fd, (char *)&hdr, sizeof(hdr)) < 0) error = errno;
if (error == 0 && strncmp((char *)hdr.e_ident, ELFMAG, SELFMAG) != 0) {
error = set_errno(ERR_INV_FORMAT, "Unsupported ELF identification code");
}
if (error == 0) {
if (hdr.e_ident[EI_DATA] == ELFDATA2LSB) {
file->big_endian = 0;
}
else if (hdr.e_ident[EI_DATA] == ELFDATA2MSB) {
file->big_endian = 1;
}
else {
error = set_errno(ERR_INV_FORMAT, "Invalid ELF data encoding ID");
}
file->byte_swap = file->big_endian != big_endian_host();
}
if (error != 0) {
/* Nothing */
}
else if (hdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (file->byte_swap) {
SWAP(hdr.e_type);
SWAP(hdr.e_machine);
SWAP(hdr.e_version);
SWAP(hdr.e_entry);
SWAP(hdr.e_phoff);
SWAP(hdr.e_shoff);
SWAP(hdr.e_flags);
SWAP(hdr.e_ehsize);
SWAP(hdr.e_phentsize);
SWAP(hdr.e_phnum);
SWAP(hdr.e_shentsize);
SWAP(hdr.e_shnum);
SWAP(hdr.e_shstrndx);
}
file->type = hdr.e_type;
file->machine = hdr.e_machine;
file->os_abi = hdr.e_ident[EI_OSABI];
if (error == 0 && hdr.e_type != ET_EXEC && hdr.e_type != ET_DYN && hdr.e_type != ET_REL) {
error = set_errno(ERR_INV_FORMAT, "Invalid ELF type ID");
}
if (error == 0 && hdr.e_version != EV_CURRENT) {
error = set_errno(ERR_INV_FORMAT, "Unsupported ELF version");
}
if (error == 0 && hdr.e_shoff == 0) {
error = set_errno(ERR_INV_FORMAT, "Invalid section header table's file offset");
}
if (error == 0 && lseek(file->fd, hdr.e_shoff, SEEK_SET) == (off_t)-1) error = errno;
if (error == 0) {
unsigned cnt = 0;
file->sections = (ELF_Section *)loc_alloc_zero(sizeof(ELF_Section) * hdr.e_shnum);
file->section_cnt = hdr.e_shnum;
while (error == 0 && cnt < hdr.e_shnum) {
int rd = 0;
Elf32_Shdr shdr;
memset(&shdr, 0, sizeof(shdr));
if (error == 0 && sizeof(shdr) < hdr.e_shentsize) error = ERR_INV_FORMAT;
if (error == 0 && (rd = read(file->fd, (char *)&shdr, hdr.e_shentsize)) < 0) error = errno;
if (error == 0 && rd != hdr.e_shentsize) error = ERR_INV_FORMAT;
if (error == 0) {
ELF_Section * sec = file->sections + cnt;
if (file->byte_swap) {
SWAP(shdr.sh_name);
SWAP(shdr.sh_type);
SWAP(shdr.sh_flags);
SWAP(shdr.sh_addr);
SWAP(shdr.sh_offset);
SWAP(shdr.sh_size);
SWAP(shdr.sh_link);
SWAP(shdr.sh_info);
SWAP(shdr.sh_addralign);
SWAP(shdr.sh_entsize);
}
sec->file = file;
sec->index = cnt;
sec->name_offset = shdr.sh_name;
sec->type = shdr.sh_type;
sec->offset = shdr.sh_offset;
sec->size = shdr.sh_size;
sec->flags = shdr.sh_flags;
sec->addr = shdr.sh_addr;
sec->link = shdr.sh_link;
sec->info = shdr.sh_info;
sec->entsize = shdr.sh_entsize;
cnt++;
}
}
}
if (error == 0 && lseek(file->fd, hdr.e_phoff, SEEK_SET) == (off_t)-1) error = errno;
if (error == 0) {
unsigned cnt = 0;
file->pheaders = (ELF_PHeader *)loc_alloc_zero(sizeof(ELF_PHeader) * hdr.e_phnum);
file->pheader_cnt = hdr.e_phnum;
while (error == 0 && cnt < hdr.e_phnum) {
int rd = 0;
Elf32_Phdr phdr;
memset(&phdr, 0, sizeof(phdr));
if (error == 0 && sizeof(phdr) < hdr.e_phentsize) error = ERR_INV_FORMAT;
if (error == 0 && (rd = read(file->fd, (char *)&phdr, hdr.e_phentsize)) < 0) error = errno;
if (error == 0 && rd != hdr.e_phentsize) error = ERR_INV_FORMAT;
if (error == 0) {
ELF_PHeader * p = file->pheaders + cnt;
if (file->byte_swap) {
SWAP(phdr.p_type);
SWAP(phdr.p_offset);
SWAP(phdr.p_vaddr);
SWAP(phdr.p_paddr);
SWAP(phdr.p_filesz);
SWAP(phdr.p_memsz);
SWAP(phdr.p_flags);
SWAP(phdr.p_align);
}
p->type = phdr.p_type;
p->offset = phdr.p_offset;
p->address = phdr.p_vaddr;
p->file_size = phdr.p_filesz;
p->mem_size = phdr.p_memsz;
p->flags = phdr.p_flags;
p->align = phdr.p_align;
cnt++;
}
}
}
str_index = hdr.e_shstrndx;
}
else if (hdr.e_ident[EI_CLASS] == ELFCLASS64) {
Elf64_Ehdr hdr;
file->elf64 = 1;
memset(&hdr, 0, sizeof(hdr));
if (error == 0 && lseek(file->fd, 0, SEEK_SET) == (off_t)-1) error = errno;
if (error == 0 && read(file->fd, (char *)&hdr, sizeof(hdr)) < 0) error = errno;
if (file->byte_swap) {
SWAP(hdr.e_type);
SWAP(hdr.e_machine);
SWAP(hdr.e_version);
SWAP(hdr.e_entry);
SWAP(hdr.e_phoff);
SWAP(hdr.e_shoff);
SWAP(hdr.e_flags);
SWAP(hdr.e_ehsize);
SWAP(hdr.e_phentsize);
SWAP(hdr.e_phnum);
SWAP(hdr.e_shentsize);
SWAP(hdr.e_shnum);
SWAP(hdr.e_shstrndx);
}
file->type = hdr.e_type;
file->machine = hdr.e_machine;
file->os_abi = hdr.e_ident[EI_OSABI];
if (error == 0 && hdr.e_type != ET_EXEC && hdr.e_type != ET_DYN && hdr.e_type != ET_REL) {
error = set_errno(ERR_INV_FORMAT, "Invalid ELF type ID");
}
if (error == 0 && hdr.e_version != EV_CURRENT) {
error = set_errno(ERR_INV_FORMAT, "Unsupported ELF version");
}
if (error == 0 && hdr.e_shoff == 0) {
error = set_errno(ERR_INV_FORMAT, "Invalid section header table's file offset");
}
if (error == 0 && lseek(file->fd, hdr.e_shoff, SEEK_SET) == (off_t)-1) error = errno;
if (error == 0) {
unsigned cnt = 0;
file->sections = (ELF_Section *)loc_alloc_zero(sizeof(ELF_Section) * hdr.e_shnum);
file->section_cnt = hdr.e_shnum;
while (error == 0 && cnt < hdr.e_shnum) {
int rd = 0;
Elf64_Shdr shdr;
memset(&shdr, 0, sizeof(shdr));
if (error == 0 && sizeof(shdr) < hdr.e_shentsize) error = ERR_INV_FORMAT;
if (error == 0 && (rd = read(file->fd, (char *)&shdr, hdr.e_shentsize)) < 0) error = errno;
if (error == 0 && rd != hdr.e_shentsize) error = ERR_INV_FORMAT;
if (error == 0) {
ELF_Section * sec = file->sections + cnt;
if (file->byte_swap) {
SWAP(shdr.sh_name);
SWAP(shdr.sh_type);
SWAP(shdr.sh_flags);
SWAP(shdr.sh_addr);
SWAP(shdr.sh_offset);
SWAP(shdr.sh_size);
SWAP(shdr.sh_link);
SWAP(shdr.sh_info);
SWAP(shdr.sh_addralign);
SWAP(shdr.sh_entsize);
}
sec->file = file;
sec->index = cnt;
sec->name_offset = shdr.sh_name;
sec->type = shdr.sh_type;
sec->offset = shdr.sh_offset;
sec->size = shdr.sh_size;
sec->flags = (U4_T)shdr.sh_flags;
sec->addr = shdr.sh_addr;
sec->link = shdr.sh_link;
sec->info = shdr.sh_info;
sec->entsize = (U4_T)shdr.sh_entsize;
cnt++;
}
}
}
if (error == 0 && lseek(file->fd, hdr.e_phoff, SEEK_SET) == (off_t)-1) error = errno;
if (error == 0) {
unsigned cnt = 0;
file->pheaders = (ELF_PHeader *)loc_alloc_zero(sizeof(ELF_PHeader) * hdr.e_phnum);
file->pheader_cnt = hdr.e_phnum;
while (error == 0 && cnt < hdr.e_phnum) {
int rd = 0;
Elf64_Phdr phdr;
memset(&phdr, 0, sizeof(phdr));
if (error == 0 && sizeof(phdr) < hdr.e_phentsize) error = ERR_INV_FORMAT;
if (error == 0 && (rd = read(file->fd, (char *)&phdr, hdr.e_phentsize)) < 0) error = errno;
if (error == 0 && rd != hdr.e_phentsize) error = ERR_INV_FORMAT;
if (error == 0) {
ELF_PHeader * p = file->pheaders + cnt;
if (file->byte_swap) {
SWAP(phdr.p_type);
SWAP(phdr.p_offset);
SWAP(phdr.p_vaddr);
SWAP(phdr.p_paddr);
SWAP(phdr.p_filesz);
SWAP(phdr.p_memsz);
SWAP(phdr.p_flags);
SWAP(phdr.p_align);
}
p->type = phdr.p_type;
p->offset = phdr.p_offset;
p->address = phdr.p_vaddr;
p->file_size = phdr.p_filesz;
p->mem_size = phdr.p_memsz;
p->flags = phdr.p_flags;
p->align = (U4_T)phdr.p_align;
cnt++;
}
}
}
str_index = hdr.e_shstrndx;
}
else {
error = set_errno(ERR_INV_FORMAT, "Invalid ELF class ID");
}
if (error == 0 && str_index != 0 && str_index < file->section_cnt) {
int rd = 0;
ELF_Section * str = file->sections + str_index;
file->str_pool = (char *)loc_alloc((size_t)str->size);
if (str->offset == 0 || str->size == 0) error = set_errno(ERR_INV_FORMAT, "Invalid ELF string pool offset or size");
if (error == 0 && lseek(file->fd, str->offset, SEEK_SET) == (off_t)-1) error = errno;
if (error == 0 && (rd = read(file->fd, file->str_pool, (size_t)str->size)) < 0) error = errno;
if (error == 0 && rd != (int)str->size) error = set_errno(ERR_INV_FORMAT, "Cannot read ELF string pool");
if (error == 0) {
unsigned i;
for (i = 1; i < file->section_cnt; i++) {
ELF_Section * sec = file->sections + i;
sec->name = file->str_pool + sec->name_offset;
}
}
}
}
if (error == 0) {
file->debug_info_file_name = get_debug_info_file_name(file, &error);
if (file->debug_info_file_name) trace(LOG_ELF, "Debug info file found %s", file->debug_info_file_name);
}
if (error != 0) {
trace(LOG_ELF, "Error opening ELF file: %d %s", error, errno_to_str(error));
file->error = get_error_report(error);
}
if (!elf_cleanup_posted) {
post_event_with_delay(elf_cleanup_event, NULL, 1000000);
elf_cleanup_posted = 1;
}
file->next = files;
return files = file;
}
ELF_File * elf_open(const char * file_name) {
ELF_File * file = find_open_file_by_name(file_name);
if (file == NULL) file = create_elf_cache(file_name);
if (file->error == NULL) return file;
set_error_report_errno(file->error);
return NULL;
}
int elf_load(ELF_Section * s) {
if (s->data != NULL) return 0;
if (s->size == 0) return 0;
s->relocate = 0;
if (s->type != SHT_REL && s->type != SHT_REL && s->type != SHT_RELA) {
unsigned i;
for (i = 1; i < s->file->section_cnt; i++) {
ELF_Section * r = s->file->sections + i;
if (r->entsize == 0 || r->size == 0) continue;
if (r->type != SHT_REL && r->type != SHT_RELA) continue;
if (r->info == s->index) {
s->relocate = 1;
break;
}
}
}
#ifdef USE_MMAP
{
long page = sysconf(_SC_PAGE_SIZE);
off_t offs = (off_t)s->offset;
offs -= offs % page;
s->mmap_size = (size_t)(s->offset - offs) + s->size;
s->mmap_addr = mmap(0, s->mmap_size, PROT_READ, MAP_PRIVATE, s->file->fd, offs);
if (s->mmap_addr == MAP_FAILED) {
s->mmap_addr = NULL;
trace(LOG_ALWAYS, "Cannot mmap section %s in ELF file %s", s->name, s->file->name);
}
else {
s->data = (char *)s->mmap_addr + (size_t)(s->offset - offs);
trace(LOG_ELF, "Section %s in ELF file %s is mapped to %#lx", s->name, s->file->name, s->data);
}
}
#endif
if (s->data == NULL) {
ELF_File * file = s->file;
if (lseek(file->fd, s->offset, SEEK_SET) == (off_t)-1) return -1;
s->data = loc_alloc((size_t)s->size);
if (read(file->fd, s->data, (size_t)s->size) < 0) {
int err = errno;
loc_free(s->data);
s->data = NULL;
errno = err;
return -1;
}
trace(LOG_ELF, "Section %s in ELF file %s is loaded", s->name, s->file->name);
}
return 0;
}
static ELF_File * open_memory_region_file(MemoryRegion * r, int * error) {
ELF_File * file = NULL;
ino_t ino = r->ino;
dev_t dev = r->dev;
if (r->file_name == NULL) return NULL;
if (dev != 0) {
if (ino == 0) ino = elf_ino(r->file_name);
if (ino != 0) file = find_open_file_by_inode(dev, ino, 0);
}
if (file == NULL) file = find_open_file_by_name(r->file_name);
if (file == NULL) file = create_elf_cache(r->file_name);
if (r->dev != 0 && file->dev != r->dev) return NULL;
if (r->ino != 0 && file->ino != r->ino) return NULL;
if (file->error == NULL) return file;
if (error != NULL && *error == 0) {
int no = set_error_report_errno(file->error);
if (get_error_code(no) != ERR_INV_FORMAT) *error = no;
}
return NULL;
}
static void add_region(MemoryMap * map, MemoryRegion * r) {
if (map->region_cnt >= map->region_max) {
map->region_max += 8;
map->regions = (MemoryRegion *)loc_realloc(map->regions, sizeof(MemoryRegion ) * map->region_max);
}
map->regions[map->region_cnt++] = *r;
}
static void search_regions(MemoryMap * map, ContextAddress addr0, ContextAddress addr1, MemoryMap * res) {
unsigned i;
for (i = 0; i < map->region_cnt; i++) {
MemoryRegion * r = map->regions + i;
if (r->file_name == NULL) continue;
if (r->addr == 0 && r->size == 0 && r->file_offs == 0 && r->sect_name == NULL) {
int error = 0;
ELF_File * file = open_memory_region_file(r, &error);
if (file != NULL) {
unsigned j;
for (j = 0; j < file->pheader_cnt; j++) {
ELF_PHeader * p = file->pheaders + j;
if (p->type != PT_LOAD) continue;
if (p->address <= addr1 && p->address + p->mem_size > addr0) {
MemoryRegion x;
memset(&x, 0, sizeof(x));
x.addr = p->address;
x.size = p->mem_size;
x.dev = file->dev;
x.ino = file->ino;
x.file_name = file->name;
x.file_offs = p->offset;
x.flags = MM_FLAG_R | MM_FLAG_W | MM_FLAG_X;
add_region(res, &x);;
}
}
}
}
else if (r->addr <= addr1 && r->addr + r->size > addr0) {
add_region(res, r);;
}
}
}
static int get_map(Context * ctx, ContextAddress addr0, ContextAddress addr1, MemoryMap * map) {
MemoryMap * client_map = NULL;
MemoryMap * target_map = NULL;
map->region_cnt = 0;
ctx = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
if (memory_map_get(ctx, &client_map, &target_map) < 0) return -1;
search_regions(client_map, addr0, addr1, map);
search_regions(target_map, addr0, addr1, map);
return 0;
}
ELF_File * elf_open_inode(Context * ctx, dev_t dev, ino_t ino, int64_t mtime) {
unsigned i;
int error = 0;
ELF_File * file = find_open_file_by_inode(dev, ino, mtime);
if (file != NULL) {
if (file->error == NULL) return file;
set_error_report_errno(file->error);
return NULL;
}
if (get_map(ctx, 0, ~(ContextAddress)0, &elf_map) < 0) return NULL;
for (i = 0; i < elf_map.region_cnt; i++) {
MemoryRegion * r = elf_map.regions + i;
file = open_memory_region_file(r, &error);
if (file == NULL) continue;
if (file->dev == dev && file->ino == ino && file->mtime == mtime) return file;
if (file->debug_info_file_name == NULL) continue;
assert(!file->debug_info_file);
file = elf_open(file->debug_info_file_name);
if (file == NULL) {
error = errno;
continue;
}
if (file->dev == dev && file->ino == ino && file->mtime == mtime) return file;
}
if (error == 0) error = ENOENT;
errno = error;
return NULL;
}
ELF_File * elf_list_first(Context * ctx, ContextAddress addr_min, ContextAddress addr_max) {
elf_list_ctx = ctx;
elf_list_pos = 0;
if (get_map(ctx, addr_min, addr_max, &elf_list) < 0) return NULL;
if (elf_list.region_cnt > 0) {
ELF_File * f = files;
while (f != NULL) {
f->listed = 0;
f = f->next;
}
return elf_list_next(ctx);
}
errno = 0;
return NULL;
}
ELF_File * elf_list_next(Context * ctx) {
assert(ctx == elf_list_ctx);
assert(elf_list.region_cnt > 0);
while (elf_list_pos < elf_list.region_cnt) {
int error = 0;
ELF_File * file = open_memory_region_file(elf_list.regions + elf_list_pos++, &error);
if (file != NULL) {
if (file->listed) continue;
file->listed = 1;
return file;
}
if (error) {
errno = error;
return NULL;
}
}
errno = 0;
return NULL;
}
void elf_list_done(Context * ctx) {
assert(ctx == elf_list_ctx);
elf_list_ctx = NULL;
elf_list_pos = 0;
elf_list.region_cnt = 0;
}
UnitAddressRange * elf_find_unit(Context * ctx, ContextAddress addr_min, ContextAddress addr_max, ContextAddress * range_rt_addr) {
unsigned i, j;
UnitAddressRange * range = NULL;
int error = 0;
if (get_map(ctx, addr_min, addr_max, &elf_map) < 0) return NULL;
for (i = 0; range == NULL && i < elf_map.region_cnt; i++) {
ContextAddress link_addr_min, link_addr_max;
MemoryRegion * r = elf_map.regions + i;
ELF_File * file = NULL;
assert(r->addr <= addr_max);
assert(r->addr + r->size > addr_min);
file = open_memory_region_file(r, &error);
if (error) exception(error);
if (r->sect_name == NULL) {
for (j = 0; range == NULL && j < file->pheader_cnt; j++) {
U8_T offs_min = 0;
U8_T offs_max = 0;
ELF_PHeader * p = file->pheaders + j;
if (p->type != PT_LOAD) continue;
if (r->flags) {
if ((p->flags & PF_R) && !(r->flags & MM_FLAG_R)) continue;
if ((p->flags & PF_W) && !(r->flags & MM_FLAG_W)) continue;
if ((p->flags & PF_X) && !(r->flags & MM_FLAG_X)) continue;
}
offs_min = addr_min - r->addr + r->file_offs;
offs_max = addr_max - r->addr + r->file_offs;
if (p->offset >= offs_max || p->offset + p->mem_size <= offs_min) continue;
link_addr_min = offs_min - p->offset + p->address;
link_addr_max = offs_max - p->offset + p->address;
if (link_addr_min < p->address) link_addr_min = p->address;
if (link_addr_max >= p->address + p->mem_size) link_addr_max = p->address + p->mem_size;
range = find_comp_unit_addr_range(get_dwarf_cache(file), link_addr_min, link_addr_max);
if (range == NULL && file->debug_info_file_name != NULL && !file->debug_info_file) {
ELF_File * debug = elf_open(file->debug_info_file_name);
if (debug == NULL) exception(errno);
debug->debug_info_file = 1;
if (j < debug->pheader_cnt) {
p = debug->pheaders + j;
link_addr_min = offs_min - p->offset + p->address;
link_addr_max = offs_max - p->offset + p->address;
if (link_addr_min < p->address) link_addr_min = p->address;
if (link_addr_max >= p->address + p->mem_size) link_addr_max = p->address + p->mem_size;
range = find_comp_unit_addr_range(get_dwarf_cache(debug), link_addr_min, link_addr_max);
}
}
if (range != NULL && range_rt_addr != NULL) {
*range_rt_addr = range->mAddr - p->address + p->offset - r->file_offs + r->addr;
}
}
}
else {
unsigned idx;
for (idx = 1; range == NULL && idx < file->section_cnt; idx++) {
ELF_Section * sec = file->sections + idx;
if (sec->name != NULL && strcmp(sec->name, r->sect_name) == 0) {
link_addr_min = addr_min - r->addr + sec->addr;
link_addr_max = addr_max - r->addr + sec->addr;
if (link_addr_min < sec->addr) link_addr_min = sec->addr;
if (link_addr_max >= sec->addr + sec->size) link_addr_max = sec->addr + sec->size;
range = find_comp_unit_addr_range(get_dwarf_cache(file), link_addr_min, link_addr_max);
if (range != NULL && range_rt_addr != NULL) {
*range_rt_addr = range->mAddr - sec->addr + r->addr;
}
}
}
}
}
/* TODO: lazy reading of comp unit objects */
#if 0
if (range != NULL) {
load_comp_unit_children(range->mUnit);
if (range->mUnit->mBaseTypes != NULL) {
load_comp_unit_children(range->mUnit->mBaseTypes);
}
}
#endif
return range;
}
ContextAddress elf_map_to_run_time_address(Context * ctx, ELF_File * file, ELF_Section * sec, ContextAddress addr) {
unsigned i;
/* Note: 'addr' is link-time address - it cannot be used as get_map() argument */
if (get_map(ctx, 0, ~(ContextAddress)0, &elf_map) < 0) return 0;
for (i = 0; i < elf_map.region_cnt; i++) {
MemoryRegion * r = elf_map.regions + i;
int same_file = 0;
if (r->dev == 0) {
same_file = strcmp(file->name, r->file_name) == 0;
}
else {
ino_t ino = r->ino;
if (ino == 0) ino = elf_ino(r->file_name);
same_file = file->ino == ino && file->dev == r->dev;
}
if (!same_file) {
/* Check if the memory map entry has a separate debug info file */
int error = 0;
ELF_File * exec = NULL;
if (!file->debug_info_file) continue;
exec = open_memory_region_file(r, &error);
if (exec == NULL) continue;
if (exec->debug_info_file_name == NULL) continue;
if (strcmp(exec->debug_info_file_name, file->name) != 0) continue;
}
if (r->sect_name == NULL) {
unsigned j;
if (file->pheader_cnt == 0 && file->type == ET_EXEC) return addr;
for (j = 0; j < file->pheader_cnt; j++) {
U8_T offs;
ELF_PHeader * p = file->pheaders + j;
if (p->type != PT_LOAD) continue;
if (addr < p->address || addr >= p->address + p->mem_size) continue;
if (r->flags) {
if ((p->flags & PF_R) && !(r->flags & MM_FLAG_R)) continue;
if ((p->flags & PF_W) && !(r->flags & MM_FLAG_W)) continue;
if ((p->flags & PF_X) && !(r->flags & MM_FLAG_X)) continue;
}
offs = addr - p->address + p->offset;
if (offs < r->file_offs || offs >= r->file_offs + r->size) continue;
return (ContextAddress)(offs - r->file_offs + r->addr);
}
}
else if (sec != NULL && strcmp(sec->name, r->sect_name) == 0) {
return (ContextAddress)(addr - sec->addr + r->addr);
}
}
return 0;
}
ContextAddress elf_map_to_link_time_address(Context * ctx, ContextAddress addr, ELF_File ** file, ELF_Section ** sec) {
unsigned i;
if (get_map(ctx, addr, addr, &elf_map) < 0) return 0;
for (i = 0; i < elf_map.region_cnt; i++) {
MemoryRegion * r = elf_map.regions + i;
ELF_File * f = NULL;
assert(r->addr <= addr);
assert(r->addr + r->size > addr);
f = open_memory_region_file(r, NULL);
if (f == NULL) continue;
if (r->sect_name == NULL) {
unsigned j;
if (f->pheader_cnt == 0 && f->type == ET_EXEC) {
*file = f;
for (j = 1; j < f->section_cnt; j++) {
ELF_Section * s = f->sections + j;
if ((s->flags & SHF_ALLOC) == 0) continue;
if (s->addr <= addr && s->addr + s->size > addr) {
*sec = s;
return addr;
}
}
*sec = NULL;
return addr;
}
for (j = 0; j < f->pheader_cnt; j++) {
U8_T offs = addr - r->addr + r->file_offs;
ELF_PHeader * p = f->pheaders + j;
if (p->type != PT_LOAD) continue;
if (offs < p->offset || offs >= p->offset + p->mem_size) continue;
if (r->flags) {
if ((p->flags & PF_R) && !(r->flags & MM_FLAG_R)) continue;
if ((p->flags & PF_W) && !(r->flags & MM_FLAG_W)) continue;
if ((p->flags & PF_X) && !(r->flags & MM_FLAG_X)) continue;
}
*file = f;
addr = (ContextAddress)(offs - p->offset + p->address);
for (j = 1; j < f->section_cnt; j++) {
ELF_Section * s = f->sections + j;
if ((s->flags & SHF_ALLOC) == 0) continue;
if (s->addr + s->size <= p->address) continue;
if (s->addr >= p->address + p->mem_size) continue;
if (s->addr <= addr && s->addr + s->size > addr) {
*sec = s;
return addr;
}
}
*sec = NULL;
return addr;
}
}
else {
unsigned j;
for (j = 1; j < f->section_cnt; j++) {
ELF_Section * s = f->sections + j;
if (strcmp(s->name, r->sect_name) == 0) {
*file = f;
*sec = s;
return (ContextAddress)(addr - r->addr + s->addr);
}
}
}
}
return 0;
}
static int get_dynamic_tag(Context * ctx, ELF_File * file, int tag, ContextAddress * addr) {
unsigned i, j;
for (i = 1; i < file->section_cnt; i++) {
ELF_Section * sec = file->sections + i;
if (sec->size == 0) continue;
if (sec->name == NULL) continue;
if (strcmp(sec->name, ".dynamic") == 0) {
ContextAddress sec_addr = elf_map_to_run_time_address(ctx, file, sec, (ContextAddress)sec->addr);
if (elf_load(sec) < 0) return -1;
if (file->elf64) {
unsigned cnt = (unsigned)(sec->size / sizeof(Elf64_Dyn));
for (j = 0; j < cnt; j++) {
Elf64_Dyn dyn = *((Elf64_Dyn *)sec->data + j);
if (file->byte_swap) SWAP(dyn.d_tag);
if (dyn.d_tag == DT_NULL) break;
if (dyn.d_tag == tag) {
if (context_read_mem(ctx, sec_addr + j * sizeof(dyn), &dyn, sizeof(dyn)) < 0) return -1;
if (file->byte_swap) {
SWAP(dyn.d_tag);
SWAP(dyn.d_un.d_ptr);
}
if (dyn.d_tag != tag) continue;
if (addr != NULL) *addr = (ContextAddress)dyn.d_un.d_ptr;
return 0;
}
}
}
else {
unsigned cnt = (unsigned)(sec->size / sizeof(Elf32_Dyn));
for (j = 0; j < cnt; j++) {
Elf32_Dyn dyn = *((Elf32_Dyn *)sec->data + j);
if (file->byte_swap) SWAP(dyn.d_tag);
if (dyn.d_tag == DT_NULL) break;
if (dyn.d_tag == tag) {
if (context_read_mem(ctx, sec_addr + j * sizeof(dyn), &dyn, sizeof(dyn)) < 0) return -1;
if (file->byte_swap) {
SWAP(dyn.d_tag);
SWAP(dyn.d_un.d_ptr);
}
if (dyn.d_tag != tag) continue;
if (addr != NULL) *addr = (ContextAddress)dyn.d_un.d_ptr;
return 0;
}
}
}
}
}
errno = ENOENT;
return -1;
}
static int sym_name_cmp(const char * x, const char * y) {
while (*x && *x == *y) {
x++;
y++;
}
if (*x == 0 && *y == 0) return 0;
if (*x == '@' && *(x + 1) == '@' && *y == 0) return 0;
if (*x < *y) return -1;
return 1;
}
static int get_global_symbol_address(Context * ctx, ELF_File * file, const char * name, ContextAddress * addr) {
unsigned i, j;
for (i = 1; i < file->section_cnt; i++) {
ELF_Section * sec = file->sections + i;
if (sec->size == 0) continue;
if (sec->name == NULL) continue;
if (sec->type == SHT_SYMTAB) {
ELF_Section * str = NULL;
if (sec->link == 0 || sec->link >= file->section_cnt) {
errno = EINVAL;
return -1;
}
str = file->sections + sec->link;
if (elf_load(sec) < 0) return -1;
if (elf_load(str) < 0) return -1;
if (file->elf64) {
unsigned cnt = (unsigned)(sec->size / sizeof(Elf64_Sym));
for (j = 0; j < cnt; j++) {
Elf64_Sym sym = *((Elf64_Sym *)sec->data + j);
if (ELF64_ST_BIND(sym.st_info) != STB_GLOBAL) continue;
if (file->byte_swap) SWAP(sym.st_name);
if (sym_name_cmp((char *)str->data + sym.st_name, name) != 0) continue;
switch (ELF64_ST_TYPE(sym.st_info)) {
case STT_OBJECT:
case STT_FUNC:
if (file->byte_swap) SWAP(sym.st_value);
*addr = elf_map_to_run_time_address(ctx, file, NULL, (ContextAddress)sym.st_value);
if (*addr != 0) return 0;
}
}
}
else {
unsigned cnt = (unsigned)(sec->size / sizeof(Elf32_Sym));
for (j = 0; j < cnt; j++) {
Elf32_Sym sym = *((Elf32_Sym *)sec->data + j);
if (ELF32_ST_BIND(sym.st_info) != STB_GLOBAL) continue;
if (file->byte_swap) SWAP(sym.st_name);
if (sym_name_cmp((char *)str->data + sym.st_name, name) != 0) continue;
switch (ELF32_ST_TYPE(sym.st_info)) {
case STT_OBJECT:
case STT_FUNC:
if (file->byte_swap) SWAP(sym.st_value);
*addr = elf_map_to_run_time_address(ctx, file, NULL, (ContextAddress)sym.st_value);
if (*addr != 0) return 0;
}
}
}
}
}
errno = ENOENT;
return -1;
}
int elf_read_memory_word(Context * ctx, ELF_File * file, ContextAddress addr, ContextAddress * word) {
U1_T buf[8];
size_t size = file->elf64 ? 8 : 4;
size_t i = 0;
U8_T n = 0;
if (context_read_mem(ctx, addr, buf, size) < 0) return -1;
for (i = 0; i < size; i++) {
n = (n << 8) | buf[file->big_endian ? i : size - i - 1];
}
*word = (ContextAddress)n;
return 0;
}
ContextAddress elf_get_debug_structure_address(Context * ctx, ELF_File ** file_ptr) {
ELF_File * file = NULL;
ContextAddress addr = 0;
for (file = elf_list_first(ctx, 0, ~(ContextAddress)0); file != NULL; file = elf_list_next(ctx)) {
if (file->type != ET_EXEC) continue;
if (file_ptr != NULL) *file_ptr = file;
#ifdef DT_MIPS_RLD_MAP
if (get_dynamic_tag(ctx, file, DT_MIPS_RLD_MAP, &addr) == 0) {
if (elf_read_memory_word(ctx, file, addr, &addr) < 0) continue;
break;
}
#endif
if (get_dynamic_tag(ctx, file, DT_DEBUG, &addr) == 0) break;
if (get_global_symbol_address(ctx, file, "_r_debug", &addr) == 0) break;
}
elf_list_done(ctx);
return addr;
}
void ini_elf(void) {
}
#endif /* ENABLE_ELF */