/******************************************************************************* * 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. * You may elect to redistribute this code under either of these licenses. * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ /* * This module implements reading and caching of ELF files. */ #include #if ENABLE_ELF #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_WRS_KERNEL) #elif defined(WIN32) #else # include # 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; #if ENABLE_DebugContext static Context * elf_list_ctx; static unsigned elf_list_pos; static MemoryMap elf_list; static MemoryMap elf_map; #endif 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->sym_addr_table); } 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_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; if (sec->type == SHT_SYMTAB) sec->sym_count = (unsigned)(sec->size / sizeof(Elf32_Sym)); 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; if (sec->type == SHT_SYMTAB) sec->sym_count = (unsigned)(sec->size / sizeof(Elf64_Sym)); 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; } #if ENABLE_DebugContext 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 * 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) { map->region_cnt = 0; ctx = context_get_group(ctx, CONTEXT_GROUP_PROCESS); #if SERVICE_MemoryMap { MemoryMap * client_map = NULL; MemoryMap * target_map = NULL; 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); } #else { int error = 0; MemoryMap target_map; memset(&target_map, 0, sizeof(target_map)); if (context_get_memory_map(ctx, &target_map) < 0) error = errno; if (!error) search_regions(&target_map, addr0, addr1, map); context_clear_memory_map(&target_map); loc_free(target_map.regions); if (error) { errno = error; return -1; } } #endif 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->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) { size_t size = file->elf64 ? 8 : 4; size_t i = 0; U8_T n = 0; U1_T buf[8]; if (ctx->mem_access == 0) ctx = context_get_group(ctx, CONTEXT_GROUP_PROCESS); 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; } #endif /* ENABLE_DebugContext */ /************************ ELF symbol tables *****************************************/ unsigned calc_symbol_name_hash(const char * s) { unsigned h = 0; while (*s) { unsigned g; if (s[0] == '@' && s[1] == '@') break; h = (h << 4) + (unsigned char)*s++; g = h & 0xf0000000; if (g) h ^= g >> 24; h &= ~g; } return h % SYM_HASH_SIZE; } int cmp_symbol_names(const char * x, const char * y) { while (*x && *x == *y) { x++; y++; } if (*x == 0 && *y == '@' && y[1] == '@') return 0; if (*y == 0 && *x == '@' && x[1] == '@') return 0; if (*x < *y) return -1; if (*x > *y) return +1; return 0; } void unpack_elf_symbol_info(ELF_Section * sym_sec, U4_T index, ELF_SymbolInfo * info) { ELF_File * file = sym_sec->file; ELF_Section * str_sec = NULL; char * str_pool = NULL; size_t str_pool_size = 0; memset(info, 0, sizeof(ELF_SymbolInfo)); if (index >= sym_sec->sym_count) str_exception(ERR_INV_FORMAT, "Invalid ELF symbol index"); if (sym_sec->link == 0 || sym_sec->link >= file->section_cnt) str_exception(ERR_INV_FORMAT, "Invalid symbol section"); str_sec = file->sections + sym_sec->link; if (elf_load(sym_sec) < 0) exception(errno); if (elf_load(str_sec) < 0) exception(errno); str_pool = (char *)str_sec->data; str_pool_size = (size_t)str_sec->size; info->sym_section = sym_sec; info->sym_index = index; if (file->elf64) { Elf64_Sym s = ((Elf64_Sym *)sym_sec->data)[index]; if (file->byte_swap) { SWAP(s.st_name); SWAP(s.st_shndx); SWAP(s.st_size); SWAP(s.st_value); } info->section_index = s.st_shndx; if (s.st_shndx > 0 && s.st_shndx < file->section_cnt) { info->section = file->sections + s.st_shndx; } if (s.st_name > 0) { if (s.st_name >= str_pool_size) str_exception(ERR_INV_FORMAT, "Invalid ELF string pool index"); info->name = str_pool + s.st_name; } info->bind = ELF64_ST_BIND(s.st_info); info->type = ELF64_ST_TYPE(s.st_info); info->value = s.st_value; info->size = s.st_size; } else { Elf32_Sym s = ((Elf32_Sym *)sym_sec->data)[index]; if (file->byte_swap) { SWAP(s.st_name); SWAP(s.st_shndx); SWAP(s.st_size); SWAP(s.st_value); } info->section_index = s.st_shndx; if (s.st_shndx > 0 && s.st_shndx < file->section_cnt) { info->section = file->sections + s.st_shndx; } if (s.st_name > 0) { if (s.st_name >= str_pool_size) str_exception(ERR_INV_FORMAT, "Invalid ELF string pool index"); info->name = str_pool + s.st_name; } info->bind = ELF32_ST_BIND(s.st_info); info->type = ELF32_ST_TYPE(s.st_info); info->value = s.st_value; info->size = s.st_size; } } static int section_symbol_comparator(const void * x, const void * y) { ELF_SecSymbol * rx = (ELF_SecSymbol *)x; ELF_SecSymbol * ry = (ELF_SecSymbol *)y; if (rx->address < ry->address) return -1; if (rx->address > ry->address) return +1; return 0; } static void create_symbol_addr_search_index(ELF_Section * sec) { ELF_File * file = sec->file; int elf64 = file->elf64; int swap = file->byte_swap; int rel = file->type == ET_REL; unsigned m = 0; sec->sym_addr_max = (unsigned)(sec->size / 16) + 16; sec->sym_addr_table = (ELF_SecSymbol *)loc_alloc(sec->sym_addr_max * sizeof(ELF_SecSymbol)); for (m = 1; m < file->section_cnt; m++) { unsigned n = 1; ELF_Section * tbl = file->sections + m; if (tbl->sym_count == 0) continue; if (elf_load(tbl) < 0) exception(errno); while (n < tbl->sym_count) { int add = 0; U8_T addr = 0; if (elf64) { Elf64_Sym s = ((Elf64_Sym *)tbl->data)[n]; if (swap) SWAP(s.st_shndx); if (s.st_shndx == sec->index) { if (swap) SWAP(s.st_value); addr = s.st_value; if (rel) addr += sec->addr; add = 1; } } else { Elf32_Sym s = ((Elf32_Sym *)tbl->data)[n]; if (swap) SWAP(s.st_shndx); if (s.st_shndx == sec->index) { if (swap) SWAP(s.st_value); addr = s.st_value; if (rel) addr += sec->addr; add = 1; } } if (add) { ELF_SecSymbol * s = NULL; if (sec->sym_addr_cnt >= sec->sym_addr_max) { sec->sym_addr_max = sec->sym_addr_max * 3 / 2; sec->sym_addr_table = (ELF_SecSymbol *)loc_realloc(sec->sym_addr_table, sec->sym_addr_max * sizeof(ELF_SecSymbol)); } s = sec->sym_addr_table + sec->sym_addr_cnt++; s->address = addr; s->section = tbl; s->index = n; } n++; } } qsort(sec->sym_addr_table, sec->sym_addr_cnt, sizeof(ELF_SecSymbol), section_symbol_comparator); } void elf_find_symbol_by_address(ELF_Section * sec, ContextAddress addr, ELF_SymbolInfo * sym_info) { unsigned l = 0; unsigned h = 0; memset(sym_info, 0, sizeof(ELF_SymbolInfo)); if (sec == NULL || addr < sec->addr) return; if (sec->sym_addr_table == NULL) create_symbol_addr_search_index(sec); h = sec->sym_addr_cnt; while (l < h) { unsigned k = (h + l) / 2; ELF_SecSymbol * info = sec->sym_addr_table + k; if (info->address > addr) { h = k; } else { ContextAddress next = k < sec->sym_addr_cnt - 1 ? (info + 1)->address : sec->addr + sec->size; assert(next >= info->address); if (next <= addr) { l = k + 1; } else { unpack_elf_symbol_info(info->section, info->index, sym_info); assert(sym_info->section == sec); sym_info->addr_index = k; return; } } } } void elf_prev_symbol_by_address(ELF_SymbolInfo * sym_info) { if (sym_info->section != NULL && sym_info->addr_index > 0) { U4_T index = sym_info->addr_index - 1; ELF_SecSymbol * info = sym_info->section->sym_addr_table + index; unpack_elf_symbol_info(info->section, info->index, sym_info); sym_info->addr_index = index; } else { memset(sym_info, 0, sizeof(ELF_SymbolInfo)); } } void elf_next_symbol_by_address(ELF_SymbolInfo * sym_info) { if (sym_info->section != NULL && sym_info->addr_index + 1 < sym_info->section->sym_addr_cnt) { U4_T index = sym_info->addr_index + 1; ELF_SecSymbol * info = sym_info->section->sym_addr_table + index; unpack_elf_symbol_info(info->section, info->index, sym_info); sym_info->addr_index = index; } else { memset(sym_info, 0, sizeof(ELF_SymbolInfo)); } } void ini_elf(void) { } #endif /* ENABLE_ELF */