/*
 * main.c - 入口与 REPL 命令分发模块
 *
 *   attach <PID>        Non-blocking attach to target process & its children
 *   d <var_name>        Read global variable / struct value
 *   d <struct>.<field>  Read struct member
 *   d <s>.<f>.<sub>     Read nested struct member
 *   m <var>=<val>       Write global variable value
 *   m <struct>.<field>=<val> - Write struct member
 *   d <struct> <addr>   Print memory at <addr> as struct <struct>
 *                       (struct name optional: auto-detect from symbol)
 *   ptype <struct>      Show struct type layout
 *   call <func>(<args>) Call a function in the target process
 *   detach              Detach from all traced processes and exit
 *   help                Show help message
 *
 * 本模块同时承载通用工具函数：safe_strcpy/trim/parse_int_value/
 * parse_hex_bytes/base_type_lookup，供各命令模块跨文件使用。
 *
 * 空闲保护：若 5 分钟无用户命令输入，自动清理资源（detach 等）并退出。
 */

#include "lnxall_trace.h"

/* REPL 空闲退出时限（秒）默认值：超过该时长无用户命令输入则自动清理退出。
 * 运行期可用命令行 -t <sec> 或 REPL 命令 'timeout <sec>' 覆盖；0 表示禁用。 */
#define REPL_IDLE_TIMEOUT_SEC 300

/* 构建标识 tag：用于在目标板上快速确认运行的是否为新版本——
 * 启动 banner 会打印；也可用 strings lnxall_trace | grep TRACE3 直接核对。
 * 每次发布修复后更新 tag 内容即可，勿用手工维护的全局版本号。 */
#define TRACE_BUILD_TAG "TRACE3-P0fix-8BRW+call-iSET+args8+slot"

/* SIGALRM 空闲超时标志：信号处理上下文置位，主循环检查后清理退出 */
static volatile sig_atomic_t g_idle_timeout = 0;

/* 当前配置的空闲自动退出时限（秒），<=0 表示禁用自动退出。
 * 默认 REPL_IDLE_TIMEOUT_SEC（300s）；命令行 -t/--timeout 或 REPL
 * 'timeout <sec>' 命令可覆盖。 */
static int g_idle_timeout_sec = REPL_IDLE_TIMEOUT_SEC;

/* Ctrl-C 中断请求标志（gdb 语义，头文件 extern）：
 *  - 'call' 进行中置位后，call_wait_for_return 消费并 PTRACE_INTERRUPT
 *    停住目标（保持停在被调函数帧内，不回退 PC）；
 *  - REPL 空闲时置位由主循环消费（取消当前输入行，重新提示）。 */
volatile sig_atomic_t g_intr_requested = 0;

/**
 * @brief SIGINT 处理函数：置位中断请求标志
 *
 * 仅置位标志，不在信号上下文做任何 ptrace/IO 操作；实际中断动作由
 * call_wait_for_return（call 期间）或主循环（REPL 空闲）消费执行。
 *
 * @param[in] sig 信号编号（固定为 SIGINT，本函数不使用）
 */
static void intr_handler(int sig)
{
    (void)sig;
    g_intr_requested = 1;
}

/**
 * @brief SIGALRM 处理函数：REPL 空闲超时置位退出标志
 *
 * @param[in] sig 信号编号（固定为 SIGALRM，本函数不使用）
 */
static void idle_timeout_handler(int sig)
{
    (void)sig;
    g_idle_timeout = 1;
}

/* 按当前 g_idle_timeout_sec 武装/解除空闲退出定时器：
 * >0 → alarm(sec)；<=0（禁用）→ alarm(0) 取消。每轮 REPL 循环顶调用。 */
static void idle_alarm_arm(void)
{
    if (g_idle_timeout_sec > 0)
        alarm((unsigned int)g_idle_timeout_sec);
    else
        alarm(0);
}

/* ========================================================================
 * Global State（头文件 extern，定义归属本模块）
 * ======================================================================== */
dbg_state_t g_dbg;

/* ========================================================================
 * Utility Functions
 * ======================================================================== */

/**
 * @brief 安全拷贝字符串（保证 NUL 结尾）
 *
 * @param[out] dst  目标缓冲区
 * @param[in]  src  源字符串
 * @param[in]  size 目标缓冲区大小（字节）
 * @return 目标缓冲区指针
 */
