From 6e6a2df0cbb41908323e6471e75928f8f30c5eee Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 16 Jul 2025 15:38:27 -0700 Subject: [PATCH] implement 'priority thread' in demon, to prefer debug events from selected thread - greatly improves multithreaded stepping --- src/ctrl/ctrl_core.c | 1 + src/demon/demon_core.h | 1 + src/demon/win32/demon_core_win32.c | 98 ++++++++++++++++++++---------- src/raddbg/raddbg_main.c | 12 ++-- 4 files changed, 73 insertions(+), 39 deletions(-) diff --git a/src/ctrl/ctrl_core.c b/src/ctrl/ctrl_core.c index cf42ce18..60293720 100644 --- a/src/ctrl/ctrl_core.c +++ b/src/ctrl/ctrl_core.c @@ -5870,6 +5870,7 @@ ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) //- rjf: setup run controls // DMN_RunCtrls run_ctrls = {0}; + run_ctrls.priority_thread = target_thread.dmn_handle; run_ctrls.ignore_previous_exception = 1; run_ctrls.run_entity_count = frozen_threads.count; run_ctrls.run_entities = push_array(scratch.arena, DMN_Handle, run_ctrls.run_entity_count); diff --git a/src/demon/demon_core.h b/src/demon/demon_core.h index 21d8a5a4..b46f316b 100644 --- a/src/demon/demon_core.h +++ b/src/demon/demon_core.h @@ -139,6 +139,7 @@ struct DMN_TrapChunkList typedef struct DMN_RunCtrls DMN_RunCtrls; struct DMN_RunCtrls { + DMN_Handle priority_thread; DMN_Handle single_step_thread; B8 ignore_previous_exception; B8 run_entities_are_unfrozen; diff --git a/src/demon/win32/demon_core_win32.c b/src/demon/win32/demon_core_win32.c index f27ba706..100cc98c 100644 --- a/src/demon/win32/demon_core_win32.c +++ b/src/demon/win32/demon_core_win32.c @@ -1801,40 +1801,14 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } - ////////////////////////// - //- rjf: resume threads which will run - // - ProfScope("resume threads which will run") - { - for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) - { - DMN_W32_Entity *thread = n->v; - DWORD resume_result = ResumeThread(thread->handle); - switch(resume_result) - { - case 0xffffffffu: - { - // TODO(rjf): error - unknown cause. need to do GetLastError, FormatMessage - }break; - default: - { - DWORD desired_counter = 0; - DWORD current_counter = resume_result - 1; - if(current_counter != desired_counter) - { - // NOTE(rjf): Warning. The user has manually suspended this thread, - // so even though from Demon's perspective it thinks this thread - // should run, it will not, because the user has manually called - // SuspendThread or used CREATE_SUSPENDED or whatever. - } - }break; - } - } - } - ////////////////////////// //- rjf: loop, consume win32 debug events until we produce the relevant demon events // + B32 priority_mode = !dmn_handle_match(dmn_handle_zero(), ctrls->priority_thread); + B32 did_priority_mode = priority_mode; + B32 do_threads_resume = 1; + DMN_W32_EntityNode *first_ran_thread = 0; + DMN_W32_EntityNode *last_ran_thread = 0; U64 begin_time = os_now_microseconds(); String8List debug_strings = {0}; DMN_Event *debug_strings_event = 0; @@ -1842,6 +1816,54 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { keep_going = 0; + //////////////////////// + //- rjf: resume threads that we want to run + // + // if priority mode? => first, just resume priority thread + // if not? => resume all non-priority threads + // + if(do_threads_resume) + { + do_threads_resume = 0; + for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) + { + DMN_W32_Entity *thread = n->v; + B32 thread_is_priority = dmn_handle_match(dmn_w32_handle_from_entity(thread), ctrls->priority_thread); + if((priority_mode && !thread_is_priority) || + (!priority_mode && did_priority_mode && thread_is_priority)) + { + continue; + } + DWORD resume_result = ResumeThread(thread->handle); + DMN_W32_EntityNode *n = push_array(scratch.arena, DMN_W32_EntityNode, 1); + SLLQueuePush(first_ran_thread, last_ran_thread, n); + n->v = thread; + switch(resume_result) + { + case 0xffffffffu: + { + // TODO(rjf): error - unknown cause. need to do GetLastError, FormatMessage + }break; + default: + { + DWORD desired_counter = 0; + DWORD current_counter = resume_result - 1; + if(current_counter != desired_counter) + { + // NOTE(rjf): Warning. The user has manually suspended this thread, + // so even though from Demon's perspective it thinks this thread + // should run, it will not, because the user has manually called + // SuspendThread or used CREATE_SUSPENDED or whatever. + } + }break; + } + if(priority_mode && thread_is_priority) + { + break; + } + } + } + //////////////////////// //- rjf: choose win32 resume code // @@ -1878,7 +1900,12 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } if(resume_good) { - evt_good = !!WaitForDebugEvent(&evt, 100); + DWORD wait_ms = 100; + if(priority_mode) + { + wait_ms = 30; + } + evt_good = !!WaitForDebugEvent(&evt, wait_ms); if(evt_good) { dmn_w32_shared->resume_needed = 1; @@ -1891,6 +1918,11 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) (void)err; keep_going = 1; } + if(priority_mode) + { + priority_mode = 0; + do_threads_resume = 1; + } } } @@ -2664,7 +2696,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // ProfScope("suspend threads which ran") { - for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) + for(DMN_W32_EntityNode *n = first_ran_thread; n != 0; n = n->next) { DMN_W32_Entity *thread = n->v; if(thread->kind != DMN_W32_EntityKind_Thread) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 0a41d192..5ef120b9 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -6,7 +6,6 @@ // //- urgent fixes // [ ] hardware breakpoints regression (global eval in ctrl) -// [ ] []string being sized by [0], due to `.` applying to first ^string // //- memory view // [ ] have smaller visible range than entire memory @@ -21,9 +20,6 @@ // [ ] disassembly sometimes has a problem where source line annotations are // periodically removed/inserted... maybe updating on fs change when we // shouldn't, non-deterministic line annotation path? -// [ ] process memory cache sometimes is not correctly updating - best repro -// case so far is (for some reason?) only hover evaluation - only spotted -// on laptop in debug builds. g0 ctrl_bindings.bindings initialization. // //- watch improvements // [ ] *ALL* expressions in watch windows need to be editable. @@ -115,7 +111,6 @@ //- eval improvements // [ ] maybe add extra caching layer to process memory querying? we pay a pretty // heavy cost even to just read 8 bytes... -// [ ] evaluate `foo.bar` symbol names without escape hatch? // [ ] serializing eval view maps (?) // [ ] EVAL LOOKUP RULES -> currently going 0 -> rdis_count, but we need // to prioritize the primary rdi @@ -149,7 +144,6 @@ // [ ] investigate wide-conversion performance // [ ] oversubscribing cores? // [ ] conversion crashes? -// [ ] fastpath lookup to determine debug info relevance? // [ ] live++ investigations - ctrl+alt+f11 in UE? // //- memory usage improvements @@ -189,6 +183,12 @@ // is not correctly incremented. // [x] output: add option for scroll-to-bottom - ensure this shows up in universal ctx menu // [x] auto-annotations for non-locals +// [x] []string being sized by [0], due to `.` applying to first ^string +// [x] process memory cache sometimes is not correctly updating - best repro +// case so far is (for some reason?) only hover evaluation - only spotted +// on laptop in debug builds. g0 ctrl_bindings.bindings initialization. +// [x] evaluate `foo.bar` symbol names without escape hatch? +// [x] fastpath lookup to determine debug info relevance? //////////////////////////////// //~ rjf: Build Options