/*
 * Copyright (C) 2011-2017 Intel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */


/*
 * This file is part of trusted loader for tRTS.
 */

#include "elf_parser.h"
#include "rts.h"
#include "util.h"
#include "elf_util.h"
#include "global_data.h"

static int elf_tls_aligned_virtual_size(const void *enclave_base,
                            size_t *aligned_virtual_size);

static ElfW(Phdr)* get_phdr(const ElfW(Ehdr)* ehdr)
{
    if (ehdr == NULL)
        return NULL;  /* Invalid image. */

    /* Check the ElfW Magic number. */
    if ((ehdr->e_ident[EI_MAG0] != ELFMAG0) ||
        (ehdr->e_ident[EI_MAG1] != ELFMAG1) ||
        (ehdr->e_ident[EI_MAG2] != ELFMAG2) ||
        (ehdr->e_ident[EI_MAG3] != ELFMAG3))
        return NULL;

    /* Enclave image should be a shared object file. */
    if (ehdr->e_type != ET_DYN)
        return NULL;

    return GET_PTR(ElfW(Phdr), ehdr, ehdr->e_phoff);
}

static ElfW(Sym)* get_sym(ElfW(Sym)* symtab, size_t idx)
{
    if(STB_WEAK == ELFW(ST_BIND)(symtab[idx].st_info)
            && 0 == symtab[idx].st_value)
    {
        return NULL;
    }

    return &symtab[idx];
}

#ifdef __x86_64__
/* Relocation for x64 (with addend) */
static int do_relocs(const ElfW(Addr) enclave_base,
        ElfW(Addr) rela_offset,
        ElfW(Addr) sym_offset,
        size_t nr_relocs)
{
    ElfW(Rela)* rela = GET_PTR(ElfW(Rela), enclave_base, rela_offset);
    ElfW(Sym)*  symtab = GET_PTR(ElfW(Sym), enclave_base, sym_offset);
    ElfW(Sym)*  sym;
    size_t      i;
    size_t aligned_virtual_size = 0;

    for (i = 0; i < nr_relocs; ++i, ++rela)
    {
        ElfW(Addr)* reloc_addr = GET_PTR(ElfW(Addr), enclave_base, rela->r_offset);

        switch (ELF64_R_TYPE(rela->r_info))
        {
            case R_X86_64_RELATIVE:
                *reloc_addr = enclave_base + (uintptr_t)rela->r_addend;
                break;

            case R_X86_64_GLOB_DAT:
            case R_X86_64_JUMP_SLOT:
            case R_X86_64_64:
                sym = get_sym(symtab, ELF64_R_SYM(rela->r_info));
                if(!sym)
                    break;
                *reloc_addr = enclave_base + sym->st_value + (uintptr_t)rela->r_addend;
                break;

            case R_X86_64_DTPMOD64:
                *reloc_addr = 1;
                break;
 
            case R_X86_64_DTPOFF64:
                sym = get_sym(symtab, ELF64_R_SYM(rela->r_info));
                if(!sym)
                    break;
                *reloc_addr = sym->st_value + (uintptr_t)rela->r_addend;
                break;

            case R_X86_64_TPOFF64:
                sym = get_sym(symtab, ELF64_R_SYM(rela->r_info));
                if(!sym)
                    break;

                if ((0 == elf_tls_aligned_virtual_size((void *)enclave_base, &aligned_virtual_size)) && (aligned_virtual_size))
                {
                    *reloc_addr = sym->st_value + (uintptr_t)rela->r_addend - aligned_virtual_size;
                    break;
                }
                else
                    return -1;

            case R_X86_64_NONE:
                break;

            default:    /* unsupported relocs */
                return -1;
        }
    }

    return 0;
}

