* Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2003-2008, 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_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"
It is expected that \a name does not contain directory components. It is
compared with the base name of \a image's name.
\param image The image.
\param name The name to check against. Can be NULL, in which case \c false
is returned.
\return \c true, iff \a name is non-NULL and matches the name of \a image.
*/
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;
}