/* spl_cli.c SIR VM launcher + gdb-style interactive debugger * * Usage: spl_cli [options] [args...] * -d, --debug stack-canary checks * -g, --debug-cli interactive debugger (REPL) * --entry entry function (default: main) * --trace print every instruction * -h, --help show help * * spl 程序 argv:argv[0]=file.sir,argv[1..]=args。 * REPL 配合 splc0 -g(.sir 尾部内嵌 debug 段:IR 行 + VAR 行)可显示 * 当前 IR 节点/源行/局部变量。 */ #include "spl_mcode.h" #include "spl_syscall.h" #include "spl_vm.h" #include #include #include #include /* ================================================================ * 命令行解析 * ================================================================ */ typedef struct { int debug; int debug_cli; int trace; const char *entry; const char *path; /* file.sir */ const char **args; /* spl 程序参数(file.sir 之后) */ int nargs; } cli_opts_t; static void usage(void) { fprintf(stderr, "Usage: spl_cli [options] [args...]\n" " -d, --debug stack-canary checks\n" " -g, --debug-cli interactive debugger (REPL)\n" " --entry entry function (default: main)\n" " --trace print every instruction\n" " -h, --help show help\n"); } /* options 可穿插;`--` 终止 option 解析(本身不作为 file/args)。 * 第一个非 option 为 file.sir,其后为 spl args。 */ static int parse_args(int argc, const char **argv, cli_opts_t *o) { memset(o, 0, sizeof *o); o->entry = "main"; int i = 1; for (; i < argc; i++) { const char *a = argv[i]; if (a[0] == '-' && a[1]) { if (strcmp(a, "--") == 0) { i++; break; } if (strcmp(a, "-d") == 0 || strcmp(a, "--debug") == 0) { o->debug = 1; } else if (strcmp(a, "-g") == 0 || strcmp(a, "--debug-cli") == 0) { o->debug_cli = 1; } else if (strcmp(a, "--trace") == 0) { o->trace = 1; } else if (strcmp(a, "--entry") == 0) { if (i + 1 >= argc) { fprintf(stderr, "spl_cli: --entry needs a name\n"); return -1; } o->entry = argv[++i]; } else if (strcmp(a, "-h") == 0 || strcmp(a, "--help") == 0) { usage(); return -1; } else { fprintf(stderr, "spl_cli: unknown option '%s'\n", a); usage(); return -1; } } else { break; } } /* 跳过前导 `--`(pipeline 调用会带 `--` 分隔 file 与 args) */ while (i < argc && strcmp(argv[i], "--") == 0) i++; if (i >= argc) { fprintf(stderr, "spl_cli: missing \n"); usage(); return -1; } o->path = argv[i++]; while (i < argc && strcmp(argv[i], "--") == 0) i++; o->args = argv + i; o->nargs = argc - i; return 0; } /* ================================================================ * Debug map(splc0 -g 追加在 .sir 尾部的文本段) * IR * VAR * ================================================================ */ typedef struct { usize ip; usize ref; int line; char kind[64]; } dbg_irline_t; typedef VEC(dbg_irline_t) dbg_irline_vec_t; typedef struct { char func[128]; char name[128]; usize offset; usize tid; int is_param; } dbg_var_t; typedef VEC(dbg_var_t) dbg_var_vec_t; typedef struct { dbg_irline_vec_t irlines; dbg_var_vec_t vars; int loaded; } dbg_map_t; static void dbg_map_load(dbg_map_t *m, const char *text) { memset(m, 0, sizeof *m); vec_init(m->irlines); vec_init(m->vars); m->loaded = 0; if (!text) return; const char *p = strstr(text, "SPLDBG"); if (!p) return; p += 6; while (p && *p) { const char *nl = strchr(p, '\n'); size_t len = nl ? (size_t)(nl - p) : strlen(p); char line[512]; if (len >= sizeof line) len = sizeof line - 1; memcpy(line, p, len); line[len] = 0; if (strncmp(line, "IR ", 3) == 0) { dbg_irline_t ir; memset(&ir, 0, sizeof ir); sscanf(line + 3, "%zu %zu %d %63s", &ir.ip, &ir.ref, &ir.line, ir.kind); vec_push(m->irlines, ir); } else if (strncmp(line, "VAR ", 4) == 0) { dbg_var_t v; memset(&v, 0, sizeof v); sscanf(line + 4, "%127s %127s %zu %zu %d", v.func, v.name, &v.offset, &v.tid, &v.is_param); vec_push(m->vars, v); } if (!nl) break; p = nl + 1; } m->loaded = 1; } /* 当前 ip 所属 IR 节点行(ip 恰好是某节点首指令) */ static const dbg_irline_t *irline_at_exact(dbg_map_t *m, usize ip) { for (usize i = 0; i < m->irlines.size; i++) if (m->irlines.data[i].ip == ip) return &m->irlines.data[i]; return NULL; } /* ================================================================ * 调试器核心 * ================================================================ */ typedef struct { spl_vm_t *vm; spl_prog_t *prog; const char *entry; dbg_map_t map; usize last_ptr; /* p 最近读到的值(x 无参重读) */ int quit; } dbg_t; /* ---- 位置/指令显示辅助 ---- */ static const char *fn_name_at(spl_prog_t *prog, usize ip) { for (usize i = 0; i < vec_size(prog->funcs); i++) { spl_vm_func_t *f = &vec_at(prog->funcs, i); if (ip >= f->address && ip < f->address + f->ninsns) return f->name ? f->name : "?"; } return "?"; } /* ip 所在函数的指令范围 [*start, *end) */ static void cur_func_bounds(spl_prog_t *prog, usize ip, usize *start, usize *end) { *start = 0; *end = vec_size(prog->insns); for (usize i = 0; i < vec_size(prog->funcs); i++) { spl_vm_func_t *f = &vec_at(prog->funcs, i); if (ip >= f->address && ip < f->address + f->ninsns) { *start = f->address; *end = f->address + f->ninsns; return; } } } /* 格式化一条指令(不含行号前缀) */ static void fmt_insn(const spl_vm_ins_t *ins, char *buf, size_t n) { snprintf(buf, n, "%-11s %-5s %zu", spl_vm_opcode_name(ins->opcode), spl_vm_type_kind_name(ins->type), ins->imm); } static void dbg_show_location(dbg_t *d) { spl_vm_t *vm = d->vm; if (vm->ip >= vec_size(vm->prog->insns)) { printf(" (end of program)\n"); return; } char ib[80]; fmt_insn(&vec_at(vm->prog->insns, vm->ip), ib, sizeof ib); printf(" %s @ ip=%zu %s [sp=%zu fp=%zu cp=%zu]\n", fn_name_at(vm->prog, vm->ip), vm->ip, ib, vm->sp, vm->fp, vm->cp); const dbg_irline_t *ir = irline_at_exact(&d->map, vm->ip); if (ir) printf(" -> ir node#%zu line %d %s\n", ir->ref, ir->line, ir->kind); } static void dbg_handle_status(dbg_t *d, int r) { if (r == 1) { printf(" program halted, exit_code=%d\n", d->vm->exit_code); d->quit = 1; } else if (r == 2) { printf(" breakpoint hit\n"); } else if (r == -1) { printf(" VM error: %s\n", d->vm->error_msg[0] ? d->vm->error_msg : "(no detail)"); d->quit = 1; } } /* ---- 变量查找(p / x 复用) ---- */ static dbg_var_t *dbg_find_var(dbg_t *d, const char *fn, const char *name) { for (usize i = 0; i < d->map.vars.size; i++) { dbg_var_t *v = &d->map.vars.data[i]; if (strcmp(v->func, fn) == 0 && strcmp(v->name, name) == 0) return v; } return NULL; } /* ================================================================ * 命令 * ================================================================ */ static int cmd_help(dbg_t *d, const char *args); static int cmd_run(dbg_t *d, const char *args); static int cmd_step(dbg_t *d, const char *args); static int cmd_next(dbg_t *d, const char *args); static int cmd_finish(dbg_t *d, const char *args); static int cmd_break(dbg_t *d, const char *args); static int cmd_delete(dbg_t *d, const char *args); static int cmd_info(dbg_t *d, const char *args); static int cmd_list(dbg_t *d, const char *args); static int cmd_backtrace(dbg_t *d, const char *args); static int cmd_stack(dbg_t *d, const char *args); static int cmd_x(dbg_t *d, const char *args); static int cmd_p(dbg_t *d, const char *args); static int cmd_quit(dbg_t *d, const char *args); typedef struct { const char *name; const char *alias; const char *usage; const char *help; int (*fn)(dbg_t *, const char *); } dbg_cmd_t; static const dbg_cmd_t cmds[] = { /* 会话 */ {"help", "h", "[cmd]", "list commands / help", cmd_help}, {"quit", "q", "", "quit debugger", cmd_quit}, /* 执行 */ {"run", "r", "", "run to next breakpoint or exit", cmd_run}, {"continue", "c", "", "alias of run", cmd_run}, {"step", "s", "", "step one instruction (into calls)", cmd_step}, {"next", "n", "", "step over a call", cmd_next}, {"finish", "f", "", "run until current function returns", cmd_finish}, /* 断点 */ {"break", "b", "", "set breakpoint (no arg: list)", cmd_break}, {"delete", "d", "", "clear all breakpoints", cmd_delete}, /* 信息 */ {"info", "i", "b|stack|locals|args|var", "breakpoints/stack/locals", cmd_info}, {"list", "l", "[N]", "disassemble ip±N of current function", cmd_list}, {"backtrace", "bt", "", "print call stack backtrace", cmd_backtrace}, {"stack", "st", "", "dump stack words", cmd_stack}, /* 内存/变量 */ {"x", "x", "", "read memory (no arg: last pointer)", cmd_x}, {"print", "p", "", "print local variable (needs splc0 -g)", cmd_p}, {NULL, NULL, NULL, NULL, NULL}, }; static const dbg_cmd_t *find_cmd(const char *name) { for (const dbg_cmd_t *c = cmds; c->name; c++) if (!strcmp(c->name, name) || (c->alias && !strcmp(c->alias, name))) return c; return NULL; } static int cmd_help(dbg_t *d, const char *args) { (void)d; if (*args) { const dbg_cmd_t *c = find_cmd(args); if (c) { printf(" %-10s %-22s %s", c->name, c->usage, c->help); if (c->alias) printf(" (alias: %s)", c->alias); printf("\n"); } else { printf(" unknown command '%s'\n", args); } return 0; } printf("commands:\n"); for (const dbg_cmd_t *c = cmds; c->name; c++) { printf(" %-10s %-22s %s", c->name, c->usage, c->help); if (c->alias) printf(" (alias: %s)", c->alias); printf("\n"); } return 0; } /* ---- 执行 ---- */ static int ip_is_breakpoint(dbg_t *d) { for (usize i = 0; i < vec_size(d->vm->breakpoints); i++) if (d->vm->breakpoints.data[i] == (usize)d->vm->ip) return 1; return 0; } static int cmd_run(dbg_t *d, const char *args) { (void)args; if (ip_is_breakpoint(d)) /* 越过当前断点指令,避免原地重停 */ spl_vm_skip_breakpoint(d->vm); dbg_handle_status(d, spl_vm_run_until(d->vm, 0)); return 0; } static int cmd_step(dbg_t *d, const char *args) { (void)args; dbg_handle_status(d, spl_vm_run_once(d->vm)); return 0; } static int cmd_next(dbg_t *d, const char *args) { (void)args; /* 执行 1 条;若进入被调(cp 增),继续执行到返回原深度 */ usize base_cp = d->vm->cp; int r = spl_vm_run_once(d->vm); if (r) return dbg_handle_status(d, r), 0; while (d->vm->cp > base_cp) { r = spl_vm_run_once(d->vm); if (r) return dbg_handle_status(d, r), 0; } return 0; } static int cmd_finish(dbg_t *d, const char *args) { (void)args; usize base_cp = d->vm->cp; while (d->vm->cp >= base_cp) { int r = spl_vm_run_once(d->vm); if (r) return dbg_handle_status(d, r), 0; } return 0; } /* ---- 断点 ---- */ static int is_all_digits(const char *s) { if (!*s) return 0; for (; *s; s++) if (!isdigit((unsigned char)*s)) return 0; return 1; } static int cmd_break(dbg_t *d, const char *args) { if (!*args) { printf(" ip breakpoints:\n"); for (usize i = 0; i < vec_size(d->vm->breakpoints); i++) printf(" %zu\n", d->vm->breakpoints.data[i]); printf(" fn breakpoints:\n"); for (usize i = 0; i < vec_size(d->vm->fn_breakpoints); i++) printf(" %s\n", d->vm->fn_breakpoints.data[i]); return 0; } while (*args && isspace((unsigned char)*args)) args++; if (is_all_digits(args)) { usize ip = (usize)strtoul(args, NULL, 10); spl_vm_add_breakpoint(d->vm, ip); printf(" breakpoint at ip=%zu\n", ip); } else { spl_vm_add_breakpoint_fn(d->vm, args); printf(" breakpoint at function '%s'\n", args); } return 0; } static int cmd_delete(dbg_t *d, const char *args) { (void)args; spl_vm_clear_breakpoints(d->vm); printf(" breakpoints cleared\n"); return 0; } /* ---- 信息 ---- */ /* 列出当前函数变量:args_flag=1 列参数,local_flag=1 列局部 */ static void list_vars(dbg_t *d, int args_flag, int local_flag) { if (!d->map.loaded) { printf(" no debug info (compile with splc0 -g)\n"); return; } const char *fn = fn_name_at(d->prog, d->vm->ip); int any = 0; for (usize i = 0; i < d->map.vars.size; i++) { dbg_var_t *v = &d->map.vars.data[i]; if (strcmp(v->func, fn) != 0) continue; if ((v->is_param && !args_flag) || (!v->is_param && !local_flag)) continue; printf(" %-20s @ fp+%-5zu %s\n", v->name, v->offset, v->is_param ? "[param]" : "[local]"); any = 1; } if (!any) printf(" (no matching variables in %s)\n", fn); } static int cmd_info(dbg_t *d, const char *args) { while (*args && isspace((unsigned char)*args)) args++; if (!strncmp(args, "b", 1) || !strncmp(args, "break", 5)) return cmd_break(d, ""); if (!strncmp(args, "st", 2) || !strncmp(args, "stack", 5)) spl_vm_stackdump(d->vm, d->vm->sp); else if (!strncmp(args, "l", 1) || !strncmp(args, "local", 5)) list_vars(d, 0, 1); else if (!strncmp(args, "a", 1) || !strncmp(args, "arg", 3)) list_vars(d, 1, 0); else if (!strncmp(args, "v", 1) || !strncmp(args, "var", 3)) list_vars(d, 1, 1); else printf(" usage: info b|stack|locals|args|var\n"); return 0; } /* l [N]:当前函数内 ip±N 反汇编 */ static int cmd_list(dbg_t *d, const char *args) { usize ip = d->vm->ip; long half = 5; if (*args) half = strtol(args, NULL, 10); if (half < 0) half = 0; usize fstart, fend; cur_func_bounds(d->prog, ip, &fstart, &fend); usize lo = (ip > (usize)half) ? ip - (usize)half : fstart; usize hi = ip + (usize)half; if (hi >= fend) hi = fend - 1; if (lo < fstart) lo = fstart; for (usize i = lo; i <= hi; i++) { char ib[80]; fmt_insn(&vec_at(d->prog->insns, i), ib, sizeof ib); printf("%s%6zu: %s", i == ip ? "=>" : " ", i, ib); const dbg_irline_t *ir = irline_at_exact(&d->map, i); if (ir) printf(" || node#%zu line %d %s", ir->ref, ir->line, ir->kind); printf("\n"); } return 0; } static int cmd_backtrace(dbg_t *d, const char *args) { (void)args; spl_vm_backtrace(d->vm, d->vm->fp); return 0; } static int cmd_stack(dbg_t *d, const char *args) { (void)args; spl_vm_stackdump(d->vm, d->vm->sp); return 0; } /* ---- 内存 / 变量 ---- */ static int cmd_x(dbg_t *d, const char *args) { while (*args && isspace((unsigned char)*args)) args++; unsigned long long addr = 0; if (*args) { if (strncmp(args, "fp+", 3) == 0 || strncmp(args, "sp+", 3) == 0) { int is_fp = args[0] == 'f'; unsigned long long off = strtoull(args + 3, NULL, 0); addr = (uintptr_t)((char *)&d->vm->stacks.data[is_fp ? d->vm->fp : d->vm->sp] + off); printf(" (%s+%llu)\n", is_fp ? "fp" : "sp", off); } else if (d->map.loaded && !isdigit((unsigned char)*args)) { const char *fn = fn_name_at(d->prog, d->vm->ip); dbg_var_t *v = dbg_find_var(d, fn, args); if (v) { addr = (uintptr_t)((char *)&d->vm->stacks.data[d->vm->fp] + v->offset); printf(" (%s @ fp+%zu)\n", v->name, v->offset); } else { printf(" no variable '%s' in %s\n", args, fn); } } else { addr = strtoull(args, NULL, 0); } } else { addr = (unsigned long long)d->last_ptr; } if (!addr) { printf(" usage: x (no arg: re-read last printed pointer)\n"); return 0; } unsigned char *sbase = (unsigned char *)d->vm->stacks.data; usize slen = d->vm->config.max_stack_depth * sizeof(spl_vm_val_t); printf(" addr=%#llx stack=[%p,+%zu) in=%d\n", addr, sbase, slen, addr >= (uintptr_t)sbase && addr < (uintptr_t)(sbase + slen)); unsigned char *p = (unsigned char *)(uintptr_t)addr; printf(" "); for (int i = 0; i < 32; i++) printf("%02x ", p[i]); printf("\n"); return 0; } static int cmd_p(dbg_t *d, const char *args) { while (*args && isspace((unsigned char)*args)) args++; if (!*args) { printf(" usage: p \n"); return 0; } if (!d->map.loaded) { printf(" no debug info (compile with splc0 -g)\n"); return 0; } const char *fn = fn_name_at(d->prog, d->vm->ip); dbg_var_t *v = dbg_find_var(d, fn, args); if (!v) { printf(" no variable '%s' in %s\n", args, fn); return 0; } spl_vm_val_t *addr = (spl_vm_val_t *)((char *)&d->vm->stacks.data[d->vm->fp] + v->offset); printf(" %s = %zu (0x%zx) @ fp+%zu\n", v->name, *addr, *addr, v->offset); d->last_ptr = (usize)*addr; /* 供 x 无参重读 */ return 0; } static int cmd_quit(dbg_t *d, const char *args) { (void)args; d->quit = 1; return 0; } static void repl_loop(dbg_t *d) { char line[1024]; while (!d->quit) { dbg_show_location(d); printf("spl-dbg> "); fflush(stdout); if (!fgets(line, sizeof line, stdin)) break; char *nl = strchr(line, '\n'); if (nl) *nl = 0; char *p = line; while (*p && isspace((unsigned char)*p)) p++; if (!*p) continue; /* 拆命令名 + 参数 */ char *sp = p; while (*sp && !isspace((unsigned char)*sp)) sp++; char saved = *sp; *sp = 0; const dbg_cmd_t *c = find_cmd(p); *sp = saved; char *args = sp; while (*args && isspace((unsigned char)*args)) args++; if (c) { c->fn(d, args); } else { printf(" unknown command '%s' (h for help)\n", p); } } } int main(int argc, const char **argv) { cli_opts_t o; if (parse_args(argc, argv, &o) != 0) return 1; spl_prog_t prog; if (spl_prog_load_from_file(o.path, &prog) != 0) { fprintf(stderr, "spl_cli: cannot load '%s'\n", o.path); return 1; } spl_syscall_register(&prog); spl_vm_t vm; spl_vm_init(&vm); if (o.debug) spl_vm_set_debug(&vm, 1); if (o.trace) spl_vm_set_trace(&vm, 1); if (spl_vm_load_prog(&vm, &prog) != 0) { fprintf(stderr, "spl_cli: failed to load prog\n"); spl_vm_drop(&vm); spl_prog_drop(&prog); return 1; } /* spl 程序 argv:argv[0]=file.sir(程序名),argv[1..]=args */ const char **spl_argv = (const char **)malloc(sizeof(char *) * (size_t)(o.nargs + 1)); if (!spl_argv) { spl_vm_drop(&vm); spl_prog_drop(&prog); return 1; } spl_argv[0] = o.path; for (int i = 0; i < o.nargs; i++) spl_argv[i + 1] = o.args[i]; if (spl_vm_prepare(&vm, o.entry, o.nargs + 1, spl_argv, NULL) != 0) { fprintf(stderr, "spl_cli: entry point '%s' not found\n", o.entry); free(spl_argv); spl_vm_drop(&vm); spl_prog_drop(&prog); return 1; } int ret = 0; if (o.debug_cli) { dbg_t d; d.vm = &vm; d.prog = &prog; d.entry = o.entry; d.quit = 0; d.last_ptr = 0; dbg_map_load(&d.map, prog.debug); repl_loop(&d); vec_free(d.map.irlines); vec_free(d.map.vars); } else { ret = spl_vm_run_until(&vm, 0); if (ret == 2) fprintf(stderr, "spl_cli: hit breakpoint (run with -g to debug)\n"); } free(spl_argv); spl_vm_drop(&vm); spl_prog_drop(&prog); if (ret < 0) return 1; return (int)vm.exit_code; }