Skip to content
Snippets Groups Projects
elf.cc 25.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • #include "elf.hh"
    
    Avi Kivity's avatar
    Avi Kivity committed
    #include "mmu.hh"
    
    #include <boost/format.hpp>
    #include <exception>
    
    #include <memory>
    
    Avi Kivity's avatar
    Avi Kivity committed
    #include <string.h>
    
    Avi Kivity's avatar
    Avi Kivity committed
    #include "align.hh"
    
    #include "debug.hh"
    
    #include <stdlib.h>
    #include <unistd.h>
    
    Avi Kivity's avatar
    Avi Kivity committed
    #include <boost/algorithm/string.hpp>
    #include <functional>
    
    
    namespace {
        typedef boost::format fmt;
    }
    
    namespace elf {
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    namespace {
    
    Avi Kivity's avatar
    Avi Kivity committed
    unsigned symbol_type(Elf64_Sym& sym)
    {
        return sym.st_info & 15;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    unsigned symbol_binding(Elf64_Sym& sym)
    {
        return sym.st_info >> 4;
    }
    
    }
    
    symbol_module::symbol_module()
        : symbol()
        , obj()
    {
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    symbol_module::symbol_module(Elf64_Sym* _sym, object* _obj)
        : symbol(_sym)
        , obj(_obj)
    {
    }
    
    void* symbol_module::relocated_addr() const
    {
        void* base = obj->base();
        if (symbol->st_shndx == SHN_UNDEF || symbol->st_shndx == SHN_ABS) {
            base = 0;
        }
        switch (symbol_type(*symbol)) {
        case STT_NOTYPE:
            return reinterpret_cast<void*>(symbol->st_value);
            break;
        case STT_OBJECT:
        case STT_FUNC:
            return base + symbol->st_value;
            break;
        default:
    
    Avi Kivity's avatar
    Avi Kivity committed
            debug(fmt("unknown relocation type %d\n") % symbol_type(*symbol));
    
    Avi Kivity's avatar
    Avi Kivity committed
            abort();
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    object::object(program& prog, std::string pathname)
        : _prog(prog)
        , _pathname(pathname)
        , _tls_segment()
        , _tls_init_size()
        , _tls_uninit_size()
        , _dynamic_table(nullptr)
    {
    }
    
    object::~object()
    {
    }
    
    
    template <>
    void* object::lookup(const char* symbol)
    {
        symbol_module sm{lookup_symbol(symbol), this};
        if (!sm.symbol || sm.symbol->st_shndx == SHN_UNDEF) {
            return nullptr;
        }
        return sm.relocated_addr();
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    file::file(program& prog, ::fileref f, std::string pathname)
    : object(prog, pathname)
        , _f(f)
    {
    load_elf_header();
    load_program_headers();
    }
    
    file::~file()
    {
    }
    
    memory_image::memory_image(program& prog, void* base)
        : object(prog, "")
    {
        _ehdr = *static_cast<Elf64_Ehdr*>(base);
        auto p = static_cast<Elf64_Phdr*>(base + _ehdr.e_phoff);
        assert(_ehdr.e_phentsize == sizeof(*p));
        _phdrs.assign(p, p + _ehdr.e_phnum);
        set_base(base);
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void memory_image::load_segment(const Elf64_Phdr& phdr)
    {
    }
    
    void memory_image::unload_segment(const Elf64_Phdr& phdr)
    {
    }
    
    void file::load_elf_header()
    {
    
        read(_f, &_ehdr, 0, sizeof(_ehdr));
    
    Avi Kivity's avatar
    Avi Kivity committed
        if (!(_ehdr.e_ident[EI_MAG0] == '\x7f'
              && _ehdr.e_ident[EI_MAG1] == 'E'
              && _ehdr.e_ident[EI_MAG2] == 'L'
              && _ehdr.e_ident[EI_MAG3] == 'F')) {
            throw std::runtime_error("bad elf header");
        }
        if (!(_ehdr.e_ident[EI_CLASS] == ELFCLASS64)) {
            throw std::runtime_error("bad elf class");
    
    Avi Kivity's avatar
    Avi Kivity committed
        if (!(_ehdr.e_ident[EI_DATA] == ELFDATA2LSB)) {
            throw std::runtime_error("bad elf endianness");
        }
        if (!(_ehdr.e_ident[EI_VERSION] == EV_CURRENT)) {
            throw std::runtime_error("bad elf version");
        }
        if (!(_ehdr.e_ident[EI_OSABI] == ELFOSABI_LINUX
              || _ehdr.e_ident[EI_OSABI] == 0)) {
            throw std::runtime_error("bad os abi");
        }
    }
    
    namespace {
    
    void* align(void* addr, ulong align, ulong offset)
    {
        return align_up(addr - offset, align) + offset;
    }
    
    }
    
    void object::set_base(void* base)
    {
        auto p = std::min_element(_phdrs.begin(), _phdrs.end(),
                                  [](Elf64_Phdr a, Elf64_Phdr b)
                                      { return a.p_type == PT_LOAD
                                            && a.p_vaddr < b.p_vaddr; });
        _base = align(base, p->p_align, p->p_vaddr & (p->p_align - 1)) - p->p_vaddr;
        auto q = std::min_element(_phdrs.begin(), _phdrs.end(),
                                  [](Elf64_Phdr a, Elf64_Phdr b)
                                      { return a.p_type == PT_LOAD
                                            && a.p_vaddr > b.p_vaddr; });
        _end = _base + q->p_vaddr + q->p_memsz;
    }
    
    void* object::base() const
    {
        return _base;
    }
    
    void* object::end() const
    {
        return _end;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void file::load_program_headers()
    {
        _phdrs.resize(_ehdr.e_phnum);
        for (unsigned i = 0; i < _ehdr.e_phnum; ++i) {
    
            read(_f, &_phdrs[i],
    
    Avi Kivity's avatar
    Avi Kivity committed
                _ehdr.e_phoff + i * _ehdr.e_phentsize,
                _ehdr.e_phentsize);
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    namespace {
    
    ulong page_size = 4096;
    
    }
    
    void file::load_segment(const Elf64_Phdr& phdr)
    {
        ulong vstart = align_down(phdr.p_vaddr, page_size);
        ulong filesz_unaligned = phdr.p_vaddr + phdr.p_filesz - vstart;
        ulong filesz = align_up(filesz_unaligned, page_size);
        ulong memsz = align_up(phdr.p_vaddr + phdr.p_memsz, page_size) - vstart;
    
        mmu::map_file(_base + vstart, filesz, false, mmu::perm_rwx,
    
                      _f, align_down(phdr.p_offset, page_size));
    
    Avi Kivity's avatar
    Avi Kivity committed
        memset(_base + vstart + filesz_unaligned, 0, filesz - filesz_unaligned);
    
        mmu::map_anon(_base + vstart + filesz, memsz - filesz, false, mmu::perm_rwx);
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    void object::load_segments()
    {
        for (unsigned i = 0; i < _ehdr.e_phnum; ++i) {
            auto &phdr = _phdrs[i];
            switch (phdr.p_type) {
            case PT_NULL:
    
    Avi Kivity's avatar
    Avi Kivity committed
            case PT_LOAD:
                load_segment(phdr);
                break;
            case PT_DYNAMIC:
                _dynamic_table = reinterpret_cast<Elf64_Dyn*>(_base + phdr.p_vaddr);
                break;
            case PT_INTERP:
            case PT_NOTE:
            case PT_PHDR:
            case PT_GNU_STACK:
            case PT_GNU_RELRO:
            case PT_GNU_EH_FRAME:
                break;
            case PT_TLS:
                _tls_segment = _base + phdr.p_vaddr;
                _tls_init_size = phdr.p_filesz;
                _tls_uninit_size = phdr.p_memsz - phdr.p_filesz;
    
                break;
            default:
                abort();
    
    Avi Kivity's avatar
    Avi Kivity committed
                throw std::runtime_error("bad p_type");
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void file::unload_segment(const Elf64_Phdr& phdr)
    {
        ulong vstart = align_down(phdr.p_vaddr, page_size);
        ulong filesz_unaligned = phdr.p_vaddr + phdr.p_filesz - vstart;
        ulong filesz = align_up(filesz_unaligned, page_size);
        ulong memsz = align_up(phdr.p_vaddr + phdr.p_memsz, page_size) - vstart;
        mmu::unmap(_base + vstart, filesz);
        mmu::unmap(_base + vstart + filesz, memsz - filesz);
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void object::unload_segments()
    {
        for (unsigned i = 0; i < _ehdr.e_phnum; ++i) {
            auto &phdr = _phdrs[i];
            switch (phdr.p_type) {
            case PT_LOAD:
    
                unload_segment(phdr);
    
    Avi Kivity's avatar
    Avi Kivity committed
                break;
            default:
                break;
            }
         }
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    template <typename T>
    T* object::dynamic_ptr(unsigned tag)
    {
        return static_cast<T*>(_base + dynamic_tag(tag).d_un.d_ptr);
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    Elf64_Xword object::dynamic_val(unsigned tag)
    {
        return dynamic_tag(tag).d_un.d_val;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    const char* object::dynamic_str(unsigned tag)
    {
        return dynamic_ptr<const char>(DT_STRTAB) + dynamic_val(tag);
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    bool object::dynamic_exists(unsigned tag)
    {
        return _dynamic_tag(tag);
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    Elf64_Dyn* object::_dynamic_tag(unsigned tag)
    {
        for (auto p = _dynamic_table; p->d_tag != DT_NULL; ++p) {
            if (p->d_tag == tag) {
                return p;
    
    Avi Kivity's avatar
    Avi Kivity committed
        return nullptr;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    Elf64_Dyn& object::dynamic_tag(unsigned tag)
    {
        auto r = _dynamic_tag(tag);
        if (!r) {
            throw std::runtime_error("missing tag");
    
    Avi Kivity's avatar
    Avi Kivity committed
        return *r;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    std::vector<const char *>
    object::dynamic_str_array(unsigned tag)
    {
        auto strtab = dynamic_ptr<const char>(DT_STRTAB);
        std::vector<const char *> r;
        for (auto p = _dynamic_table; p->d_tag != DT_NULL; ++p) {
            if (p->d_tag == tag) {
                r.push_back(strtab + p->d_un.d_val);
            }
    
    Avi Kivity's avatar
    Avi Kivity committed
        return r;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    symbol_module object::symbol(unsigned idx)
    {
        auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
        assert(dynamic_val(DT_SYMENT) == sizeof(Elf64_Sym));
        auto sym = &symtab[idx];
        auto nameidx = sym->st_name;
        auto name = dynamic_ptr<const char>(DT_STRTAB) + nameidx;
        auto ret = _prog.lookup(name);
        auto binding = sym->st_info >> 4;
        if (!ret.symbol && binding == STB_WEAK) {
            return symbol_module(sym, this);
        }
        if (!ret.symbol) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            debug(fmt("failed looking up symbol %1%\n") % name);
    
    Avi Kivity's avatar
    Avi Kivity committed
            abort();
        }
        return ret;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    Elf64_Xword object::symbol_tls_module(unsigned idx)
    {
    
        debug("not looking up symbol module\n");
    
    Avi Kivity's avatar
    Avi Kivity committed
        return 0;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void object::relocate_rela()
    {
        auto rela = dynamic_ptr<Elf64_Rela>(DT_RELA);
        assert(dynamic_val(DT_RELAENT) == sizeof(Elf64_Rela));
        unsigned nb = dynamic_val(DT_RELASZ) / sizeof(Elf64_Rela);
        for (auto p = rela; p < rela + nb; ++p) {
            auto info = p->r_info;
            u32 sym = info >> 32;
            u32 type = info & 0xffffffff;
            void *addr = _base + p->r_offset;
            auto addend = p->r_addend;
            switch (type) {
            case R_X86_64_NONE:
                break;
            case R_X86_64_64:
                *static_cast<void**>(addr) = symbol(sym).relocated_addr() + addend;
                break;
            case R_X86_64_RELATIVE:
                *static_cast<void**>(addr) = _base + addend;
                break;
            case R_X86_64_JUMP_SLOT:
            case R_X86_64_GLOB_DAT:
                *static_cast<void**>(addr) = symbol(sym).relocated_addr();
                break;
            case R_X86_64_DPTMOD64:
                *static_cast<u64*>(addr) = symbol_tls_module(sym);
                break;
            case R_X86_64_DTPOFF64:
                *static_cast<u64*>(addr) = symbol(sym).symbol->st_value;
                break;
            case R_X86_64_TPOFF64:
                *static_cast<u64*>(addr) = symbol(sym).symbol->st_value - tls_data().size;
                break;
            default:
                abort();
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    extern "C" { void __elf_resolve_pltgot(void); }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void object::relocate_pltgot()
    {
        auto rel = dynamic_ptr<Elf64_Rela>(DT_JMPREL);
        auto nrel = dynamic_val(DT_PLTRELSZ) / sizeof(*rel);
        for (auto p = rel; p < rel + nrel; ++p) {
            auto info = p->r_info;
              u32 type = info & 0xffffffff;
              assert(type = R_X86_64_JUMP_SLOT);
              void *addr = _base + p->r_offset;
              // The JUMP_SLOT entry already points back to the PLT, just
              // make sure it is relocated relative to the object base.
              *static_cast<u64*>(addr) += reinterpret_cast<u64>(_base);
        }
        auto pltgot = dynamic_ptr<void*>(DT_PLTGOT);
        // PLTGOT resolution has a special calling convention, with the symbol
        // index and some word pushed on the stack, so we need an assembly
        // stub to convert it back to the standard calling convention.
        pltgot[1] = this;
        pltgot[2] = reinterpret_cast<void*>(__elf_resolve_pltgot);
    }
    
    void* object::resolve_pltgot(unsigned index)
    {
        auto rel = dynamic_ptr<Elf64_Rela>(DT_JMPREL);
        auto slot = rel[index];
        auto info = slot.r_info;
        u32 sym = info >> 32;
        u32 type = info & 0xffffffff;
        assert(type == R_X86_64_JUMP_SLOT);
        void *addr = _base + slot.r_offset;
        auto ret = *static_cast<void**>(addr) = symbol(sym).relocated_addr();
        return ret;
    }
    
    void object::relocate()
    {
        assert(!dynamic_exists(DT_REL));
        if (dynamic_exists(DT_RELA)) {
            relocate_rela();
    
    Avi Kivity's avatar
    Avi Kivity committed
        if (dynamic_exists(DT_JMPREL)) {
            relocate_pltgot();
        }
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    unsigned long
    elf64_hash(const char *name)
    {
        unsigned long h = 0, g;
        while (*name) {
            h = (h << 4) + (unsigned char)(*name++);
            if ((g = h & 0xf0000000)) {
                h ^= g >> 24;
    
    Avi Kivity's avatar
    Avi Kivity committed
            h  &=  0x0fffffff;
    
    Avi Kivity's avatar
    Avi Kivity committed
        return h;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    Elf64_Sym* object::lookup_symbol_old(const char* name)
    {
        auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
        auto strtab = dynamic_ptr<char>(DT_STRTAB);
        auto hashtab = dynamic_ptr<Elf64_Word>(DT_HASH);
        auto nbucket = hashtab[0];
        auto buckets = hashtab + 2;
        auto chain = buckets + nbucket;
        for (auto ent = buckets[elf64_hash(name) % nbucket];
                ent != STN_UNDEF;
                ent = chain[ent]) {
            auto &sym = symtab[ent];
            if (strcmp(name, &strtab[sym.st_name]) == 0) {
                return &sym;
            }
        }
        return nullptr;
    }
    
    uint_fast32_t
    dl_new_hash(const char *s)
    {
        uint_fast32_t h = 5381;
        for (unsigned char c = *s; c != '\0'; c = *++s) {
            h = h * 33 + c;
    
    Avi Kivity's avatar
    Avi Kivity committed
        return h & 0xffffffff;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    Elf64_Sym* object::lookup_symbol_gnu(const char* name)
    {
        auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
        auto strtab = dynamic_ptr<char>(DT_STRTAB);
        auto hashtab = dynamic_ptr<Elf64_Word>(DT_GNU_HASH);
        auto nbucket = hashtab[0];
        auto symndx = hashtab[1];
        auto maskwords = hashtab[2];
        auto shift2 = hashtab[3];
        auto bloom = reinterpret_cast<const Elf64_Xword*>(hashtab + 4);
        auto C = sizeof(*bloom) * 8;
        auto hashval = dl_new_hash(name);
        auto bword = bloom[(hashval / C) % maskwords];
        auto hashbit1 = hashval % C;
        auto hashbit2 = (hashval >> shift2) % C;
        if ((bword >> hashbit1) == 0 || (bword >> hashbit2) == 0) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            return nullptr;
        }
    
    Avi Kivity's avatar
    Avi Kivity committed
        auto buckets = reinterpret_cast<const Elf64_Word*>(bloom + maskwords);
        auto chains = buckets + nbucket - symndx;
        auto idx = buckets[hashval % nbucket];
        if (idx == 0) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            return nullptr;
        }
    
    Avi Kivity's avatar
    Avi Kivity committed
        do {
            if ((chains[idx] & ~1) != (hashval & ~1)) {
                continue;
    
    Avi Kivity's avatar
    Avi Kivity committed
            if (strcmp(&strtab[symtab[idx].st_name], name) == 0) {
                return &symtab[idx];
    
    Avi Kivity's avatar
    Avi Kivity committed
        } while ((chains[idx++] & 1) == 0);
        return nullptr;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    Elf64_Sym* object::lookup_symbol(const char* name)
    {
        Elf64_Sym* sym;
        if (dynamic_exists(DT_GNU_HASH)) {
            sym = lookup_symbol_gnu(name);
        } else {
            sym = lookup_symbol_old(name);
    
    Avi Kivity's avatar
    Avi Kivity committed
        if (sym && sym->st_shndx == SHN_UNDEF) {
            sym = nullptr;
    
    Avi Kivity's avatar
    Avi Kivity committed
        return sym;
    }
    
    unsigned object::symtab_len()
    {
        if (dynamic_exists(DT_HASH)) {
            auto hashtab = dynamic_ptr<Elf64_Word>(DT_HASH);
            return hashtab[1];
        }
        auto hashtab = dynamic_ptr<Elf64_Word>(DT_GNU_HASH);
        auto nbucket = hashtab[0];
        auto symndx = hashtab[1];
        auto maskwords = hashtab[2];
        auto bloom = reinterpret_cast<const Elf64_Xword*>(hashtab + 4);
        auto buckets = reinterpret_cast<const Elf64_Word*>(bloom + maskwords);
        auto chains = buckets + nbucket - symndx;
        unsigned len = 0;
        for (unsigned b = 0; b < nbucket; ++b) {
            auto idx = buckets[b];
            if (idx == 0) {
                continue;
            }
            do {
                ++len;
            } while ((chains[idx++] & 1) == 0);
        }
        return len;
    }
    
    dladdr_info object::lookup_addr(const void* addr)
    {
        dladdr_info ret;
        if (addr < _base || addr >= _end) {
            return ret;
        }
        auto strtab = dynamic_ptr<char>(DT_STRTAB);
        auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
        auto len = symtab_len();
        symbol_module best;
        for (unsigned i = 1; i < len; ++i) {
            auto& sym = symtab[i];
            auto type = sym.st_info & 15;
            if (type != STT_OBJECT && type != STT_FUNC) {
                continue;
            }
            auto bind = (sym.st_info >> 4) & 15;
            if (bind != STB_GLOBAL && bind != STB_WEAK) {
                continue;
            }
            symbol_module sm{&sym, this};
            auto s_addr = sm.relocated_addr();
            if (s_addr < addr) {
                continue;
            }
            if (!best.symbol || sm.relocated_addr() < best.relocated_addr()) {
                best = sm;
            }
        }
        if (!best.symbol) {
            return ret;
        }
        ret.fname = _pathname.c_str();
        ret.base = _base;
        ret.sym = strtab + best.symbol->st_name;
        ret.addr = best.relocated_addr();
        return ret;
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    static std::string dirname(std::string path)
    {
        auto pos = path.rfind('/');
        if (pos == path.npos) {
            return "/";
        }
        return path.substr(0, pos);
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void object::load_needed()
    {
    
    Avi Kivity's avatar
    Avi Kivity committed
        std::vector<std::string> rpath;
        if (dynamic_exists(DT_RPATH)) {
            std::string rpath_str = dynamic_str(DT_RPATH);
            boost::replace_all(rpath_str, "$ORIGIN", dirname(_pathname));
            boost::split(rpath, rpath_str, boost::is_any_of(":"));
        }
    
    Avi Kivity's avatar
    Avi Kivity committed
        auto needed = dynamic_str_array(DT_NEEDED);
        for (auto lib : needed) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            if (_prog.add_object(lib, rpath) == nullptr)
    
                debug(fmt("could not load %s\n") % lib);
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    tls_data object::tls()
    {
        return tls_data{_tls_segment, _tls_init_size + _tls_uninit_size};
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    std::string object::soname()
    {
        return dynamic_exists(DT_SONAME) ? dynamic_str(DT_SONAME) : std::string();
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    std::vector<Elf64_Phdr> object::phdrs()
    {
        return _phdrs;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    std::string object::pathname()
    {
        return _pathname;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void object::run_init_func()
    {
        if (!dynamic_exists(DT_INIT_ARRAY)) {
            return;
    
    Avi Kivity's avatar
    Avi Kivity committed
        auto inits = dynamic_ptr<void (*)()>(DT_INIT_ARRAY);
        auto nr = dynamic_val(DT_INIT_ARRAYSZ) / sizeof(*inits);
        for (auto i = 0u; i < nr; ++i) {
            inits[i]();
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    program* s_program;
    
    program::program(::filesystem& fs, void* addr)
        : _fs(fs)
        , _next_alloc(addr)
        , _core(new elf::memory_image(*this, reinterpret_cast<void*>(0x200000)))
    {
        _core->load_segments();
        assert(!s_program);
        s_program = this;
        set_object("libc.so.6", _core.get());
    
        set_object("libm.so.6", _core.get());
    
    Avi Kivity's avatar
    Avi Kivity committed
        set_object("ld-linux-x86-64.so.2", _core.get());
        set_object("libpthread.so.0", _core.get());
        set_object("libdl.so.2", _core.get());
    
        set_object("librt.so.1", _core.get());
    
    void program::set_search_path(std::initializer_list<std::string> path)
    {
        _search_path = path;
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    tls_data program::tls()
    {
        return _core->tls();
    }
    
    void program::set_object(std::string name, object* obj)
    {
        _files[name] = obj;
        if (std::find(_modules.begin(), _modules.end(), obj) == _modules.end()) {
            _modules.push_back(obj);
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    static std::string getcwd()
    {
        auto r = ::getcwd(NULL, 0);
        std::string rs = r;
        free(r);
        return rs;
    }
    
    static std::string canonicalize(std::string p)
    {
        auto r = realpath(p.c_str(), NULL);
        if (r) {
            std::string rs = r;
            free(r);
            return rs;
        } else {
            return p;
        }
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    object* program::add_object(std::string name, std::vector<std::string> extra_path)
    
    Avi Kivity's avatar
    Avi Kivity committed
    {
    
        fileref f;
        if (name.find('/') == name.npos) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            for (auto mod : _modules) {
                if (name == mod->soname()) {
                    return mod;
                }
            }
            std::vector<std::string> search_path;
            search_path.insert(search_path.end(), extra_path.begin(), extra_path.end());
            search_path.insert(search_path.end(), _search_path.begin(), _search_path.end());
            for (auto dir : search_path) {
                auto dname = canonicalize(dir + "/" + name);
                f = fileref_from_fname(dname);
                if (f) {
                    name = dname;
                    break;
                }
            }
    
        } else {
            if (name[0] != '/') {
                name = getcwd() + "/" + name;
    
            name = canonicalize(name);
            f = fileref_from_fname(name);
        }
    
        if (!_files.count(name) && f) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            auto ef = new file(*this, f, name);
            ef->set_base(_next_alloc);
            _files[name] = ef;
            _modules.push_back(ef);
            ef->load_segments();
            _next_alloc = ef->end();
            add_debugger_obj(ef);
            ef->load_needed();
            ef->relocate();
            ef->run_init_func();
    
            return ef;
        }
    
        if (!_files.count(name)) {
            return nullptr;
    
    Avi Kivity's avatar
    Avi Kivity committed
        // TODO: we'll need to refcount the objects here or in the dl*() wrappers
        return _files[name];
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void program::remove_object(std::string name)
    {
        auto ef = _files[name];
    
    Avi Kivity's avatar
    Avi Kivity committed
        del_debugger_obj(ef);
        _files.erase(name);
        _modules.erase(std::find(_modules.begin(), _modules.end(), ef));
        ef->unload_segments();
        delete ef;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    object* program::s_objs[100];
    
    Avi Kivity's avatar
    Avi Kivity committed
    void program::add_debugger_obj(object* obj)
    {
        auto p = s_objs;
        while (*p) {
            ++p;
    
    Avi Kivity's avatar
    Avi Kivity committed
        *p = obj;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void program::del_debugger_obj(object* obj)
    {
        auto p = s_objs;
        while (*p && *p != obj) {
            ++p;
        }
        if (!*p) {
            return;
    
    Avi Kivity's avatar
    Avi Kivity committed
        while ((p[0] = p[1]) != nullptr) {
            ++p;
        }
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    symbol_module program::lookup(const char* name)
    {
        for (auto module : _modules) {
            if (auto sym = module->lookup_symbol(name)) {
                return symbol_module(sym, module);
    
    Avi Kivity's avatar
    Avi Kivity committed
        return symbol_module(nullptr, nullptr);
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void* program::do_lookup_function(const char* name)
    {
        auto sym = lookup(name);
        if (!sym.symbol) {
            throw std::runtime_error("symbol not found");
    
    Avi Kivity's avatar
    Avi Kivity committed
        if ((sym.symbol->st_info & 15) != STT_FUNC) {
            throw std::runtime_error("symbol is not a function");
    
    Avi Kivity's avatar
    Avi Kivity committed
        return sym.relocated_addr();
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void program::with_modules(std::function<void (std::vector<object*>&)> f)
    {
        // FIXME: locking?
        std::vector<object*> tmp = _modules;
        f(tmp);
    }
    
    
    dladdr_info program::lookup_addr(const void* addr)
    {
        for (auto module : _modules) {
            auto ret = module->lookup_addr(addr);
            if (ret.fname) {
                return ret;
            }
        }
        return {};
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    program* get_program()
    {
        return s_program;
    }
    
    init_table get_init(Elf64_Ehdr* header)
    {
        void* pbase = static_cast<void*>(header);
        void* base = pbase;
        auto phdr = static_cast<Elf64_Phdr*>(pbase + header->e_phoff);
        auto n = header->e_phnum;
        bool base_adjusted = false;
        init_table ret;
        for (auto i = 0; i < n; ++i, ++phdr) {
            if (!base_adjusted && phdr->p_type == PT_LOAD) {
                base_adjusted = true;
                base -= phdr->p_vaddr;
            }
            if (phdr->p_type == PT_DYNAMIC) {
                auto dyn = reinterpret_cast<Elf64_Dyn*>(phdr->p_vaddr);
                unsigned ndyn = phdr->p_memsz / sizeof(*dyn);
                const Elf64_Rela* rela = nullptr;
                const Elf64_Rela* jmp = nullptr;
                const Elf64_Sym* symtab = nullptr;
                const Elf64_Word* hashtab = nullptr;
                const char* strtab = nullptr;
                unsigned nrela = 0;
                unsigned njmp = 0;
                for (auto d = dyn; d < dyn + ndyn; ++d) {
                    switch (d->d_tag) {
                    case DT_INIT_ARRAY:
                        ret.start = reinterpret_cast<void (**)()>(d->d_un.d_ptr);
                        break;
                    case DT_INIT_ARRAYSZ:
                        ret.count = d->d_un.d_val / sizeof(ret.start);
                        break;
                    case DT_RELA:
                        rela = reinterpret_cast<const Elf64_Rela*>(d->d_un.d_ptr);
                        break;
                    case DT_RELASZ:
                        nrela = d->d_un.d_val / sizeof(*rela);
                        break;
                    case DT_SYMTAB:
                        symtab = reinterpret_cast<const Elf64_Sym*>(d->d_un.d_ptr);
                        break;
                    case DT_HASH:
                        hashtab = reinterpret_cast<const Elf64_Word*>(d->d_un.d_ptr);
                        break;
                    case DT_STRTAB:
                        strtab = reinterpret_cast<const char*>(d->d_un.d_ptr);
                        break;
                    case DT_JMPREL:
                        jmp = reinterpret_cast<const Elf64_Rela*>(d->d_un.d_ptr);
                        break;
                    case DT_PLTRELSZ:
                        njmp = d->d_un.d_val / sizeof(*jmp);
                        break;
                    }
    
    Avi Kivity's avatar
    Avi Kivity committed
                }
    
    Avi Kivity's avatar
    Avi Kivity committed
                auto nbucket = hashtab[0];
                auto buckets = hashtab + 2;
                auto chain = buckets + nbucket;
                auto relocate_table = [=](const Elf64_Rela *rtab, unsigned n) {
    
                    if (!rtab) {
                        return;
                    }
    
    Avi Kivity's avatar
    Avi Kivity committed
                    for (auto r = rtab; r < rtab + n; ++r) {
                        auto info = r->r_info;
                        u32 sym = info >> 32;
                        u32 type = info & 0xffffffff;
                        void *addr = base + r->r_offset;
                        auto addend = r->r_addend;
                        auto lookup = [=]() {
                            auto name = strtab + symtab[sym].st_name;
                            for (auto ent = buckets[elf64_hash(name) % nbucket];
                                    ent != STN_UNDEF;
                                    ent = chain[ent]) {
                                auto &sym = symtab[ent];
                                if (strcmp(name, &strtab[sym.st_name]) == 0) {
                                    return &sym;
                                }
                            }
                            abort();
                        };
                        switch (type) {
                        case R_X86_64_NONE:
    
    Avi Kivity's avatar
    Avi Kivity committed
                            break;
    
    Avi Kivity's avatar
    Avi Kivity committed
                        case R_X86_64_64:
                            *static_cast<u64*>(addr) = lookup()->st_value + addend;
    
    Avi Kivity's avatar
    Avi Kivity committed
                            break;
    
    Avi Kivity's avatar
    Avi Kivity committed
                        case R_X86_64_RELATIVE:
                            *static_cast<void**>(addr) = base + addend;
    
    Avi Kivity's avatar
    Avi Kivity committed
                            break;
    
    Avi Kivity's avatar
    Avi Kivity committed
                        case R_X86_64_JUMP_SLOT:
                        case R_X86_64_GLOB_DAT:
                            *static_cast<u64*>(addr) = lookup()->st_value;
    
    Avi Kivity's avatar
    Avi Kivity committed
                            break;
    
    Avi Kivity's avatar
    Avi Kivity committed
                        case R_X86_64_DPTMOD64:
                            abort();
                            //*static_cast<u64*>(addr) = symbol_module(sym);
    
    Avi Kivity's avatar
    Avi Kivity committed
                            break;
    
    Avi Kivity's avatar
    Avi Kivity committed
                        case R_X86_64_DTPOFF64:
                            *static_cast<u64*>(addr) = lookup()->st_value;
    
    Avi Kivity's avatar
    Avi Kivity committed
                            break;
    
    Avi Kivity's avatar
    Avi Kivity committed
                        case R_X86_64_TPOFF64:
                            // FIXME: assumes TLS segment comes before DYNAMIC segment
                            *static_cast<u64*>(addr) = lookup()->st_value - ret.tls.size;
    
    Avi Kivity's avatar
    Avi Kivity committed
                            break;
    
                        case R_X86_64_IRELATIVE:
                            *static_cast<void**>(addr) = reinterpret_cast<void *(*)()>(base + addend)();
                            break;
    
    Avi Kivity's avatar
    Avi Kivity committed
                        default:
                            abort();
    
    Avi Kivity's avatar
    Avi Kivity committed
                    }
                };
                relocate_table(rela, nrela);
                relocate_table(jmp, njmp);
            } else if (phdr->p_type == PT_TLS) {
                ret.tls.start = reinterpret_cast<void*>(phdr->p_vaddr);
    
                ret.tls.filesize = phdr->p_filesz;
    
    Avi Kivity's avatar
    Avi Kivity committed
                ret.tls.size = phdr->p_memsz;
    
    Avi Kivity's avatar
    Avi Kivity committed
        return ret;
    }
    
    Avi Kivity's avatar
    Avi Kivity committed
    extern "C" { void* elf_resolve_pltgot(unsigned long index, elf::object* obj); }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void* elf_resolve_pltgot(unsigned long index, elf::object* obj)
    
        return obj->resolve_pltgot(index);