* Copyright 2008-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2003-2012, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*
* Copyright 2002, Manuel J. Petit. All rights reserved.
* Copyright 2001, Travis Geiselbrecht. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#include "elf_load_image.h"
#include <stdio.h>
#include <string.h>
#include <syscalls.h>
#include "add_ons.h"
#include "elf_haiku_version.h"
#include "elf_symbol_lookup.h"
#include "elf_tls.h"
#include "elf_versioning.h"
#include "images.h"
#include "runtime_loader_private.h"
static const char* sSearchPathSubDir = NULL;
static const char*
get_program_path()
{
return gProgramImage != NULL ? gProgramImage->path : NULL;
}
static int32
count_regions(const char* imagePath, char const* buff, int phnum, int phentsize)
{
elf_phdr* pheaders;
int32 count = 0;
int i;
for (i = 0; i < phnum; i++) {
pheaders = (elf_phdr*)(buff + i * phentsize);
switch (pheaders->p_type) {
case PT_NULL:
break;
case PT_LOAD:
count += 1;
if (pheaders->p_memsz != pheaders->p_filesz) {
addr_t A = TO_PAGE_SIZE(pheaders->p_vaddr
+ pheaders->p_memsz);
addr_t B = TO_PAGE_SIZE(pheaders->p_vaddr
+ pheaders->p_filesz);
if (A != B)
count += 1;
}
break;
case PT_DYNAMIC:
break;
case PT_INTERP:
break;
case PT_NOTE:
break;
case PT_SHLIB:
break;
case PT_PHDR:
break;
case PT_EH_FRAME:
case PT_RELRO:
break;
case PT_STACK:
break;
case PT_TLS:
break;
case PT_ARM_UNWIND:
break;
case PT_RISCV_ATTRIBUTES:
break;
default:
FATAL("%s: Unhandled pheader type in count 0x%" B_PRIx32 "\n",
imagePath, pheaders->p_type);
break;
}
}
return count;
}
static status_t
parse_program_headers(image_t* image, char* buff, int phnum, int phentsize)
{
elf_phdr* pheader;
int regcount;
int i;
image->dso_tls_id = unsigned(-1);
regcount = 0;
for (i = 0; i < phnum; i++) {
pheader = (elf_phdr*)(buff + i * phentsize);
switch (pheader->p_type) {
case PT_NULL:
break;
case PT_LOAD:
{
uint32 flags = 0;
if (pheader->p_flags & PF_WRITE) {
flags |= RFLAG_WRITABLE;
}
if (pheader->p_flags & PF_EXECUTE) {
flags |= RFLAG_EXECUTABLE;
}
elf_region_t& region = image->regions[regcount];
region.start = pheader->p_vaddr;
region.vmstart = PAGE_BASE(pheader->p_vaddr);
region.fdstart = pheader->p_offset;
region.fdsize = pheader->p_filesz;
region.delta = 0;
region.flags = flags;
if (pheader->p_memsz == pheader->p_filesz) {
region.size = pheader->p_memsz;
region.vmsize = TO_PAGE_SIZE(pheader->p_memsz
+ PAGE_OFFSET(pheader->p_vaddr));
} else {
region.size = pheader->p_filesz;
region.vmsize = TO_PAGE_SIZE(pheader->p_filesz
+ PAGE_OFFSET(pheader->p_vaddr));
addr_t A = TO_PAGE_SIZE(pheader->p_vaddr + pheader->p_memsz);
addr_t B = TO_PAGE_SIZE(pheader->p_vaddr + pheader->p_filesz);
if (A != B) {
regcount++;
elf_region_t& regionB = image->regions[regcount];
regionB.start = pheader->p_vaddr;
regionB.size = pheader->p_memsz - pheader->p_filesz;
regionB.vmstart = region.vmstart + region.vmsize;
regionB.vmsize
= TO_PAGE_SIZE(pheader->p_memsz
+ PAGE_OFFSET(pheader->p_vaddr))
- region.vmsize;
regionB.fdstart = 0;
regionB.fdsize = 0;
regionB.delta = 0;
regionB.flags = flags | RFLAG_ANON;
}
}
regcount++;
break;
}
case PT_DYNAMIC:
image->dynamic_ptr = pheader->p_vaddr;
break;
case PT_INTERP:
break;
case PT_NOTE:
break;
case PT_SHLIB:
break;
case PT_PHDR:
break;
case PT_EH_FRAME:
case PT_RELRO:
break;
case PT_STACK:
break;
case PT_TLS:
image->dso_tls_id
= TLSBlockTemplates::Get().Register(
TLSBlockTemplate((void*)pheader->p_vaddr,
pheader->p_filesz, pheader->p_memsz));
break;
case PT_ARM_UNWIND:
break;
case PT_RISCV_ATTRIBUTES:
break;
default:
FATAL("%s: Unhandled pheader type in parse 0x%" B_PRIx32 "\n",
image->path, pheader->p_type);
return B_BAD_DATA;
}
}
return B_OK;
}
static bool
assert_dynamic_loadable(image_t* image)
{
uint32 i;
if (!image->dynamic_ptr)
return true;
for (i = 0; i < image->num_regions; i++) {
if (image->dynamic_ptr >= image->regions[i].start
&& image->dynamic_ptr
< image->regions[i].start + image->regions[i].size) {
return true;
}
}
return false;
}
static bool
parse_dynamic_segment(image_t* image)
{
elf_dyn* d;
int i;
int sonameOffset = -1;
image->symhash = 0;
image->syms = 0;
image->strtab = 0;
d = (elf_dyn*)image->dynamic_ptr;
if (!d)
return true;
for (i = 0; d[i].d_tag != DT_NULL; i++) {
switch (d[i].d_tag) {
case DT_NEEDED:
image->num_needed += 1;
break;
case DT_HASH:
image->symhash
= (uint32*)(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_STRTAB:
image->strtab
= (char*)(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_SYMTAB:
image->syms = (elf_sym*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_REL:
image->rel = (elf_rel*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_RELSZ:
image->rel_len = d[i].d_un.d_val;
break;
case DT_RELA:
image->rela = (elf_rela*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_RELASZ:
image->rela_len = d[i].d_un.d_val;
break;
case DT_JMPREL:
image->pltrel = (elf_rel*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_PLTRELSZ:
image->pltrel_len = d[i].d_un.d_val;
break;
case DT_INIT:
image->init_routine
= (d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_FINI:
image->term_routine
= (d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_SONAME:
sonameOffset = d[i].d_un.d_val;
break;
case DT_GNU_HASH:
{
uint32* gnuhash = (uint32*)
(d[i].d_un.d_ptr + image->regions[0].delta);
const uint32 bucketCount = gnuhash[0];
const uint32 symIndex = gnuhash[1];
const uint32 maskWordsCount = gnuhash[2];
const uint32 bloomSize = maskWordsCount * (sizeof(elf_addr) / 4);
image->gnuhash.mask_words_count_mask = maskWordsCount - 1;
image->gnuhash.shift2 = gnuhash[3];
image->gnuhash.bucket_count = bucketCount;
image->gnuhash.bloom = (elf_addr*)(gnuhash + 4);
image->gnuhash.buckets = gnuhash + 4 + bloomSize;
image->gnuhash.chain0 = image->gnuhash.buckets + bucketCount - symIndex;
break;
}
case DT_VERSYM:
image->symbol_versions = (elf_versym*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_VERDEF:
image->version_definitions = (elf_verdef*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_VERDEFNUM:
image->num_version_definitions = d[i].d_un.d_val;
break;
case DT_VERNEED:
image->needed_versions = (elf_verneed*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_VERNEEDNUM:
image->num_needed_versions = d[i].d_un.d_val;
break;
case DT_SYMBOLIC:
image->flags |= RFLAG_SYMBOLIC;
break;
case DT_FLAGS:
{
uint32 flags = d[i].d_un.d_val;
if ((flags & DF_SYMBOLIC) != 0)
image->flags |= RFLAG_SYMBOLIC;
if ((flags & DF_STATIC_TLS) != 0) {
FATAL("Static TLS model is not supported.\n");
return false;
}
break;
}
case DT_INIT_ARRAY:
image->init_array = (addr_t*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_INIT_ARRAYSZ:
image->init_array_len = d[i].d_un.d_val;
break;
case DT_PREINIT_ARRAY:
image->preinit_array = (addr_t*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_PREINIT_ARRAYSZ:
image->preinit_array_len = d[i].d_un.d_val;
break;
case DT_FINI_ARRAY:
image->term_array = (addr_t*)
(d[i].d_un.d_ptr + image->regions[0].delta);
break;
case DT_FINI_ARRAYSZ:
image->term_array_len = d[i].d_un.d_val;
break;
default:
continue;
}
}
if (!image->symhash || !image->syms || !image->strtab)
return false;
if (sonameOffset >= 0)
strlcpy(image->name, STRING(image, sonameOffset), sizeof(image->name));
return true;
}
status_t
parse_elf_header(elf_ehdr* eheader, int32* _pheaderSize,
int32* _sheaderSize)
{
if (memcmp(eheader->e_ident, ELFMAG, 4) != 0)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_ident[4] != ELF_CLASS)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_phoff == 0)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_phentsize < sizeof(elf_phdr))
return B_NOT_AN_EXECUTABLE;
*_pheaderSize = eheader->e_phentsize * eheader->e_phnum;
*_sheaderSize = eheader->e_shentsize * eheader->e_shnum;
if (*_pheaderSize <= 0)
return B_NOT_AN_EXECUTABLE;
return B_OK;
}
#if defined(_COMPAT_MODE)
#if defined(__x86_64__)
status_t
parse_elf32_header(Elf32_Ehdr* eheader, int32* _pheaderSize,
int32* _sheaderSize)
{
if (memcmp(eheader->e_ident, ELFMAG, 4) != 0)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_ident[4] != ELFCLASS32)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_phoff == 0)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_phentsize < sizeof(Elf32_Phdr))
return B_NOT_AN_EXECUTABLE;
*_pheaderSize = eheader->e_phentsize * eheader->e_phnum;
*_sheaderSize = eheader->e_shentsize * eheader->e_shnum;
if (*_pheaderSize <= 0)
return B_NOT_AN_EXECUTABLE;
return B_OK;
}
#else
status_t
parse_elf64_header(Elf64_Ehdr* eheader, int32* _pheaderSize,
int32* _sheaderSize)
{
if (memcmp(eheader->e_ident, ELFMAG, 4) != 0)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_ident[4] != ELFCLASS64)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_phoff == 0)
return B_NOT_AN_EXECUTABLE;
if (eheader->e_phentsize < sizeof(Elf64_Phdr))
return B_NOT_AN_EXECUTABLE;
*_pheaderSize = eheader->e_phentsize * eheader->e_phnum;
*_sheaderSize = eheader->e_shentsize * eheader->e_shnum;
if (*_pheaderSize <= 0)
return B_NOT_AN_EXECUTABLE;
return B_OK;
}
#endif
#endif
status_t
load_image(char const* name, image_type type, const char* rpath, const char* runpath,
const char* requestingObjectPath, image_t** _image)
{
int32 pheaderSize, sheaderSize;
char path[PATH_MAX];
ssize_t length;
char pheaderBuffer[4096];
int32 numRegions;
image_t* found;
image_t* image;
status_t status;
int fd;
elf_ehdr eheader;
if (type != B_ADD_ON_IMAGE) {
found = find_loaded_image_by_name(name, APP_OR_LIBRARY_TYPE);
if (found == NULL && type != B_APP_IMAGE && gProgramImage != NULL) {
if (const char* lastSlash = strrchr(name, '/')) {
if (strcmp(gProgramImage->name, lastSlash + 1) == 0)
found = gProgramImage;
}
}
if (found) {
atomic_add(&found->ref_count, 1);
*_image = found;
KTRACE("rld: load_container(\"%s\", type: %d, %s: \"%s\") "
"already loaded", name, type,
runpath != NULL ? "runpath" : "rpath", runpath != NULL ? runpath : rpath);
return B_OK;
}
}
KTRACE("rld: load_container(\"%s\", type: %d, %s: \"%s\")", name, type,
runpath != NULL ? "runpath" : "rpath", runpath != NULL ? runpath : rpath);
strlcpy(path, name, sizeof(path));
fd = open_executable(path, type, rpath, runpath, get_program_path(),
requestingObjectPath, sSearchPathSubDir);
if (fd < 0) {
FATAL("Cannot open file %s (needed by %s): %s\n", name, requestingObjectPath, strerror(fd));
KTRACE("rld: load_container(\"%s\"): failed to open file", name);
return fd;
}
status = _kern_normalize_path(path, true, path);
if (status != B_OK)
goto err1;
if (type != B_ADD_ON_IMAGE) {
found = find_loaded_image_by_name(path, APP_OR_LIBRARY_TYPE);
if (found) {
atomic_add(&found->ref_count, 1);
*_image = found;
_kern_close(fd);
KTRACE("rld: load_container(\"%s\"): already loaded after all",
name);
return B_OK;
}
}
length = _kern_read(fd, 0, &eheader, sizeof(eheader));
if (length != sizeof(eheader)) {
status = B_NOT_AN_EXECUTABLE;
FATAL("%s: Troubles reading ELF header\n", path);
goto err1;
}
status = parse_elf_header(&eheader, &pheaderSize, &sheaderSize);
if (status < B_OK) {
FATAL("%s: Incorrect ELF header\n", path);
goto err1;
}
if (pheaderSize > (int)sizeof(pheaderBuffer)) {
FATAL("%s: Cannot handle program headers bigger than %lu\n",
path, sizeof(pheaderBuffer));
status = B_UNSUPPORTED;
goto err1;
}
length = _kern_read(fd, eheader.e_phoff, pheaderBuffer, pheaderSize);
if (length != pheaderSize) {
FATAL("%s: Could not read program headers: %s\n", path,
strerror(length));
status = B_BAD_DATA;
goto err1;
}
numRegions = count_regions(path, pheaderBuffer, eheader.e_phnum,
eheader.e_phentsize);
if (numRegions <= 0) {
FATAL("%s: Troubles parsing Program headers, numRegions = %" B_PRId32
"\n", path, numRegions);
status = B_BAD_DATA;
goto err1;
}
image = create_image(name, path, numRegions);
if (image == NULL) {
FATAL("%s: Failed to allocate image_t object\n", path);
status = B_NO_MEMORY;
goto err1;
}
status = parse_program_headers(image, pheaderBuffer, eheader.e_phnum,
eheader.e_phentsize);
if (status < B_OK)
goto err2;
if (!assert_dynamic_loadable(image)) {
FATAL("%s: Dynamic segment must be loadable (implementation "
"restriction)\n", image->path);
status = B_UNSUPPORTED;
goto err2;
}
status = map_image(fd, path, image, eheader.e_type == ET_EXEC);
if (status < B_OK) {
FATAL("%s: Could not map image: %s\n", image->path, strerror(status));
status = B_ERROR;
goto err2;
}
if (!parse_dynamic_segment(image)) {
FATAL("%s: Troubles handling dynamic section\n", image->path);
status = B_BAD_DATA;
goto err3;
}
if (eheader.e_entry != 0)
image->entry_point = eheader.e_entry + image->regions[0].delta;
analyze_image_haiku_version_and_abi(fd, image, eheader, sheaderSize,
pheaderBuffer, sizeof(pheaderBuffer));
if (sSearchPathSubDir == NULL) {
#if __GNUC__ == 2 || (defined(_COMPAT_MODE) && !defined(__x86_64__))
if ((image->abi & B_HAIKU_ABI_MAJOR) == B_HAIKU_ABI_GCC_4)
sSearchPathSubDir = "x86";
#endif
#if __GNUC__ >= 4 || (defined(_COMPAT_MODE) && !defined(__x86_64__))
if ((image->abi & B_HAIKU_ABI_MAJOR) == B_HAIKU_ABI_GCC_2)
sSearchPathSubDir = "x86_gcc2";
#endif
}
set_abi_api_version(image->abi, image->api_version);
if (image->abi == B_HAIKU_ABI_GCC_2_ANCIENT)
image->find_undefined_symbol = find_undefined_symbol_dependencies_only;
status = init_image_version_infos(image);
image->type = type;
register_image(image, fd, path);
image_event(image, IMAGE_EVENT_LOADED);
_kern_close(fd);
enqueue_loaded_image(image);
*_image = image;
KTRACE("rld: load_container(\"%s\"): done: id: %" B_PRId32 " (ABI: %#"
B_PRIx32 ")", name, image->id, image->abi);
return B_OK;
err3:
unmap_image(image);
err2:
delete_image_struct(image);
err1:
_kern_close(fd);
KTRACE("rld: load_container(\"%s\"): failed: %s", name,
strerror(status));
return status;
}