/*
 * symbol_parse.c - ELF 符号表与进程路径解析模块
 *
 * 解析目标可执行文件/共享库的 ELF 符号表（.symtab/.dynsym），支持
 * 32 位（ELFCLASS32）与 64 位（ELFCLASS64）目标文件；并从 /proc 解析
 * 可执行路径与加载基址，提供运行期符号查询接口（find_symbol /
 * is_local_symbol 等）。DWARF4/5 调试信息解析见 dwarf_parse.c。
 */

#include "lnxall_trace.h"

/* ========================================================================
 * ELF Symbol Table Parsing
 * ======================================================================== */

/* ---- 小端未对齐读取（ELF 文件按目标小端序，ARM/x86 均为小端） ---- */
static uint16_t elf_r16(const uint8_t *p) { uint16_t v; memcpy(&v, p, 2); return v; }
static uint32_t elf_r32(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v; }
static uint64_t elf_r64(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v; }

/* ELF 符号表解析中间数据（elf_parse_symbols/elf_parse_so_symbols 共用） */
typedef struct {
    int        is_elf64;   /* 符号条目为 Elf64_Sym(24B) / Elf32_Sym(16B) */
    uint8_t   *symtab;     /* 符号表原始字节（.symtab 或 .dynsym） */
    char      *strtab;     /* 符号名字符串表 */
    size_t     symtab_sz;  /* symtab 字节数 */
    size_t     strtab_sz;  /* strtab 字节数 */
} elf_file_data_t;

/**
 * @brief 打开并解析 ELF 文件头/section 表/shstrtab（32/64 位通用）
 *
 * ELF32 与 ELF64 的头与 section 布局不同，本函数统一按 e_ident[EI_CLASS]
 * 分支解析，输出与位数无关的 elf_file_hdr_t（声明见 lnxall_trace.h）。
 *
 * @param[in]  path   文件路径
 * @param[out] out_fp 成功时返回已打开的文件指针
 * @param[out] hdr    成功时返回位数无关的 ELF 头信息
 * @return 0 成功；-1 失败（失败时文件已关闭，hdr 无需释放）
 */
