/*
 * Copyright (c) 2020 Google Inc. All rights reserved
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <lib/backtrace/symbolize.h>
#include <trace.h>

#include "elf_sym.h"

#define LOCAL_TRACE 0

#undef ELF_64BIT
#if !IS_64BIT || USER_32BIT
#define ELF_64BIT 0
#else
#define ELF_64BIT 1
#endif

#if ELF_64BIT
#define ELF_SHDR Elf64_Shdr
#define ELF_EHDR Elf64_Ehdr
#define ELF_SYM Elf64_Sym
#else
#define ELF_SHDR Elf32_Shdr
#define ELF_EHDR Elf32_Ehdr
#define ELF_SYM Elf32_Sym
#endif

static inline bool range_within_app_img(uintptr_t start,
                                        size_t size,
                                        struct trusty_app_img* app_img) {
    uintptr_t end;
    if (__builtin_add_overflow(start, size, &end)) {
        return false;
    }
    return app_img->img_start <= start && end <= app_img->img_end;
}

static inline bool range_within_range(uintptr_t start0,
                                      size_t size0,
                                      uintptr_t start1,
                                      size_t size1) {
    uintptr_t end0;
    if (__builtin_add_overflow(start0, size0, &end0)) {
        return false;
    }
    uintptr_t end1;
    if (__builtin_add_overflow(start1, size1, &end1)) {
        return false;
    }
    return start1 <= start0 && end0 <= end1;
}

int trusty_app_symbolize(struct trusty_app* app,
                         uintptr_t pc,
                         struct pc_symbol_info* info) {
    if (!app) {
        goto out_no_symbol;
    }
    /* Adjust pc to be relative to app image */
    if (__builtin_sub_overflow(pc, app->load_bias, &pc)) {
        goto out_no_symbol;
    }
    /* pc must be within the app image */
    struct trusty_app_img* app_img = &app->app_img;
    if (app_img->img_end <= app_img->img_start) {
        goto out_no_symbol;
    }
    if (pc > app_img->img_end - app_img->img_start) {
        goto out_no_symbol;
    }

    ELF_EHDR* ehdr = (ELF_EHDR*)app_img->img_start;
    ELF_SHDR* shdr = (ELF_SHDR*)((uintptr_t)ehdr + ehdr->e_shoff);

    ELF_SHDR* symtab_shdr = NULL;
    ELF_SHDR* strtab_shdr = NULL;

    /* Find section headers for .symtab and .strtab */
    for (size_t i = 0; i < ehdr->e_shnum; i++) {
        if (shdr[i].sh_type == SHT_SYMTAB) {
            symtab_shdr = shdr + i;
        }
        if (shdr[i].sh_type == SHT_STRTAB) {
            strtab_shdr = shdr + i;
        }
    }

    /* Handle the case when app is not built with .symtab or .strtab */
    if (!symtab_shdr || !strtab_shdr) {
        LTRACEF("App built without symbol table\n");
        goto out_no_symbol;
    }

    uintptr_t symtab_start = app_img->img_start + symtab_shdr->sh_offset;
    size_t symtab_size = symtab_shdr->sh_size;
    uintptr_t strtab_start = app_img->img_start + strtab_shdr->sh_offset;
    size_t strtab_size = strtab_shdr->sh_size;

    /* Validate .symtab and .strtab locations */
    if (!range_within_app_img(symtab_start, symtab_size, app_img)) {
        TRACEF(".symtab section is not within the app image\n");
        goto out_no_symbol;
    }
    if (!range_within_app_img(strtab_start, strtab_size, app_img)) {
        TRACEF(".strtab section is not within the app image\n");
        goto out_no_symbol;
    }

    /* Find closest symbol preceding pc */
    info->offset = ULONG_MAX;
    for (uintptr_t curr = symtab_start;
         curr < symtab_start + symtab_shdr->sh_size;
         curr += symtab_shdr->sh_entsize) {
        /* Entry must be within .symtab section */
        if (!range_within_range(curr, symtab_shdr->sh_entsize, symtab_start,
                                symtab_size)) {
            TRACEF(".symtab section is malformed\n");
            goto out_no_symbol;
        }

        ELF_SYM* symtab_entry = (ELF_SYM*)curr;
        /* We are looking for a symbol of a function */
        if (ELF_ST_TYPE(symtab_entry->st_info) != STT_FUNC) {
            continue;
        }

        uintptr_t func_start = symtab_entry->st_value;
        if (func_start <= pc && info->offset > pc - func_start) {
            /* Offset must be within .strtab section */
            if (symtab_entry->st_name >= strtab_size) {
                TRACEF(".strtab section is malformed\n");
                goto out_no_symbol;
            }

            info->symbol = (const char*)(strtab_start + symtab_entry->st_name);
            info->offset = pc - func_start;
            info->size = symtab_entry->st_size;
        }
    }

    if (info->offset == ULONG_MAX) {
        goto out_no_symbol;
    }
    return NO_ERROR;

out_no_symbol:
    info->symbol = NULL;
    info->offset = 0;
    info->size = 0;
    return ERR_NOT_FOUND;
}