/*
 * cmd_so.c - so 符号加载命令模块（loadso / unloadso）
 *
 *   loadso <path>    加载共享对象符号并换算为进程地址
 *   unloadso <path>  按路径(或 basename)卸载对应 so 的符号
 *
 * 本模块拥有 so 符号解析（elf_parse_so_symbols）与 so 加载基址查询
 * （find_so_load_base）；ELF 公共解析实现 elf_parse_common 位于
 * symbol_parse.c，由两个模块共用。
 */

#include "lnxall_trace.h"

/**
 * @brief 解析共享库(.so)的 ELF 符号表，追加写入 g_dbg.so_sym_table
 *
 * 仅接受 64 位 ET_DYN 文件；符号地址为文件内相对值，
 * 由 cmd_load_so 依据运行期加载基址统一换算为进程地址。
 *
 * @param[in] path so 文件路径
 * @return 0 成功（至少解析出 1 个符号），-1 失败
 */
int elf_parse_so_symbols(const char *path)
{
    return elf_parse_common(path, g_dbg.so_sym_table, &g_dbg.so_sym_count,
                            NULL, 1);
}

/**
 * @brief 从 /proc/<pid>/maps 中查找共享库(.so)的运行期加载基址
 *
 * @param[in] pid     目标进程
 * @param[in] so_path so 路径（绝对路径或仅文件名均可）
 * @return 命中映射段起始地址；未找到返回 0
 */
unsigned long find_so_load_base(pid_t pid, const char *so_path)
{
    char path[64];
    FILE *fp;
    char line[512];
    const char *base_name = strrchr(so_path, '/');

    base_name = base_name ? base_name + 1 : so_path;

    snprintf(path, sizeof(path), "/proc/%d/maps", pid);
    fp = fopen(path, "r");
    if (!fp) { perror("fopen /proc/pid/maps"); return 0; }

    while (fgets(line, sizeof(line), fp)) {
        unsigned long start, end;
        char perms[8];
        char fname[256] = {0};
        if (sscanf(line, "%lx-%lx %7s %*s %*s %*s %255s",
                   &start, &end, perms, fname) == 4 && fname[0] == '/') {
            const char *fb = strrchr(fname, '/');
            fb = fb ? fb + 1 : fname;
            if (strcmp(fname, so_path) == 0 || strcmp(fb, base_name) == 0) {
                DBG("so: matched '%s' at 0x%lx-0x%lx [%s]",
                    fname, start, end, perms);
                fclose(fp);
                fp = NULL;
                return start;
            }
        }
    }
    fclose(fp);
    fp = NULL;
    return 0;
}

/**
 * @brief 加载指定 so 的符号：loadso <path>
 *
 * 先经 /proc/<pid>/maps 确认 so 已加载并取得加载基址，再解析其符号
 * 并换算为进程空间地址；同时记录加载区间供 'unloadso' 反查。
 *
 * @param[in] path so 文件路径（绝对路径或仅文件名）
 * @return 0 成功，-1 失败
 */
