kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 429defa5e448fa66882d0d0b729424b59b73fa09
parent e7154750753d813668b4616f6ecedc709077f075
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 28 May 2026 22:15:33 -0700

dbg: use standard step commands

Diffstat:
Mdoc/DBG.md | 2+-
Mdoc/DBG_TODO.md | 6+++---
Mdriver/dbg.c | 171++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/arch/aa64/dbg.c | 43+++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/arch.h | 4++++
Msrc/arch/rv64/dbg.c | 35+++++++++++++++++++++++++++++++++++
Msrc/arch/x64/dbg.c | 30++++++++++++++++++++++++++++++
Msrc/dbg/step.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mtest/dbg/cases/repl-help/expected | 4++--
Mtest/dbg/cases/toy-file-line-step/expected | 15+++++++++++++++
Mtest/dbg/cases/toy-file-line-step/stdin | 2+-
Mtest/dbg/cases/toy-local-inspection/expected | 8++++++++
Mtest/dbg/cases/toy-resume-without-stop/stdin | 2+-
Atest/dbg/cases/toy-step-finish/args | 3+++
Atest/dbg/cases/toy-step-finish/expected | 29+++++++++++++++++++++++++++++
Atest/dbg/cases/toy-step-finish/main.toy | 10++++++++++
Atest/dbg/cases/toy-step-finish/stdin | 6++++++
17 files changed, 374 insertions(+), 83 deletions(-)

