//
// sharedLib.cpp
// 

#include "sharedLib.h"
#include <bfd.h>
#include <elf.h>
#include <sys/link_elf.h>

sharedLibInfo::sharedLibInfo(unsigned pid)
{
    char base[2048]; // shut up
    sprintf(base, "/proc/%u/", pid);
    m_procBase = std::string(base);
    processFileImage();
    processRunningImage();
}

unsigned sharedLibInfo::numLibs() const
{
    return m_libs.size();
}

const sharedLib& sharedLibInfo::getLib(unsigned index) const
{
    return m_libs[index];
}

void sharedLibInfo::processFileImage()
{
    bfd_init(); // I wonder if this can be called many times?

    std::string image = m_procBase + "file";
    bfd* bfdHandle = bfd_openr(image.c_str(), "default");
    if (bfdHandle == 0)
    {
        throw 5;
    }
    // from this point on we need to at least close the bfd on an error
    try
    {
        // make sure we have an object file (executable)
        if (bfd_check_format(bfdHandle, bfd_object) == 0)
        {
            throw 5;
        }
        // we only do 32 bit stuff (for now at least)
        int arch_size = bfd_get_arch_size (bfdHandle);
        if (arch_size == -1) {
            throw 5;
        }

        if (arch_size != 32)
        {
            throw 5;
        }
        // now find out the load address of the .dynamic section
        struct bfd_section* dyninfo_sect = bfd_get_section_by_name (bfdHandle, ".dynamic");
        if (dyninfo_sect == 0)
        {
            throw 5;
        }
        bfd_vma dyninfo_addr = bfd_section_vma (bfdHandle, dyninfo_sect);
        m_dynamicSize = bfd_section_size (exec_bfd, dyninfo_sect);
        m_dynamicBase = dyninfo_addr;
        //printf(".dynamic vma is: %08lx\n", m_dynamicBase);
        //printf(".dynamic size is %u\n", m_dynamicSize);
    }
    catch (...)
    {
        bfd_close(bfdHandle);
        throw;
    }
    // that's all we need to do for now
    bfd_close(bfdHandle);
}

void sharedLibInfo::processRunningImage()
{
    // the idea here is to open the procfs image, read through the
    // .dynamic section in order to find where the list of shared libraries
    // is, then read out those libraries...

    std::string running = m_procBase + "mem";

    FILE* image = fopen(running.c_str(), "r");
    if (image == 0)
    {
        throw 5;
    }
    Elf32_Dyn* dyn_entries = 0;  // since we can delete 0 if we need to...
    unsigned lib_ptr = 0;
    try
    {
        unsigned entries = m_dynamicSize / sizeof(Elf32_Dyn);
        // read in the (hopefully) .dynamic section
        try
        {
            dyn_entries = new Elf32_Dyn[entries];
        }
        catch (...)
        {
            throw 5;
        }
        if (fseek(image, m_dynamicBase, SEEK_SET) != 0)
        {
            throw 5;
        }
        if (fread(dyn_entries, sizeof(Elf32_Dyn), entries, image) != entries)
        {
            throw 5;
        }
        // we should now have a table of dynamic entries...
        for (unsigned entry = 0; entry < entries; ++entry)
        {
            Elf32_Dyn& dyn = dyn_entries[entry];
            //printf("%u: %08x %08x\n", entry, dyn.d_tag, dyn.d_un.d_ptr);
            if (dyn.d_tag == DT_DEBUG)
            {
                lib_ptr = dyn.d_un.d_ptr;
            }
        }
        if (lib_ptr == 0)
        {
            throw 5;
        }
        // lib_ptr now points to some good stuff...
        //printf("lib_ptr is: %08x\n", lib_ptr);
        // read in the cool r_debug stuff
        if (fseek(image, lib_ptr, SEEK_SET) != 0)
        {
            throw 5;
        }
        struct r_debug rDebug;
        if (fread(&rDebug, sizeof(rDebug), 1, image) != 1)
        {
            throw 5;
        }
        //printf("pointer to lib list is %p\n", rDebug.r_map);
        // now that we know where the libraries are...
        Link_map* lm = rDebug.r_map;
        bool skipped = false; // the first entry is the executable
        while (lm)
        {
            Link_map actual;
            if (fseek(image, size_t(lm), SEEK_SET) != 0)
            {
                throw 5;
            }
            if (fread(&actual, sizeof(actual), 1, image) != 1)
            {
                throw 5;
            }
            if (skipped)
            {
                sharedLib sl(readString(size_t(actual.l_name), image), size_t(actual.l_addr));
                m_libs.push_back(sl);
            }
            skipped = true;
            lm = actual.l_next;
        }
    }
    catch (...)
    {
        delete [] dyn_entries;
        fclose(image);
        throw;
    }
    delete [] dyn_entries;
    fclose(image);
}

std::string sharedLibInfo::readString(uintptr_t addr, FILE* image)
{
    std::string s;
    if (fseek(image, addr, SEEK_SET) != 0)
    {
        throw 5;
    }
    int c = 0;
    do
    {
        c = fgetc(image);
        if (c == -1)
        {
            throw 5;
        }
        s += c;
    } while (c);
    return s;
}