int cmd_load_so(const char *path)
{
    unsigned long base = 0;
    int before = 0;
    int i = 0;
    const char *slash = NULL;
    size_t base_len = 0;

    if (!g_dbg.is_attached) {
        printf("Error: Not attached.\n");
        return -1;
    }
    if (path == NULL || *path == '\0') {
        printf("Usage: loadso <shared-object-path>\n");
        return -1;
    }
    if (g_dbg.so_load_count >= MAX_SO_LOADS) {
        printf("Error: too many loaded so records (%d), unload some first\n",
               MAX_SO_LOADS);
        return -1;
    }

    base = find_so_load_base(g_dbg.target_pid, path);
    if (base == 0) {
        printf("Error: '%s' not found in /proc/%d/maps (not loaded by target?)\n",
               path, g_dbg.target_pid);
        return -1;
    }

    before = g_dbg.so_sym_count;
    if (elf_parse_so_symbols(path) != 0) {
        g_dbg.so_sym_count = before;  /* roll back partial symbols */
        return -1;
    }

    /* Convert file-relative addresses to process-space addresses */
    for (i = before; i < g_dbg.so_sym_count; i++) {
        g_dbg.so_sym_table[i].addr += base;
    }

    /* Record load range for 'unloadso' */
    slash = strrchr(path, '/');
    slash = (slash != NULL) ? slash + 1 : path;
    base_len = strlen(slash);
    if (base_len >= SO_BASE_MAX) base_len = SO_BASE_MAX - 1;
    memcpy(g_dbg.so_loads[g_dbg.so_load_count].base, slash, base_len);
    g_dbg.so_loads[g_dbg.so_load_count].base[base_len] = '\0';
    snprintf(g_dbg.so_loads[g_dbg.so_load_count].path, SO_PATH_MAX, "%s", path);
    g_dbg.so_loads[g_dbg.so_load_count].sym_start = before;
    g_dbg.so_loads[g_dbg.so_load_count].sym_count =
        g_dbg.so_sym_count - before;
    g_dbg.so_load_count++;

    if (g_dbg.verbose)
        printf("Loaded %d symbol(s) from '%s' (base=0x%lx)\n",
               g_dbg.so_sym_count - before, path, base);
    return 0;
}

/**
 * @brief 卸载指定 so 的全部符号：unloadso <path>
 *
 * 按路径(或 basename)匹配 'loadso' 历史记录，从 so_sym_table 中删除
 * 该 so 的符号区间并 compact，同时更新后续记录的起始下标。
 *
 * @param[in] path  so 路径或文件名(与 loadso 参数一致)
 * @return 0 成功，-1 失败
 */
int cmd_unload_so(const char *path)
{
    int idx = -1;
    int i = 0;
    const char *slash = NULL;
    const char *base = NULL;

    if (!g_dbg.is_attached) {
        printf("Error: Not attached.\n");
        return -1;
    }
    if (path == NULL || *path == '\0') {
        printf("Usage: unloadso <shared-object-path>\n");
        return -1;
    }

    slash = strrchr(path, '/');
    base = (slash != NULL) ? slash + 1 : path;

    /* 先按完整路径匹配，再按 basename 匹配 */
    for (i = 0; i < g_dbg.so_load_count; i++) {
        if (strcmp(g_dbg.so_loads[i].path, path) == 0) { idx = i; break; }
    }
    if (idx < 0) {
        for (i = 0; i < g_dbg.so_load_count; i++) {
            if (strcmp(g_dbg.so_loads[i].base, base) == 0) { idx = i; break; }
        }
    }
    if (idx < 0) {
        printf("Error: '%s' has no loaded symbol record (use 'loadso' first)\n", path);
        return -1;
    }

    /* 删除符号区间：后续符号前移 compact */
    {
        int start  = g_dbg.so_loads[idx].sym_start;
        int count  = g_dbg.so_loads[idx].sym_count;
        int remain = g_dbg.so_sym_count - (start + count);
        char unload_path[SO_PATH_MAX];

        snprintf(unload_path, SO_PATH_MAX, "%s", g_dbg.so_loads[idx].path);

        if (remain > 0) {
            memmove(&g_dbg.so_sym_table[start],
                    &g_dbg.so_sym_table[start + count],
                    (size_t)remain * sizeof(symbol_t));
        }
        g_dbg.so_sym_count -= count;

        /* 更新后续记录的起始下标 */
        for (i = idx + 1; i < g_dbg.so_load_count; i++) {
            g_dbg.so_loads[i].sym_start -= count;
        }
        /* 删除本记录：后续记录前移 */
        for (i = idx; i < g_dbg.so_load_count - 1; i++) {
            g_dbg.so_loads[i] = g_dbg.so_loads[i + 1];
        }
        g_dbg.so_load_count--;

        if (g_dbg.verbose)
            printf("Unloaded %d symbol(s) of '%s'\n", count, unload_path);
        return 0;
    }
}