int elf_read_file(const char *path, FILE **out_fp, elf_file_hdr_t *hdr)
{
    FILE *fp = fopen(path, "rb");
    if (!fp) { perror("fopen ELF"); return -1; }

    /* 读 ELF 头原始字节（Elf64_Ehdr 为 64 字节，覆盖 32 位头的 52 字节） */
    uint8_t raw[64];
    if (fread(raw, 1, sizeof(raw), fp) != sizeof(raw)) {
        fprintf(stderr, "Error: '%s' too short to be an ELF file\n", path);
        fclose(fp);
        return -1;
    }
    if (memcmp(raw, ELFMAG, SELFMAG) != 0) {
        fprintf(stderr, "Error: '%s' is not an ELF file\n", path);
        fclose(fp);
        return -1;
    }
    hdr->is_elf64 = (raw[EI_CLASS] == ELFCLASS64);
    if (!hdr->is_elf64 && raw[EI_CLASS] != ELFCLASS32) {
        fprintf(stderr, "Error: '%s' unknown ELF class %d\n",
                path, raw[EI_CLASS]);
        fclose(fp);
        return -1;
    }

    if (hdr->is_elf64) {
        hdr->e_type      = elf_r16(raw + 16);
        hdr->e_shoff     = (unsigned long)elf_r64(raw + 40);
        hdr->e_shentsize = elf_r16(raw + 58);
        hdr->e_shnum     = elf_r16(raw + 60);
        hdr->e_shstrndx  = elf_r16(raw + 62);
    } else {
        hdr->e_type      = elf_r16(raw + 16);
        hdr->e_shoff     = (unsigned long)elf_r32(raw + 32);
        hdr->e_shentsize = elf_r16(raw + 46);
        hdr->e_shnum     = elf_r16(raw + 48);
        hdr->e_shstrndx  = elf_r16(raw + 50);
    }

    if (hdr->e_shnum == 0 || hdr->e_shoff == 0) {
        fprintf(stderr, "Error: ELF has no section headers (stripped?)\n");
        fclose(fp);
        return -1;
    }
    if (hdr->e_shentsize <
        (hdr->is_elf64 ? sizeof(Elf64_Shdr) : sizeof(Elf32_Shdr))) {
        fprintf(stderr, "Error: '%s' bad section header entry size\n", path);
        fclose(fp);
        return -1;
    }
    if (hdr->e_shstrndx >= hdr->e_shnum) {
        fprintf(stderr, "Error: '%s' bad shstrndx\n", path);
        fclose(fp);
        return -1;
    }

    /* --- section 头表原始字节 → 统一 elf_section_t --- */
    size_t sraw_sz = (size_t)hdr->e_shentsize * hdr->e_shnum;
    uint8_t *sraw = malloc(sraw_sz);
    hdr->shdrs = calloc((size_t)hdr->e_shnum, sizeof(elf_section_t));
    if (!sraw || !hdr->shdrs) {
        free(sraw);
        elf_free_file(hdr);
        fclose(fp);
        return -1;
    }
    if (fseek(fp, (long)hdr->e_shoff, SEEK_SET) != 0 ||
        fread(sraw, 1, sraw_sz, fp) != sraw_sz) {
        free(sraw);
        elf_free_file(hdr);
        fclose(fp);
        return -1;
    }
    for (int i = 0; i < hdr->e_shnum; i++) {
        const uint8_t *sh = sraw + (size_t)i * hdr->e_shentsize;
        elf_section_t *s = &hdr->shdrs[i];
        s->name_off = elf_r32(sh + 0);       /* 32/64 位均 4 字节 */
        if (hdr->is_elf64) {
            s->type   = (unsigned long)elf_r32(sh + 4);
            s->offset = (unsigned long)elf_r64(sh + 24);
            s->size   = (unsigned long)elf_r64(sh + 32);
        } else {
            s->type   = (unsigned long)elf_r32(sh + 4);
            s->offset = (unsigned long)elf_r32(sh + 16);
            s->size   = (unsigned long)elf_r32(sh + 20);
        }
    }
    free(sraw);

    /* --- section 名字符串表 --- */
    const elf_section_t *shstr_sh = &hdr->shdrs[hdr->e_shstrndx];
    hdr->shstrtab = malloc((size_t)shstr_sh->size);
    if (!hdr->shstrtab) {
        elf_free_file(hdr);
        fclose(fp);
        return -1;
    }
    if (fseek(fp, (long)shstr_sh->offset, SEEK_SET) != 0 ||
        fread(hdr->shstrtab, 1, (size_t)shstr_sh->size, fp) != shstr_sh->size) {
        elf_free_file(hdr);
        fclose(fp);
        return -1;
    }
    for (int i = 0; i < hdr->e_shnum; i++) {
        elf_section_t *s = &hdr->shdrs[i];
        s->name = (s->name_off < shstr_sh->size) ?
                  hdr->shstrtab + s->name_off : "";
    }

    *out_fp = fp;
    return 0;
}

/**
 * @brief 释放 elf_read_file 解析出的 ELF 头信息
 *
 * @param[in,out] hdr 待释放的 ELF 头信息（释放后清零）
 */
void elf_free_file(elf_file_hdr_t *hdr)
{
    free(hdr->shdrs);
    free(hdr->shstrtab);
    memset(hdr, 0, sizeof(*hdr));
}

/**
 * @brief 从已解析的 ELF 头信息中加载符号表/字符串表到 data
 *
 * 符号表优先 .symtab/.strtab，缺失时回退 .dynsym/.dynstr。
 * 失败时由调用方统一释放 data 中已分配指针。
 *
 * @param[in]  fp   已打开、未定位的 ELF 文件
 * @param[in]  hdr  位数无关的 ELF 头信息（elf_read_file 解析）
 * @param[out] data 加载结果
 * @return 0 成功，-1 失败
 */