diff --git a/doc/DBG.md b/doc/DBG.md @@ -476,7 +476,7 @@ against real debug info; see §10 for the failure modes each addresses. - [ ] `test/dbg/guarded_copy_segv`: `read_mem` from NULL returns nonzero, worker survives the next resume - [ ] `test/dbg/source_step` (gated on `cfree_jit_view`): scripted - REPL drives `n` / `sl` / `finish`, assert reported source line + REPL drives `n` / `step` / `finish`, assert reported source line at each stop ### Bigger follow-ons diff --git a/doc/DBG_TODO.md b/doc/DBG_TODO.md @@ -71,8 +71,8 @@ experience is solid. - [ ] Add debugger-control Toy coverage for: - [x] `b sym`, `run`, `cont` - [x] `b file:line` - - [x] `s`, `next` - - [ ] `sl`, `finish` + - [x] `stepi`, `next` + - [x] `step`, `finish` - [x] `bt` - [x] `info reg` - [x] `p`, `set`, `info locals`, `info args` @@ -129,7 +129,7 @@ experience is solid. - [x] Add line editing and history. - [x] Add completion for commands, symbols, locals, files, and breakpoint ids. - [ ] Repeat the last stepping command on a blank line. -- [ ] Print a compact source context after source-level stops. +- [x] Print a compact source context after source-level stops. - [ ] Make stop messages distinguish user breakpoints, internal step completions, signals, traps, and program exits cleanly. - [x] Add `disasm` / `x/i` around the current PC. diff --git a/driver/dbg.c b/driver/dbg.c @@ -78,8 +78,8 @@ void driver_help_dbg(void) { " Mirrors `cfree run` for compile flags and argv shape, but " "instead\n" " of calling the entry directly drops into a REPL that drives the\n" - " JIT session: breakpoints, single-step (insn / source line / " - "over),\n" + " JIT session: breakpoints, source-line stepping (into / over),\n" + " instruction stepping,\n" " finish, backtrace, registers, locals/args, variable read/write,\n" " and raw memory examine. -g is forced on so source lines and\n" " variable locations are available at runtime.\n" @@ -105,9 +105,9 @@ void driver_help_dbg(void) { " q, quit exit (Ctrl-D also works)\n" " r, run start fresh execution at entry\n" " c, cont continue after a stop\n" - " s, step single-step one instruction\n" - " sl step to next source line (into " + " s, step step to next source line (into " "calls)\n" + " si, stepi single-step one instruction\n" " n, next step to next source line (over " "calls)\n" " finish run until current frame returns\n" @@ -396,6 +396,8 @@ typedef struct DbgState { DriverLineHistory line_history; } DbgState; +#define DBG_LIST_CTX 5 /* lines printed before/after the target */ + /* SIGINT trampoline. The handler in env.c calls our cb with this state; * we forward into the session. cfree_jit_session_interrupt is documented * async-signal-safe. */ @@ -777,7 +779,23 @@ static void dbg_print_pc(DbgState* s, uint64_t pc) { } } +static void dbg_print_source_listing(DbgState* s, CfreeSlice file, + uint32_t line, uint64_t pc, + int report_errors); + static void dbg_render_stop(DbgState* s, const CfreeStopInfo* st) { + CfreeSlice file = {0}; + uint32_t line = 0; + uint32_t col = 0; + int has_source = 0; + + if (st->kind != CFREE_STOP_EXIT && s->dwarf && + cfree_dwarf_addr_to_line(s->dwarf, dbg_pc_rt_to_img(s, st->regs.pc), + &file, &line, &col) == CFREE_OK && + file.s) { + has_source = 1; + } + switch (st->kind) { case CFREE_STOP_BREAKPOINT: { Bp* b = NULL; @@ -811,6 +829,9 @@ static void dbg_render_stop(DbgState* s, const CfreeStopInfo* st) { driver_printf("Program exited with code %d\n", st->exit_code); break; } + if (has_source) + dbg_print_source_listing(s, file, line, dbg_pc_rt_to_img(s, st->regs.pc), + 0); } /* ============================================================ @@ -824,8 +845,8 @@ static void dbg_render_stop(DbgState* s, const CfreeStopInfo* st) { typedef enum DbgRunMode { RUN_FRESH, /* r — _call from entry */ RUN_CONTINUE, /* c — _resume(continue) */ - RUN_STEP_INSN, /* s — _resume(step_insn) */ - RUN_STEP_LINE, /* sl — _resume(step_line) */ + RUN_STEP_INSN, /* si — _resume(step_insn) */ + RUN_STEP_LINE, /* s — _resume(step_line) */ RUN_NEXT_LINE, /* n — _resume(next_line) */ RUN_STEP_OUT, /* finish — _resume(step_out) */ } DbgRunMode; @@ -2161,15 +2182,79 @@ static void dbg_cmd_disasm(DbgState* s, uint64_t addr, size_t count) { } /* ============================================================ - * `list file:line` + * Source listing * ============================================================ * Print a context window of source lines centered on `file:line`. * * Reads `file` from disk via env.file_io. When the file isn't on disk * (e.g. the DWARF came from a `.o` / `.a` whose source isn't available - * here), report the DWARF line number alone and omit the snippet. */ + * here), command-mode reports the DWARF line number alone and source stops + * silently keep the already-rendered location line. */ -#define DBG_LIST_CTX 5 /* lines printed before/after the target */ +static void dbg_print_source_listing(DbgState* s, CfreeSlice file, + uint32_t line, uint64_t pc, + int report_errors) { + char path[1024]; + CfreeFileData fd; + const CfreeFileIO* io; + + if (!file.s || file.len == 0 || file.len >= sizeof(path)) { + if (report_errors) { + driver_errf(DBG_TOOL, "bad file in '%.*s'", CFREE_SLICE_ARG(file)); + } + return; + } + driver_memcpy(path, file.s, file.len); + path[file.len] = '\0'; + + /* Try to read the file via file_io. On miss, fall back to the + * DWARF-only summary line per doc/DBG.md §10. */ + io = s->env && s->env->file_io.read_all ? &s->env->file_io : NULL; + if (!io || io->read_all(io->user, path, &fd) != CFREE_OK) { + if (report_errors) { + driver_printf("%.*s:%u [source not available; pc=0x%llx]\n", + CFREE_SLICE_ARG(cfree_slice_cstr(path)), line, + (unsigned long long)pc); + } + return; + } + + /* Walk the buffer, counting newlines. Print lines in + * [target-DBG_LIST_CTX, target+DBG_LIST_CTX]. */ + { + uint32_t target = line; + uint32_t lo = target > DBG_LIST_CTX ? target - DBG_LIST_CTX : 1; + uint32_t hi = target + DBG_LIST_CTX; + uint32_t cur = 1; + const uint8_t* p = fd.data; + const uint8_t* end = fd.data + fd.size; + const uint8_t* line_start = p; + while (p <= end) { + int eol = (p == end) || (*p == '\n'); + if (eol) { + if (p == end && p == line_start && end > fd.data && end[-1] == '\n') + break; + if (cur >= lo && cur <= hi) { + size_t len = (size_t)(p - line_start); + driver_printf( + "%6u%.*s %.*s\n", cur, + CFREE_SLICE_ARG(cfree_slice_cstr(cur == target ? " >" : " ")), + (int)len, (const char*)line_start); + } + ++cur; + if (p == end) break; + line_start = p + 1; + } + ++p; + } + if (report_errors && cur <= target) { + driver_errf(DBG_TOOL, "file has only %u lines; %u requested", cur - 1u, + target); + } + } + + if (io->release) io->release(io->user, &fd); +} static void dbg_cmd_list(DbgState* s, const char* spec) { const char* colon; @@ -2178,9 +2263,7 @@ static void dbg_cmd_list(DbgState* s, const char* spec) { uint64_t line_u; size_t used; uint64_t pc; - int rc; - CfreeFileData fd; - const CfreeFileIO* io; + CfreeSlice file; if (!s->dwarf) { driver_errf(DBG_TOOL, "no DWARF: cannot resolve %.*s", @@ -2228,53 +2311,10 @@ static void dbg_cmd_list(DbgState* s, const char* spec) { CFREE_SLICE_ARG(cfree_slice_cstr(spec))); return; } - rc = 0; - (void)rc; - } - - /* Try to read the file via file_io. On miss, fall back to the - * DWARF-only summary line per doc/DBG.md §10. */ - io = s->env && s->env->file_io.read_all ? &s->env->file_io : NULL; - if (!io || io->read_all(io->user, path, &fd) != CFREE_OK) { - driver_printf("%.*s:%u [source not available; pc=0x%llx]\n", - CFREE_SLICE_ARG(cfree_slice_cstr(path)), (uint32_t)line_u, - (unsigned long long)pc); - return; - } - - /* Walk the buffer, counting newlines. Print lines in - * [target-DBG_LIST_CTX, target+DBG_LIST_CTX]. */ - { - uint32_t target = (uint32_t)line_u; - uint32_t lo = target > DBG_LIST_CTX ? target - DBG_LIST_CTX : 1; - uint32_t hi = target + DBG_LIST_CTX; - uint32_t cur = 1; - const uint8_t* p = fd.data; - const uint8_t* end = fd.data + fd.size; - const uint8_t* line_start = p; - while (p <= end) { - int eol = (p == end) || (*p == '\n'); - if (eol) { - if (cur >= lo && cur <= hi) { - size_t len = (size_t)(p - line_start); - driver_printf( - "%6u%.*s %.*s\n", cur, - CFREE_SLICE_ARG(cfree_slice_cstr(cur == target ? " >" : " ")), - (int)len, (const char*)line_start); - } - ++cur; - if (p == end) break; - line_start = p + 1; - } - ++p; - } - if (cur <= target) { - driver_errf(DBG_TOOL, "file has only %u lines; %u requested", cur - 1u, - target); - } } - if (io->release) io->release(io->user, &fd); + file = cfree_slice_cstr(path); + dbg_print_source_listing(s, file, (uint32_t)line_u, pc, 1); } /* ============================================================ @@ -2425,9 +2465,9 @@ static void dbg_cmd_help(void) { " select language for jit/expr input\n" " r, run start fresh execution at entry\n" " c, cont continue after a stop\n" - " s, step single-step one instruction\n" - " sl step to next source line (into " + " s, step step to next source line (into " "calls)\n" + " si, stepi single-step one instruction\n" " n, next step to next source line (over " "calls)\n" " finish run until current frame returns\n" @@ -2544,14 +2584,11 @@ static int dbg_dispatch(DbgState* s, char* line) { return 0; } if (driver_streq(cmd, "s") || driver_streq(cmd, "step")) { - /* Single instruction step. GDB's `s` (step-into-source-line) is - * spelled `sl` here so this command keeps its historical - * "instruction step" meaning. */ - dbg_drive(s, RUN_STEP_INSN); + dbg_drive(s, RUN_STEP_LINE); return 0; } - if (driver_streq(cmd, "sl")) { - dbg_drive(s, RUN_STEP_LINE); + if (driver_streq(cmd, "si") || driver_streq(cmd, "stepi")) { + dbg_drive(s, RUN_STEP_INSN); return 0; } if (driver_streq(cmd, "n") || driver_streq(cmd, "next")) { @@ -2901,13 +2938,13 @@ static void dbg_complete_commands(DriverLineCompletionList* out, const char* prefix, size_t prefix_len) { static const char* const cmds[] = { "help", "quit", "exit", "run", "cont", - "continue", "step", "sl", "next", "finish", + "continue", "step", "stepi", "next", "finish", "jit", "edit", "expr", "bt", "backtrace", "where", "break", "info", "ignore", "delete", "enable", "disable", "print", "set", "jump", "list", "disasm", "x", "x/i", ":language", ":lang", "language", "abort", "h", "q", - "r", "c", "s", "sl", "n", + "r", "c", "s", "si", "n", "b", "d", "p", "l", "e", }; size_t i; diff --git a/src/arch/aa64/dbg.c b/src/arch/aa64/dbg.c @@ -292,6 +292,46 @@ static int aa64_dbg_is_call(const ArchDbgInsn* insn) { return (word & AA64_DBG_BL_MASK) == AA64_DBG_BL_OP; } +static CfreeStatus aa64_dbg_direct_call_target(const ArchDbgInsn* insn, + u64* target_out) { + uint32_t word = 0; + AA64BrImm f; + int64_t imm; + if (!insn || !target_out) return CFREE_INVALID; + if (insn->len != AA64_DBG_INSN_LEN) return CFREE_UNSUPPORTED; + memcpy(&word, insn->bytes, sizeof(word)); + if ((word & AA64_DBG_BL_MASK) != AA64_DBG_BL_OP) return CFREE_NOT_FOUND; + f = aa64_brimm_unpack(word); + imm = sign_extend(f.imm26, 26); + *target_out = insn->pc + (u64)(imm * 4); + return CFREE_OK; +} + +static CfreeStatus aa64_dbg_direct_jump_target(const ArchDbgInsn* insn, + u64* target_out) { + uint32_t word = 0; + AA64BrImm f; + int64_t imm; + if (!insn || !target_out) return CFREE_INVALID; + if (insn->len != AA64_DBG_INSN_LEN) return CFREE_UNSUPPORTED; + memcpy(&word, insn->bytes, sizeof(word)); + if ((word & AA64_BR_IMM_FAMILY_MASK) != AA64_BR_IMM_FAMILY_MATCH) + return CFREE_NOT_FOUND; + f = aa64_brimm_unpack(word); + if (f.op != 0) return CFREE_NOT_FOUND; + imm = sign_extend(f.imm26, 26); + *target_out = insn->pc + (u64)(imm * 4); + return CFREE_OK; +} + +static CfreeStatus aa64_dbg_link_register_return_address( + const CfreeUnwindFrame* frame, u64* target_out) { + if (!frame || !target_out) return CFREE_INVALID; + if (frame->regs[AA64_LR] == 0) return CFREE_NOT_FOUND; + *target_out = frame->regs[AA64_LR]; + return CFREE_OK; +} + const ArchDbgOps aa64_dbg_ops = { .min_insn_len = AA64_DBG_INSN_LEN, .max_insn_len = AA64_DBG_INSN_LEN, @@ -300,4 +340,7 @@ const ArchDbgOps aa64_dbg_ops = { .decode_insn = aa64_dbg_decode_insn, .build_displaced_shim = aa64_dbg_build_displaced_shim, .is_call = aa64_dbg_is_call, + .direct_call_target = aa64_dbg_direct_call_target, + .direct_jump_target = aa64_dbg_direct_jump_target, + .link_register_return_address = aa64_dbg_link_register_return_address, }; diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -169,6 +169,10 @@ typedef struct ArchDbgOps { u32 scratch_cap, u32* sentinel_off, u64* fallthrough_pc); int (*is_call)(const ArchDbgInsn* insn); + CfreeStatus (*direct_call_target)(const ArchDbgInsn* insn, u64* target_out); + CfreeStatus (*direct_jump_target)(const ArchDbgInsn* insn, u64* target_out); + CfreeStatus (*link_register_return_address)(const CfreeUnwindFrame* frame, + u64* target_out); } ArchDbgOps; typedef struct ArchImpl { diff --git a/src/arch/rv64/dbg.c b/src/arch/rv64/dbg.c @@ -375,6 +375,38 @@ static int rv64_dbg_is_call(const ArchDbgInsn* insn) { return rv_rd(word) != RV_ZERO; } +static CfreeStatus rv64_dbg_direct_call_target(const ArchDbgInsn* insn, + u64* target_out) { + uint32_t word = 0; + if (!insn || !target_out) return CFREE_INVALID; + if (insn->len != 4u) return CFREE_UNSUPPORTED; + memcpy(&word, insn->bytes, sizeof(word)); + if (rv_opcode(word) != RV_JAL || rv_rd(word) == RV_ZERO) + return CFREE_NOT_FOUND; + *target_out = insn->pc + (u64)rv_j_imm(word); + return CFREE_OK; +} + +static CfreeStatus rv64_dbg_direct_jump_target(const ArchDbgInsn* insn, + u64* target_out) { + uint32_t word = 0; + if (!insn || !target_out) return CFREE_INVALID; + if (insn->len != 4u) return CFREE_UNSUPPORTED; + memcpy(&word, insn->bytes, sizeof(word)); + if (rv_opcode(word) != RV_JAL || rv_rd(word) != RV_ZERO) + return CFREE_NOT_FOUND; + *target_out = insn->pc + (u64)rv_j_imm(word); + return CFREE_OK; +} + +static CfreeStatus rv64_dbg_link_register_return_address( + const CfreeUnwindFrame* frame, u64* target_out) { + if (!frame || !target_out) return CFREE_INVALID; + if (frame->regs[RV_RA] == 0) return CFREE_NOT_FOUND; + *target_out = frame->regs[RV_RA]; + return CFREE_OK; +} + const ArchDbgOps rv64_dbg_ops = { .min_insn_len = 4u, .max_insn_len = 4u, @@ -383,4 +415,7 @@ const ArchDbgOps rv64_dbg_ops = { .decode_insn = rv64_dbg_decode_insn, .build_displaced_shim = rv64_dbg_build_displaced_shim, .is_call = rv64_dbg_is_call, + .direct_call_target = rv64_dbg_direct_call_target, + .direct_jump_target = rv64_dbg_direct_jump_target, + .link_register_return_address = rv64_dbg_link_register_return_address, }; diff --git a/src/arch/x64/dbg.c b/src/arch/x64/dbg.c @@ -400,6 +400,34 @@ static int x64_dbg_is_call(const ArchDbgInsn* insn) { return d.is_call != 0; } +static CfreeStatus x64_dbg_direct_call_target(const ArchDbgInsn* insn, + u64* target_out) { + X64DbgDecode d; + if (!insn || !target_out) return CFREE_INVALID; + if (x64_dbg_measure(insn->bytes, insn->len, insn->pc, &d) != CFREE_OK) + return CFREE_UNSUPPORTED; + if (!d.is_call || d.pc_rel_kind != X64_DBG_PCREL_REL32) + return CFREE_NOT_FOUND; + *target_out = (u64)((i64)(insn->pc + insn->len) + d.disp); + return CFREE_OK; +} + +static CfreeStatus x64_dbg_direct_jump_target(const ArchDbgInsn* insn, + u64* target_out) { + X64DbgDecode d; + if (!insn || !target_out) return CFREE_INVALID; + if (x64_dbg_measure(insn->bytes, insn->len, insn->pc, &d) != CFREE_OK) + return CFREE_UNSUPPORTED; + if (d.is_call || (d.pc_rel_kind != X64_DBG_PCREL_REL32 && + d.pc_rel_kind != X64_DBG_PCREL_REL8)) + return CFREE_NOT_FOUND; + if (insn->bytes[d.opc_off] != X64_JMP_REL32_BYTE && + insn->bytes[d.opc_off] != 0xebu) + return CFREE_NOT_FOUND; + *target_out = (u64)((i64)(insn->pc + insn->len) + d.disp); + return CFREE_OK; +} + const ArchDbgOps x64_dbg_ops = { .min_insn_len = 1u, .max_insn_len = ARCH_DBG_MAX_INSN_BYTES, @@ -408,4 +436,6 @@ const ArchDbgOps x64_dbg_ops = { .decode_insn = x64_dbg_decode_insn, .build_displaced_shim = x64_dbg_build_displaced_shim, .is_call = x64_dbg_is_call, + .direct_call_target = x64_dbg_direct_call_target, + .direct_jump_target = x64_dbg_direct_jump_target, }; diff --git a/src/dbg/step.c b/src/dbg/step.c @@ -12,6 +12,8 @@ #define DBG_STEP_LINE_INSN_CAP 1024u +static CfreeStatus run_step_line_loop(CfreeJitSession* s); + /* DWARF line/CFI tables are authored in image-relative vaddrs (cfree's * debug emitter writes them, the JIT view applies relocs against final * image vaddrs). Stop PCs and the values dropped onto the stack by @@ -46,6 +48,24 @@ static int stop_is_internal_completion(const CfreeJitSession* s) { return s->stop.kind == CFREE_STOP_BREAKPOINT && s->stop.bp_id == 0; } +static CfreeStatus direct_call_target(CfreeJitSession* s, uint64_t* target) { + ArchDbgInsn insn; + if (!s->arch_dbg || !s->arch_dbg->direct_call_target) + return CFREE_NOT_FOUND; + if (dbg_arch_decode_insn(s, s->stop.regs.pc, &insn) != CFREE_OK) + return CFREE_NOT_FOUND; + return s->arch_dbg->direct_call_target(&insn, target); +} + +static CfreeStatus direct_jump_target(CfreeJitSession* s, uint64_t* target) { + ArchDbgInsn insn; + if (!s->arch_dbg || !s->arch_dbg->direct_jump_target) + return CFREE_NOT_FOUND; + if (dbg_arch_decode_insn(s, s->stop.regs.pc, &insn) != CFREE_OK) + return CFREE_NOT_FOUND; + return s->arch_dbg->direct_jump_target(&insn, target); +} + static CfreeStatus dwarf_line_for(CfreeJitSession* s, uint64_t pc, CfreeSlice* file, uint32_t* line) { uint32_t col = 0; @@ -73,6 +93,43 @@ static int line_changed(CfreeSlice base_file, uint32_t base_line, return 0; } +static CfreeStatus try_step_into_direct_call(CfreeJitSession* s, int* did) { + uint64_t target = 0; + u32 bp_id = 0; + CfreeStatus st; + *did = 0; + st = direct_call_target(s, &target); + if (st == CFREE_NOT_FOUND || st == CFREE_UNSUPPORTED) return CFREE_OK; + if (st != CFREE_OK) return st; + st = dbg_bp_set_internal(s, target, &bp_id); + if (st != CFREE_OK) return st; + *did = 1; + st = dbg_session_signal_resume(s); + if (st != CFREE_OK) return st; + st = dbg_session_wait_stop(s); + if (st != CFREE_OK) return st; + if (stop_is_internal_completion(s)) { + return run_step_line_loop(s); + } + return CFREE_OK; +} + +static CfreeStatus try_follow_direct_jump(CfreeJitSession* s, int* did) { + uint64_t target = 0; + u32 bp_id = 0; + CfreeStatus st; + *did = 0; + st = direct_jump_target(s, &target); + if (st == CFREE_NOT_FOUND || st == CFREE_UNSUPPORTED) return CFREE_OK; + if (st != CFREE_OK) return st; + st = dbg_bp_set_internal(s, target, &bp_id); + if (st != CFREE_OK) return st; + *did = 1; + st = dbg_session_signal_resume(s); + if (st != CFREE_OK) return st; + return dbg_session_wait_stop(s); +} + static CfreeStatus run_step_line_loop(CfreeJitSession* s) { CfreeSlice base_file = CFREE_SLICE_NULL; uint32_t base_line = 0; @@ -88,8 +145,18 @@ static CfreeStatus run_step_line_loop(CfreeJitSession* s) { uint32_t new_line = 0; CfreeDwarfSubprogram new_sub; int have_new_sub; + int did_call = 0; + int did_jump = 0; + CfreeStatus st; - if (do_one_displaced(s) != CFREE_OK) { + st = try_step_into_direct_call(s, &did_call); + if (st != CFREE_OK) return st; + if (did_call) return CFREE_OK; + + st = try_follow_direct_jump(s, &did_jump); + if (st != CFREE_OK) return st; + + if (!did_jump && do_one_displaced(s) != CFREE_OK) { /* Lifter declined; surface whatever the current stop is. */ return CFREE_OK; } @@ -106,8 +173,6 @@ static CfreeStatus run_step_line_loop(CfreeJitSession* s) { /* Left the original subprogram — STEP_LINE follows in. */ return CFREE_OK; } - } else if (have_sub != have_new_sub) { - return CFREE_OK; } if (line_changed(base_file, base_line, new_file, new_line)) { @@ -123,11 +188,17 @@ static CfreeStatus run_step_out(CfreeJitSession* s) { CfreeStatus st; frame = s->stop.regs; frame.pc = step_rt_to_img(s, frame.pc); /* CFI lookup is in image space */ - if (cfree_dwarf_unwind_step(s->dwarf, &frame) != CFREE_OK) - return CFREE_UNSUPPORTED; - /* On success unwind_step writes frame.pc from the saved return-address - * register / stack slot — already a runtime PC, no inverse translation - * needed before the internal bp install. */ + st = cfree_dwarf_unwind_step(s->dwarf, &frame); + if (st != CFREE_OK) { + if (!s->arch_dbg || !s->arch_dbg->link_register_return_address || + s->arch_dbg->link_register_return_address(&s->stop.regs, &frame.pc) != + CFREE_OK) { + return st; + } + } + /* On success unwind_step or the link-register fallback writes frame.pc from + * the saved return-address register / stack slot — already a runtime PC, no + * inverse translation needed before the internal bp install. */ if (frame.pc == 0) return CFREE_NOT_FOUND; st = dbg_bp_set_internal(s, frame.pc, &bp_id); if (st != CFREE_OK) return st; diff --git a/test/dbg/cases/repl-help/expected b/test/dbg/cases/repl-help/expected @@ -6,8 +6,8 @@ Commands (abbrev. shown): select language for jit/expr input r, run start fresh execution at entry c, cont continue after a stop - s, step single-step one instruction - sl step to next source line (into calls) + s, step step to next source line (into calls) + si, stepi single-step one instruction n, next step to next source line (over calls) finish run until current frame returns jit [LANG|NAME] { ... } compile and append a language snippet diff --git a/test/dbg/cases/toy-file-line-step/expected b/test/dbg/cases/toy-file-line-step/expected @@ -1,6 +1,21 @@ cfree dbg — 'h' for help, 'q' to quit Breakpoint 1 at 0xADDR (@CASE@/main.toy:2) Breakpoint 1 (@CASE@/main.toy:2) hit at 0xADDR <main+0x60> at @CASE@/main.toy:2:16 + 1 fn main(): i32 { + 2 > let x: i64 = 10; + 3 let y: i64 = x + 32; + 4 return y as i32; + 5 } Breakpoint hit at 0xADDR <main+0x64> at @CASE@/main.toy:2:16 + 1 fn main(): i32 { + 2 > let x: i64 = 10; + 3 let y: i64 = x + 32; + 4 return y as i32; + 5 } Breakpoint hit at 0xADDR <main+0x68> at @CASE@/main.toy:3:20 + 1 fn main(): i32 { + 2 let x: i64 = 10; + 3 > let y: i64 = x + 32; + 4 return y as i32; + 5 } Program exited with code 42 diff --git a/test/dbg/cases/toy-file-line-step/stdin b/test/dbg/cases/toy-file-line-step/stdin @@ -1,6 +1,6 @@ b @CASE@/main.toy:2 run -step +stepi next cont q diff --git a/test/dbg/cases/toy-local-inspection/expected b/test/dbg/cases/toy-local-inspection/expected @@ -1,6 +1,14 @@ cfree dbg — 'h' for help, 'q' to quit Breakpoint 1 at 0xADDR (@CASE@/main.toy:3) Breakpoint 1 (@CASE@/main.toy:3) hit at 0xADDR <helper+0x78> at @CASE@/main.toy:3:10 + 1 fn helper(v: i64): i64 { + 2 let n: i64 = v + 2; + 3 > return n; + 4 } + 5 + 6 fn main(): i32 { + 7 return helper(40) as i32; + 8 } v = 40 n = 42 v = 40 diff --git a/test/dbg/cases/toy-resume-without-stop/stdin b/test/dbg/cases/toy-resume-without-stop/stdin @@ -1,6 +1,6 @@ cont step -sl +stepi next finish q diff --git a/test/dbg/cases/toy-step-finish/args b/test/dbg/cases/toy-step-finish/args @@ -0,0 +1,3 @@ +--language +toy +@CASE@/main.toy diff --git a/test/dbg/cases/toy-step-finish/expected b/test/dbg/cases/toy-step-finish/expected @@ -0,0 +1,29 @@ +cfree dbg — 'h' for help, 'q' to quit +Breakpoint 1 at 0xADDR (@CASE@/main.toy:8) +Breakpoint 1 (@CASE@/main.toy:8) hit at 0xADDR <main+0x68> at @CASE@/main.toy:8:25 + 3 return y; + 4 } + 5 + 6 fn main(): i32 { + 7 let base: i64 = 41; + 8 > let out: i64 = helper(base); + 9 return out as i32; + 10 } +Breakpoint hit at 0xADDR <helper+0x64> at @CASE@/main.toy:2:20 + 1 fn helper(v: i64): i64 { + 2 > let y: i64 = v + 1; + 3 return y; + 4 } + 5 + 6 fn main(): i32 { + 7 let base: i64 = 41; +Breakpoint hit at 0xADDR <main+0x78> at @CASE@/main.toy:8:25 + 3 return y; + 4 } + 5 + 6 fn main(): i32 { + 7 let base: i64 = 41; + 8 > let out: i64 = helper(base); + 9 return out as i32; + 10 } +Program exited with code 42 diff --git a/test/dbg/cases/toy-step-finish/main.toy b/test/dbg/cases/toy-step-finish/main.toy @@ -0,0 +1,10 @@ +fn helper(v: i64): i64 { + let y: i64 = v + 1; + return y; +} + +fn main(): i32 { + let base: i64 = 41; + let out: i64 = helper(base); + return out as i32; +} diff --git a/test/dbg/cases/toy-step-finish/stdin b/test/dbg/cases/toy-step-finish/stdin @@ -0,0 +1,6 @@ +b @CASE@/main.toy:8 +run +step +finish +cont +q