594 lines
13 KiB
C++
594 lines
13 KiB
C++
/**
|
|
* \brief Genode's dynamic linker
|
|
* \author Sebastian Sumpf
|
|
* \date 2014-10-26
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2014 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
#include <base/env.h>
|
|
#include <base/printf.h>
|
|
#include <os/config.h>
|
|
#include <util/list.h>
|
|
#include <util/string.h>
|
|
|
|
#include <dynamic.h>
|
|
#include <init.h>
|
|
|
|
using namespace Linker;
|
|
|
|
namespace Linker {
|
|
struct Dynamic;
|
|
struct Ld;
|
|
struct Ld_vtable;
|
|
struct Binary;
|
|
struct Link_map;
|
|
struct Debug;
|
|
|
|
};
|
|
|
|
/**
|
|
* Genode args to the 'main' function
|
|
*/
|
|
extern char **genode_argv;
|
|
extern int genode_argc;
|
|
extern char **genode_envp;
|
|
|
|
static Binary *binary = 0;
|
|
bool Linker::bind_now = false;
|
|
Link_map *Link_map::first;
|
|
|
|
/**
|
|
* Registers dtors
|
|
*/
|
|
int genode_atexit(Linker::Func);
|
|
|
|
|
|
/**************************************************************
|
|
** ELF object types (shared object, dynamic binaries, ldso **
|
|
**************************************************************/
|
|
|
|
/**
|
|
* The actual ELF object, one per file
|
|
*/
|
|
struct Linker::Elf_object : Object, Genode::Fifo<Elf_object>::Element
|
|
{
|
|
Dynamic dyn;
|
|
Link_map map;
|
|
unsigned ref_count = 1;
|
|
unsigned flags = 0;
|
|
bool relocated = false;
|
|
|
|
Elf_object(Dependency const *dep, Elf::Addr reloc_base)
|
|
: Object(reloc_base), dyn(dep)
|
|
{ }
|
|
|
|
Elf_object(char const *path, Dependency const *dep, unsigned flags = 0)
|
|
:
|
|
Object(path, Linker::load(Linker::file(path))), dyn(dep, this, &_file->phdr),
|
|
flags(flags)
|
|
{
|
|
/* register for static construction and relocation */
|
|
Init::list()->insert(this);
|
|
obj_list()->enqueue(this);
|
|
|
|
/* add to link map */
|
|
Debug::state_change(Debug::ADD, nullptr);
|
|
setup_link_map();
|
|
Debug::state_change(Debug::CONSISTENT, &map);
|
|
}
|
|
|
|
virtual ~Elf_object()
|
|
{
|
|
if (!_file)
|
|
return;
|
|
|
|
if (verbose_loading)
|
|
PDBG("destroy: %s", name());
|
|
|
|
/* remove from link map */
|
|
Debug::state_change(Debug::DELETE, &map);
|
|
Link_map::remove(&map);
|
|
Debug::state_change(Debug::CONSISTENT, nullptr);
|
|
|
|
/* remove from loaded objects list */
|
|
obj_list()->remove(this);
|
|
}
|
|
|
|
/**
|
|
* Return symbol of given number from ELF
|
|
*/
|
|
Elf::Sym const *symbol(unsigned sym_index) const
|
|
{
|
|
if (sym_index > dyn.hash_table->nchains())
|
|
return 0;
|
|
|
|
return dyn.symtab + sym_index;
|
|
}
|
|
|
|
/**
|
|
* Return name of given symbol
|
|
*/
|
|
char const *symbol_name(Elf::Sym const *sym) const {
|
|
return dyn.strtab + sym->st_name;
|
|
}
|
|
|
|
/**
|
|
* Lookup symbol name in this ELF
|
|
*/
|
|
Elf::Sym const *lookup_symbol(char const *name, unsigned long hash) const
|
|
{
|
|
Hash_table *h = dyn.hash_table;
|
|
|
|
if (!h->buckets())
|
|
return nullptr;
|
|
|
|
unsigned long sym_index = h->buckets()[hash % h->nbuckets()];
|
|
|
|
/* traverse hash chain */
|
|
for (; sym_index != STN_UNDEF; sym_index = h->chains()[sym_index])
|
|
{
|
|
/* bad object */
|
|
if (sym_index > h->nchains())
|
|
return nullptr;
|
|
|
|
Elf::Sym const *sym = symbol(sym_index);
|
|
char const *sym_name = symbol_name(sym);
|
|
|
|
/* this omitts everything but 'NOTYPE', 'OBJECT', and 'FUNC' */
|
|
if (sym->type() > STT_FUNC)
|
|
continue;
|
|
|
|
if (sym->st_value == 0)
|
|
continue;
|
|
|
|
/* check for symbol name */
|
|
if (name[0] != sym_name[0] || Genode::strcmp(name, sym_name))
|
|
continue;
|
|
|
|
return sym;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Fill-out link map infos for this ELF
|
|
*/
|
|
void setup_link_map()
|
|
{
|
|
map.addr = _file ? _file->start + reloc_base() : reloc_base();
|
|
map.path = name();
|
|
map.dynamic = (void *)dyn.dynamic;
|
|
|
|
Link_map::add(&map);
|
|
};
|
|
|
|
|
|
Link_map *link_map() override { return ↦ }
|
|
Dynamic *dynamic() override { return &dyn; }
|
|
|
|
void relocate() override
|
|
{
|
|
if (!relocated)
|
|
dyn.relocate();
|
|
|
|
relocated = true;
|
|
}
|
|
|
|
void info(Genode::addr_t addr, Genode::Address_info &info) override
|
|
{
|
|
info.path = name();
|
|
info.base = map.addr;
|
|
info.addr = 0;
|
|
|
|
Hash_table *h = dyn.hash_table;
|
|
|
|
for (unsigned long sym_index = 0; sym_index < h->nchains(); sym_index++)
|
|
{
|
|
Elf::Sym const *sym = symbol(sym_index);
|
|
|
|
/* skip */
|
|
if (sym->st_shndx == SHN_UNDEF || sym->st_shndx == SHN_COMMON)
|
|
continue;
|
|
|
|
Genode::addr_t sym_addr = reloc_base() + sym->st_value;
|
|
if (sym_addr > addr || sym_addr < info.addr)
|
|
continue;
|
|
|
|
info.addr = sym_addr;
|
|
info.name = symbol_name(sym);
|
|
|
|
if (info.addr == addr)
|
|
break;
|
|
}
|
|
|
|
if (!info.addr)
|
|
throw Genode::Address_info::Invalid_address();
|
|
}
|
|
|
|
/**
|
|
* Next in initializion list
|
|
*/
|
|
Object *next_init() const override {
|
|
return Genode::List<Object>::Element::next();
|
|
}
|
|
|
|
/**
|
|
* Next in object list
|
|
*/
|
|
Object *next_obj() const override {
|
|
return Genode::Fifo<Elf_object>::Element::next();
|
|
}
|
|
|
|
/**
|
|
* Object list
|
|
*/
|
|
static Genode::Fifo<Elf_object> *obj_list()
|
|
{
|
|
static Genode::Fifo<Elf_object> _list;
|
|
return &_list;
|
|
}
|
|
|
|
|
|
void load() override { ref_count++; }
|
|
bool unload() override { return !(--ref_count) && !(flags & Genode::Shared_object::KEEP); }
|
|
|
|
bool is_linker() const override { return false; }
|
|
bool is_binary() const override { return false; }
|
|
};
|
|
|
|
|
|
/**
|
|
* The dynamic linker object (ld.lib.so)
|
|
*/
|
|
struct Linker::Ld : Dependency, Elf_object
|
|
{
|
|
Ld()
|
|
: Dependency(this, nullptr), Elf_object(this, relocation_address())
|
|
{
|
|
Genode::strncpy(_name, linker_name(), Object::MAX_PATH);
|
|
}
|
|
|
|
void setup_link_map()
|
|
{
|
|
Elf_object::setup_link_map();
|
|
|
|
/**
|
|
* Use DT_HASH table address for linker, assuming that it will always be at
|
|
* the beginning of the file
|
|
*/
|
|
map.addr = trunc_page((Elf::Addr)dynamic()->hash_table);
|
|
}
|
|
|
|
void load_phdr()
|
|
{
|
|
_file = Linker::load(name(), false);
|
|
}
|
|
|
|
void relocate_global() { dynamic()->relocate_non_plt(true); }
|
|
void update_dependency(Dependency const *dep) { dynamic()->dep = dep; }
|
|
|
|
static Ld *linker();
|
|
|
|
bool is_linker() const override { return true; }
|
|
|
|
/**
|
|
* Entry point for jump relocations, it is called from assembly code and is implemented
|
|
* right below)
|
|
*/
|
|
static Elf::Addr jmp_slot(Dependency const *dep, Elf::Size index) asm("jmp_slot");
|
|
};
|
|
|
|
|
|
Elf::Addr Ld::jmp_slot(Dependency const *dep, Elf::Size index)
|
|
{
|
|
Genode::Lock::Guard guard(Elf_object::lock());
|
|
|
|
if (verbose_relocation)
|
|
PDBG("SLOT %p " EFMT, dep->obj, index);
|
|
|
|
try {
|
|
Reloc_jmpslot slot(dep, dep->obj->dynamic()->pltrel_type,
|
|
dep->obj->dynamic()->pltrel, index);
|
|
return slot.target_addr();
|
|
} catch (...) { PERR("LD: Jump slot relocation failed. FATAL!"); }
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Ld object with different vtable typeinfo
|
|
*/
|
|
struct Linker::Ld_vtable : Ld
|
|
{
|
|
Ld_vtable()
|
|
{
|
|
Elf_object::obj_list()->enqueue(this);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Linker object used during bootstrapping on stack (see: 'init_rtld')
|
|
*/
|
|
Linker::Ld *Linker::Ld::linker()
|
|
{
|
|
static Ld_vtable _linker;
|
|
return &_linker;
|
|
}
|
|
|
|
|
|
/**
|
|
* The dynamic binary to load
|
|
*/
|
|
struct Linker::Binary : Root_object, Elf_object
|
|
{
|
|
Binary()
|
|
: Elf_object(binary_name(), new (Genode::env()->heap()) Dependency(this, this))
|
|
{
|
|
/* create dep for binary and linker */
|
|
Dependency *binary = const_cast<Dependency *>(dynamic()->dep);
|
|
dep.enqueue(binary);
|
|
Dependency *linker = new (Genode::env()->heap()) Dependency(Ld::linker(), this);
|
|
dep.enqueue(linker);
|
|
|
|
/* update linker dep */
|
|
Ld::linker()->update_dependency(linker);
|
|
|
|
/* place linker on second place in link map as well */
|
|
Ld::linker()->setup_link_map();
|
|
|
|
/* load dependencies */
|
|
binary->load_needed(&dep);
|
|
|
|
/* relocate and call constructors */
|
|
Init::list()->initialize();
|
|
}
|
|
|
|
Elf::Addr lookup_symbol(char const *name)
|
|
{
|
|
Elf::Sym const *symbol = 0;
|
|
|
|
if ((symbol = Elf_object::lookup_symbol(name, Hash_table::hash(name))))
|
|
return reloc_base() + symbol->st_value;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int call_entry_point()
|
|
{
|
|
/* call static construtors and register destructors */
|
|
Func * const ctors_start = (Func *)lookup_symbol("_ctors_start");
|
|
Func * const ctors_end = (Func *)lookup_symbol("_ctors_end");
|
|
for (Func * ctor = ctors_end; ctor != ctors_start; (*--ctor)());
|
|
|
|
Func * const dtors_start = (Func *)lookup_symbol("_dtors_start");
|
|
Func * const dtors_end = (Func *)lookup_symbol("_dtors_end");
|
|
for (Func * dtor = dtors_start; dtor != dtors_end; genode_atexit(*dtor++));
|
|
|
|
/* call main function of the program */
|
|
typedef int (*Main)(int, char **, char **);
|
|
Main const main = reinterpret_cast<Main>(_file->entry);
|
|
|
|
return main(genode_argc, genode_argv, genode_envp);
|
|
}
|
|
|
|
void relocate() override
|
|
{
|
|
/* relocate ourselves */
|
|
Elf_object::relocate();
|
|
|
|
/*
|
|
* After having loaded the main program, we relocate the linker's
|
|
* symbols again such that, for example type informations, which are
|
|
* also present within the main program, become relocated to the correct
|
|
* positions
|
|
*/
|
|
Ld::linker()->relocate_global();
|
|
}
|
|
|
|
bool is_binary() const override { return true; }
|
|
};
|
|
|
|
|
|
/***************************************
|
|
** Global Linker namespace functions **
|
|
***************************************/
|
|
|
|
Object *Linker::load(char const *path, Dependency *dep, unsigned flags)
|
|
{
|
|
for (Object *e = Elf_object::obj_list()->head(); e; e = e->next_obj()) {
|
|
|
|
if (verbose_loading)
|
|
PDBG("LOAD: %s == %s", Linker::file(path), e->name());
|
|
|
|
if (!Genode::strcmp(Linker::file(path), e->name())) {
|
|
e->load();
|
|
return e;
|
|
}
|
|
}
|
|
|
|
Elf_object *e = new (Genode::env()->heap()) Elf_object(path, dep, flags);
|
|
dep->obj = e;
|
|
return e;
|
|
}
|
|
|
|
|
|
Object *Linker::obj_list_head()
|
|
{
|
|
return Elf_object::obj_list()->head();
|
|
}
|
|
|
|
|
|
Elf::Sym const *Linker::lookup_symbol(unsigned sym_index, Dependency const *dep,
|
|
Elf::Addr *base, bool undef, bool other)
|
|
{
|
|
Elf_object const *e = static_cast<Elf_object *>(dep->obj);
|
|
Elf::Sym const *symbol = e->symbol(sym_index);
|
|
|
|
if (!symbol) {
|
|
PWRN("LD: Unkown symbol index %x", sym_index);
|
|
return 0;
|
|
}
|
|
|
|
if (symbol->bind() == STB_LOCAL) {
|
|
*base = dep->obj->reloc_base();
|
|
return symbol;
|
|
}
|
|
|
|
return lookup_symbol(e->symbol_name(symbol), dep, base, undef, other);
|
|
}
|
|
|
|
|
|
Elf::Sym const *Linker::lookup_symbol(char const *name, Dependency const *dep,
|
|
Elf::Addr *base, bool undef, bool other)
|
|
{
|
|
Dependency const *curr = dep->root ? dep->root->dep.head() : dep;
|
|
unsigned long hash = Hash_table::hash(name);
|
|
Elf::Sym const *weak_symbol = 0;
|
|
Elf::Addr weak_base = 0;
|
|
Elf::Sym const *symbol = 0;
|
|
|
|
//TODO: handle vertab and search in object list
|
|
for (;curr; curr = curr->next()) {
|
|
|
|
if (other && curr == dep)
|
|
continue;
|
|
|
|
Elf_object const *elf = static_cast<Elf_object *>(curr->obj);
|
|
|
|
if ((symbol = elf->lookup_symbol(name, hash)) && (symbol->st_value || undef)) {
|
|
|
|
if (dep->root && verbose_lookup)
|
|
PINF("Lookup %s obj_src %s st %p info %x weak: %u", name, elf->name(), symbol, symbol->st_info, symbol->weak());
|
|
|
|
if (!undef && symbol->st_shndx == SHN_UNDEF)
|
|
continue;
|
|
|
|
if (!symbol->weak() && symbol->st_shndx != SHN_UNDEF) {
|
|
*base = elf->reloc_base();
|
|
return symbol;
|
|
}
|
|
|
|
if (!weak_symbol) {
|
|
weak_symbol = symbol;
|
|
weak_base = elf->reloc_base();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* try searching binary's dependencies */
|
|
if (!weak_symbol && dep->root) {
|
|
if (binary && dep != binary->dep.head()) {
|
|
return lookup_symbol(name, binary->dep.head(), base, undef, other);
|
|
} else {
|
|
PERR("Could not lookup symbol \"%s\"", name);
|
|
throw Not_found();
|
|
}
|
|
}
|
|
|
|
if (dep->root && verbose_lookup)
|
|
PDBG("Return %p", weak_symbol);
|
|
|
|
if (!weak_symbol)
|
|
throw Not_found();
|
|
|
|
*base = weak_base;
|
|
return weak_symbol;
|
|
}
|
|
|
|
|
|
void Linker::load_linker_phdr()
|
|
{
|
|
if (!Ld::linker()->file())
|
|
Ld::linker()->load_phdr();
|
|
}
|
|
|
|
|
|
/********************
|
|
** Initialization **
|
|
********************/
|
|
|
|
/**
|
|
* Called before anything else, even '_main', we cannot access any global data
|
|
* here, we have to relocate our own ELF first
|
|
*/
|
|
extern "C" void init_rtld()
|
|
{
|
|
/*
|
|
* Allocate on stack, since the linker has not been relocated yet, the vtable
|
|
* type relocation might prdouce a wrong vtable pointer (at least on ARM), do
|
|
* not call any virtual funtions of this object
|
|
*/
|
|
Ld linker_stack;
|
|
linker_stack.relocate();
|
|
|
|
/* make sure this does not get destroyed the usual way */
|
|
linker_stack.ref_count++;
|
|
|
|
/*
|
|
* Create actual linker object with different vtable type and set PLT to new
|
|
* DAG.
|
|
*/
|
|
Ld::linker()->dynamic()->plt_setup();
|
|
}
|
|
|
|
|
|
static void dump_loaded()
|
|
{
|
|
Object *o = Elf_object::obj_list()->head();
|
|
for(; o; o = o->next_obj()) {
|
|
|
|
if (o->is_binary())
|
|
continue;
|
|
|
|
Genode::printf(" " EFMT " .. " EFMT ": %s\n",
|
|
o->link_map()->addr, o->link_map()->addr + o->size() - 1,
|
|
o->name());
|
|
}
|
|
}
|
|
|
|
|
|
int main()
|
|
{
|
|
/* load program headers of linker now */
|
|
if (!Ld::linker()->file())
|
|
Ld::linker()->load_phdr();
|
|
|
|
/* read configuration */
|
|
try {
|
|
/* bind immediately */
|
|
bind_now = Genode::config()->xml_node().attribute("ld_bind_now").has_value("yes");
|
|
} catch (...) { }
|
|
|
|
/* load binary and all dependencies */
|
|
try {
|
|
binary = new(Genode::env()->heap()) Binary();
|
|
} catch (...) {
|
|
PERR("LD: Failed to load program");
|
|
return -1;
|
|
}
|
|
|
|
/* print loaded object information */
|
|
try {
|
|
if (Genode::config()->xml_node().attribute("ld_verbose").has_value("yes")) {
|
|
PINF(" %lx .. %lx: stack area", Genode::Native_config::stack_area_virtual_base(),
|
|
Genode::Native_config::stack_area_virtual_base() +
|
|
Genode::Native_config::stack_area_virtual_size() - 1);
|
|
dump_loaded();
|
|
}
|
|
} catch (...) { }
|
|
|
|
Link_map::dump();
|
|
|
|
/* start binary */
|
|
return binary->call_entry_point();
|
|
}
|
|
|