commit f8a6d1101582182af525f54ad681e7467a83014b parent c13664a12083de13e646f1d51ceae9788c89e71a Author: Ryan Sepassi <rsepassi@gmail.com> Date: Tue, 9 Jun 2026 15:58:44 -0700 dbg: add --script / --command / --batch for non-interactive use --script FILE feeds debugger commands from a file before stdin. --command CMD / -c CMD injects a single inline command. Both are repeatable and order-preserving. --batch suppresses the banner, exits after all sources drain without falling through to stdin, and returns exit 1 when any command sets an error (symbol not found, bad address, etc.). Error tracking is via a dbg_errf(s, ...) macro that wraps driver_errf and increments s->error_count; replaces driver_errf(DBG_TOOL, ...) at the ~115 dispatch/command call sites that have DbgState* s in scope. Script files are loaded through driver_load_bytes (no direct libc I/O). test/dbg/run.sh gains expected_rc support: an optional file whose contents override the implicit "must exit 0" requirement. Five new golden cases: batch-command, batch-script-file, batch-script-interactive, batch-error-exit, batch-command-order. All 29 pass + 1 xfail unchanged. Diffstat:
27 files changed, 333 insertions(+), 121 deletions(-)
diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c @@ -49,6 +49,12 @@ * argv parsing (mirrors run.c) * ============================================================ */ +typedef enum { DBG_ENTRY_FILE, DBG_ENTRY_CMD } DbgEntryKind; +typedef struct { + DbgEntryKind kind; + const char* value; /* argv pointer — no per-entry allocation */ +} DbgScriptEntry; + typedef struct DbgOpts { DriverEnv* env; size_t argv_bound; @@ -64,6 +70,11 @@ typedef struct DbgOpts { char** prog_argv; uint32_t prog_argc; + + DbgScriptEntry* script_entries; + uint32_t nscript_entries; + uint32_t script_entries_cap; + int batch_mode; } DbgOpts; void driver_help_dbg(void) { @@ -139,6 +150,16 @@ void driver_help_dbg(void) { " info functions [PATTERN] list JIT functions matching PATTERN\n" " info variables [PATTERN] list JIT globals matching PATTERN\n" "\n" + "BATCH / SCRIPTING\n" + " --script FILE execute debugger commands from FILE (repeatable)\n" + " --command CMD execute CMD as if typed at the REPL (repeatable)\n" + " -c CMD same as --command\n" + " --batch non-interactive: suppress banner, exit after\n" + " --script / --command sources drain; exit 1 on\n" + " any command error\n" + " Note: multi-line jit{} / expr{} blocks in --script files must fit\n" + " on a single line (continuation reads go to stdin, not the file).\n" + "\n" "SIGNALS\n" " Ctrl-C is forwarded into the running session as an interrupt; at\n" " the REPL prompt it terminates the program normally.\n" @@ -151,7 +172,7 @@ void driver_help_dbg(void) { "commands)\n" "\n" "EXIT CODES\n" - " 0 clean exit 1 compile/link or session error 2 " + " 0 clean exit 1 compile/link/command error 2 " "bad " "usage\n"))); } @@ -196,6 +217,26 @@ static int dbg_set_default_language(DbgOpts* o, const char* name) { return 0; } +static int dbg_script_entry_push(DbgOpts* o, DbgEntryKind kind, + const char* value) { + if (o->nscript_entries >= o->script_entries_cap) { + uint32_t nc = o->script_entries_cap ? o->script_entries_cap * 2 : 4; + size_t old_sz = (size_t)o->script_entries_cap * sizeof(DbgScriptEntry); + size_t new_sz = (size_t)nc * sizeof(DbgScriptEntry); + DbgScriptEntry* nb = o->env->heap->realloc(o->env->heap, + o->script_entries, + old_sz, new_sz, + _Alignof(DbgScriptEntry)); + if (!nb) { driver_errf(DBG_TOOL, "out of memory"); return 1; } + o->script_entries = nb; + o->script_entries_cap = nc; + } + o->script_entries[o->nscript_entries].kind = kind; + o->script_entries[o->nscript_entries].value = value; + o->nscript_entries++; + return 0; +} + static int dbg_parse(int argc, char** argv, DbgOpts* o) { int i; int after_dash_dash = 0; @@ -265,6 +306,28 @@ static int dbg_parse(int argc, char** argv, DbgOpts* o) { continue; } + if (driver_streq(a, "--script")) { + if (++i >= argc) { + driver_errf(DBG_TOOL, "--script requires a FILE argument"); + return 1; + } + if (dbg_script_entry_push(o, DBG_ENTRY_FILE, argv[i]) != 0) return 1; + continue; + } + if (driver_streq(a, "--command") || driver_streq(a, "-c")) { + if (++i >= argc) { + driver_errf(DBG_TOOL, "%.*s requires a CMD argument", + KIT_SLICE_ARG(kit_slice_cstr(a))); + return 1; + } + if (dbg_script_entry_push(o, DBG_ENTRY_CMD, argv[i]) != 0) return 1; + continue; + } + if (driver_streq(a, "--batch")) { + o->batch_mode = 1; + continue; + } + if (a[0] == '-' && a[1] != '\0') { driver_errf(DBG_TOOL, "unknown flag: %.*s", KIT_SLICE_ARG(kit_slice_cstr(a))); @@ -298,6 +361,9 @@ static void dbg_options_release(DbgOpts* o) { driver_inputs_release(&o->inputs); driver_cflags_fini(&o->cf, o->env); driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv)); + if (o->script_entries) + driver_free(o->env, o->script_entries, + (size_t)o->script_entries_cap * sizeof(DbgScriptEntry)); } /* Compile every C source through a compiler owned by the caller and JIT-link @@ -406,8 +472,18 @@ typedef struct DbgState { KitStopInfo last_stop; uint64_t jit_counter; DriverLineHistory line_history; + + DbgScriptEntry* script_entries; /* transferred from DbgOpts */ + uint32_t nscript_entries; + int batch_mode; + int error_count; } DbgState; +/* Like driver_errf but increments s->error_count so --batch can propagate + * command failures to the exit code. */ +#define dbg_errf(s, ...) \ + (driver_errf(DBG_TOOL, __VA_ARGS__), (void)((s)->error_count++)) + #define DBG_LIST_CTX 5 /* lines printed before/after the target */ /* SIGINT trampoline. The handler in env.c calls our cb with this state; @@ -529,7 +605,7 @@ static Bp* dbg_bp_grow(DbgState* s) { nb = s->env->heap->realloc(s->env->heap, s->bps, old_size, new_size, _Alignof(Bp)); if (!nb) { - driver_errf(DBG_TOOL, "out of memory growing breakpoint table"); + dbg_errf(s, "out of memory growing breakpoint table"); return NULL; } /* Zero the freshly grown tail so future driver_free walks it cleanly. */ @@ -649,24 +725,24 @@ static int dbg_resolve_loc(DbgState* s, const char* spec, BpKind* kind_out, uint64_t pc; if (file_n == 0) { - driver_errf(DBG_TOOL, "empty file in '%.*s'", + dbg_errf(s, "empty file in '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(spec))); return 1; } file = dbg_dup(s->env, spec, file_n, &file_size); if (!file) { - driver_errf(DBG_TOOL, "out of memory"); + dbg_errf(s, "out of memory"); return 1; } used = dbg_parse_uint(colon + 1, &line64); if (!used || colon[1 + used] != '\0') { - driver_errf(DBG_TOOL, "expected file.c:LINE, got '%.*s'", + dbg_errf(s, "expected file.c:LINE, got '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(spec))); driver_free(s->env, file, file_size); return 1; } if (!s->dwarf) { - driver_errf(DBG_TOOL, "no DWARF: cannot resolve %.*s", + dbg_errf(s, "no DWARF: cannot resolve %.*s", KIT_SLICE_ARG(kit_slice_cstr(spec))); driver_free(s->env, file, file_size); return 1; @@ -675,7 +751,7 @@ static int dbg_resolve_loc(DbgState* s, const char* spec, BpKind* kind_out, KitStatus rc = kit_dwarf_line_to_addr(s->dwarf, kit_slice_cstr(file), (uint32_t)line64, &pc); if (rc == KIT_NOT_FOUND) { - driver_errf(DBG_TOOL, "no line %u in %.*s", (uint32_t)line64, + dbg_errf(s, "no line %u in %.*s", (uint32_t)line64, KIT_SLICE_ARG(kit_slice_cstr(file))); driver_free(s->env, file, file_size); return 1; @@ -686,21 +762,21 @@ static int dbg_resolve_loc(DbgState* s, const char* spec, BpKind* kind_out, uint32_t k; kit_dwarf_line_to_addr_all(s->dwarf, kit_slice_cstr(file), (uint32_t)line64, cands, 8u, &n); - driver_errf(DBG_TOOL, "ambiguous: %.*s:%u matches %u files", + dbg_errf(s, "ambiguous: %.*s:%u matches %u files", KIT_SLICE_ARG(kit_slice_cstr(file)), (uint32_t)line64, (unsigned)n); for (k = 0; k < n && k < 8u; ++k) { - driver_errf(DBG_TOOL, " %.*s (0x%llx)", KIT_SLICE_ARG(cands[k].file), + dbg_errf(s, " %.*s (0x%llx)", KIT_SLICE_ARG(cands[k].file), (unsigned long long)cands[k].pc); } - if (n > 8u) driver_errf(DBG_TOOL, " ... and %u more", n - 8u); - driver_errf(DBG_TOOL, "use a longer path suffix (e.g. b dir/%.*s:%u)", + if (n > 8u) dbg_errf(s, " ... and %u more", n - 8u); + dbg_errf(s, "use a longer path suffix (e.g. b dir/%.*s:%u)", KIT_SLICE_ARG(kit_slice_cstr(file)), (uint32_t)line64); driver_free(s->env, file, file_size); return 1; } if (rc != KIT_OK) { - driver_errf(DBG_TOOL, "no line entry for %.*s", + dbg_errf(s, "no line entry for %.*s", KIT_SLICE_ARG(kit_slice_cstr(spec))); driver_free(s->env, file, file_size); return 1; @@ -718,7 +794,7 @@ static int dbg_resolve_loc(DbgState* s, const char* spec, BpKind* kind_out, uint64_t v; size_t used = dbg_parse_uint(spec, &v); if (!used || spec[used] != '\0') { - driver_errf(DBG_TOOL, "trailing junk in address '%.*s'", + dbg_errf(s, "trailing junk in address '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(spec))); return 1; } @@ -737,18 +813,18 @@ static int dbg_resolve_loc(DbgState* s, const char* spec, BpKind* kind_out, uint64_t off = 0; if (name_n == 0) { - driver_errf(DBG_TOOL, "empty symbol in '%.*s'", + dbg_errf(s, "empty symbol in '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(spec))); return 1; } name = dbg_dup(s->env, spec, name_n, &name_size); if (!name) { - driver_errf(DBG_TOOL, "out of memory"); + dbg_errf(s, "out of memory"); return 1; } resolved = kit_jit_lookup(s->jit, kit_slice_cstr(name)); if (!resolved) { - driver_errf(DBG_TOOL, "symbol not found: %.*s", + dbg_errf(s, "symbol not found: %.*s", KIT_SLICE_ARG(kit_slice_cstr(name))); driver_free(s->env, name, name_size); return 1; @@ -758,7 +834,7 @@ static int dbg_resolve_loc(DbgState* s, const char* spec, BpKind* kind_out, if (plus) { size_t used = dbg_parse_uint(plus + 1, &off); if (!used || plus[1 + used] != '\0') { - driver_errf(DBG_TOOL, "bad offset in '%.*s'", + dbg_errf(s, "bad offset in '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(spec))); return 1; } @@ -925,25 +1001,25 @@ static int dbg_drive(DbgState* s, DbgRunMode mode) { } s->has_stop = 0; } else if (mode != RUN_FRESH && !s->has_stop) { - driver_errf(DBG_TOOL, "no program is running; use 'r' to start"); + dbg_errf(s, "no program is running; use 'r' to start"); return 1; } if (mode == RUN_FRESH && !s->entry_addr) { if (!s->entry_name || !*s->entry_name) { - driver_errf(DBG_TOOL, "no entry symbol configured"); + dbg_errf(s, "no entry symbol configured"); return 1; } s->entry_addr = kit_jit_lookup(s->jit, kit_slice_cstr(s->entry_name)); if (!s->entry_addr) { - driver_errf(DBG_TOOL, "entry symbol not found: %.*s", + dbg_errf(s, "entry symbol not found: %.*s", KIT_SLICE_ARG(kit_slice_cstr(s->entry_name))); return 1; } } if (driver_install_sigint(dbg_on_sigint, s) != 0) { - driver_errf(DBG_TOOL, "failed to install SIGINT handler"); + dbg_errf(s, "failed to install SIGINT handler"); return 1; } @@ -1016,11 +1092,11 @@ static void dbg_cmd_bt(DbgState* s) { int level = 0; if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); + dbg_errf(s, "no program is stopped"); return; } if (!s->dwarf) { - driver_errf(DBG_TOOL, "no DWARF: backtrace unavailable"); + dbg_errf(s, "no DWARF: backtrace unavailable"); return; } @@ -1123,7 +1199,7 @@ static void dbg_cmd_bt(DbgState* s) { frame.cfa = next_fp + 2u * (uint64_t)ptr; } if (++level > 256) { - driver_errf(DBG_TOOL, "backtrace truncated at 256 frames"); + dbg_errf(s, "backtrace truncated at 256 frames"); break; } } @@ -1382,7 +1458,7 @@ static void dbg_cmd_print(DbgState* s, const char* name) { size_t got; if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); + dbg_errf(s, "no program is stopped"); return; } @@ -1396,7 +1472,7 @@ static void dbg_cmd_print(DbgState* s, const char* name) { dbg_translate_loc(s, &loc); if (dbg_read_value(s, &loc, &s->last_stop.regs, stack_buf, sizeof(stack_buf), &buf, &alloc, &got) != 0) { - driver_errf(DBG_TOOL, "could not read %.*s", + dbg_errf(s, "could not read %.*s", KIT_SLICE_ARG(kit_slice_cstr(name))); return; } @@ -1422,7 +1498,7 @@ static void dbg_cmd_print(DbgState* s, const char* name) { return; } } - driver_errf(DBG_TOOL, "no variable or symbol named '%.*s'", + dbg_errf(s, "no variable or symbol named '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(name))); } } @@ -1442,7 +1518,7 @@ static void dbg_cmd_set(DbgState* s, const char* name, uint64_t value) { size_t i; if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); + dbg_errf(s, "no program is stopped"); return; } { @@ -1452,7 +1528,7 @@ static void dbg_cmd_set(DbgState* s, const char* name, uint64_t value) { kit_slice_cstr(name), &loc) : KIT_NOT_FOUND; if (rc != KIT_OK) { - driver_errf(DBG_TOOL, "no variable named '%.*s'", + dbg_errf(s, "no variable named '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(name))); return; } @@ -1467,33 +1543,33 @@ static void dbg_cmd_set(DbgState* s, const char* name, uint64_t value) { uint64_t addr = s->last_stop.regs.cfa + (uint64_t)(int64_t)loc.v.frame_ofs; if (kit_jit_session_write_mem(s->session, addr, buf, sz) != KIT_OK) { - driver_errf(DBG_TOOL, "memory write failed"); + dbg_errf(s, "memory write failed"); } return; } case KIT_DLOC_GLOBAL: if (kit_jit_session_write_mem(s->session, loc.v.global, buf, sz) != KIT_OK) { - driver_errf(DBG_TOOL, "memory write failed"); + dbg_errf(s, "memory write failed"); } return; case KIT_DLOC_REG: { KitUnwindFrame fr = s->last_stop.regs; if (loc.v.reg >= 32) { - driver_errf(DBG_TOOL, "register %u outside the snapshot range", + dbg_errf(s, "register %u outside the snapshot range", loc.v.reg); return; } fr.regs[loc.v.reg] = value; if (kit_jit_session_set_regs(s->session, &fr) != KIT_OK) { - driver_errf(DBG_TOOL, "register write failed"); + dbg_errf(s, "register write failed"); return; } s->last_stop.regs = fr; return; } case KIT_DLOC_EXPR: - driver_errf(DBG_TOOL, "cannot set '%.*s': location is a DWARF expression", + dbg_errf(s, "cannot set '%.*s': location is a DWARF expression", KIT_SLICE_ARG(kit_slice_cstr(name))); return; } @@ -1508,13 +1584,13 @@ static void dbg_cmd_set(DbgState* s, const char* name, uint64_t value) { static void dbg_cmd_jump(DbgState* s, uint64_t pc) { KitUnwindFrame fr; if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); + dbg_errf(s, "no program is stopped"); return; } fr = s->last_stop.regs; fr.pc = pc; if (kit_jit_session_set_regs(s->session, &fr) != 0) { - driver_errf(DBG_TOOL, "jump failed (pc 0x%llx outside image?)", + dbg_errf(s, "jump failed (pc 0x%llx outside image?)", (unsigned long long)pc); return; } @@ -1532,11 +1608,11 @@ static void dbg_cmd_info_vars(DbgState* s, uint32_t mask, const char* label) { int printed = 0; if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); + dbg_errf(s, "no program is stopped"); return; } if (!s->dwarf) { - driver_errf(DBG_TOOL, "no DWARF: %.*s unavailable", + dbg_errf(s, "no DWARF: %.*s unavailable", KIT_SLICE_ARG(kit_slice_cstr(label))); return; } @@ -1576,11 +1652,11 @@ static void dbg_cmd_info_reg(DbgState* s) { uint32_t i; if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); + dbg_errf(s, "no program is stopped"); return; } if (n == 0) { - driver_errf(DBG_TOOL, "no register table for this arch"); + dbg_errf(s, "no register table for this arch"); return; } driver_printf("pc 0x%016llx\n", (unsigned long long)s->last_stop.regs.pc); @@ -1627,7 +1703,7 @@ static void dbg_cmd_info_syms(DbgState* s, KitSymKind want, int printed = 0; if (kit_jit_sym_iter_new(s->jit, &it) != KIT_OK) { - driver_errf(DBG_TOOL, "symbol enumeration unavailable"); + dbg_errf(s, "symbol enumeration unavailable"); return; } for (;;) { @@ -1952,12 +2028,12 @@ static int dbg_jit_compile_append_ex(DbgState* s, KitLanguage lang, dbg_jit_uses_default_name(s->compiler, lang, input_name)) { if (dbg_make_repl_source_name(s->compiler, lang, source_id, generated_name, sizeof(generated_name)) != 0) { - driver_errf(DBG_TOOL, "repl source name overflow"); + dbg_errf(s, "repl source name overflow"); return 1; } if (dbg_source_intern_name(s, kit_slice_cstr(generated_name), &effective_name) != 0) { - driver_errf(DBG_TOOL, "out of memory naming repl source"); + dbg_errf(s, "out of memory naming repl source"); return 1; } generated_source_name = 1; @@ -1976,7 +2052,7 @@ static int dbg_jit_compile_append_ex(DbgState* s, KitLanguage lang, if (st == KIT_OK) st = kit_compile_session_stage(session, &sin, &ob); if (st != KIT_OK || !ob) { if (ob) kit_obj_builder_free(ob); - driver_errf(DBG_TOOL, "jit compile failed"); + dbg_errf(s, "jit compile failed"); return 1; } { @@ -1997,7 +2073,7 @@ static int dbg_jit_compile_append_ex(DbgState* s, KitLanguage lang, * staged declarations so the frontend never advertises a symbol the JIT * image does not have. */ kit_compile_session_abort(session); - driver_errf(DBG_TOOL, "jit append failed"); + dbg_errf(s, "jit append failed"); return 1; } /* Published: make the staged declarations durable. */ @@ -2013,7 +2089,7 @@ static int dbg_jit_compile_append_ex(DbgState* s, KitLanguage lang, caps.cache_repl_toplevel_source; if (cache_source && input_kind == KIT_FRONTEND_INPUT_REPL_TOPLEVEL && dbg_source_cache_put(s, sin.name, src, len) != 0) { - driver_errf(DBG_TOOL, "out of memory caching source for list"); + dbg_errf(s, "out of memory caching source for list"); } } dbg_refresh_dwarf(s); @@ -2043,7 +2119,7 @@ static int dbg_parse_jit_lang_arg(DbgState* s, const char* rest, while (*p && !dbg_isspace((unsigned char)*p) && *p != '{') ++p; tag_n = (size_t)(p - tag); if (tag_n == 0 || tag_n >= sizeof(tag_buf)) { - driver_errf(DBG_TOOL, "language/name is too long"); + dbg_errf(s, "language/name is too long"); return 1; } driver_memcpy(tag_buf, tag, tag_n); @@ -2052,7 +2128,7 @@ static int dbg_parse_jit_lang_arg(DbgState* s, const char* rest, if (input_name == tag_buf && dbg_source_intern_name(s, kit_slice_cstr(input_name), &input_name) != 0) { - driver_errf(DBG_TOOL, "out of memory naming repl source"); + dbg_errf(s, "out of memory naming repl source"); return 1; } while (*p && dbg_isspace((unsigned char)*p)) ++p; @@ -2076,7 +2152,7 @@ static void dbg_cmd_jit(DbgState* s, const char* rest) { if (dbg_parse_jit_lang_arg(s, rest, &lang, &input_name, &p) != 0) return; if (*p != '{') { - driver_errf(DBG_TOOL, "usage: jit [c|toy|asm|name.ext] { ... }"); + dbg_errf(s, "usage: jit [c|toy|asm|name.ext] { ... }"); return; } ++p; @@ -2098,7 +2174,7 @@ static void dbg_cmd_jit(DbgState* s, const char* rest) { driver_flush_stdout(); n = driver_read_line(line, sizeof(line)); if (n <= 0) { - driver_errf(DBG_TOOL, "unterminated jit block"); + dbg_errf(s, "unterminated jit block"); goto out; } depth += dbg_brace_delta(line); @@ -2120,7 +2196,7 @@ static void dbg_cmd_jit(DbgState* s, const char* rest) { goto out; oom: - driver_errf(DBG_TOOL, "out of memory"); + dbg_errf(s, "out of memory"); out: if (src) driver_free(s->env, src, cap); } @@ -2135,18 +2211,18 @@ static void dbg_cmd_edit(DbgState* s, const char* rest) { if (dbg_parse_jit_lang_arg(s, rest, &lang, &input_name, &p) != 0) return; if (*p) { - driver_errf(DBG_TOOL, "usage: edit [c|toy|asm|name.ext]"); + dbg_errf(s, "usage: edit [c|toy|asm|name.ext]"); return; } if (!driver_edit_temp(s->env, dbg_jit_language_suffix(s->compiler, lang, suffix, sizeof suffix), NULL, 0, &src, &len)) { - driver_errf(DBG_TOOL, "editor failed"); + dbg_errf(s, "editor failed"); return; } if (len == 0) { - driver_errf(DBG_TOOL, "empty editor buffer; nothing appended"); + dbg_errf(s, "empty editor buffer; nothing appended"); goto out; } (void)dbg_jit_compile_append(s, lang, input_name, (const char*)src, len); @@ -2183,7 +2259,7 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { return; } if (n >= sizeof(tmp)) { - driver_errf(DBG_TOOL, "usage: :language c|toy|asm|wat|wasm"); + dbg_errf(s, "usage: :language c|toy|asm|wat|wasm"); return; } driver_memcpy(tmp, p, n); @@ -2191,7 +2267,7 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { lang = dbg_jit_language_for_tag(s, tmp, &name); (void)name; if (lang == KIT_LANG_COUNT) { - driver_errf(DBG_TOOL, "unsupported language: %.*s", + dbg_errf(s, "unsupported language: %.*s", KIT_SLICE_ARG(kit_slice_cstr(tmp))); return; } @@ -2230,13 +2306,13 @@ static int dbg_call_u64_entry(DbgState* s, void* entry, const uint64_t* args, KitStopInfo stop; if (driver_install_sigint(dbg_on_sigint, s) != 0) { - driver_errf(DBG_TOOL, "failed to install SIGINT handler"); + dbg_errf(s, "failed to install SIGINT handler"); return 1; } if (kit_jit_session_call_u64(s->session, entry, args, nargs, &ret, &stop) != KIT_OK) { driver_restore_sigint(); - driver_errf(DBG_TOOL, "call failed (debuggee must be idle or exited)"); + dbg_errf(s, "call failed (debuggee must be idle or exited)"); return 1; } driver_restore_sigint(); @@ -2266,7 +2342,7 @@ static void dbg_cmd_expr(DbgState* s, const char* expr) { while (*expr && dbg_isspace((unsigned char)*expr)) ++expr; if (!*expr) { - driver_errf(DBG_TOOL, "usage: expr EXPR | expr { STATEMENTS }"); + dbg_errf(s, "usage: expr EXPR | expr { STATEMENTS }"); return; } @@ -2290,7 +2366,7 @@ static void dbg_cmd_expr(DbgState* s, const char* expr) { driver_flush_stdout(); n = driver_read_line(line, sizeof(line)); if (n <= 0) { - driver_errf(DBG_TOOL, "unterminated expr block"); + dbg_errf(s, "unterminated expr block"); goto out; } depth += dbg_brace_delta(line); @@ -2320,12 +2396,12 @@ static void dbg_cmd_expr(DbgState* s, const char* expr) { attempt = ++s->expr_attempt; num_len = dbg_u64_dec(num, sizeof(num), attempt); if (!num_len) { - driver_errf(DBG_TOOL, "expression counter overflow"); + dbg_errf(s, "expression counter overflow"); goto out; } prefix_len = driver_strlen("__kit_dbg_expr_"); if (prefix_len + num_len + 1u > sizeof(name)) { - driver_errf(DBG_TOOL, "expression symbol too long"); + dbg_errf(s, "expression symbol too long"); goto out; } driver_memcpy(name, "__kit_dbg_expr_", prefix_len); @@ -2346,7 +2422,7 @@ static void dbg_cmd_expr(DbgState* s, const char* expr) { entry = kit_jit_lookup(s->jit, kit_slice_cstr(name)); if (!entry) { - driver_errf(DBG_TOOL, "expression thunk not found: %.*s", + dbg_errf(s, "expression thunk not found: %.*s", KIT_SLICE_ARG(kit_slice_cstr(name))); goto out; } @@ -2358,7 +2434,7 @@ static void dbg_cmd_expr(DbgState* s, const char* expr) { goto out; oom: - driver_errf(DBG_TOOL, "out of memory"); + dbg_errf(s, "out of memory"); out: if (body) driver_free(s->env, body, body_cap); } @@ -2374,7 +2450,7 @@ static void dbg_cmd_examine(DbgState* s, uint64_t addr, size_t count) { size_t remaining = count; if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); + dbg_errf(s, "no program is stopped"); return; } @@ -2382,7 +2458,7 @@ static void dbg_cmd_examine(DbgState* s, uint64_t addr, size_t count) { size_t chunk = remaining > sizeof(buf) ? sizeof(buf) : remaining; size_t i; if (kit_jit_session_read_mem(s->session, addr, buf, chunk) != KIT_OK) { - driver_errf(DBG_TOOL, "read failed at 0x%llx", (unsigned long long)addr); + dbg_errf(s, "read failed at 0x%llx", (unsigned long long)addr); return; } for (i = 0; i < chunk; i += 16) { @@ -2411,18 +2487,18 @@ static void dbg_cmd_disasm(DbgState* s, uint64_t addr, size_t count) { size_t shown = 0; if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); + dbg_errf(s, "no program is stopped"); return; } if (count == 0) count = 8; if (count > 32) { - driver_errf(DBG_TOOL, "disasm count too large"); + dbg_errf(s, "disasm count too large"); return; } byte_count = count * 16u; if (byte_count > sizeof(buf)) byte_count = sizeof(buf); if (kit_jit_session_read_mem(s->session, addr, buf, byte_count) != KIT_OK) { - driver_errf(DBG_TOOL, "read failed at 0x%llx", (unsigned long long)addr); + dbg_errf(s, "read failed at 0x%llx", (unsigned long long)addr); return; } @@ -2431,7 +2507,7 @@ static void dbg_cmd_disasm(DbgState* s, uint64_t addr, size_t count) { dctx.context = s->ctx; if (kit_disasm_iter_new(&dctx, buf, byte_count, addr, s->view, &it) != KIT_OK) { - driver_errf(DBG_TOOL, "disassembler unavailable"); + dbg_errf(s, "disassembler unavailable"); return; } while (shown < count) { @@ -2439,7 +2515,7 @@ static void dbg_cmd_disasm(DbgState* s, uint64_t addr, size_t count) { KitIterResult r = kit_disasm_iter_next(it, &insn); if (r == KIT_ITER_END) break; if (r != KIT_ITER_ITEM) { - driver_errf(DBG_TOOL, "disassembly failed"); + dbg_errf(s, "disassembly failed"); break; } driver_printf("0x%llx: %-8.*s", (unsigned long long)insn.vaddr, @@ -2498,10 +2574,9 @@ static void dbg_print_source_bytes(const uint8_t* data, size_t size, } ++p; } - if (report_errors && cur <= target) { + if (report_errors && cur <= target) driver_errf(DBG_TOOL, "file has only %u lines; %u requested", cur - 1u, target); - } } static void dbg_print_source_listing(DbgState* s, KitSlice file, uint32_t line, @@ -2513,7 +2588,7 @@ static void dbg_print_source_listing(DbgState* s, KitSlice file, uint32_t line, if (!file.s || file.len == 0) { if (report_errors) { - driver_errf(DBG_TOOL, "bad file in '%.*s'", KIT_SLICE_ARG(file)); + dbg_errf(s, "bad file in '%.*s'", KIT_SLICE_ARG(file)); } return; } @@ -2526,7 +2601,7 @@ static void dbg_print_source_listing(DbgState* s, KitSlice file, uint32_t line, if (file.len >= sizeof(path)) { if (report_errors) { - driver_errf(DBG_TOOL, "bad file in '%.*s'", KIT_SLICE_ARG(file)); + dbg_errf(s, "bad file in '%.*s'", KIT_SLICE_ARG(file)); } return; } @@ -2560,19 +2635,19 @@ static void dbg_cmd_list(DbgState* s, const char* spec) { KitSlice file; if (!s->dwarf) { - driver_errf(DBG_TOOL, "no DWARF: cannot resolve %.*s", + dbg_errf(s, "no DWARF: cannot resolve %.*s", KIT_SLICE_ARG(kit_slice_cstr(spec))); return; } colon = driver_strchr(spec, ':'); if (!colon || !dbg_isdigit((unsigned char)colon[1])) { - driver_errf(DBG_TOOL, "usage: list file:line"); + dbg_errf(s, "usage: list file:line"); return; } flen = (size_t)(colon - spec); if (flen == 0 || flen >= sizeof(path)) { - driver_errf(DBG_TOOL, "bad file in '%.*s'", + dbg_errf(s, "bad file in '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(spec))); return; } @@ -2580,7 +2655,7 @@ static void dbg_cmd_list(DbgState* s, const char* spec) { path[flen] = '\0'; used = dbg_parse_uint(colon + 1, &line_u); if (!used || colon[1 + used] != '\0') { - driver_errf(DBG_TOOL, "usage: list file:line"); + dbg_errf(s, "usage: list file:line"); return; } @@ -2589,19 +2664,19 @@ static void dbg_cmd_list(DbgState* s, const char* spec) { KitStatus st = kit_dwarf_line_to_addr(s->dwarf, kit_slice_cstr(path), (uint32_t)line_u, &pc); if (st == KIT_NOT_FOUND) { - driver_errf(DBG_TOOL, "no line %u in %.*s", (uint32_t)line_u, + dbg_errf(s, "no line %u in %.*s", (uint32_t)line_u, KIT_SLICE_ARG(kit_slice_cstr(path))); return; } if (st == KIT_AMBIGUOUS) { - driver_errf(DBG_TOOL, + dbg_errf(s, "ambiguous: %.*s:%u matches multiple files; " "use a longer path suffix", KIT_SLICE_ARG(kit_slice_cstr(path)), (uint32_t)line_u); return; } if (st != KIT_OK) { - driver_errf(DBG_TOOL, "no line entry for %.*s", + dbg_errf(s, "no line entry for %.*s", KIT_SLICE_ARG(kit_slice_cstr(spec))); return; } @@ -2641,7 +2716,7 @@ static void dbg_cmd_break(DbgState* s, const char* spec) { Bp* b; if (!*spec) { - driver_errf(DBG_TOOL, "usage: b <0xADDR | sym[+off] | file.c:line>"); + dbg_errf(s, "usage: b <0xADDR | sym[+off] | file.c:line>"); return; } if (dbg_resolve_loc(s, spec, &kind, &addr) != 0) return; @@ -2659,12 +2734,12 @@ static void dbg_cmd_break(DbgState* s, const char* spec) { b->addr = addr; b->spec = dbg_dup(s->env, spec, driver_strlen(spec), &b->spec_size); if (!b->spec) { - driver_errf(DBG_TOOL, "out of memory"); + dbg_errf(s, "out of memory"); return; } if (dbg_bp_arm(s, b) != 0) { - driver_errf(DBG_TOOL, "failed to arm breakpoint at 0x%llx", + dbg_errf(s, "failed to arm breakpoint at 0x%llx", (unsigned long long)addr); b->session_id = 0; b->enabled = 0; @@ -2698,7 +2773,7 @@ static void dbg_cmd_info_b(DbgState* s) { static void dbg_cmd_ignore(DbgState* s, int id, uint64_t count) { Bp* b = dbg_bp_find(s, id); if (!b) { - driver_errf(DBG_TOOL, "no breakpoint %d", id); + dbg_errf(s, "no breakpoint %d", id); return; } if (b->session_id) { @@ -2708,7 +2783,7 @@ static void dbg_cmd_ignore(DbgState* s, int id, uint64_t count) { b->skip_count = count; if (b->enabled) { if (dbg_bp_arm(s, b) != 0) { - driver_errf(DBG_TOOL, "failed to re-arm breakpoint %d", id); + dbg_errf(s, "failed to re-arm breakpoint %d", id); return; } } @@ -2718,19 +2793,19 @@ static void dbg_cmd_ignore(DbgState* s, int id, uint64_t count) { static void dbg_cmd_delete(DbgState* s, int id) { if (dbg_bp_remove(s, id) != 0) { - driver_errf(DBG_TOOL, "no breakpoint %d", id); + dbg_errf(s, "no breakpoint %d", id); } } static void dbg_cmd_set_enabled(DbgState* s, int id, int enable) { Bp* b = dbg_bp_find(s, id); if (!b) { - driver_errf(DBG_TOOL, "no breakpoint %d", id); + dbg_errf(s, "no breakpoint %d", id); return; } if (enable && !b->enabled) { if (dbg_bp_arm(s, b) != 0) { - driver_errf(DBG_TOOL, "failed to re-arm breakpoint %d", id); + dbg_errf(s, "failed to re-arm breakpoint %d", id); return; } b->enabled = 1; @@ -2864,9 +2939,9 @@ static int dbg_dispatch(DbgState* s, char* line) { if (s->has_stop && s->session) { kit_jit_session_resume(s->session, KIT_RESUME_ABORT, NULL); s->has_stop = 0; - driver_errf(DBG_TOOL, "execution aborted"); + dbg_errf(s, "execution aborted"); } else { - driver_errf(DBG_TOOL, "nothing to abort"); + dbg_errf(s, "nothing to abort"); } return 0; } @@ -2901,7 +2976,7 @@ static int dbg_dispatch(DbgState* s, char* line) { } if (driver_streq(cmd, "expr")) { if (!s->session) { - driver_errf(DBG_TOOL, "no JIT session"); + dbg_errf(s, "no JIT session"); return 0; } dbg_cmd_expr(s, rest); @@ -2939,7 +3014,7 @@ static int dbg_dispatch(DbgState* s, char* line) { dbg_take_word(rest, &pat); dbg_cmd_info_syms(s, KIT_SK_OBJ, *pat ? pat : NULL); } else { - driver_errf(DBG_TOOL, "unknown 'info' subcommand: %.*s", + dbg_errf(s, "unknown 'info' subcommand: %.*s", KIT_SLICE_ARG(kit_slice_cstr(what))); } return 0; @@ -2951,12 +3026,12 @@ static int dbg_dispatch(DbgState* s, char* line) { size_t used; rest = dbg_take_word(rest, &name); if (!*name) { - driver_errf(DBG_TOOL, "usage: set NAME VALUE"); + dbg_errf(s, "usage: set NAME VALUE"); return 0; } /* Accept an optional `=` between name and value: `set x = 5`. */ if (driver_streq(name, "=")) { - driver_errf(DBG_TOOL, "usage: set NAME VALUE"); + dbg_errf(s, "usage: set NAME VALUE"); return 0; } rest = dbg_take_word(rest, &val_s); @@ -2964,12 +3039,12 @@ static int dbg_dispatch(DbgState* s, char* line) { rest = dbg_take_word(rest, &val_s); } if (!*val_s) { - driver_errf(DBG_TOOL, "usage: set NAME VALUE"); + dbg_errf(s, "usage: set NAME VALUE"); return 0; } used = dbg_parse_uint(val_s, &v); if (!used || val_s[used] != '\0') { - driver_errf(DBG_TOOL, "expected integer value, got '%.*s'", + dbg_errf(s, "expected integer value, got '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(val_s))); return 0; } @@ -2982,12 +3057,12 @@ static int dbg_dispatch(DbgState* s, char* line) { size_t used; dbg_take_word(rest, &addr_s); if (!*addr_s) { - driver_errf(DBG_TOOL, "usage: jump ADDR"); + dbg_errf(s, "usage: jump ADDR"); return 0; } used = dbg_parse_uint(addr_s, &addr); if (!used || addr_s[used] != '\0') { - driver_errf(DBG_TOOL, "bad address '%.*s'", + dbg_errf(s, "bad address '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(addr_s))); return 0; } @@ -3003,17 +3078,17 @@ static int dbg_dispatch(DbgState* s, char* line) { rest = dbg_take_word(rest, &id_s); dbg_take_word(rest, &cnt_s); if (!*id_s || !*cnt_s) { - driver_errf(DBG_TOOL, "usage: ignore N COUNT"); + dbg_errf(s, "usage: ignore N COUNT"); return 0; } used = dbg_parse_uint(id_s, &id); if (!used || id_s[used] != '\0') { - driver_errf(DBG_TOOL, "expected breakpoint id"); + dbg_errf(s, "expected breakpoint id"); return 0; } used = dbg_parse_uint(cnt_s, &cnt); if (!used || cnt_s[used] != '\0') { - driver_errf(DBG_TOOL, "expected hit count"); + dbg_errf(s, "expected hit count"); return 0; } dbg_cmd_ignore(s, (int)id, cnt); @@ -3025,12 +3100,12 @@ static int dbg_dispatch(DbgState* s, char* line) { size_t used; dbg_take_word(rest, &arg); if (!*arg) { - driver_errf(DBG_TOOL, "usage: d <id>"); + dbg_errf(s, "usage: d <id>"); return 0; } used = dbg_parse_uint(arg, &id); if (!used || arg[used] != '\0') { - driver_errf(DBG_TOOL, "expected breakpoint id"); + dbg_errf(s, "expected breakpoint id"); return 0; } dbg_cmd_delete(s, (int)id); @@ -3043,7 +3118,7 @@ static int dbg_dispatch(DbgState* s, char* line) { dbg_take_word(rest, &arg); used = dbg_parse_uint(arg, &id); if (!used || arg[used] != '\0') { - driver_errf(DBG_TOOL, "expected breakpoint id"); + dbg_errf(s, "expected breakpoint id"); return 0; } dbg_cmd_set_enabled(s, (int)id, driver_streq(cmd, "enable")); @@ -3053,7 +3128,7 @@ static int dbg_dispatch(DbgState* s, char* line) { char* name; dbg_take_word(rest, &name); if (!*name) { - driver_errf(DBG_TOOL, "usage: p <name>"); + dbg_errf(s, "usage: p <name>"); return 0; } dbg_cmd_print(s, name); @@ -3063,7 +3138,7 @@ static int dbg_dispatch(DbgState* s, char* line) { char* loc; dbg_take_word(rest, &loc); if (!*loc) { - driver_errf(DBG_TOOL, "usage: list file:line"); + dbg_errf(s, "usage: list file:line"); return 0; } dbg_cmd_list(s, loc); @@ -3079,7 +3154,7 @@ static int dbg_dispatch(DbgState* s, char* line) { if (*addr_s) { used = dbg_parse_uint(addr_s, &addr); if (!used || addr_s[used] != '\0') { - driver_errf(DBG_TOOL, "bad address '%.*s'", + dbg_errf(s, "bad address '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(addr_s))); return 0; } @@ -3087,7 +3162,7 @@ static int dbg_dispatch(DbgState* s, char* line) { if (*count_s) { used = dbg_parse_uint(count_s, &count); if (!used || count_s[used] != '\0') { - driver_errf(DBG_TOOL, "bad count '%.*s'", + dbg_errf(s, "bad count '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(count_s))); return 0; } @@ -3104,12 +3179,12 @@ static int dbg_dispatch(DbgState* s, char* line) { size_t used; rest = dbg_take_word(rest, &addr_s); if (!*addr_s) { - driver_errf(DBG_TOOL, "usage: x <addr> [count]"); + dbg_errf(s, "usage: x <addr> [count]"); return 0; } used = dbg_parse_uint(addr_s, &addr); if (!used || addr_s[used] != '\0') { - driver_errf(DBG_TOOL, "bad address '%.*s'", + dbg_errf(s, "bad address '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(addr_s))); return 0; } @@ -3117,7 +3192,7 @@ static int dbg_dispatch(DbgState* s, char* line) { if (*count_s) { used = dbg_parse_uint(count_s, &count); if (!used || count_s[used] != '\0') { - driver_errf(DBG_TOOL, "bad count '%.*s'", + dbg_errf(s, "bad count '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(count_s))); return 0; } @@ -3134,7 +3209,7 @@ static int dbg_dispatch(DbgState* s, char* line) { if (s->session) { dbg_cmd_expr(s, raw); } else { - driver_errf(DBG_TOOL, "unknown command: %.*s (try 'h')", + dbg_errf(s, "unknown command: %.*s (try 'h')", KIT_SLICE_ARG(kit_slice_cstr(cmd))); } return 0; @@ -3395,12 +3470,75 @@ static void dbg_complete(void* user, const char* line, size_t cursor, } /* ============================================================ + * Script source helpers + * ============================================================ */ + +/* Execute a single command string as if typed at the REPL. + * Returns 1 if the REPL should exit (quit command), 0 otherwise. */ +static int dbg_run_script_cmd(DbgState* s, const char* cmd) { + char line[LINE_CAP]; + size_t n = driver_strlen(cmd); + if (n >= LINE_CAP) n = LINE_CAP - 1; + driver_memcpy(line, cmd, n); + line[n] = '\0'; + return dbg_dispatch(s, line); +} + +/* Execute debugger commands from a file, one per line. + * Returns 1 if the REPL should exit (quit command), 0 otherwise. */ +static int dbg_run_script_file(DbgState* s, const char* path) { + DriverLoad load = {0}; + KitSlice bytes; + const char* p; + const char* end; + char line[LINE_CAP]; + if (driver_load_bytes(&s->env->file_io, DBG_TOOL, path, &load, &bytes) != 0) + return 0; /* driver_load_bytes already emitted an error */ + p = (const char*)bytes.data; + end = p + bytes.len; + while (p < end) { + const char* nl = p; + size_t len; + while (nl < end && *nl != '\n') nl++; + len = (size_t)(nl - p); + /* Strip trailing carriage return. */ + if (len > 0 && p[len - 1] == '\r') len--; + if (len >= LINE_CAP) len = LINE_CAP - 1; + driver_memcpy(line, p, len); + line[len] = '\0'; + p = (nl < end) ? nl + 1 : end; + if (dbg_dispatch(s, line)) { + driver_release_bytes(&s->env->file_io, &load); + return 1; + } + } + driver_release_bytes(&s->env->file_io, &load); + return 0; +} + +/* ============================================================ * REPL * ============================================================ */ static void dbg_repl(DbgState* s) { char line[LINE_CAP]; - driver_printf("kit dbg — 'h' for help, 'q' to quit\n"); + uint32_t i; + + if (!s->batch_mode) driver_printf("kit dbg — 'h' for help, 'q' to quit\n"); + + /* Phase 1: drain --script / --command sources in order. */ + for (i = 0; i < s->nscript_entries; i++) { + DbgScriptEntry* e = &s->script_entries[i]; + int quit = (e->kind == DBG_ENTRY_FILE) + ? dbg_run_script_file(s, e->value) + : dbg_run_script_cmd(s, e->value); + if (quit) return; + } + + /* Phase 2: in --batch mode, exit without falling through to stdin. */ + if (s->batch_mode) return; + + /* Phase 3: interactive loop. */ for (;;) { int n; n = driver_read_line_edit(s->env, "(kit) ", line, sizeof(line), @@ -3410,7 +3548,7 @@ static void dbg_repl(DbgState* s) { return; } if (n == -1) { - driver_errf(DBG_TOOL, "stdin read error"); + dbg_errf(s, "stdin read error"); return; } if (n == -2) { /* SIGINT during prompt */ @@ -3489,6 +3627,9 @@ int driver_dbg(int argc, char** argv) { st.prog_argc = (int)o.prog_argc; st.prog_argv = o.prog_argv; st.entry_name = o.entry; + st.script_entries = o.script_entries; + st.nscript_entries = o.nscript_entries; + st.batch_mode = o.batch_mode; if (driver_inputs_count(&o.inputs) != 0) { st.entry_addr = kit_jit_lookup(jit, kit_slice_cstr(o.entry)); @@ -3534,5 +3675,5 @@ int driver_dbg(int argc, char** argv) { kit_target_free(target); dbg_options_release(&o); driver_env_fini(&env); - return 0; + return (st.batch_mode && st.error_count > 0) ? 1 : 0; } diff --git a/test/dbg/cases/batch-command-order/args b/test/dbg/cases/batch-command-order/args @@ -0,0 +1,10 @@ +--batch +--language +toy +--script +@CASE@/first.dbg +-c +run +--script +@CASE@/second.dbg +@CASE@/main.toy diff --git a/test/dbg/cases/batch-command-order/expected b/test/dbg/cases/batch-command-order/expected @@ -0,0 +1,3 @@ +Breakpoint 1 at 0xADDR (main) +Breakpoint 1 (main) hit at 0xADDR <main> +Program exited with code 9 diff --git a/test/dbg/cases/batch-command-order/first.dbg b/test/dbg/cases/batch-command-order/first.dbg @@ -0,0 +1 @@ +b main diff --git a/test/dbg/cases/batch-command-order/main.toy b/test/dbg/cases/batch-command-order/main.toy @@ -0,0 +1 @@ +fn main(): i32 { return 9 as i32; } diff --git a/test/dbg/cases/batch-command-order/second.dbg b/test/dbg/cases/batch-command-order/second.dbg @@ -0,0 +1 @@ +cont diff --git a/test/dbg/cases/batch-command-order/stdin b/test/dbg/cases/batch-command-order/stdin diff --git a/test/dbg/cases/batch-command/args b/test/dbg/cases/batch-command/args @@ -0,0 +1,10 @@ +--batch +--language +toy +-c +b main +-c +run +-c +cont +@CASE@/main.toy diff --git a/test/dbg/cases/batch-command/expected b/test/dbg/cases/batch-command/expected @@ -0,0 +1,3 @@ +Breakpoint 1 at 0xADDR (main) +Breakpoint 1 (main) hit at 0xADDR <main> +Program exited with code 7 diff --git a/test/dbg/cases/batch-command/main.toy b/test/dbg/cases/batch-command/main.toy @@ -0,0 +1 @@ +fn main(): i32 { return 7 as i32; } diff --git a/test/dbg/cases/batch-command/stdin b/test/dbg/cases/batch-command/stdin diff --git a/test/dbg/cases/batch-error-exit/args b/test/dbg/cases/batch-error-exit/args @@ -0,0 +1,3 @@ +--batch +-c +b no_such_symbol diff --git a/test/dbg/cases/batch-error-exit/expected b/test/dbg/cases/batch-error-exit/expected diff --git a/test/dbg/cases/batch-error-exit/expected_rc b/test/dbg/cases/batch-error-exit/expected_rc @@ -0,0 +1 @@ +1 diff --git a/test/dbg/cases/batch-error-exit/stderr b/test/dbg/cases/batch-error-exit/stderr @@ -0,0 +1 @@ +dbg: symbol not found: no_such_symbol diff --git a/test/dbg/cases/batch-error-exit/stdin b/test/dbg/cases/batch-error-exit/stdin diff --git a/test/dbg/cases/batch-script-file/args b/test/dbg/cases/batch-script-file/args @@ -0,0 +1,6 @@ +--batch +--language +toy +--script +@CASE@/cmds.dbg +@CASE@/main.toy diff --git a/test/dbg/cases/batch-script-file/cmds.dbg b/test/dbg/cases/batch-script-file/cmds.dbg @@ -0,0 +1,3 @@ +b main +run +cont diff --git a/test/dbg/cases/batch-script-file/expected b/test/dbg/cases/batch-script-file/expected @@ -0,0 +1,3 @@ +Breakpoint 1 at 0xADDR (main) +Breakpoint 1 (main) hit at 0xADDR <main> +Program exited with code 3 diff --git a/test/dbg/cases/batch-script-file/main.toy b/test/dbg/cases/batch-script-file/main.toy @@ -0,0 +1 @@ +fn main(): i32 { return 3 as i32; } diff --git a/test/dbg/cases/batch-script-file/stdin b/test/dbg/cases/batch-script-file/stdin diff --git a/test/dbg/cases/batch-script-interactive/args b/test/dbg/cases/batch-script-interactive/args @@ -0,0 +1,5 @@ +--language +toy +--script +@CASE@/cmds.dbg +@CASE@/main.toy diff --git a/test/dbg/cases/batch-script-interactive/cmds.dbg b/test/dbg/cases/batch-script-interactive/cmds.dbg @@ -0,0 +1,3 @@ +b main +run +cont diff --git a/test/dbg/cases/batch-script-interactive/expected b/test/dbg/cases/batch-script-interactive/expected @@ -0,0 +1,4 @@ +kit dbg — 'h' for help, 'q' to quit +Breakpoint 1 at 0xADDR (main) +Breakpoint 1 (main) hit at 0xADDR <main> +Program exited with code 5 diff --git a/test/dbg/cases/batch-script-interactive/main.toy b/test/dbg/cases/batch-script-interactive/main.toy @@ -0,0 +1 @@ +fn main(): i32 { return 5 as i32; } diff --git a/test/dbg/cases/batch-script-interactive/stdin b/test/dbg/cases/batch-script-interactive/stdin @@ -0,0 +1 @@ +q diff --git a/test/dbg/run.sh b/test/dbg/run.sh @@ -84,6 +84,7 @@ dbg_case() { dc_stdin_file="$dc_dir/stdin" dc_expected="$dc_dir/expected" dc_expected_stderr="$dc_dir/stderr" + dc_expected_rc_file="$dc_dir/expected_rc" dc_xfail_file="$dc_dir/xfail" dc_raw_stdout="$KIT_WORK/$dc_name.stdout.raw" dc_actual_stdout="$KIT_WORK/$dc_name.stdout" @@ -146,18 +147,26 @@ dbg_case() { : > "$dc_actual_stderr" fi - if [ "$dc_rc" -ne 0 ]; then + if [ -e "$dc_expected_rc_file" ]; then + dc_want_rc=$(sed -n '1p' "$dc_expected_rc_file") + if [ "$dc_rc" -ne "$dc_want_rc" ]; then + dc_ok=0 + dc_why="kit dbg exit=$dc_rc (expected $dc_want_rc)" + fi + elif [ "$dc_rc" -ne 0 ]; then dc_ok=0 dc_why="kit dbg exit=$dc_rc" - elif ! diff -u "$dc_expected_norm" "$dc_actual_stdout" >/dev/null 2>&1; then + fi + if [ "$dc_ok" -eq 1 ] && ! diff -u "$dc_expected_norm" "$dc_actual_stdout" >/dev/null 2>&1; then dc_ok=0 dc_why="stdout" - elif [ -e "$dc_expected_stderr" ]; then + fi + if [ "$dc_ok" -eq 1 ] && [ -e "$dc_expected_stderr" ]; then if ! diff -u "$dc_expected_stderr" "$dc_actual_stderr" >/dev/null 2>&1; then dc_ok=0 dc_why="stderr" fi - elif [ -s "$dc_raw_stderr" ]; then + elif [ "$dc_ok" -eq 1 ] && [ -s "$dc_raw_stderr" ]; then dc_ok=0 dc_why="unexpected stderr" fi