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 ## 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 - Fixed the debugger leaking debug info conversion process handles. This
addresses cases where the debugger was preventing stale debug info from being addresses cases where the debugger was preventing stale debug info from being
updated, after a rebuild. 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("ip_vaddr: 0x%I64x\n", ip_vaddr);
log_infof("dbgi_key: {0x%I64x, 0x%I64x}\n", dbgi_key.u64[0], dbgi_key.u64[1]); 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}; Rng1U64 line_vaddr_rng = {0};
{ {
U64 ip_voff = ctrl_voff_from_vaddr(module, ip_vaddr); 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) if(lines.first != 0)
{ {
line_voff_rng = lines.first->v.voff_range; 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); 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("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); 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 // 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 // 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; 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. // rjf: trap @ destination, if we can - we can avoid a single-step this way.
if(point->jump_dest_vaddr != 0) 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) if(good_line_info && good_machine_code)
{ {
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max}; U64 line_opl_vaddr = line_vaddr_rng.max;
ctrl_trap_list_push(arena, &result.traps, &trap); 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 // 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); CTRL_Entity *module = ctrl_module_from_process_vaddr(process, ip_vaddr);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); 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}; Rng1U64 line_vaddr_rng = {0};
{ {
U64 ip_voff = ctrl_voff_from_vaddr(module, ip_vaddr); 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) if(lines.first != 0)
{ {
line_voff_rng = lines.first->v.voff_range; 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); 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; 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. // rjf: trap @ destination, if we can - we can avoid a single-step this way.
if(point->jump_dest_vaddr != 0) 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) if(good_line_info && good_machine_code)
{ {
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max}; U64 line_opl_vaddr = line_vaddr_rng.max;
ctrl_trap_list_push(arena, &result.traps, &trap); 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 // rjf: store goodness