#include "elf_symbol_lookup.h"
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include "add_ons.h"
#include "errors.h"
#include "images.h"
#include "runtime_loader_private.h"
static bool
equals_image_name(const image_t* image, const char* name)
{
if (name == NULL)
return false;
const char* lastSlash = strrchr(name, '/');
return strcmp(image->name, lastSlash != NULL ? lastSlash + 1 : name) == 0;
}
uint32
elf_hash(const char* _name)
{
const uint8* name = (const uint8*)_name;
uint32 h = 0;
while (*name != '\0') {
h = (h << 4) + *name++;
h ^= (h >> 24) & 0xf0;
}
return (h & 0x0fffffff);
}
uint32
elf_gnuhash(const char* _name)
{
const uint8* name = (const uint8*)_name;
uint32 h = 5381;
for (uint8 c = *name; c != '\0'; c = *++name)
h = (h * 33) + c;
return h;
}
struct match_result {
elf_sym* symbol;
elf_sym* versioned_symbol;
uint32 versioned_symbol_count;
match_result() : symbol(NULL), versioned_symbol(NULL), versioned_symbol_count(0) {}
};
static bool
match_symbol(const image_t* image, const SymbolLookupInfo& lookupInfo, uint32 symIdx,
match_result& result)
{
elf_sym* symbol = &image->syms[symIdx];
if (symbol->st_shndx == SHN_UNDEF)
return false;
if (symbol->Bind() != STB_GLOBAL && symbol->Bind() != STB_WEAK)
return false;
uint32 type = symbol->Type();
if ((lookupInfo.type == B_SYMBOL_TYPE_TEXT && type != STT_FUNC)
|| (lookupInfo.type == B_SYMBOL_TYPE_DATA
&& type != STT_OBJECT && type != STT_FUNC)) {
return false;
}
if (strcmp(SYMNAME(image, symbol), lookupInfo.name) != 0)
return false;
if (image->symbol_versions == NULL) {
if (lookupInfo.version == NULL) {
result.symbol = symbol;
return true;
}
if (equals_image_name(image, lookupInfo.version->file_name)) {
return false;
}
result.symbol = symbol;
return true;
}
uint32 versionID = image->symbol_versions[symIdx];
uint32 versionIndex = VER_NDX(versionID);
elf_version_info& version = image->versions[versionIndex];
if (versionIndex == VER_NDX_LOCAL)
return false;
if (lookupInfo.version != NULL) {
if (version.hash == lookupInfo.version->hash
&& strcmp(version.name, lookupInfo.version->name) == 0) {
result.symbol = symbol;
return true;
}
if ((versionID & VER_NDX_FLAG_HIDDEN) == 0
&& versionIndex == VER_NDX_GLOBAL
&& (lookupInfo.flags & LOOKUP_FLAG_DEFAULT_VERSION)
== 0) {
result.symbol = symbol;
return true;
}
} else {
if (versionIndex == VER_NDX_GLOBAL
|| ((lookupInfo.flags & LOOKUP_FLAG_DEFAULT_VERSION) == 0
&& versionIndex == VER_NDX_INITIAL)) {
result.symbol = symbol;
return true;
}
if ((versionID & VER_NDX_FLAG_HIDDEN) == 0) {
result.versioned_symbol_count++;
result.versioned_symbol = symbol;
}
}
return false;
}
static elf_sym*
find_symbol_gnuhash(const image_t* image, const SymbolLookupInfo& lookupInfo)
{
const uint32 wordSize = sizeof(elf_addr) * 8;
const uint32 firstHash = lookupInfo.gnuhash & (wordSize - 1);
const uint32 secondHash = lookupInfo.gnuhash >> image->gnuhash.shift2;
const uint32 index = (lookupInfo.gnuhash / wordSize) & image->gnuhash.mask_words_count_mask;
const elf_addr bloomWord = image->gnuhash.bloom[index];
if (((bloomWord >> firstHash) & (bloomWord >> secondHash) & 1) == 0)
return NULL;
const uint32 bucket = image->gnuhash.buckets[lookupInfo.gnuhash % image->gnuhash.bucket_count];
if (bucket == 0)
return NULL;
match_result result;
const uint32* chain0 = image->gnuhash.chain0;
const uint32* hashValue = &chain0[bucket];
do {
if (((*hashValue ^ lookupInfo.gnuhash) >> 1) != 0)
continue;
uint32 symIndex = hashValue - chain0;
if (match_symbol(image, lookupInfo, symIndex, result))
return result.symbol;
} while ((*hashValue++ & 1) == 0);
if (result.versioned_symbol_count == 1)
return result.versioned_symbol;
return NULL;
}
static elf_sym*
find_symbol_sysv(const image_t* image, const SymbolLookupInfo& lookupInfo)
{
if (image->dynamic_ptr == 0)
return NULL;
match_result result;
uint32 bucket = lookupInfo.hash % HASHTABSIZE(image);
for (uint32 symIndex = HASHBUCKETS(image)[bucket]; symIndex != STN_UNDEF;
symIndex = HASHCHAINS(image)[symIndex]) {
if (match_symbol(image, lookupInfo, symIndex, result))
return result.symbol;
}
if (result.versioned_symbol_count == 1)
return result.versioned_symbol;
return NULL;
}
elf_sym*
find_symbol(image_t* image, const SymbolLookupInfo& lookupInfo)
{
if (image->gnuhash.buckets != NULL) {
if (lookupInfo.gnuhash == 0)
const_cast<uint32&>(lookupInfo.gnuhash) = elf_gnuhash(lookupInfo.name);
return find_symbol_gnuhash(image, lookupInfo);
}
if (lookupInfo.hash == 0)
const_cast<uint32&>(lookupInfo.hash) = elf_hash(lookupInfo.name);
return find_symbol_sysv(image, lookupInfo);
}
void
patch_defined_symbol(image_t* image, const char* name, void** symbol,
int32* type)
{
RuntimeLoaderSymbolPatcher* patcher = image->defined_symbol_patchers;
while (patcher != NULL && *symbol != 0) {
image_t* inImage = image;
patcher->patcher(patcher->cookie, NULL, image, name, &inImage,
symbol, type);
patcher = patcher->next;
}
}
void
patch_undefined_symbol(image_t* rootImage, image_t* image, const char* name,
image_t** foundInImage, void** symbol, int32* type)
{
if (*foundInImage != NULL)
patch_defined_symbol(*foundInImage, name, symbol, type);
RuntimeLoaderSymbolPatcher* patcher = image->undefined_symbol_patchers;
while (patcher != NULL) {
patcher->patcher(patcher->cookie, rootImage, image, name, foundInImage,
symbol, type);
patcher = patcher->next;
}
}
status_t
find_symbol(image_t* image, const SymbolLookupInfo& lookupInfo,
void **_location)
{
elf_sym* symbol = find_symbol(image, lookupInfo);
if (symbol == NULL)
return B_ENTRY_NOT_FOUND;
void* location = (void*)(symbol->st_value + image->regions[0].delta);
int32 symbolType = lookupInfo.type;
patch_defined_symbol(image, lookupInfo.name, &location, &symbolType);
if (_location != NULL)
*_location = location;
return B_OK;
}
status_t
find_symbol_breadth_first(image_t* image, const SymbolLookupInfo& lookupInfo,
image_t** _foundInImage, void** _location)
{
image_t* queue[count_loaded_images()];
uint32 count = 0;
uint32 index = 0;
queue[count++] = image;
image->flags |= RFLAG_VISITED;
elf_sym* candidateSymbol = NULL;
image_t* candidateImage = NULL;
while (index < count) {
image = queue[index++];
elf_sym* symbol = find_symbol(image, lookupInfo);
if (symbol != NULL) {
bool isWeak = symbol->Bind() == STB_WEAK;
if (candidateImage == NULL || !isWeak) {
candidateSymbol = symbol;
candidateImage = image;
if (!isWeak)
break;
}
}
for (uint32 i = 0; i < image->num_needed; i++) {
image_t* needed = image->needed[i];
if ((needed->flags & RFLAG_VISITED) == 0) {
queue[count++] = needed;
needed->flags |= RFLAG_VISITED;
}
}
}
for (uint32 i = 0; i < count; i++)
queue[i]->flags &= ~RFLAG_VISITED;
if (candidateSymbol == NULL)
return B_ENTRY_NOT_FOUND;
*_location = (void*)(candidateSymbol->st_value
+ candidateImage->regions[0].delta);
int32 symbolType = lookupInfo.type;
patch_defined_symbol(candidateImage, lookupInfo.name, _location,
&symbolType);
if (_foundInImage != NULL)
*_foundInImage = candidateImage;
return B_OK;
}
elf_sym*
find_undefined_symbol_dependencies_only(image_t* rootImage, image_t* image,
const SymbolLookupInfo& lookupInfo, image_t** foundInImage)
{
if (elf_sym* symbol = lookupInfo.requestingSymbol) {
if (symbol->st_shndx != SHN_UNDEF
&& ((symbol->Bind() == STB_GLOBAL)
|| (symbol->Bind() == STB_WEAK))) {
*foundInImage = image;
return symbol;
}
}
elf_sym* symbol = find_symbol(image, lookupInfo);
if (symbol != NULL) {
*foundInImage = image;
return symbol;
}
for (uint32 i = 0; i < image->num_needed; i++) {
if (image->needed[i]->dynamic_ptr) {
symbol = find_symbol(image->needed[i], lookupInfo);
if (symbol != NULL) {
*foundInImage = image->needed[i];
return symbol;
}
}
}
return NULL;
}
elf_sym*
find_undefined_symbol_global(image_t* rootImage, image_t* image,
const SymbolLookupInfo& lookupInfo, image_t** _foundInImage)
{
image_t* candidateImage = NULL;
elf_sym* candidateSymbol = NULL;
bool symbolic = (image->flags & RFLAG_SYMBOLIC) != 0;
if (symbolic) {
candidateSymbol = find_symbol(image, lookupInfo);
if (candidateSymbol != NULL) {
if (candidateSymbol->Bind() != STB_WEAK) {
*_foundInImage = image;
return candidateSymbol;
}
candidateImage = image;
}
}
image_t* otherImage = get_loaded_images().head;
while (otherImage != NULL) {
if (otherImage == rootImage
? !symbolic
: (otherImage->type != B_ADD_ON_IMAGE
&& (otherImage->flags
& (RTLD_GLOBAL | RFLAG_USE_FOR_RESOLVING)) != 0)) {
if (elf_sym* symbol = find_symbol(otherImage, lookupInfo)) {
*_foundInImage = otherImage;
return symbol;
}
}
otherImage = otherImage->next;
}
if (candidateSymbol != NULL)
*_foundInImage = candidateImage;
return candidateSymbol;
}
elf_sym*
find_undefined_symbol_add_on(image_t* rootImage, image_t* image,
const SymbolLookupInfo& lookupInfo, image_t** _foundInImage)
{
if (rootImage == image) {
if (elf_sym* symbol = lookupInfo.requestingSymbol) {
if (symbol->st_shndx != SHN_UNDEF
&& (symbol->Bind() == STB_GLOBAL)) {
*_foundInImage = image;
return symbol;
}
}
}
image_t* candidateImage = NULL;
elf_sym* candidateSymbol = NULL;
bool symbolic = (image->flags & RFLAG_SYMBOLIC) != 0;
if (symbolic) {
candidateSymbol = find_symbol(image, lookupInfo);
if (candidateSymbol != NULL) {
if (candidateSymbol->Bind() != STB_WEAK) {
*_foundInImage = image;
return candidateSymbol;
}
candidateImage = image;
}
}
image_t* otherImage = get_loaded_images().head;
while (otherImage != NULL) {
if (otherImage != rootImage
&& otherImage->type != B_ADD_ON_IMAGE
&& (otherImage->flags
& (RTLD_GLOBAL | RFLAG_USE_FOR_RESOLVING)) != 0) {
if (elf_sym* symbol = find_symbol(otherImage, lookupInfo)) {
if (symbol->Bind() != STB_WEAK || image->abi >= B_HAIKU_ABI_GCC_4) {
*_foundInImage = otherImage;
return symbol;
}
if (candidateSymbol == NULL) {
candidateSymbol = symbol;
candidateImage = otherImage;
}
}
}
otherImage = otherImage->next;
}
if (!symbolic && candidateSymbol == NULL && image == rootImage) {
candidateSymbol = find_symbol(image, lookupInfo);
candidateImage = image;
}
if (candidateSymbol != NULL)
*_foundInImage = candidateImage;
return candidateSymbol;
}
int
resolve_symbol(image_t* rootImage, image_t* image, elf_sym* sym,
SymbolLookupCache* cache, addr_t* symAddress, image_t** symbolImage)
{
uint32 index = sym - image->syms;
if (cache->IsSymbolValueCached(index)) {
*symAddress = cache->SymbolValueAt(index, symbolImage);
return B_OK;
}
elf_sym* sharedSym;
image_t* sharedImage;
const char* symName = SYMNAME(image, sym);
int32 type = B_SYMBOL_TYPE_ANY;
if (sym->Type() == STT_FUNC)
type = B_SYMBOL_TYPE_TEXT;
if (sym->Bind() == STB_LOCAL) {
sharedImage = image;
sharedSym = sym;
} else {
const elf_version_info* versionInfo = NULL;
if (image->symbol_versions != NULL) {
uint32 versionIndex = VER_NDX(image->symbol_versions[index]);
if (versionIndex >= VER_NDX_INITIAL)
versionInfo = image->versions + versionIndex;
}
sharedSym = rootImage->find_undefined_symbol(rootImage, image,
SymbolLookupInfo(symName, type, versionInfo, 0, sym), &sharedImage);
}
enum {
SUCCESS,
ERROR_NO_SYMBOL,
ERROR_WRONG_TYPE,
ERROR_NOT_EXPORTED,
ERROR_UNPATCHED
};
uint32 lookupError = ERROR_UNPATCHED;
bool tlsSymbol = sym->Type() == STT_TLS;
void* location = NULL;
if (sharedSym == NULL) {
if (sym->Bind() == STB_WEAK) {
location = sharedImage = NULL;
} else {
lookupError = ERROR_NO_SYMBOL;
sharedImage = NULL;
}
} else if (sym->Type() != STT_NOTYPE
&& sym->Type() != sharedSym->Type()
&& (sym->Type() != STT_OBJECT || sharedSym->Type() != STT_FUNC)) {
lookupError = ERROR_WRONG_TYPE;
sharedImage = NULL;
} else if (sharedSym->Bind() != STB_GLOBAL
&& sharedSym->Bind() != STB_WEAK) {
lookupError = ERROR_NOT_EXPORTED;
sharedImage = NULL;
} else {
location = (void*)sharedSym->st_value;
if (!tlsSymbol) {
location
= (void*)((addr_t)location + sharedImage->regions[0].delta);
} else
lookupError = SUCCESS;
}
if (!tlsSymbol) {
patch_undefined_symbol(rootImage, image, symName, &sharedImage,
&location, &type);
}
if (type == 0 || (location == NULL && sym->Bind() != STB_WEAK && lookupError != SUCCESS)) {
switch (lookupError) {
case ERROR_NO_SYMBOL:
FATAL("%s: Could not resolve symbol '%s'\n",
image->path, symName);
break;
case ERROR_WRONG_TYPE:
FATAL("%s: Found symbol '%s' in shared image but wrong "
"type\n", image->path, symName);
break;
case ERROR_NOT_EXPORTED:
FATAL("%s: Found symbol '%s', but not exported\n",
image->path, symName);
break;
case ERROR_UNPATCHED:
FATAL("%s: Found symbol '%s', but was hidden by symbol "
"patchers\n", image->path, symName);
break;
}
if (report_errors())
gErrorMessage.AddString("missing symbol", symName);
return B_MISSING_SYMBOL;
}
cache->SetSymbolValueAt(index, (addr_t)location, sharedImage);
if (symbolImage)
*symbolImage = sharedImage;
*symAddress = (addr_t)location;
return B_OK;
}