char *safe_strcpy(char *dst, const char *src, size_t size)
{
    if (size == 0) return dst;
    strncpy(dst, src, size - 1);
    dst[size - 1] = '\0';
    return dst;
}

/**
 * @brief 去除字符串首尾空白字符（就地修改）
 *
 * @param[in,out] str 输入字符串
 * @return 去除首部空白后的指针
 */
char *trim(char *str)
{
    char *end;

    while (isspace((unsigned char)*str)) str++;
    if (*str == '\0') return str;
    end = str + strlen(str) - 1;
    while (end > str && isspace((unsigned char)*end)) end--;
    *(end + 1) = '\0';
    return str;
}

/**
 * @brief 解析整数值（十进制/十六进制/八进制），并容忍尾部 ',' 与 ')'
 *
 * @param[in]  s   输入串
 * @param[out] out 解析结果
 * @return 0 成功，-1 失败
 */
int parse_int_value(const char *s, unsigned long *out)
{
    char *end;

    if (!s || !*s) return -1;
    *out = strtoul(s, &end, 0);
    if (*end != '\0' && *end != ',' && *end != ')') return -1;
    return 0;
}

/**
 * @brief 解析十六进制字节串，支持 "0x11223344"、"11 22 33 44"、"11223344" 形式
 *
 * @param[in]  s        输入串（"0x" 前缀与空格/逗号分隔符均可）
 * @param[out] out      输出字节缓冲区
 * @param[in]  max_out  输出缓冲区容量（字节数）
 * @return 解析出的字节数；-1 表示语法错误或超出容量
 */
int parse_hex_bytes(const char *s, unsigned char *out, size_t max_out)
{
    const char *p = s;
    size_t cnt = 0;
    int hi = -1;

    for (; *p != '\0'; p++) {
        int v;
        char c = *p;
        if (c == ' ' || c == '\t' || c == ',') continue;
        if (c == '0' && (p[1] == 'x' || p[1] == 'X')) {
            p++;    /* 跳过 '0'，外层 p++ 再跳过 'x'/'X' */
            continue;
        }
        if      (c >= '0' && c <= '9')      v = c - '0';
        else if (c >= 'a' && c <= 'f')      v = c - 'a' + 10;
        else if (c >= 'A' && c <= 'F')      v = c - 'A' + 10;
        else return -1;
        if (hi < 0) {
            hi = v;
        } else {
            if (cnt >= max_out) return -1;
            out[cnt] = (unsigned char)((hi << 4) | v);
            cnt++;
            hi = -1;
        }
    }
    if (hi >= 0) return -1;     /* 奇数个十六进制数字 */
    return (int)cnt;
}

/**
 * @brief 识别命令行前缀是否为可构造的 C 基础类型（出参声明语法）
 *
 * 支持的声明形式：'<type> <name> = <value>'，类型可为
 * char/short/int/long/float/double（含 unsigned 前缀）。
 * 类型字节数用编译期 sizeof 表达：调试器与目标进程同架构同 ABI，
 * sizeof 即目标侧类型大小（如 long 在 LP64=8、ILP32=4）。
 *
 * @param[in]  spec     用户输入的命令行
 * @param[out] out_size 可选：返回类型字节数
 * @return 0 是基础类型声明，-1 不是
 */
int base_type_lookup(const char *spec, unsigned long *out_size)
{
    char w1[32] = {0};
    int n = 0;

    if (spec == NULL) return -1;
    if (sscanf(spec, "%31s%n", w1, &n) != 1) return -1;

    if (strcmp(w1, "unsigned") == 0) {
        char w2[32] = {0};
        if (sscanf(spec + n, "%31s", w2) != 1) return -1;
        if (strcmp(w2, "char") == 0)   { if (out_size) *out_size = sizeof(char);   return 0; }
        if (strcmp(w2, "short") == 0)  { if (out_size) *out_size = sizeof(short);  return 0; }
        if (strcmp(w2, "int") == 0)    { if (out_size) *out_size = sizeof(int);    return 0; }
        if (strcmp(w2, "long") == 0)   { if (out_size) *out_size = sizeof(long);   return 0; }
        return -1;
    }
    if (strcmp(w1, "char") == 0)   { if (out_size) *out_size = sizeof(char);   return 0; }
    if (strcmp(w1, "short") == 0)  { if (out_size) *out_size = sizeof(short);  return 0; }
    if (strcmp(w1, "int") == 0)    { if (out_size) *out_size = sizeof(int);    return 0; }
    if (strcmp(w1, "long") == 0)   { if (out_size) *out_size = sizeof(long);   return 0; }
    if (strcmp(w1, "float") == 0)  { if (out_size) *out_size = sizeof(float);  return 0; }
    if (strcmp(w1, "double") == 0) { if (out_size) *out_size = sizeof(double); return 0; }
    return -1;
}

