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:
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