transmit user breakpoints hit from demon -> ctrl using ctrl-defined IDs, correllate those IDs to rd-defined IDs, use rd-defined IDs in stop events to count bp hits, rather than re-resolving breakpoints. further fixes & work on first pass of data breakpoints

This commit is contained in:
Ryan Fleury
2025-04-12 14:28:38 -07:00
parent d894f7112f
commit 0f380e9228
6 changed files with 181 additions and 71 deletions
+25 -11
View File
@@ -396,6 +396,7 @@ ctrl_serialized_string_from_msg_list(Arena *arena, CTRL_MsgList *msgs)
CTRL_UserBreakpoint *bp = &n->v;
str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->kind);
str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->flags);
str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->id);
str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->string.size);
str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->string.str, bp->string.size);
str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->pt);
@@ -518,6 +519,7 @@ ctrl_msg_list_from_serialized_string(Arena *arena, String8 string)
CTRL_UserBreakpoint *bp = &n->v;
read_off += str8_deserial_read_struct(string, read_off, &bp->kind);
read_off += str8_deserial_read_struct(string, read_off, &bp->flags);
read_off += str8_deserial_read_struct(string, read_off, &bp->id);
read_off += str8_deserial_read_struct(string, read_off, &bp->string.size);
bp->string.str = push_array_no_zero(arena, U8, bp->string.size);
read_off += str8_deserial_read(string, read_off, bp->string.str, bp->string.size, 1);
@@ -5731,21 +5733,28 @@ ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg)
}
// rjf: user breakpoints
for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next)
{
DMN_Trap *trap = n->v;
DMN_Trap *opl = n->v + n->count;
for(;trap < opl; trap += 1)
if(event->user_data != 0)
{
if(dmn_handle_match(trap->process, event->process) &&
trap->vaddr == event->instruction_pointer &&
(!dmn_handle_match(event->thread, target_thread.dmn_handle) || !target_thread_is_on_user_bp_and_trap_net_trap))
CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)event->user_data;
hit_user_bp = 1;
}
for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next)
{
DMN_Trap *trap = n->v;
DMN_Trap *opl = n->v + n->count;
for(;trap < opl; trap += 1)
{
CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)trap->id;
hit_user_bp = 1;
if(user_bp != 0 && user_bp->condition.size != 0)
if(dmn_handle_match(trap->process, event->process) &&
trap->vaddr == event->instruction_pointer &&
(!dmn_handle_match(event->thread, target_thread.dmn_handle) || !target_thread_is_on_user_bp_and_trap_net_trap))
{
str8_list_push(temp.arena, &conditions, user_bp->condition);
CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)trap->id;
hit_user_bp = 1;
if(user_bp != 0 && user_bp->condition.size != 0)
{
str8_list_push(temp.arena, &conditions, user_bp->condition);
}
}
}
}
@@ -6090,6 +6099,11 @@ ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg)
event->exception_kind = ctrl_exception_kind_from_dmn(stop_event->exception_kind);
event->vaddr_rng = r1u64(stop_event->address, stop_event->address);
event->rip_vaddr = stop_event->instruction_pointer;
if(stop_cause == CTRL_EventCause_UserBreakpoint && stop_event->user_data != 0)
{
CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)stop_event->user_data;
event->u64_code = user_bp->id;
}
ctrl_c2u_push_events(&evts);
}
+1
View File
@@ -281,6 +281,7 @@ struct CTRL_UserBreakpoint
{
CTRL_UserBreakpointKind kind;
CTRL_UserBreakpointFlags flags;
U64 id;
String8 string;
TxtPt pt;
U64 size;
+3
View File
@@ -1795,6 +1795,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P
evt->cause = cause;
evt->thread = thread->kind == CTRL_EntityKind_Thread ? thread->handle : ctrl_handle_zero();
evt->vaddr = event->rip_vaddr;
evt->id = event->u64_code;
}
}break;
@@ -2417,6 +2418,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P
{
CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_FileNameAndLineColNumber};
ctrl_user_bp.flags = ctrl_bp_flags;
ctrl_user_bp.id = bp->id;
ctrl_user_bp.string = n->string;
ctrl_user_bp.pt = bp->pt;
ctrl_user_bp.condition = bp->condition;
@@ -2430,6 +2432,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P
{
CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_Expression};
ctrl_user_bp.flags = ctrl_bp_flags;
ctrl_user_bp.id = bp->id;
ctrl_user_bp.string = bp->vaddr_expr;
ctrl_user_bp.condition = bp->condition;
ctrl_user_bp.size = bp->size;
+2
View File
@@ -40,6 +40,7 @@ typedef struct D_Breakpoint D_Breakpoint;
struct D_Breakpoint
{
D_BreakpointFlags flags;
U64 id;
String8 file_path;
TxtPt pt;
String8 vaddr_expr;
@@ -98,6 +99,7 @@ struct D_Event
CTRL_Handle thread;
U64 vaddr;
U64 code;
U64 id;
};
typedef struct D_EventNode D_EventNode;
+146 -39
View File
@@ -1568,51 +1568,53 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
}
}
//////////////////////////
//- rjf: gather all flagged traps, bucketed by process
//
typedef struct DMN_FlaggedTrapTask DMN_FlaggedTrapTask;
struct DMN_FlaggedTrapTask
{
DMN_FlaggedTrapTask *next;
DMN_Handle process;
DMN_TrapChunkList traps;
};
DMN_FlaggedTrapTask *first_flagged_trap_task = 0;
DMN_FlaggedTrapTask *last_flagged_trap_task= 0;
for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next)
{
for(U64 n_idx = 0; n_idx < n->count; n_idx += 1)
{
DMN_Trap *trap = n->v+n_idx;
if(trap->flags != 0)
{
DMN_FlaggedTrapTask *task = 0;
for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next)
{
if(dmn_handle_match(t->process, trap->process))
{
task = t;
break;
}
}
if(task == 0)
{
task = push_array(scratch.arena, DMN_FlaggedTrapTask, 1);
SLLQueuePush(first_flagged_trap_task, last_flagged_trap_task, task);
task->process = trap->process;
}
dmn_trap_chunk_list_push(scratch.arena, &task->traps, 8, trap);
}
}
}
//////////////////////////
//- rjf: write all debug register states, for flagged-traps
//
ProfScope("write all debug register states, for flagged-traps")
{
//- rjf: gather all flagged traps, bucketed by process
typedef struct DMN_FlaggedTrapTask DMN_FlaggedTrapTask;
struct DMN_FlaggedTrapTask
{
DMN_FlaggedTrapTask *next;
DMN_Handle process;
DMN_TrapChunkList traps;
};
DMN_FlaggedTrapTask *first_task = 0;
DMN_FlaggedTrapTask *last_task = 0;
for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next)
{
for(U64 n_idx = 0; n_idx < n->count; n_idx += 1)
{
DMN_Trap *trap = n->v+n_idx;
if(trap->flags != 0)
{
DMN_FlaggedTrapTask *task = 0;
for(DMN_FlaggedTrapTask *t = first_task; t != 0; t = t->next)
{
if(dmn_handle_match(t->process, trap->process))
{
task = t;
break;
}
}
if(task == 0)
{
task = push_array(scratch.arena, DMN_FlaggedTrapTask, 1);
SLLQueuePush(first_task, last_task, task);
task->process = trap->process;
}
dmn_trap_chunk_list_push(scratch.arena, &task->traps, 8, trap);
}
}
}
//- rjf: for each flagged trap task, iterate all threads in the
// associated process, and prepare debug registers accordingly
for(DMN_FlaggedTrapTask *t = first_task; t != 0; t = t->next)
for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next)
{
DMN_Handle process = t->process;
DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process);
@@ -1648,9 +1650,11 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
case 3:{addr_reg = &regs.dr3;}break;
}
addr_reg->u64 = trap->vaddr;
regs.dr7.u64 |= bit9|bit10|bit11;
regs.dr7.u64 |= (1ull << (trap_idx*2));
regs.dr7.u64 |= (1ull << (trap_idx*2+1));
// NOTE(rjf): global-enable regs.dr7.u64 |= (1ull << (trap_idx*2+1));
regs.dr7.u64 &= ~((U64)(bit17|bit18|bit19|bit20) << (trap_idx*4));
regs.dr7.u64 &= ~((U64)(bit15|bit16));
switch(trap->flags)
{
case DMN_TrapFlag_BreakOnExecute:
@@ -2217,6 +2221,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
//- rjf: check if this trap is a usage-code-specified trap or something else
B32 hit_user_trap = 0;
U64 user_trap_id = 0;
if(is_trap)
{
for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next)
@@ -2226,6 +2231,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
if(dmn_handle_match(n->v[idx].process, dmn_w32_handle_from_entity(process)) && n->v[idx].vaddr == instruction_pointer)
{
hit_user_trap = 1;
user_trap_id = n->v[idx].id;
break;
}
}
@@ -2314,6 +2320,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
e->code = exception->ExceptionCode;
e->flags = exception->ExceptionFlags;
e->instruction_pointer = (U64)exception->ExceptionAddress;
e->user_data = user_trap_id;
//- rjf: fill according to exception code
switch(exception->ExceptionCode)
@@ -2343,6 +2350,71 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
case DMN_W32_EXCEPTION_SINGLE_STEP:
{
e->kind = DMN_EventKind_SingleStep;
// NOTE(rjf): data breakpoints are reported via single-steps
// over the instructions which caused the breakpoint to be
// hit - so if we have data breakpoints set, we need to
// check this thread's debug registers, to determine if this
// is a regular single-step or a data breakpoint hit.
if(first_flagged_trap_task != 0)
{
// rjf: first determine the flagged trap index
U64 flagged_trap_idx = 0;
switch(thread->arch)
{
default:{NotImplemented;}break;
case Arch_x64:
{
REGS_RegBlockX64 regs = {0};
dmn_w32_thread_read_reg_block(thread->arch, thread->handle, &regs);
if(regs.dr6.u64 & 0xF)
{
e->kind = DMN_EventKind_Breakpoint;
if(0){}
else if(regs.dr7.u64 & (1ull<<0) && regs.dr6.u64 & (1ull<<0)) { flagged_trap_idx = 1; }
else if(regs.dr7.u64 & (1ull<<1) && regs.dr6.u64 & (1ull<<1)) { flagged_trap_idx = 2; }
else if(regs.dr7.u64 & (1ull<<2) && regs.dr6.u64 & (1ull<<2)) { flagged_trap_idx = 3; }
else if(regs.dr7.u64 & (1ull<<3) && regs.dr6.u64 & (1ull<<3)) { flagged_trap_idx = 3; }
}
}break;
}
// rjf: find the flagged trap task for this thread's process
DMN_W32_Entity *process = thread->parent;
DMN_FlaggedTrapTask *task = 0;
for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next)
{
if(dmn_handle_match(t->process, dmn_w32_handle_from_entity(process)))
{
task = t;
break;
}
}
// rjf: find the trap
DMN_Trap *trap = 0;
if(task != 0)
{
U64 trap_idx = 0;
for(DMN_TrapChunkNode *n = task->traps.first; n != 0; n = n->next)
{
for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1)
{
if(trap_idx == flagged_trap_idx)
{
trap = &n->v[n_idx];
break;
}
}
}
}
// rjf: fill event based on trap
if(trap != 0)
{
e->user_data = trap->id;
}
}
}break;
//- rjf: fill throw info
@@ -2563,7 +2635,9 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
}
}
////////////////////////
//- rjf: gather new thread-names
//
ProfScope("gather new thread names") if(dmn_w32_GetThreadDescription != 0)
{
for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first;
@@ -2628,6 +2702,39 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
}
}
//////////////////////////
//- rjf: clear all debug register states, for flagged-traps
//
ProfScope("clear all debug register states, for flagged-traps")
{
for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next)
{
DMN_Handle process = t->process;
DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process);
for(DMN_W32_Entity *child = process_entity->first;
child != &dmn_w32_entity_nil;
child = child->next)
{
if(child->kind == DMN_W32_EntityKind_Thread)
{
switch(child->arch)
{
default:{}break;
//- rjf: x64
case Arch_x64:
{
REGS_RegBlockX64 regs = {0};
dmn_w32_thread_read_reg_block(child->arch, child->handle, &regs);
regs.dr7.u64 = 0;
dmn_w32_thread_write_reg_block(child->arch, child->handle, &regs);
}break;
}
}
}
}
}
//////////////////////////
//- rjf: unset single step bit
//
+4 -21
View File
@@ -15804,6 +15804,7 @@ Z(getting_started)
//- rjf: fill breakpoint
D_Breakpoint *dst_bp = &breakpoints.v[idx];
dst_bp->flags = flags;
dst_bp->id = src_bp->id;
dst_bp->file_path = src_bp_loc.file_path;
dst_bp->pt = src_bp_loc.pt;
dst_bp->vaddr_expr = src_bp_loc.expr;
@@ -15944,31 +15945,13 @@ Z(getting_started)
// rjf: increment breakpoint hit counts
if(evt->cause == D_EventCause_UserBreakpoint)
{
RD_CfgList bps = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("breakpoint"));
for(RD_CfgNode *n = bps.first; n != 0; n = n->next)
RD_Cfg *bp = rd_cfg_from_id(evt->id);
if(bp != &rd_nil_cfg)
{
RD_Cfg *bp = n->v;
RD_Cfg *hit_count_root = rd_cfg_child_from_string_or_alloc(bp, str8_lit("hit_count"));
U64 hit_count = 0;
try_u64_from_str8_c_rules(hit_count_root->first->string, &hit_count);
RD_Location loc = rd_location_from_cfg(bp);
D_LineList loc_lines = d_lines_from_file_path_line_num(scratch.arena, loc.file_path, loc.pt.line);
E_Value loc_value = e_value_from_string(loc.expr);
if(loc_lines.first != 0)
{
for(D_LineNode *n = loc_lines.first; n != 0; n = n->next)
{
if(contains_1u64(n->v.voff_range, voff))
{
hit_count += 1;
break;
}
}
}
else if(loc_value.u64 != 0 && vaddr == loc_value.u64)
{
hit_count += 1;
}
hit_count += 1;
rd_cfg_new_replacef(hit_count_root, "%I64u", hit_count);
}
}