/**
 * @brief 记录用户输入命令到 syslog
 *
 * 用户输入仅作参数传入，避免作为格式串（防格式串注入）。
 *
 * @param[in] line 用户原始输入行
 */
void log_user_command(const char *line)
{
    char buf[MAX_CMD_LEN] = {0};
    size_t len = 0;

    if (line == NULL) {
        return;
    }

    /* 去除结尾换行/回车，保留原始输入 */
    len = strlen(line);
    while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
        len--;
    }
    if (len == 0 || len >= sizeof(buf)) {
        return;
    }

    memcpy(buf, line, len);
    buf[len] = '\0';

    /* 用户输入仅作参数传入，避免作为格式串（防格式串注入） */
    syslog(LOG_USER | LOG_WARNING, "user command: %s", buf);
}

/**
 * @brief 打印命令帮助信息
 */
static void show_help(void)
{
    printf("\n"
           "  Commands:\n"
           "    attach <PID>                Non-blocking attach to target process\n"
           "    d <var_name>                Read variable / struct (auto-detects struct layout)\n"
           "    d <struct>.<field>          Read struct/nested member value\n"
           "    d <addr>,<len>              Dump target memory at <addr> for <len> bytes\n"
           "                                (16 bytes per line: addr + hex + ascii)\n"
           "    d &<var_name>               Show address/size/kind of a symbol\n"
           "    d <func_name>               Show function address (e.g. d dev_get_tag)\n"
           "    d <struct_name> <addr>      Print memory at <addr> as struct layout\n"
           "                                (struct name optional: auto-detected from\n"
           "                                the symbol covering <addr>, e.g. d node 0x40a000)\n"
           "    m <var>=<value>             Write variable (int or float/double auto-detected)\n"
           "    m <struct>.<field>=<value>  Write struct member value\n"
           "    m <addr>,<len>=<hex>        Write target memory, hex byte string repeated\n"
           "                                to fill <len> bytes (e.g. m 0x40a000,8=0x1122334455667788)\n"
           "    call <func>(arg1,...)       Call function (synchronous; target auto-resumes)\n"
           "    <func>(arg1,...)            Same as 'call' - keyword optional\n"
           "    detach                      Detach and exit\n"
           "    <addr>                      Look up global symbol by address (hex or dec,\n"
           "                                e.g. 0x40a000 / 4259840)\n"
           "    c / continue                Manually resume the target if needed\n"
           "    threads                     Show tracer thread registry vs /proc states\n"
           "                                (which tids are frozen/new/untracked)\n"
           "    resume <tid>                Manually CONT a traced thread stuck in 't'\n"
           "    timeout [<sec>]             Show/set idle auto-exit timeout (default 300s);\n"
           "                                0 or 'off' disables auto-exit\n"
           "    verbose on|off              Toggle debug info printing (default off);\n"
           "                                bare 'verbose' shows current state\n"
           "    ptype <struct_name>         Show struct type layout with member offsets\n"
           "    loadso <so-path>            Load a shared object's symbol table\n"
           "    unloadso <so-path>          Unload all symbols of a loaded so (path or\n"
           "                                basename matches, e.g. unloadso libdev.so)\n"
           "    <type> <name> = <val>       Create out-param scratch symbol\n"
           "                                e.g. 'double value = 0'; pass &value to 'call'\n"
           "    struct <type> <name> = 0    Create out-param scratch struct symbol\n"
           "                                e.g. 'struct node_t node = 0'; d node\n"
           "    help                        Show this help\n"
           "    quit / exit / q             Same as detach\n"
           "\n");
}