static int elf_load_sections(FILE *fp, const elf_file_hdr_t *hdr,
                             elf_file_data_t *data)
{
    /* --- locate .symtab / .strtab (fallback: .dynsym / .dynstr) --- */
    const elf_section_t *sym_shdr = NULL, *str_shdr = NULL;
    for (int i = 0; i < hdr->e_shnum; i++) {
        const elf_section_t *s = &hdr->shdrs[i];
        if (strcmp(s->name, ".symtab") == 0)      sym_shdr = s;
        else if (strcmp(s->name, ".strtab") == 0)  str_shdr = s;
    }
    if (!sym_shdr || !str_shdr) {
        for (int i = 0; i < hdr->e_shnum; i++) {
            const elf_section_t *s = &hdr->shdrs[i];
            if (!sym_shdr && strcmp(s->name, ".dynsym") == 0) sym_shdr = s;
            else if (!str_shdr && strcmp(s->name, ".dynstr") == 0) str_shdr = s;
        }
    }
    if (!sym_shdr || !str_shdr) {
        fprintf(stderr, "Error: No symbol table in file (stripped?)\n");
        return -1;
    }
    /* .symtab 缺失时回退 .dynsym：仅动态符号可解析，未被导出/引用的
     * 全局变量会全部缺失（stripped 二进制典型现象） */
    data->symtab_sz = sym_shdr->size;
    data->symtab = malloc(data->symtab_sz);
    if (!data->symtab) return -1;
    if (fseek(fp, (long)sym_shdr->offset, SEEK_SET) != 0) return -1;
    if (fread(data->symtab, 1, data->symtab_sz, fp) != data->symtab_sz)
        return -1;

    data->strtab_sz = str_shdr->size;
    data->strtab = malloc(data->strtab_sz);
    if (!data->strtab) return -1;
    if (fseek(fp, (long)str_shdr->offset, SEEK_SET) != 0) return -1;
    if (fread(data->strtab, 1, data->strtab_sz, fp) != data->strtab_sz)
        return -1;
    return 0;
}

/**
 * @brief 将 ELF 符号表过滤并填充到目标符号表（仅全局/弱符号）
 *
 * @param[in]  data      已加载的 ELF 符号数据
 * @param[out] table     目标符号表
 * @param[in,out] count  符号计数（在 table 内递增写入）
 * @param[in]  max_count 表容量上限
 * @return 0 成功
 */
static int elf_fill_sym_table(const elf_file_data_t *data,
                              symbol_t *table, int *count, int max_count)
{
    /* 符号条目宽度：Elf64_Sym = 24 字节，Elf32_Sym = 16 字节 */
    size_t esz = data->is_elf64 ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym);
    if (data->symtab_sz % esz != 0) {
        DBG("ELF: symbol table size %zu not a multiple of entry %zu",
            data->symtab_sz, esz);
        return -1;
    }
    int nsym = (int)(data->symtab_sz / esz);

    /* 符号过滤：仅全局/弱符号，且类型为 OBJECT/FUNC/NOTYPE */
    for (int i = 0; i < nsym && *count < max_count; i++) {
        const uint8_t *ent = data->symtab + (size_t)i * esz;
        uint32_t st_name;
        uint64_t st_value, st_size;
        uint8_t  st_info;
        if (data->is_elf64) {
            const Elf64_Sym *s = (const Elf64_Sym *)ent;
            st_name  = s->st_name;
            st_value = s->st_value;
            st_size  = s->st_size;
            st_info  = s->st_info;
        } else {
            const Elf32_Sym *s = (const Elf32_Sym *)ent;
            st_name  = s->st_name;
            st_value = s->st_value;
            st_size  = s->st_size;
            st_info  = s->st_info;
        }
        if (st_name == 0 || st_name >= data->strtab_sz) continue;

        const char *name = data->strtab + st_name;
        int info_type = st_info & 0xf;        /* STT_*（32/64 位布局一致） */
        int info_bind = (st_info >> 4) & 0xf; /* STB_* */

        if (info_bind != STB_GLOBAL && info_bind != STB_WEAK)
            continue;
        if (info_type != STT_OBJECT && info_type != STT_FUNC &&
            info_type != STT_NOTYPE)
            continue;
        /* BSS 全局变量（STT_OBJECT）的 st_size 可能为 0（链接器未填大小），
         * 地址仍有效，不能因此丢弃；仅过滤无类型且零大小的 NOTYPE 符号 */
        if (st_size == 0 && info_type == STT_NOTYPE)
            continue;

        symbol_t *sym = &table[*count];
        safe_strcpy(sym->name, name, MAX_NAME_LEN);
        sym->addr = (unsigned long)st_value;
        sym->size = (unsigned long)st_size;
        sym->type = (info_type == STT_FUNC) ? SYM_FUNC : SYM_OBJECT;
        sym->kind = (info_type == STT_FUNC) ? SYM_KIND_FUNC : SYM_KIND_UNKNOWN;
        sym->struct_idx = -1;
        sym->dwarf_type_ref = 0;
        sym->has_dwarf = 0;
        (*count)++;
    }

    return 0;
}

