/*!A cross-platform build utility based on Lua
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (C) 2015-present, Xmake Open Source Community.
 *
 * @author      ruki
 * @file        readsyms.c
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME "readsyms_elf"
#define TB_TRACE_MODULE_DEBUG (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "prefix.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * private implementation
 */

tb_bool_t xm_binutils_elf_read_symbols_32(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State *lua) {
    tb_assert_and_check_return_val(istream && lua, tb_false);

    // build context and ensure .symtab/.strtab are available
    xm_elf_context_t ctx;
    xm_binutils_elf_get_context_32(istream, base_offset, &ctx);

    if (!ctx.symtab_offset || !ctx.symstr_offset) {
        // no symbol table present: return empty result
        lua_newtable(lua);
        return tb_true;
    }

    // create result table
    lua_newtable(lua);

    // compute symbol count and seek to .symtab
    tb_uint32_t sym_count = (tb_uint32_t)(ctx.symtab_size / sizeof(xm_elf32_symbol_t));
    if (!tb_stream_seek(istream, base_offset + ctx.symtab_offset)) {
        return tb_false;
    }

    tb_uint32_t result_count = 0;
    for (tb_uint32_t i = 0; i < sym_count; i++) {
        xm_elf32_symbol_t sym;
        if (!tb_stream_bread(istream, (tb_byte_t*)&sym, sizeof(sym))) {
            return tb_false;
        }

        // skip NULL symbol entries
        if (sym.st_name == 0 && sym.st_value == 0 && sym.st_size == 0) {
            continue;
        }

        // skip section/file symbols
        tb_uint8_t type = sym.st_info & 0xf;
        if (type == 3 || type == 4) {
            continue;
        }

        // read symbol name from .strtab
        tb_char_t name[256];
        if (!xm_binutils_read_string(istream, base_offset + ctx.symstr_offset + sym.st_name, name, sizeof(name)) || !name[0]) {
            continue;
        }

        // skip internal ('.', '$') and local-defined symbols
        if (name[0] == '.' || name[0] == '$') {
            continue;
        }

        tb_uint8_t bind = (sym.st_info >> 4) & 0xf;
        if (bind == 0 && sym.st_shndx != 0) {
            continue;
        }

        // push symbol entry: name + nm-style type
        lua_pushinteger(lua, result_count + 1);
        lua_newtable(lua);

        lua_pushstring(lua, "name");
        lua_pushstring(lua, name);
        lua_settable(lua, -3);

        tb_char_t type_char = xm_binutils_elf_get_symbol_type_char(sym.st_info, sym.st_shndx);
        tb_char_t type_str[2] = {type_char, '\0'};
        lua_pushstring(lua, "type");
        lua_pushstring(lua, type_str);
        lua_settable(lua, -3);

        lua_settable(lua, -3);
        result_count++;
    }

    return tb_true;
}

tb_bool_t xm_binutils_elf_read_symbols_64(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State *lua) {
    tb_assert_and_check_return_val(istream && lua, tb_false);

    // build context and ensure .symtab/.strtab are available
    xm_elf_context_t ctx;
    xm_binutils_elf_get_context_64(istream, base_offset, &ctx);

    if (!ctx.symtab_offset || !ctx.symstr_offset) {
        // no symbol table present: return empty result
        lua_newtable(lua);
        return tb_true;
    }

    // create result table
    lua_newtable(lua);

    // compute symbol count and seek to .symtab
    tb_uint32_t sym_count = (tb_uint32_t)(ctx.symtab_size / sizeof(xm_elf64_symbol_t));
    if (!tb_stream_seek(istream, base_offset + ctx.symtab_offset)) {
        return tb_false;
    }

    tb_uint32_t result_count = 0;
    for (tb_uint32_t i = 0; i < sym_count; i++) {
        xm_elf64_symbol_t sym;
        if (!tb_stream_bread(istream, (tb_byte_t*)&sym, sizeof(sym))) {
            return tb_false;
        }

        // skip NULL symbol entries
        if (sym.st_name == 0 && sym.st_value == 0 && sym.st_size == 0) {
            continue;
        }

        // skip section/file symbols
        tb_uint8_t type = sym.st_info & 0xf;
        if (type == 3 || type == 4) {
            continue;
        }

        // read symbol name from .strtab
        tb_char_t name[256];
        if (!xm_binutils_read_string(istream, base_offset + ctx.symstr_offset + sym.st_name, name, sizeof(name)) || !name[0]) {
            continue;
        }

        // skip internal ('.', '$') and local-defined symbols
        if (name[0] == '.' || name[0] == '$') {
            continue;
        }

        tb_uint8_t bind = (sym.st_info >> 4) & 0xf;
        if (bind == 0 && sym.st_shndx != 0) {
            continue;
        }

        // push symbol entry: name + nm-style type
        lua_pushinteger(lua, result_count + 1);
        lua_newtable(lua);

        lua_pushstring(lua, "name");
        lua_pushstring(lua, name);
        lua_settable(lua, -3);

        tb_char_t type_char = xm_binutils_elf_get_symbol_type_char(sym.st_info, sym.st_shndx);
        tb_char_t type_str[2] = {type_char, '\0'};
        lua_pushstring(lua, "type");
        lua_pushstring(lua, type_str);
        lua_settable(lua, -3);

        lua_settable(lua, -3);
        result_count++;
    }

    return tb_true;
}

tb_bool_t xm_binutils_elf_read_symbols(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State *lua) {
    tb_assert_and_check_return_val(istream && lua, tb_false);

    // read and check ELF magic
    tb_uint8_t magic[4];
    if (!tb_stream_seek(istream, base_offset)) {
        return tb_false;
    }
    if (!tb_stream_bread(istream, magic, 4)) {
        return tb_false;
    }
    if (magic[0] != 0x7f || magic[1] != 'E' || magic[2] != 'L' || magic[3] != 'F') {
        return tb_false;
    }

    // check ELF class (32-bit or 64-bit)
    tb_uint8_t elf_class;
    if (!tb_stream_seek(istream, base_offset + 4)) {
        return tb_false;
    }
    if (!tb_stream_bread(istream, (tb_byte_t*)&elf_class, 1)) {
        return tb_false;
    }

    if (elf_class == 1) {
        return xm_binutils_elf_read_symbols_32(istream, base_offset, lua);
    } else if (elf_class == 2) {
        return xm_binutils_elf_read_symbols_64(istream, base_offset, lua);
    }

    return tb_false;
}