#elif defined(__i386__)
/* Relocation for x86 (without addend) */
static int do_relocs(const ElfW(Addr) enclave_base,
        ElfW(Addr) rel_offset,
        ElfW(Addr) sym_offset,
        size_t nr_relocs)
{
    ElfW(Rel)*  rel = GET_PTR(ElfW(Rel), enclave_base, rel_offset);
    ElfW(Sym)*  symtab = GET_PTR(ElfW(Sym), enclave_base, sym_offset);
    ElfW(Sym)*  sym = NULL;
    size_t      i;
    size_t aligned_virtual_size = 0;

    for (i = 0; i < nr_relocs; ++i, ++rel)
    {
        ElfW(Addr)* reloc_addr = GET_PTR(ElfW(Addr), enclave_base, rel->r_offset);

        if(R_386_RELATIVE == ELF32_R_TYPE(rel->r_info))
        {
            *reloc_addr += enclave_base; /* B+A */
            continue;
        }
        sym = get_sym(symtab, ELF32_R_SYM(rel->r_info));
        if(!sym)  /* when the weak symbol is not implemented, sym is NULL */
            continue;
        switch (ELF32_R_TYPE(rel->r_info))
        {
            case R_386_GLOB_DAT:
            case R_386_JMP_SLOT:    /* S */
                *reloc_addr = enclave_base + sym->st_value;
                break;

            case R_386_32:          /* S+A */
                *reloc_addr += enclave_base + sym->st_value;
                break;

            case R_386_PC32:        /* S+A-P */
                *reloc_addr += (enclave_base + sym->st_value - (ElfW(Addr))reloc_addr);
                break;

            case R_386_NONE:
                break;

            case R_386_TLS_DTPMOD32:
                *reloc_addr = 1;
                break;
 
            case R_386_TLS_DTPOFF32:
                *reloc_addr = sym->st_value;
                break;

            case R_386_TLS_TPOFF:
                if ((0 == elf_tls_aligned_virtual_size((void *)enclave_base, &aligned_virtual_size)) && (aligned_virtual_size))
                {
                    *reloc_addr += sym->st_value - aligned_virtual_size;
                    break;
                }
                else
                    return -1;

            case R_386_TLS_TPOFF32:
                if ((0 == elf_tls_aligned_virtual_size((void *)enclave_base, &aligned_virtual_size)) && (aligned_virtual_size))
                {
                    *reloc_addr += aligned_virtual_size - sym->st_value;
                    break;
                }
                else
                    return -1;

            default:    /* unsupported relocs */
                return -1;
        }
    }

    return 0;
}
#endif

#define DO_REL(base_addr, rel_offset, sym_offset, total_sz, rel_entry_sz)   \
do {                                                        \
    if (rel_offset)                                         \
    {                                                       \
        size_t n;                                           \
        if (rel_entry_sz == 0)                              \
            return -1;                                      \
        n = total_sz/rel_entry_sz;                          \
        if (do_relocs((ElfW(Addr))base_addr, rel_offset, sym_offset, n)) \
            return -1;                                      \
    }                                                       \
} while (0)

/* By default all symbol is linked as global symbol by link editor. When call global symbol,
 * we first call .plt entry. It should have problems if the call goloal symbol when relocation
 * is not done.
 * Declare relocate_enclave as .hidden is to make it local symbol.
 * Since this function is called before relocation is done, we must make
 * it local symbol, so the code is like "fce3:	e8 98 12 00 00    call   10f80 <relocate_enclave>"
 * 0x9812=0x10f80-0xfce8
 */
__attribute__ ((visibility ("hidden")))
int relocate_enclave(void* enclave_base)
{
    ElfW(Half) phnum = 0;
    ElfW(Ehdr) *ehdr = (ElfW(Ehdr)*)enclave_base;
    ElfW(Phdr) *phdr = get_phdr(ehdr);

    if (phdr == NULL)
        return -1;  /* Invalid image. */

    for (; phnum < ehdr->e_phnum; phnum++, phdr++)
    {
        /* Search for dynamic segment */
        if (phdr->p_type == PT_DYNAMIC)
        {
            size_t      count;
            size_t      n_dyn = phdr->p_filesz/sizeof(ElfW(Dyn));
            ElfW(Dyn)   *dyn = GET_PTR(ElfW(Dyn), ehdr, phdr->p_paddr);

            ElfW(Addr)   sym_offset = 0;
            ElfW(Addr)   rel_offset = 0;
            ElfW(Addr)   plt_offset = 0;

            size_t   rel_total_sz = 0;
            size_t   rel_entry_sz = 0;
            size_t   plt_total_sz = 0;

            for (count = 0; count < n_dyn; count++, dyn++)
            {
                if (dyn->d_tag == DT_NULL)  /* End */
                    break;

                switch (dyn->d_tag)
                {
                    case DT_SYMTAB: /* symbol table */
                        sym_offset = dyn->d_un.d_ptr;
                        break;

                    case RTS_DT_REL:/* Rel (x86) or Rela (x64) relocs */
                        rel_offset = dyn->d_un.d_ptr;
                        break;

                    case RTS_DT_RELSZ:
                        rel_total_sz = dyn->d_un.d_val;
                        break;

                    case RTS_DT_RELENT:
                        rel_entry_sz = dyn->d_un.d_val;
                        break;

                    case DT_JMPREL: /* PLT relocs */
                        plt_offset = dyn->d_un.d_ptr;
                        break;

                    case DT_PLTRELSZ:
                        plt_total_sz = dyn->d_un.d_val;
                        break;
                }
            }

            DO_REL(enclave_base, rel_offset, sym_offset, rel_total_sz, rel_entry_sz);
            DO_REL(enclave_base, plt_offset, sym_offset, plt_total_sz, rel_entry_sz);
        }
    }

    return 0;
}

