haiku/src/system/runtime_loader/elf_symbol_lookup.cpp

467 lines
12 KiB
C++
Raw Normal View History

/*
* Copyright 2008-2009, 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"
static bool
equals_image_name(image_t* image, const char* name)
{
const char* lastSlash = strrchr(name, '/');
return strcmp(image->name, lastSlash != NULL ? lastSlash + 1 : name) == 0;
}
// #pragma mark -
uint32
elf_hash(const char* _name)
{
const uint8* name = (const uint8*)_name;
uint32 hash = 0;
uint32 temp;
while (*name) {
hash = (hash << 4) + *name++;
if ((temp = hash & 0xf0000000)) {
hash ^= temp >> 24;
}
hash &= ~temp;
}
return hash;
}
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;
}
}
Elf32_Sym*
find_symbol(image_t* image, const SymbolLookupInfo& lookupInfo)
{
if (image->dynamic_ptr == 0)
return NULL;
Elf32_Sym* versionedSymbol = NULL;
uint32 versionedSymbolCount = 0;
uint32 bucket = lookupInfo.hash % HASHTABSIZE(image);
for (uint32 i = HASHBUCKETS(image)[bucket]; i != STN_UNDEF;
i = HASHCHAINS(image)[i]) {
Elf32_Sym* symbol = &image->syms[i];
if (symbol->st_shndx != SHN_UNDEF
&& ((ELF32_ST_BIND(symbol->st_info)== STB_GLOBAL)
|| (ELF32_ST_BIND(symbol->st_info) == STB_WEAK))
&& !strcmp(SYMNAME(image, symbol), lookupInfo.name)) {
// check if the type matches
uint32 type = ELF32_ST_TYPE(symbol->st_info);
if ((lookupInfo.type == B_SYMBOL_TYPE_TEXT && type != STT_FUNC)
|| (lookupInfo.type == B_SYMBOL_TYPE_DATA
&& type != STT_OBJECT)) {
continue;
}
// check the version
// Handle the simple cases -- the image doesn't have version
// information -- first.
if (image->symbol_versions == NULL) {
if (lookupInfo.version == NULL) {
// No specific symbol version was requested either, so the
// symbol is just fine.
return symbol;
}
// A specific version is requested. If it's the dependency
// referred to by the requested version, it's apparently an
// older version of the dependency and we're not happy.
if (equals_image_name(image, lookupInfo.version->file_name)) {
// TODO: That should actually be kind of fatal!
return NULL;
}
// This is some other image. We accept the symbol.
return symbol;
}
// The image has version information. Let's see what we've got.
uint32 versionID = image->symbol_versions[i];
uint32 versionIndex = VER_NDX(versionID);
elf_version_info& version = image->versions[versionIndex];
// skip local versions
if (versionIndex == VER_NDX_LOCAL)
continue;
if (lookupInfo.version != NULL) {
// a specific version is requested
// compare the versions
if (version.hash == lookupInfo.version->hash
&& strcmp(version.name, lookupInfo.version->name) == 0) {
// versions match
return symbol;
}
// The versions don't match. We're still fine with the
// base version, if it is public and we're not looking for
// the default version.
if ((versionID & VER_NDX_FLAG_HIDDEN) == 0
&& versionIndex == VER_NDX_GLOBAL
&& (lookupInfo.flags & LOOKUP_FLAG_DEFAULT_VERSION)
== 0) {
// TODO: Revise the default version case! That's how
// FreeBSD implements it, but glibc doesn't handle it
// specially.
return symbol;
}
} else {
// No specific version requested, but the image has version
// information. This can happen in either of these cases:
//
// * The dependent object was linked against an older version
// of the now versioned dependency.
// * The symbol is looked up via find_image_symbol() or dlsym().
//
// In the first case we return the base version of the symbol
// (VER_NDX_GLOBAL or VER_NDX_INITIAL), or, if that doesn't
// exist, the unique, non-hidden versioned symbol.
//
// In the second case we want to return the public default
// version of the symbol. The handling is pretty similar to the
// first case, with the exception that we treat VER_NDX_INITIAL
// as regular version.
// VER_NDX_GLOBAL is always good, VER_NDX_INITIAL is fine, if
// we don't look for the default version.
if (versionIndex == VER_NDX_GLOBAL
|| ((lookupInfo.flags & LOOKUP_FLAG_DEFAULT_VERSION) == 0
&& versionIndex == VER_NDX_INITIAL)) {
return symbol;
}
// If not hidden, remember the version -- we'll return it, if
// it is the only one.
if ((versionID & VER_NDX_FLAG_HIDDEN) == 0) {
versionedSymbolCount++;
versionedSymbol = symbol;
}
}
}
}
return versionedSymbolCount == 1 ? versionedSymbol : NULL;
}
status_t
find_symbol(image_t* image, const SymbolLookupInfo& lookupInfo,
void **_location)
{
// get the symbol in the image
Elf32_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;
bool found = false;
while (index < count) {
// pop next image
image = queue[index++];
if (find_symbol(image, lookupInfo, _location) == B_OK) {
if (_foundInImage != NULL)
*_foundInImage = image;
found = true;
break;
}
// push needed images
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;
}
}
}
// clear visited flags
for (uint32 i = 0; i < count; i++)
queue[i]->flags &= ~RFLAG_VISITED;
return found ? B_OK : B_ENTRY_NOT_FOUND;
}
Elf32_Sym*
find_undefined_symbol_beos(image_t* rootImage, image_t* image,
const SymbolLookupInfo& lookupInfo, image_t** foundInImage)
{
// BeOS style symbol resolution: It is sufficient to check the direct
// dependencies. The linker would have complained, if the symbol wasn't
// there.
for (uint32 i = 0; i < image->num_needed; i++) {
if (image->needed[i]->dynamic_ptr) {
Elf32_Sym *symbol = find_symbol(image->needed[i],
lookupInfo);
if (symbol) {
*foundInImage = image->needed[i];
return symbol;
}
}
}
return NULL;
}
Elf32_Sym*
find_undefined_symbol_global(image_t* rootImage, image_t* image,
const SymbolLookupInfo& lookupInfo, image_t** foundInImage)
{
// Global load order symbol resolution: All loaded images are searched for
// the symbol in the order they have been loaded. We skip add-on images and
// RTLD_LOCAL images though.
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)) {
Elf32_Sym *symbol = find_symbol(otherImage, lookupInfo);
if (symbol) {
*foundInImage = otherImage;
return symbol;
}
}
otherImage = otherImage->next;
}
return NULL;
}
Elf32_Sym*
find_undefined_symbol_add_on(image_t* rootImage, image_t* image,
const SymbolLookupInfo& lookupInfo, image_t** foundInImage)
{
// Do a breadth-first resolution in the add-on dependency scope,
// skipping the add-on itself.
Elf32_Sym* foundSymbol = NULL;
image_t* queue[count_loaded_images()];
uint32 count = 0;
uint32 index = 0;
queue[count++] = image;
image->flags |= RFLAG_VISITED;
image_t* currentImage;
while (index < count) {
// pop next image
currentImage = queue[index++];
if (currentImage != image) {
foundSymbol = find_symbol(currentImage, lookupInfo);
if (foundSymbol != NULL) {
if (foundInImage != NULL)
*foundInImage = currentImage;
break;
}
}
// push needed images
for (uint32 i = 0; i < currentImage->num_needed; i++) {
image_t* needed = currentImage->needed[i];
if ((needed->flags & RFLAG_VISITED) == 0) {
queue[count++] = needed;
needed->flags |= RFLAG_VISITED;
}
}
}
// clear visited flags
for (uint32 i = 0; i < count; i++)
queue[i]->flags &= ~RFLAG_VISITED;
return foundSymbol;
}
int
resolve_symbol(image_t* rootImage, image_t* image, struct Elf32_Sym* sym,
addr_t* symAddress)
{
switch (sym->st_shndx) {
case SHN_UNDEF:
{
struct Elf32_Sym* sharedSym;
image_t* sharedImage;
const char* symName = SYMNAME(image, sym);
// get the version info
const elf_version_info* versionInfo = NULL;
if (image->symbol_versions != NULL) {
uint32 index = sym - image->syms;
uint32 versionIndex = VER_NDX(image->symbol_versions[index]);
if (versionIndex >= VER_NDX_INITIAL)
versionInfo = image->versions + versionIndex;
}
int32 type = B_SYMBOL_TYPE_ANY;
if (ELF32_ST_TYPE(sym->st_info) == STT_FUNC)
type = B_SYMBOL_TYPE_TEXT;
else if (ELF32_ST_TYPE(sym->st_info) == STT_OBJECT)
type = B_SYMBOL_TYPE_DATA;
// it's undefined, must be outside this image, try the other images
sharedSym = rootImage->find_undefined_symbol(rootImage, image,
SymbolLookupInfo(symName, type, versionInfo), &sharedImage);
void* location = NULL;
enum {
ERROR_NO_SYMBOL,
ERROR_WRONG_TYPE,
ERROR_NOT_EXPORTED,
ERROR_UNPATCHED
};
uint32 lookupError = ERROR_UNPATCHED;
if (sharedSym == NULL) {
// symbol not found at all
lookupError = ERROR_NO_SYMBOL;
sharedImage = NULL;
} else if (ELF32_ST_TYPE(sym->st_info) != STT_NOTYPE
&& ELF32_ST_TYPE(sym->st_info)
!= ELF32_ST_TYPE(sharedSym->st_info)) {
// symbol not of the requested type
lookupError = ERROR_WRONG_TYPE;
sharedImage = NULL;
} else if (ELF32_ST_BIND(sharedSym->st_info) != STB_GLOBAL
&& ELF32_ST_BIND(sharedSym->st_info) != STB_WEAK) {
// symbol not exported
lookupError = ERROR_NOT_EXPORTED;
sharedImage = NULL;
} else {
// symbol is fine, get its location
location = (void*)(sharedSym->st_value
+ sharedImage->regions[0].delta);
}
patch_undefined_symbol(rootImage, image, symName, &sharedImage,
&location, &type);
if (location == NULL) {
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;
}
*symAddress = (addr_t)location;
return B_OK;
}
case SHN_ABS:
*symAddress = sym->st_value + image->regions[0].delta;
return B_NO_ERROR;
case SHN_COMMON:
// TODO: finish this
FATAL("%s: elf_resolve_symbol: COMMON symbol, finish me!\n",
image->path);
return B_ERROR; //ERR_NOT_IMPLEMENTED_YET;
default:
// standard symbol
*symAddress = sym->st_value + image->regions[0].delta;
return B_NO_ERROR;
}
}