/**
 * @brief ELF 符号表解析公共实现（主程序 / 共享库符号表共用）
 *
 * @param[in]  path         文件路径
 * @param[out] table        目标符号表
 * @param[in,out] count     符号计数
 * @param[out] out_pie_flag 可选：非 NULL 时回写 ET_DYN 标志（仅主程序场景）
 * @param[in]  require_dyn  非 0：要求 ET_DYN 且未解析出符号时报错（so 场景）
 * @return 0 成功，-1 失败
 *
 * @note 由本文件的 elf_parse_symbols 与 cmd_so.c 的
 *       elf_parse_so_symbols 共用（声明见 lnxall_trace.h）。
 */
int elf_parse_common(const char *path, symbol_t *table, int *count,
                     unsigned long *out_pie_flag, int require_dyn)
{
    FILE *fp = NULL;
    elf_file_hdr_t hdr;
    elf_file_data_t data = {0};
    int ret = -1;
    int base_count = *count;

    memset(&hdr, 0, sizeof(hdr));
    if (elf_read_file(path, &fp, &hdr) != 0)
        return -1;

    if (require_dyn && hdr.e_type != ET_DYN) {
        fprintf(stderr, "Error: '%s' is not a shared object (ET_DYN)\n", path);
        goto cleanup;
    }
    if (out_pie_flag) *out_pie_flag = (hdr.e_type == ET_DYN) ? 1 : 0;

    DBG("ELF: class=%s type=%s, sections=%d",
        hdr.is_elf64 ? "64-bit" : "32-bit",
        (hdr.e_type == ET_DYN) ? "ET_DYN(PIE)" :
        (hdr.e_type == ET_EXEC) ? "ET_EXEC" : "OTHER",
        hdr.e_shnum);

    data.is_elf64 = hdr.is_elf64;
    if (elf_load_sections(fp, &hdr, &data) != 0) goto cleanup;

    elf_fill_sym_table(&data, table, count, MAX_SYMBOLS);

    if (require_dyn && *count == base_count) {
        fprintf(stderr, "Error: no usable symbols in '%s'\n", path);
        goto cleanup;
    }
    ret = 0;

cleanup:
    free(data.symtab);
    free(data.strtab);
    elf_free_file(&hdr);
    if (fp) fclose(fp);
    return ret;
}

/**
 * @brief 解析主程序 ELF 符号表到 g_dbg.sym_table
 *
 * 同时重置所有符号表计数（attach 流程一次性调用）。
 *
 * @param[in]  path         可执行文件路径
 * @param[out] out_pie_flag 回写 PIE 标志（ET_DYN=1）
 * @return 0 成功，-1 失败
 */
int elf_parse_symbols(const char *path, unsigned long *out_pie_flag)
{
    g_dbg.sym_count = 0;
    g_dbg.so_sym_count = 0;
    g_dbg.so_load_count = 0;
    g_dbg.local_sym_count = 0;
    return elf_parse_common(path, g_dbg.sym_table, &g_dbg.sym_count,
                            out_pie_flag, 0);
}

/* ========================================================================
 * Resolve executable path & load base from /proc
 * ======================================================================== */

int resolve_exe_path(pid_t pid, char *buf, size_t bufsz)
{
    char link[64];
    ssize_t n;
    snprintf(link, sizeof(link), "/proc/%d/exe", pid);
    n = readlink(link, buf, bufsz - 1);
    if (n < 0) { perror("readlink /proc/pid/exe"); return -1; }
    buf[n] = '\0';
    return 0;
}