/**
 * @brief 执行单条调试命令（REPL 分发）
 *
 * @param[in,out] line 用户输入的命令行（会被 trim 就地修改）
 * @return 0 继续 REPL；1 退出 REPL；-1 命令执行失败
 */
int exec_command(char *line)
{
    char *cmd = trim(line);

    DBG("repl: command='%s'", cmd);
    if (cmd[0] == '\0') return 0;

    /* attach */
    if (strncmp(cmd, "attach ", 7) == 0 || strncmp(cmd, "ATTACH ", 7) == 0) {
        char *arg = trim(cmd + 7);
        char *ep;
        long pid_val = strtol(arg, &ep, 10);
        if (*ep != '\0' || pid_val <= 0) {
            printf("Error: Invalid PID: '%s'\n", arg); return -1;
        }
        if (g_dbg.is_attached) {
            printf("Already attached to PID %d. Detach first.\n",
                   g_dbg.target_pid); return -1;
        }
        return do_attach((pid_t)pid_val);
    }

    /* continue / c — resume a target left stopped by a finished call */
    if (strcasecmp(cmd, "c") == 0 || strcasecmp(cmd, "continue") == 0) {
        if (!g_dbg.is_attached) {
            printf("Not attached.\n"); return -1;
        }
        if (!g_dbg.call_sync_done) {
            printf("Nothing to continue (target is not stopped).\n"); return -1;
        }
        if (g_dbg.call_stopped_in_frame) {
            /* Target is still inside a callee from an aborted call.  The
             * frame was kept (no PC rewind) so locks it holds can still be
             * released, but the function may return into the restored NOP
             * slot — warn the user before resuming. */
            printf("Warning: resuming %s() left stopped inside its frame; "
                   "it may return into restored code.\n",
                   g_dbg.call_func_name);
        }
        if (ptrace_continue_target(g_dbg.target_pid) != 0)
            return -1;
        resume_all_threads();
        g_dbg.call_sync_done = 0;
        g_dbg.call_stopped_in_frame = 0;
        printf("Resumed.\n");
        return 0;
    }

    /* threads — dump thread registry (排障) */
    if (strcasecmp(cmd, "threads") == 0) {
        return cmd_threads_dump();
    }

    /* resume <tid> — manually CONT a stuck traced thread (排障兜底) */
    if (strncmp(cmd, "resume ", 7) == 0 || strncmp(cmd, "RESUME ", 7) == 0) {
        char *arg = trim(cmd + 7);
        char *ep;
        long tid_val = strtol(arg, &ep, 10);
        if (*ep != '\0' || tid_val <= 0) {
            printf("Usage: resume <tid>\n"); return -1;
        }
        return cmd_resume_tid((pid_t)tid_val);
    }

    /* detach / quit / exit / q (case-insensitive) */
    if (strcasecmp(cmd, "detach") == 0 || strcasecmp(cmd, "quit") == 0 ||
        strcasecmp(cmd, "exit") == 0   || strcasecmp(cmd, "q") == 0) {
        do_detach();
        return 1;  /* exit REPL */
    }

    /* help */
    if (strcmp(cmd, "help") == 0) { show_help(); return 0; }

    /* timeout [<sec>] — 查看/设置 REPL 空闲自动退出时限；0 或 off 禁用。
     * 生效时机：命令返回后下一轮循环顶重新武装定时器。 */
    if (strncmp(cmd, "timeout", 7) == 0 &&
        (cmd[7] == '\0' || isspace((unsigned char)cmd[7]))) {
        const char *arg = trim(cmd + 7);
        if (*arg == '\0' || strcasecmp(arg, "show") == 0) {
            if (g_idle_timeout_sec <= 0)
                printf("Idle auto-exit is disabled.\n");
            else
                printf("Idle auto-exit after %d second(s) without input.\n",
                       g_idle_timeout_sec);
            return 0;
        }
        if (strcasecmp(arg, "off") == 0 || strcasecmp(arg, "disable") == 0)
            arg = "0";
        char *ep;
        long v = strtol(arg, &ep, 10);
        if (*ep != '\0' || v < 0) {
            printf("Usage: timeout <sec>   (0 or 'off' disables auto-exit)\n");
            return -1;
        }
        g_idle_timeout_sec = (int)v;
        if (v == 0)
            printf("Idle auto-exit disabled.\n");
        else
            printf("Idle auto-exit set to %d second(s); effective on next prompt.\n",
                   (int)v);
        return 0;
    }

    /* verbose on|off|1|0 — toggle debug info printing (default off) */
    if (strncmp(cmd, "verbose", 7) == 0 ||
        strncmp(cmd, "VERBOSE", 7) == 0) {
        const char *arg = trim(cmd + 7);
        if (*arg == '\0') {
            printf("Verbose output is %s\n", g_dbg.verbose ? "ON" : "OFF");
            return 0;
        }
        if (strcasecmp(arg, "on") == 0 || strcmp(arg, "1") == 0) {
            g_dbg.verbose = 1;
            printf("Verbose output ON\n");
            return 0;
        }
        if (strcasecmp(arg, "off") == 0 || strcmp(arg, "0") == 0) {
            g_dbg.verbose = 0;
            printf("Verbose output OFF\n");
            return 0;
        }
        printf("Usage: verbose on|off  (or verbose to show current state)\n");
        return -1;
    }

    /* Out-param construction: 'struct <typename> <name> = <value>'
     * 或 '<type> <name> = <value>'（如 "double value = 0"） */
    if (strncasecmp(cmd, "struct ", 7) == 0 || base_type_lookup(cmd, NULL) == 0) {
        return cmd_create_local(cmd);
    }

    /* 纯数值输入 → 按地址反查全局符号（如 0x40a000 / 4259840）
     * 数字判断已覆盖 0x 前缀（'0' 属数字），strtoul base=0 自动识别十六进制 */
    if (cmd[0] >= '0' && cmd[0] <= '9') {
        char *ep2 = NULL;
        unsigned long addr = strtoul(cmd, &ep2, 0);
        if (ep2 != cmd && *ep2 == '\0') return cmd_lookup_symbol_by_addr(addr);
    }

    /* d <var> 或 d <addr>,<len> 或 d &<name> */
    if ((cmd[0] == 'd' || cmd[0] == 'D') &&
        (cmd[1] == ' ' || cmd[1] == '\t')) {
        char *var = trim(cmd + 2);
        if (*var == '\0') { printf("Usage: d <var_name>\n"); return -1; }
        if (strchr(var, ',') != NULL) return cmd_dump_memory(var);
        if (*var == '&') {
            char *sym_name = trim(var + 1);
            if (*sym_name == '\0') {
                printf("Usage: d &<var_name>\n"); return -1;
            }
            return cmd_print_addr(sym_name);
        }
        /* d <struct_name> <addr> — 按结构体布局打印 addr 处的内存 */
        if (strchr(var, ' ') != NULL || strchr(var, '\t') != NULL) {
            char vn[MAX_NAME_LEN];
            safe_strcpy(vn, var, MAX_NAME_LEN);
            char *sep = strpbrk(vn, " \t");
            if (sep != NULL) {
                *sep = '\0';
                char *sname = trim(vn);
                char *aspec = trim(sep + 1);
                if (*sname != '\0' && *aspec != '\0')
                    return cmd_print_struct_ptr(sname, aspec);
            }
            printf("Usage: d <struct_name> <addr>\n");
            return -1;
        }
        /* d <addr> — 地址字面量：反查覆盖该地址的符号，
         * 若为结构体符号则按其类型打印（结构体名可选） */
        {
            unsigned long a;
            if (parse_int_value(var, &a) == 0)
                return cmd_print_struct_ptr(NULL, var);
        }
        return cmd_read_var(var);
    }

    /* m <var>=<val> 或 m <addr>,<len>=<hex> */
    if ((cmd[0] == 'm' || cmd[0] == 'M') &&
        (cmd[1] == ' ' || cmd[1] == '\t')) {
        char *expr = trim(cmd + 2);
        char *eq = strchr(expr, '=');
        if (!eq) { printf("Usage: m <var>=<value>\n"); return -1; }
        *eq = '\0';
        char *vname = trim(expr);
        char *vstr  = trim(eq + 1);
        if (strchr(vname, ',') != NULL) return cmd_write_memory(vname, vstr);
        return cmd_write_var(vname, vstr);
    }

    /* call <func>(args) */
    if (strncmp(cmd, "call ", 5) == 0 || strncmp(cmd, "CALL ", 5) == 0) {
        char *spec = trim(cmd + 5);
        if (*spec == '\0') {
            printf("Usage: call <func_name>(arg1, arg2, ...)\n"); return -1;
        }
        return cmd_call_function(spec);
    }

    /* 隐式函数调用：直接输入 func(arg1, arg2, ...) 等价于 call func(...)。
     * 判定规则：命令以 C 标识符开头且其后紧跟 '('（形如 ident(...)）；
     * 现有命令均为"命令名 + 空格"格式（d/m 要求第 2 字符为空格），
     * 不会误匹配，故放在 call 分支之后安全兜底 */
    {
        const char *p = cmd;
        if ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || *p == '_') {
            const char *q = p + 1;
            while ((*q >= 'A' && *q <= 'Z') || (*q >= 'a' && *q <= 'z') ||
                   (*q >= '0' && *q <= '9') || *q == '_') {
                q++;
            }
            if (*q == '(') {
                return cmd_call_function(p);
            }
        }
    }

    /* loadso <so-path> — load a shared object's symbol table */
    if (strncmp(cmd, "loadso ", 7) == 0 || strncmp(cmd, "LOADSO ", 7) == 0) {
        char *path = trim(cmd + 7);
        if (*path == '\0') {
            printf("Usage: loadso <shared-object-path>\n"); return -1;
        }
        return cmd_load_so(path);
    }

    /* unloadso <so-path> — unload all symbols of a loaded shared object */
    if (strncmp(cmd, "unloadso ", 9) == 0 || strncmp(cmd, "UNLOADSO ", 9) == 0) {
        char *path = trim(cmd + 9);
        if (*path == '\0') {
            printf("Usage: unloadso <shared-object-path>\n"); return -1;
        }
        return cmd_unload_so(path);
    }

    /* ptype <struct_type_name> */
    if (strncmp(cmd, "ptype ", 6) == 0 || strncmp(cmd, "PTYPE ", 6) == 0) {
        char *tname = trim(cmd + 6);
        return cmd_ptype(tname);
    }

    printf("Unknown command: '%s'.  Type 'help'.\n", cmd);
    return -1;
}