int elf_tls_info(const void* enclave_base,
        uintptr_t *tls_addr, size_t *tdata_size)
{
    ElfW(Half) phnum = 0;
    const ElfW(Ehdr) *ehdr = (const ElfW(Ehdr)*)enclave_base;
    ElfW(Phdr) *phdr = get_phdr(ehdr);

    if (!tls_addr || !tdata_size)
        return -1;

    if (phdr == NULL)
        return -1;  /* Invalid image. */

    /* Search for TLS segment */
    *tls_addr = 0;
    *tdata_size = 0;
    for (; phnum < ehdr->e_phnum; phnum++, phdr++)
    {
        if (phdr->p_type == PT_TLS)
        {
            /* tls_addr here is got from the program header, the address
             * need to be added by the enclave base.
             */
            *tls_addr = (size_t)enclave_base + phdr->p_vaddr;
            *tdata_size = phdr->p_filesz;
            break;
        }
    }

    return 0;
}

static int elf_tls_aligned_virtual_size(const void *enclave_base,
                                        size_t *aligned_virtual_size)
{
    ElfW(Half) phnum = 0;
    const ElfW(Ehdr) *ehdr = (const ElfW(Ehdr)*)enclave_base;
    ElfW(Phdr) *phdr = get_phdr(ehdr);
    size_t virtual_size =0, align = 0;

    if (phdr == NULL)
        return -1;

    if (!aligned_virtual_size)
        return -1;

    *aligned_virtual_size = 0;
    for (; phnum < ehdr->e_phnum; phnum++, phdr++)
    {
        if (phdr->p_type == PT_TLS)
        {
            virtual_size = phdr->p_memsz;
            align = phdr->p_align;

            /* p_align == 0 or p_align == 1 means no alignment is required */
            if (align == 0 || align == 1)
                *aligned_virtual_size = virtual_size;
            else
                *aligned_virtual_size = (virtual_size + align - 1) & (~(align - 1));

            break;
        }
    }

    return 0;
}

int elf_get_init_array(const void* enclave_base,
        uintptr_t *init_array_addr, size_t *init_array_size)
{
    ElfW(Half) phnum = 0;
    const ElfW(Ehdr) *ehdr = (const ElfW(Ehdr)*)enclave_base;
    ElfW(Phdr) *phdr = get_phdr(ehdr);

    if (!init_array_addr || !init_array_size)
        return -1;

    if (phdr == NULL)
        return -1;  /* Invalid image. */

    *init_array_addr = 0;
    *init_array_size = 0;

    /* Search for Dynamic segment */
    for (; phnum < ehdr->e_phnum; phnum++, phdr++)
    {
        if (phdr->p_type == PT_DYNAMIC)
        {
            size_t      count;
            size_t      n_dyn = phdr->p_filesz/sizeof(ElfW(Dyn));
            ElfW(Dyn)   *dyn = GET_PTR(ElfW(Dyn), ehdr, phdr->p_paddr);
            
            for (count = 0; count < n_dyn; count++, dyn++)
            {
                switch (dyn->d_tag)
                {
                    case DT_INIT_ARRAY:
                        *init_array_addr = dyn->d_un.d_ptr;
                        break;
                    case DT_INIT_ARRAYSZ:
                        *init_array_size = dyn->d_un.d_val;
                        break;
                }
            }
        }
    }

    return 0;
}
/* vim: set ts=4 sw=4 et cin: */
