/*
 * cmd_attach.c - attach / detach 命令模块
 *
 * do_attach：非阻塞挂接（PTRACE_SEIZE），解析可执行文件路径、加载基址，
 * 载入 ELF 符号表与 DWARF 结构体类型并做地址重定位；
 * do_detach：停止并 detach 子进程/兄弟线程/主目标，清理符号表与
 * out-param 堆副本。
 */

#include "lnxall_trace.h"

void free_out_param_symbols(void)
{
    for (int i = 0; i < g_dbg.local_sym_count; i++) {
        if ((void *)(g_dbg.local_sym_table[i].addr) != NULL) {
            free((void *)g_dbg.local_sym_table[i].addr);
            g_dbg.local_sym_table[i].addr = 0;
        }        
    }
    g_dbg.local_sym_count = 0;
}

int do_attach(pid_t pid)
{
    int status;

    if (kill(pid, 0) == -1) {
        fprintf(stderr, "Error: Process %d does not exist\n", pid);
        return -1;
    }

    /* Non-blocking attach: PTRACE_SEIZE does NOT stop the target
     * 刻意不设 PTRACE_O_TRACEEXIT：tracee 退出时仍会以 WIFEXITED/WIFSIGNALED
     * 终态报给 waitpid，足以清理登记；设 TRACEEXIT 只会额外插入一段
     * PTRACE_EVENT_EXIT 的 event-stop（线程停在 't'，须另行 CONT 放行）。
     * 高频创建/销毁线程的业务下，每个线程退出都多一个 stop 事件与 t 滞留
     * 窗口，且 handle_child_status 无 EVENT_EXIT 专门分支，徒增事件风暴。 */
    if (ptrace(PTRACE_SEIZE, pid, NULL,
               (void *)(PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK |
                        PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC)) == -1) {
        perror("PTRACE_SEIZE");
        if (errno == EPERM)
            fprintf(stderr, "Hint: Try as root or set "
                    "/proc/sys/kernel/yama/ptrace_scope to 0\n");
        return -1;
    }

    /* Drain any pending stop notification */
    pid_t w = waitpid(pid, &status, WNOHANG);
    if (w == pid && WIFSTOPPED(status))
        ptrace(PTRACE_CONT, pid, NULL, NULL);

    /* Release stale out-param copies from any previous session before
     * re-using the table (detach normally already frees these). */
    free_out_param_symbols();

    g_dbg.target_pid  = pid;
    g_dbg.is_attached = 1;

    resolve_exe_path(pid, g_dbg.exe_path, sizeof(g_dbg.exe_path));
    DBG("attach: exe_path='%s'", g_dbg.exe_path);
    g_dbg.base_addr = find_load_base(pid, g_dbg.exe_path);
    DBG("attach: base_addr=0x%lx", g_dbg.base_addr);

    unsigned long pie_flag = 0;
    if (elf_parse_symbols(g_dbg.exe_path, &pie_flag) != 0) {
        /* 符号表加载失败（32 位 ELF 目标 / stripped 等）：跳过地址
         * 调整与 DWARF 解析。若继续对 32 位 ELF 用 64 位结构读取
         * section 头，字段错位将导致越界访问而段错误。 */
        fprintf(stderr, "Warning: symbol table not loaded for '%s'\n",
                g_dbg.exe_path);
    } else {
        g_dbg.is_pie = (pie_flag != 0);
        DBG("attach: pie_flag=%lu, sym_count=%d", pie_flag, g_dbg.sym_count);
        adjust_symbol_addresses();
        if (g_dbg.is_attached) {
            /* DWARF 解析失败时提示（如 stripped 或未加 -g 编译），
             * 具体原因见 dwarf_parse_types 内部的 [DWARF] 诊断输出 */
            if (dwarf_parse_types(g_dbg.exe_path) != 0) {
                fprintf(stderr, "Warning: DWARF debug info not loaded "
                                "(see [DWARF] diagnostics above)\n");
            }
            DBG("attach: struct_count=%d", g_dbg.struct_count);
        }
    }

    /* The return-breakpoint NOP slot is NOT cached here: per external
     * guidance it must be recomputed dynamically on each 'call' command
     * (see cmd_call_function), without any comparison to the current PC. */
    g_dbg.nop_slot_addr = 0;

    /* attach 汇总信息 + 解析统计（各类条目 个数/上限，任一达到上限即可能
     * 截断，详见上方 [DWARF] overflow/warning 诊断）；解析失败/资源受限
     * 的原因同样见上方 stderr 错误输出 */
    printf("Attached to PID %d (non-blocking)\n", pid);
    printf("  Executable: %s\n", g_dbg.exe_path);
    printf("  Base: 0x%lx  [%s]\n", g_dbg.base_addr,
           g_dbg.is_pie ? "PIE" : "non-PIE");
    printf("  Symbols:    %d / %d\n", g_dbg.sym_count, MAX_SYMBOLS);
    printf("  Structs:    %d / %d\n", g_dbg.struct_count, MAX_STRUCTS);
    printf("  So refs:    %d / %d\n", g_so_count, MAX_STRUCT_OFFSETS);
    printf("  Var refs:   %d / %d\n", g_vr_count, MAX_VAR_REFS);
    printf("  Typedefs:   %d / %d\n", g_td_count, MAX_TYPEDEF_REFS);
    printf("  Arrays:     %d / %d\n", g_arr_count, MAX_ARRAY_REFS);
    printf("  Base sizes: %d / %d\n", g_ts_count, MAX_BASE_TYPE_SIZES);
    return 0;
}