unsigned long find_load_base(pid_t pid, const char *exe_path)
{
    char path[64];
    FILE *fp;
    char line[512];

    snprintf(path, sizeof(path), "/proc/%d/maps", pid);
    DBG("maps: scanning %s for exe_path='%s'", path, exe_path);
    fp = fopen(path, "r");
    if (!fp) { perror("fopen /proc/pid/maps"); return 0; }

    /*
     * Find the first mapping that belongs to the target executable.
     * This is the load base — symbol offsets from the ELF are added to it.
     * We do NOT filter by 'x' permission: the first segment might be
     * read-only (r--p) on some configurations.
     */
    while (fgets(line, sizeof(line), fp)) {
        unsigned long start, end;
        char perms[8];
        if (sscanf(line, "%lx-%lx %7s", &start, &end, perms) == 3) {
            char *p = strstr(line, exe_path);
            if (p) {
                /* Ensure it's the full path, not a substring */
                char c = p[strlen(exe_path)];
                if (c == '\n' || c == '\r' || c == '\0' || c == ' ') {
                    DBG("maps: MATCHED exe [%s] at 0x%lx-0x%lx",
                        perms, start, end);
                    fclose(fp);
                    return start;
                }
            }
        }
    }
    fclose(fp);

    /*
     * Fallback: no mapping matched exe_path.
     * Take the first mapping regardless (statically linked, etc.)
     */
    DBG("maps: no exe match, trying fallback (first any-mapping)");
    fp = fopen(path, "r");
    if (!fp) return 0;
    while (fgets(line, sizeof(line), fp)) {
        unsigned long start, end;
        char perms[8];
        if (sscanf(line, "%lx-%lx %7s", &start, &end, perms) == 3) {
            DBG("maps: FALLBACK using 0x%lx-0x%lx [%s]", start, end, perms);
            fclose(fp);
            return start;
        }
    }
    fclose(fp);
    return 0;
}

void adjust_symbol_addresses(void)
{
    DBG("sym: adjusting addresses, is_pie=%d, base_addr=0x%lx",
        g_dbg.is_pie, g_dbg.base_addr);
    int i;
    for (i = 0; i < g_dbg.sym_count; i++) {
        if (g_dbg.is_pie) {
            unsigned long raw = g_dbg.sym_table[i].addr;
            g_dbg.sym_table[i].addr += g_dbg.base_addr;
            DBG("sym: %-32s raw=0x%lx -> adj=0x%lx",
                g_dbg.sym_table[i].name, raw, g_dbg.sym_table[i].addr);
        }
    }
}

/* ========================================================================
 * Symbol Lookup
 * ======================================================================== */

symbol_t *find_symbol(const char *name, sym_type_t want_type)
{
    int i;
    for (i = 0; i < g_dbg.sym_count; i++) {
        if (strcmp(g_dbg.sym_table[i].name, name) == 0 &&
            g_dbg.sym_table[i].type == want_type)
            return &g_dbg.sym_table[i];
    }
    /* .so symbols loaded via 'loadso' take lower priority */
    for (i = 0; i < g_dbg.so_sym_count; i++) {
        if (strcmp(g_dbg.so_sym_table[i].name, name) == 0 &&
            g_dbg.so_sym_table[i].type == want_type)
            return &g_dbg.so_sym_table[i];
    }
    /* user-created out-param scratch symbols ('<type> <name> = <val>') */
    for (i = 0; i < g_dbg.local_sym_count; i++) {
        if (strcmp(g_dbg.local_sym_table[i].name, name) == 0 &&
            g_dbg.local_sym_table[i].type == want_type)
            return &g_dbg.local_sym_table[i];
    }
    return NULL;
}

symbol_t *find_symbol_any(const char *name)
{
    int i;
    for (i = 0; i < g_dbg.sym_count; i++) {
        if (strcmp(g_dbg.sym_table[i].name, name) == 0)
            return &g_dbg.sym_table[i];
    }
    /* .so symbols loaded via 'loadso' take lower priority */
    for (i = 0; i < g_dbg.so_sym_count; i++) {
        if (strcmp(g_dbg.so_sym_table[i].name, name) == 0)
            return &g_dbg.so_sym_table[i];
    }
    /* user-created out-param scratch symbols ('<type> <name> = <val>') */
    for (i = 0; i < g_dbg.local_sym_count; i++) {
        if (strcmp(g_dbg.local_sym_table[i].name, name) == 0)
            return &g_dbg.local_sym_table[i];
    }
    return NULL;
}

/* True if @p s points into the debugger-side out-param table.  Local
 * symbols are backed by debugger-process heap copies, so their 'addr'
 * must never be passed to the target as-is; callers that need a real
 * target address must stage the value on the target stack first. */
int is_local_symbol(const symbol_t *s)
{
    return s >= g_dbg.local_sym_table &&
           s <  g_dbg.local_sym_table + g_dbg.local_sym_count;
}
