fix step over/into line operations when source line maps to multiple (potentially) discontiguous code ranges, or at least line info records; this fixes weird stepping with Clang-generated line info for for loops, function prologues in Odin, amongst other similar issues

This commit is contained in:
Ryan Fleury
2026-04-08 14:42:30 -07:00
parent 5bc791566b
commit be270a8f8d
2 changed files with 112 additions and 13 deletions
+10
View File
@@ -2,6 +2,16 @@
## Debugger Changes
- Fixed the debugger incorrectly evaluating pointer casts of registers in
register space, rather than promoting them to process address space
evaluations. This fixes cases where expressions like `(int *)rax` were not
displaying correctly.
- Fixed the debugger incorrectly requiring multiple step operations when source
lines mapped to multiple discontiguous ranges of instructions. This most
notably showed up with certain styles of `for`-loops, especially with Clang
and other non-C/C++ languages, and function prologues in non-C/C++ languages.
- Fixed the debugger treating the `foo, x` fastpath for `hex(foo)` differently
than `hex(foo)`, in that the hexadecimal setting was not being inherited.
- Fixed the debugger leaking debug info conversion process handles. This
addresses cases where the debugger was preventing stale debug info from being
updated, after a rebuild.
+97 -8
View File
@@ -345,7 +345,9 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
log_infof("ip_vaddr: 0x%I64x\n", ip_vaddr);
log_infof("dbgi_key: {0x%I64x, 0x%I64x}\n", dbgi_key.u64[0], dbgi_key.u64[1]);
// rjf: ip => line vaddr range
// rjf: ip => line info
String8 file_path = {0};
S64 line_num = 0;
Rng1U64 line_vaddr_rng = {0};
{
U64 ip_voff = ctrl_voff_from_vaddr(module, ip_vaddr);
@@ -354,6 +356,8 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
if(lines.first != 0)
{
line_voff_rng = lines.first->v.voff_range;
file_path = lines.first->v.file_path;
line_num = lines.first->v.pt.line;
line_vaddr_rng = ctrl_vaddr_range_from_voff_range(module, line_voff_rng);
log_infof("line: {%S:%I64i}\n", lines.first->v.file_path, lines.first->v.pt.line);
}
@@ -361,6 +365,18 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max);
}
// rjf: gather other ranges on this same textual line, which we don't want to return to
Rng1U64List all_vaddr_ranges_on_same_line = {0};
{
D_LineList lines = d_lines_from_dbgi_key_file_path_line_num(scratch.arena, dbgi_key, file_path, line_num);
for EachNode(n, D_LineNode, lines.first)
{
Rng1U64 voff_range = n->v.voff_range;
Rng1U64 vaddr_range = ctrl_vaddr_range_from_voff_range(module, voff_range);
rng1u64_list_push(scratch.arena, &all_vaddr_ranges_on_same_line, vaddr_range);
}
}
// rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range
//
// MSVC exports line info at these line numbers when /JMC (Just My Code) debugging
@@ -446,6 +462,16 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
add = 0;
}
// rjf: omit if this jump stays inside of any range on this same textual line
for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first)
{
if(contains_1u64(n->v, point->jump_dest_vaddr))
{
add = 0;
break;
}
}
// rjf: trap @ destination, if we can - we can avoid a single-step this way.
if(point->jump_dest_vaddr != 0)
{
@@ -475,11 +501,28 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
}
}
// rjf: push trap for natural linear flow
// rjf: push traps for natural linear flow
if(good_line_info && good_machine_code)
{
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max};
ctrl_trap_list_push(arena, &result.traps, &trap);
U64 line_opl_vaddr = line_vaddr_rng.max;
for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first)
{
U64 opl = n->v.max;
B32 opl_in_line = 0;
for EachNode(n2, Rng1U64Node, all_vaddr_ranges_on_same_line.first)
{
if(n != n2 && opl == n2->v.min)
{
opl_in_line = 1;
break;
}
}
if(!opl_in_line)
{
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, opl};
ctrl_trap_list_push(arena, &result.traps, &trap);
}
}
}
// rjf: store goodness
@@ -513,7 +556,9 @@ d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread)
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, ip_vaddr);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
// rjf: ip => line vaddr range
// rjf: ip => line info
String8 file_path = {0};
S64 line_num = 0;
Rng1U64 line_vaddr_rng = {0};
{
U64 ip_voff = ctrl_voff_from_vaddr(module, ip_vaddr);
@@ -522,7 +567,24 @@ d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread)
if(lines.first != 0)
{
line_voff_rng = lines.first->v.voff_range;
file_path = lines.first->v.file_path;
line_num = lines.first->v.pt.line;
line_vaddr_rng = ctrl_vaddr_range_from_voff_range(module, line_voff_rng);
log_infof("line: {%S:%I64i}\n", lines.first->v.file_path, lines.first->v.pt.line);
}
log_infof("voff_range: {0x%I64x, 0x%I64x}\n", line_voff_rng.min, line_voff_rng.max);
log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max);
}
// rjf: gather other ranges on this same textual line, which we don't want to return to
Rng1U64List all_vaddr_ranges_on_same_line = {0};
{
D_LineList lines = d_lines_from_dbgi_key_file_path_line_num(scratch.arena, dbgi_key, file_path, line_num);
for EachNode(n, D_LineNode, lines.first)
{
Rng1U64 voff_range = n->v.voff_range;
Rng1U64 vaddr_range = ctrl_vaddr_range_from_voff_range(module, voff_range);
rng1u64_list_push(scratch.arena, &all_vaddr_ranges_on_same_line, vaddr_range);
}
}
@@ -616,6 +678,16 @@ d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread)
add = 0;
}
// rjf: omit if this jump stays inside of any range on this same textual line
for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first)
{
if(contains_1u64(n->v, point->jump_dest_vaddr))
{
add = 0;
break;
}
}
// rjf: trap @ destination, if we can - we can avoid a single-step this way.
if(point->jump_dest_vaddr != 0)
{
@@ -638,11 +710,28 @@ d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread)
}
}
// rjf: push trap for natural linear flow
// rjf: push traps for natural linear flow
if(good_line_info && good_machine_code)
{
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max};
ctrl_trap_list_push(arena, &result.traps, &trap);
U64 line_opl_vaddr = line_vaddr_rng.max;
for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first)
{
U64 opl = n->v.max;
B32 opl_in_line = 0;
for EachNode(n2, Rng1U64Node, all_vaddr_ranges_on_same_line.first)
{
if(n != n2 && opl == n2->v.min)
{
opl_in_line = 1;
break;
}
}
if(!opl_in_line)
{
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, opl};
ctrl_trap_list_push(arena, &result.traps, &trap);
}
}
}
// rjf: store goodness