int do_detach(void)
{
    if (!g_dbg.is_attached) {
        printf("Not attached.\n"); return 0;
    }

    printf("Detaching...\n");

    /* The synchronous call design guarantees call_in_progress == 0 here: a
     * call either completed or was aborted inside the 'call' command with
     * the full pre-call state already restored.  A target left stopped by
     * a finished/aborted call is detached as-is (PTRACE_DETACH resumes it
     * at its restored PC). */

    /* Stop, then detach children */
    for (int i = 0; i < g_dbg.child_count; i++) {
        pid_t cpid = g_dbg.children[i].pid;
        ptrace(PTRACE_INTERRUPT, cpid, NULL, NULL);
        int st;
        pid_t w = waitpid(cpid, &st, WNOHANG | __WALL);
        if (w > 0 && WIFSTOPPED(st)) {
            printf("do_detach childen. \n");
        }
        ptrace(PTRACE_DETACH, cpid, NULL, NULL);
    }

    /* Detach sibling threads that were stopped for an infcall */
    for (int i = 0; i < g_dbg.thread_count; i++) {
        if (ptrace(PTRACE_DETACH, g_dbg.thread_list[i], NULL, NULL) == -1)
            DBG("do_detach: DETACH tid=%d: %s",
                g_dbg.thread_list[i], strerror(errno));
    }
    g_dbg.thread_count = 0;
    /* Detach threads created during infcalls */
    for (int i = 0; i < g_dbg.new_thread_count; i++) {
        if (ptrace(PTRACE_DETACH, g_dbg.new_threads[i], NULL, NULL) == -1)
            DBG("do_detach: DETACH newtid=%d: %s",
                g_dbg.new_threads[i], strerror(errno));
    }
    g_dbg.new_thread_count = 0;

    /* Stop, then detach main target */
    ptrace(PTRACE_INTERRUPT, g_dbg.target_pid, NULL, NULL);
    int st;
    pid_t w = waitpid(g_dbg.target_pid, &st, WNOHANG | __WALL);
    if (w > 0 && WIFSTOPPED(st)) {
         printf("do_detach. \n");
    }
    /* Defensive: remove an injected return-breakpoint if one somehow
     * survives (should not happen with the synchronous design). */
    if (g_dbg.call_in_progress) {
        call_restore_break();
        g_dbg.call_in_progress = 0;
        g_dbg.breakpoint_len = 0;
    }
    ptrace(PTRACE_DETACH, g_dbg.target_pid, NULL, NULL);

    printf("Detached.\n");
    g_dbg.is_attached = 0;
    g_dbg.target_pid   = 0;
    g_dbg.child_count  = 0;
    g_dbg.sym_count    = 0;
    g_dbg.so_sym_count = 0;
    g_dbg.so_load_count = 0;
    /* Free debugger-side copies of out-param scratch symbols */
    free_out_param_symbols();
    return 0;
}