/* ========================================================================
 * Main
 * ======================================================================== */

/**
 * @brief 程序入口：初始化、可选自动 attach、REPL 主循环
 *
 * @param[in] argc 参数个数
 * @param[in] argv 参数表：[PID] [-t <sec>|--timeout <sec>]（PID 可选；
 *            -t 覆盖空闲自动退出时限，0 禁用，默认 300s）
 * @return 0 正常退出
 */
int main(int argc, char *argv[])
{
    struct sigaction sa;
    char cmd_line[MAX_CMD_LEN];
    pid_t auto_pid = -1;

    memset(&g_dbg, 0, sizeof(g_dbg));

    /* 命令行选项解析（须在 banner 打印前完成，banner 要显示生效的时限）。
     * 用法：lnxall_trace [PID] [-t <sec> | --timeout <sec>]
     *   -t/--timeout <sec>：空闲自动退出时限，0 表示禁用（默认 300s）；
     *   选项可出现在 PID 前或后；首个非选项数字参数作为待 attach 的 PID。 */
    for (int ai = 1; ai < argc; ai++) {
        if (strcmp(argv[ai], "-t") == 0 || strcmp(argv[ai], "--timeout") == 0) {
            if (ai + 1 < argc) {
                long v = atol(argv[++ai]);
                if (v >= 0)
                    g_idle_timeout_sec = (int)v;
                else
                    printf("Warning: invalid idle timeout '%s', "
                           "keeping default %d s.\n",
                           argv[ai], REPL_IDLE_TIMEOUT_SEC);
            }
            continue;
        }
        if (argv[ai][0] != '-' && auto_pid < 0)
            auto_pid = (pid_t)atoi(argv[ai]);
    }

    printf("===================================================\n"
           "  lnxall-trace -- Non-blocking Process Tracer\n"
           "  ver 3.0  build: %s %s  [%s]\n",
           __DATE__, __TIME__, TRACE_BUILD_TAG);
    if (g_idle_timeout_sec > 0)
        printf("  Idle auto-exit: %d s without input "
               "(override: -t <sec> / 'timeout <sec>')\n", g_idle_timeout_sec);
    else
        printf("  Idle auto-exit: disabled (-t 0 / 'timeout off')\n");
#if defined(ARCH_AARCH64)
    printf("  Platform: Linux / ARM64 (aarch64, LP64)\n");
#elif defined(ARCH_ARM32)
    printf("  Platform: Linux / ARM32 (arm, ILP32)\n");
#elif defined(ARCH_X86_64)
    printf("  Platform: Linux / x86_64 (LP64)\n");
#else
    printf("  Platform: Linux / unknown\n");
#endif
    printf("===================================================\n"
           "Type 'help' for commands.\n\n");

    /* SIGCHLD handler for child trace events.
     * 内核 do_notify_parent_cldstop()（kernel/signal.c）发送 SIGCHLD 的条件
     * 与 why 无关：只要 handler 非 SIG_IGN 且未设 SA_NOCLDSTOP 即发送——
     * 故此处不得设 SA_NOCLDSTOP。设了它会把 ptrace-stop（CLD_TRAPPED，
     * 含 PTRACE_INTERRUPT 完成的 PTRACE_EVENT_STOP）的 SIGCHLD 一并抑制，
     * 只余 __wake_up_parent 唤醒阻塞中的 waitpid：命令执行期的事件靠 waitpid
     * 收走没问题，但 REPL 空闲时 select() 不会被唤醒 → self-pipe 无字节 →
     * 已停线程的事件滞留 wait 队列，线程长期停在 't'（本工具曾实测的永久
     * State=t 现象）直至下次执行命令才被顺带收走。
     * 设 SA_RESTART：handler 只向 self-pipe 写字节、不做其它 IO，命令执行
     * 中的慢 IO（ELF/DWARF 解析等）不会被本信号打断；select 被中断后自动
     * 重启并立即看到管道可读，即时唤醒消费。为兼容不同内核的信号语义差异，
     * REPL 内层循环另有 200ms 轮询兜底（见下）。 */
    /* SIGCHLD self-pipe 通知通道必须先于 handler 就绪（handler 只向管道写
     * 字节）。失败属资源极端场景，直接退出由用户重试。 */
    if (sigchld_notify_init() != 0) {
        perror("sigchld_notify_init(pipe)");
        return 1;
    }
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigchld_handler;
    sa.sa_flags   = SA_RESTART;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);

    /* SIGALRM handler for REPL idle timeout (auto-exit) */
    sa.sa_handler = idle_timeout_handler;
    sa.sa_flags   = 0;   /* 不设 SA_RESTART：中断阻塞的 fgets 以检查超时标志 */
    sigemptyset(&sa.sa_mask);
    sigaction(SIGALRM, &sa, NULL);

    /* SIGINT (Ctrl-C), gdb semantics: during an in-flight 'call' the wait
     * loop interrupts the target and keeps it stopped in the callee frame;
     * at the REPL prompt it just cancels the current input line. */
    sa.sa_handler = intr_handler;
    sa.sa_flags   = 0;   /* 不设 SA_RESTART：中断阻塞的 fgets 以检查中断标志 */
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);

    openlog("ptrace-deubgger", LOG_PID|LOG_CONS|LOG_NDELAY, LOG_USER); /* 进程退出时自动销毁 */

    /* Optional: auto-attach from command line (auto_pid 由入口参数解析得到) */
    if (auto_pid > 0) {
        printf("Auto-attaching to PID %d...\n", auto_pid);
        do_attach(auto_pid);
    }

    /* REPL loop */
    while (1) {
        int reprompt = 0;   /* Ctrl-C 取消当前输入行：回循环顶重新提示并重置超时 */

        if (g_dbg.is_attached)
            printf("[lnxall:%d]%s > ", g_dbg.target_pid,
                   g_dbg.call_sync_done ? "(stopped)" : "");
        else
            printf("[ptrace] > ");
        fflush(stdout);

        /* 空闲自动清理退出：每轮循环顶按 g_idle_timeout_sec 重置定时器
         * （默认 300s；'timeout <sec>' 或 -t 可改；<=0 禁用）。 */
        g_idle_timeout = 0;
        idle_alarm_arm();

        /* 内层输入循环：以 select() 同时监听 stdin 与 SIGCHLD self-pipe。
         * tracee 的 ptrace 事件（fork/clone/stop → CLD_TRAPPED SIGCHLD，须
         * 去除 SA_NOCLDSTOP 才会送达，见上方 SIGCHLD 安装处注释）由 handler
         * 写入通知管道——只要还有未消费的事件，管道就保持可读并即时唤醒
         * select，水平触发不丢事件（不再依赖可能被内核合并丢弃的标准 SIGCHLD
         * 边沿打断，否则停止事件会滞留 wait 队列，线程长期停在 't' 直到下次
         * stop_all_threads 类命令顺带收走）。此外 select 带 200ms 超时轮询
         * wait 队列兜底（信号语义差异下的确定性放行），双保险杜绝滞留。
         * 事件消费后原地续读当前输入行、不重打提示符（否则后台事件较频繁时
         * 会刷出连续提示符）。内层仅在真正读到一行、5 分钟超时、Ctrl-C 取消
         * 当前行、或 EOF/读错误时退出。 */
        for (;;) {
            int nfd = sigchld_notify_fd();
            fd_set rfds;
            struct timeval tv;
            struct timeval *tvp = NULL;
            /* 事件轮询兜底周期：部分内核/配置下 ptrace-stop 不产生 SIGCHLD
             * （详见上方 SIGCHLD 注释），REPL 空闲 select 将无法被信号唤醒。
             * attach 期间每 200ms 强制 WNOHANG 轮询一次 wait 队列，保证迟到/
             * 无主 stop 事件最迟一个周期内被消费放行，杜绝线程长期停在 't'。
             * 未 attach 时无事件源，保持无限阻塞。self-pipe 就绪时仍即时唤醒
             * （低延迟主路径），轮询仅是信号语义差异下的确定性兜底。 */
            if (g_dbg.is_attached) {
                tv.tv_sec  = 0;
                tv.tv_usec = 200 * 1000;   /* 200 ms */
                tvp = &tv;
            }
            FD_ZERO(&rfds);
            FD_SET(STDIN_FILENO, &rfds);
            FD_SET(nfd, &rfds);
            int sr = select(nfd + 1, &rfds, NULL, NULL, tvp);
            if (sr < 0) {
                if (errno == EINTR) {   /* SIGALRM 空闲超时 / Ctrl-C 取消输入行 */
                    if (g_idle_timeout) {
                        printf("\nNo command for %d seconds, auto exiting.\n",
                               g_idle_timeout_sec);
                        goto repl_quit;
                    }
                    if (g_intr_requested) {
                        g_intr_requested = 0;
                        printf("\n");
                        reprompt = 1;   /* 出内层，回循环顶重新提示并重置超时 */
                        break;
                    }
                    continue;
                }
                printf("\n");           /* select 失败 */
                goto repl_quit;
            }
            if (FD_ISSET(nfd, &rfds)) {
                /* tracee 事件通知：消费 wait 队列中全部停止事件（fork/clone
                 * 放行、无主 stop 恢复运行），再读空管道复位水平触发 */
                process_child_events();
                sigchld_notify_drain();
                continue;
            }
            if (sr == 0) {
                /* select 超时：轮询兜底消费 wait 队列中滞留的停止事件
                 * （无信号唤醒场景下的确定性放行，见上方注释） */
                process_child_events();
                continue;
            }
            if (FD_ISSET(STDIN_FILENO, &rfds)) {
                if (fgets(cmd_line, sizeof(cmd_line), stdin))
                    break;              /* 读到一行输入 */

                if (g_idle_timeout) {
                    printf("\nNo command for %d seconds, auto exiting.\n",
                           g_idle_timeout_sec);
                    goto repl_quit;
                }
                printf("\n");           /* EOF / 读取错误 */
                goto repl_quit;
            }
        }

        if (reprompt) continue;
        alarm(0);                       /* 输入到达，取消超时定时 */

        size_t len = strlen(cmd_line);
        if (len > 0 && cmd_line[len - 1] == '\n') cmd_line[len - 1] = '\0';

        log_user_command(cmd_line);

        int rc = exec_command(cmd_line);
        if (rc == 1) break;
    }

repl_quit:

    if (g_dbg.is_attached) do_detach();
    printf("Goodbye.\n");
    return 0;
}
