#include "elf_got.h"
#if defined(OBJFORMAT_ELF)
/*
 * Check if we need a global offset table slot for a
 * given symbol
 */
bool
needGotSlot(Elf_Sym * symbol) {
    /* using global here should give an upper bound */
    /* I don't believe we need to relocate STB_LOCAL
     * symbols via the GOT; however I'm unsure about
     * STB_WEAK.
     *
     * Any more restrictive filter here would result
     * in a smaller GOT, which is preferrable.
     */
    return ELF_ST_BIND(symbol->st_info) == STB_GLOBAL
        || ELF_ST_BIND(symbol->st_info) == STB_WEAK;
}

bool
makeGot(ObjectCode * oc) {
    size_t got_slots = 0;

    /* we need to find all symbol tables (elf can have multiple)
     * and need to iterate over all symbols, to check how many
     * got slots we need at most
     */
    ASSERT( oc->info != NULL );
    ASSERT( oc->info->sectionHeader != NULL );
    for(int i=0; i < oc->n_sections; i++) {
        if(SHT_SYMTAB == oc->info->sectionHeader[i].sh_type) {
            Elf_Sym *symTab =
                (Elf_Sym*)((uint8_t*)oc->info->elfHeader
                                   + oc->info->sectionHeader[i].sh_offset);
            size_t n_symbols = oc->info->sectionHeader[i].sh_size
                               / sizeof(Elf_Sym);
            for(size_t j=0; j < n_symbols; j++) {
                if(needGotSlot(&symTab[j])) {
                    got_slots += 1;
                }
            }
        }
    }
    if(got_slots > 0) {
        oc->info->got_size = got_slots * sizeof(void *);
         void * mem = mmap(NULL, oc->info->got_size,
                           PROT_READ | PROT_WRITE,
                           MAP_ANON | MAP_PRIVATE,
                           -1, 0);
        if (mem == MAP_FAILED) {
            errorBelch("MAP_FAILED. errno=%d", errno);
            return EXIT_FAILURE;
        }
        oc->info->got_start = (void*)mem;
        /* update got_addr */
        size_t slot = 0;
        for(ElfSymbolTable *symTab = oc->info->symbolTables;
            symTab != NULL; symTab = symTab->next)
            for(size_t i=0; i < symTab->n_symbols; i++)
                if(needGotSlot(symTab->symbols[i].elf_sym))
                    symTab->symbols[i].got_addr
                            = (uint8_t *)oc->info->got_start
                              + (slot++ * sizeof(void*));
    }
    return EXIT_SUCCESS;
}

bool
fillGot(ObjectCode * oc) {
    /* fill the GOT table */
    for(ElfSymbolTable *symTab = oc->info->symbolTables;
        symTab != NULL; symTab = symTab->next) {
        for(size_t i=0; i < symTab->n_symbols; i++) {
            ElfSymbol * symbol = &symTab->symbols[i];
            if(needGotSlot(symbol->elf_sym)) {
                /* no type are undefined symbols */
                if(   STT_NOTYPE == ELF_ST_TYPE(symbol->elf_sym->st_info)
                   || STB_WEAK   == ELF_ST_BIND(symbol->elf_sym->st_info)) {
                    if(0x0 == symbol->addr) {
                        symbol->addr = lookupSymbol_(symbol->name);
                        if(0x0 == symbol->addr) {
                            errorBelch("Failed to lookup symbol: %s\n",
                                       symbol->name);
                            return EXIT_FAILURE;
                        }
                    } else {
                        // we already have the address.
                    }
                } /* else it was defined somewhere in the same object, and
                  * we should have the address already.
                  */
                if(0x0 == symbol->addr) {
                    errorBelch(
                        "Something went wrong! Symbol %s has null address.\n",
                            symbol->name);
                    return EXIT_FAILURE;
                }
                if(0x0 == symbol->got_addr) {
                    errorBelch("Not good either!");
                    return EXIT_FAILURE;
                }
                *(void**)symbol->got_addr = symbol->addr;
            }
        }
    }
    return EXIT_SUCCESS;
}
bool
verifyGot(ObjectCode * oc) {
    for(ElfSymbolTable *symTab = oc->info->symbolTables;
        symTab != NULL; symTab = symTab->next) {
        for(size_t i=0; i < symTab->n_symbols; i++) {
            ElfSymbol * symbol = &symTab->symbols[i];
            if(symbol->got_addr) {
                ASSERT((void*)(*(void**)symbol->got_addr)
                       == (void*)symbol->addr);
            }
            ASSERT(0 == ((uintptr_t)symbol->addr & 0xffff000000000000));
        }
    }
    return EXIT_SUCCESS;
}

void
freeGot(ObjectCode * oc) {
//    munmap(oc->info->got_start, oc->info->got_size);
    oc->info->got_start = 0x0;
    oc->info->got_size = 0;
}
#endif
