From e7243a2c2856494967aadd836778f14a913fa3f8 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 11 Jan 2024 16:02:27 -0800 Subject: [PATCH] GetThreadDescription (win 10+ thread name) API support --- src/ctrl/ctrl_core.c | 1 + src/demon/win32/demon_os_win32.c | 74 +++++++++++++++ src/demon/win32/demon_os_win32.h | 4 + src/df/core/df_core.c | 32 ++++++- src/df/core/df_core.h | 1 + src/raddbg/raddbg.cpp | 152 ++----------------------------- 6 files changed, 121 insertions(+), 143 deletions(-) diff --git a/src/ctrl/ctrl_core.c b/src/ctrl/ctrl_core.c index 7ef616da..bcc9c1c6 100644 --- a/src/ctrl/ctrl_core.c +++ b/src/ctrl/ctrl_core.c @@ -1516,6 +1516,7 @@ ctrl_thread__next_demon_event(Arena *arena, CTRL_Msg *msg, DEMON_RunCtrls *run_c out_evt->stack_base = demon_stack_base_vaddr_from_thread(event->thread); out_evt->tls_root = demon_tls_root_vaddr_from_thread(event->thread); out_evt->rip_vaddr = event->instruction_pointer; + out_evt->string = event->string; }break; case DEMON_EventKind_LoadModule: { diff --git a/src/demon/win32/demon_os_win32.c b/src/demon/win32/demon_os_win32.c index 6f5876e0..60600a4b 100644 --- a/src/demon/win32/demon_os_win32.c +++ b/src/demon/win32/demon_os_win32.c @@ -4,6 +4,8 @@ //////////////////////////////// //~ rjf: Globals +global GetThreadDescriptionFunctionType *demon_w32_GetThreadDescription = 0; + global B32 demon_w32_resume_needed = 0; global DWORD demon_w32_resume_pid = 0; global DWORD demon_w32_resume_tid = 0; @@ -26,6 +28,17 @@ global String8List demon_w32_environment = {0}; //////////////////////////////// //~ rjf: Helpers +internal U64 +demon_w32_hash_from_string(String8 string) +{ + U64 result = 5381; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + internal DEMON_W32_Ext* demon_w32_ext_alloc(void){ DEMON_W32_Ext *result = demon_w32_proc_ext_free; @@ -351,6 +364,11 @@ demon_os_init(void){ demon_w32_ext_arena = arena_alloc(); demon_w32_detach_proc_arena = arena_alloc(); + // rjf: load Windows 10+ GetThreadDescription API + { + demon_w32_GetThreadDescription = (GetThreadDescriptionFunctionType *)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "GetThreadDescription"); + } + // rjf: setup environment variables { CHAR *this_proc_env = GetEnvironmentStrings(); @@ -771,6 +789,19 @@ demon_os_run(Arena *arena, DEMON_OS_RunCtrls *ctrls){ DWORD sus_result = SuspendThread(thread_ext->thread.handle); (void)sus_result; + // rjf: unpack thread name + String8 thread_name = {0}; + if(demon_w32_GetThreadDescription != 0) + { + WCHAR *thread_name_w = 0; + HRESULT hr = demon_w32_GetThreadDescription(evt.u.CreateThread.hThread, &thread_name_w); + if(SUCCEEDED(hr)) + { + thread_name = str8_from_16(arena, str16_cstring((U16 *)thread_name_w)); + LocalFree(thread_name_w); + } + } + // rjf: determine if this is the halter thread B32 is_halter = (evt.dwThreadId == demon_w32_halter_thread_id); @@ -780,6 +811,7 @@ demon_os_run(Arena *arena, DEMON_OS_RunCtrls *ctrls){ e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->code = evt.dwThreadId; + e->string = thread_name; } } }break; @@ -1140,6 +1172,48 @@ demon_os_run(Arena *arena, DEMON_OS_RunCtrls *ctrls){ } } + //- rjf: gather new thread-names + if(demon_w32_GetThreadDescription != 0) + { + for(DEMON_Entity *process = demon_ent_root->first; + process != 0; + process = process->next) + { + if(process->kind != DEMON_EntityKind_Process) { continue; } + for(DEMON_Entity *thread = process->first; + thread != 0; + thread = thread->next) + { + if(thread->kind != DEMON_EntityKind_Thread) { continue; } + DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); + if(thread_ext->thread.last_name_hash == 0 || + thread_ext->thread.name_gather_time_us+1000000 <= os_now_microseconds()) + { + String8 name = {0}; + { + WCHAR *thread_name_w = 0; + HRESULT hr = demon_w32_GetThreadDescription(thread_ext->thread.handle, &thread_name_w); + if(SUCCEEDED(hr)) + { + name = str8_from_16(scratch.arena, str16_cstring((U16 *)thread_name_w)); + LocalFree(thread_name_w); + } + } + U64 name_hash = demon_w32_hash_from_string(name); + if(name.size != 0 && name_hash != thread_ext->thread.last_name_hash) + { + DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_SetThreadName); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(thread); + e->string = push_str8_copy(arena, name); + } + thread_ext->thread.name_gather_time_us = os_now_microseconds(); + thread_ext->thread.last_name_hash = name_hash; + } + } + } + } + // TODO(allen): handle errors? if (!got_new_event) ? if (!good_state) ? // TODO(allen): per-Architecture diff --git a/src/demon/win32/demon_os_win32.h b/src/demon/win32/demon_os_win32.h index e62a7662..3fcea46a 100644 --- a/src/demon/win32/demon_os_win32.h +++ b/src/demon/win32/demon_os_win32.h @@ -35,6 +35,8 @@ union DEMON_W32_Ext struct{ HANDLE handle; U64 thread_local_base; + U64 last_name_hash; + U64 name_gather_time_us; } thread; struct{ HANDLE handle; @@ -68,6 +70,7 @@ struct DEMON_W32_EntityNode DEMON_Entity *entity; }; +typedef HRESULT GetThreadDescriptionFunctionType(HANDLE hThread, WCHAR **ppszThreadDescription); //////////////////////////////// //~ NOTE(allen): Win32 Demon Exceptions @@ -352,6 +355,7 @@ struct DEMON_PeOptionalHeader32Plus //////////////////////////////// //~ rjf: Helpers +internal U64 demon_w32_hash_from_string(String8 string); internal DEMON_W32_Ext* demon_w32_ext_alloc(void); internal DEMON_W32_Ext* demon_w32_ext(DEMON_Entity *entity); diff --git a/src/df/core/df_core.c b/src/df/core/df_core.c index ba5db393..adc8b628 100644 --- a/src/df/core/df_core.c +++ b/src/df/core/df_core.c @@ -1852,6 +1852,7 @@ df_entity_change_parent(DF_StateDeltaHistory *hist, DF_Entity *entity, DF_Entity internal void df_entity_equip_txt_pt(DF_Entity *entity, TxtPt point) { + df_require_entity_nonnil(entity, return); entity->text_point = point; entity->flags |= DF_EntityFlag_HasTextPoint; df_entity_notify_mutation(entity); @@ -1860,6 +1861,7 @@ df_entity_equip_txt_pt(DF_Entity *entity, TxtPt point) internal void df_entity_equip_txt_pt_alt(DF_Entity *entity, TxtPt point) { + df_require_entity_nonnil(entity, return); entity->text_point_alt = point; entity->flags |= DF_EntityFlag_HasTextPointAlt; df_entity_notify_mutation(entity); @@ -1868,6 +1870,7 @@ df_entity_equip_txt_pt_alt(DF_Entity *entity, TxtPt point) internal void df_entity_equip_entity_handle(DF_Entity *entity, DF_Handle handle) { + df_require_entity_nonnil(entity, return); entity->entity_handle = handle; entity->flags |= DF_EntityFlag_HasEntityHandle; df_entity_notify_mutation(entity); @@ -1876,6 +1879,7 @@ df_entity_equip_entity_handle(DF_Entity *entity, DF_Handle handle) internal void df_entity_equip_b32(DF_Entity *entity, B32 b32) { + df_require_entity_nonnil(entity, return); entity->b32 = b32; entity->flags |= DF_EntityFlag_HasB32; df_entity_notify_mutation(entity); @@ -1884,6 +1888,7 @@ df_entity_equip_b32(DF_Entity *entity, B32 b32) internal void df_entity_equip_u64(DF_Entity *entity, U64 u64) { + df_require_entity_nonnil(entity, return); entity->u64 = u64; entity->flags |= DF_EntityFlag_HasU64; df_entity_notify_mutation(entity); @@ -1892,6 +1897,7 @@ df_entity_equip_u64(DF_Entity *entity, U64 u64) internal void df_entity_equip_rng1u64(DF_Entity *entity, Rng1U64 range) { + df_require_entity_nonnil(entity, return); entity->rng1u64 = range; entity->flags |= DF_EntityFlag_HasRng1U64; df_entity_notify_mutation(entity); @@ -1900,6 +1906,7 @@ df_entity_equip_rng1u64(DF_Entity *entity, Rng1U64 range) internal void df_entity_equip_color_rgba(DF_Entity *entity, Vec4F32 rgba) { + df_require_entity_nonnil(entity, return); Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); Vec3F32 hsv = hsv_from_rgb(rgb); Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); @@ -1909,6 +1916,7 @@ df_entity_equip_color_rgba(DF_Entity *entity, Vec4F32 rgba) internal void df_entity_equip_color_hsva(DF_Entity *entity, Vec4F32 hsva) { + df_require_entity_nonnil(entity, return); entity->color_hsva = hsva; entity->flags |= DF_EntityFlag_HasColor; df_entity_notify_mutation(entity); @@ -1917,6 +1925,7 @@ df_entity_equip_color_hsva(DF_Entity *entity, Vec4F32 hsva) internal void df_entity_equip_death_timer(DF_Entity *entity, F32 seconds_til_death) { + df_require_entity_nonnil(entity, return); entity->flags |= DF_EntityFlag_DiesWithTime; entity->life_left = seconds_til_death; df_entity_notify_mutation(entity); @@ -1925,6 +1934,7 @@ df_entity_equip_death_timer(DF_Entity *entity, F32 seconds_til_death) internal void df_entity_equip_cfg_src(DF_Entity *entity, DF_CfgSrc cfg_src) { + df_require_entity_nonnil(entity, return); entity->cfg_src = cfg_src; df_entity_notify_mutation(entity); } @@ -1934,6 +1944,7 @@ df_entity_equip_cfg_src(DF_Entity *entity, DF_CfgSrc cfg_src) internal void df_entity_equip_ctrl_machine_id(DF_Entity *entity, CTRL_MachineID machine_id) { + df_require_entity_nonnil(entity, return); entity->ctrl_machine_id = machine_id; entity->flags |= DF_EntityFlag_HasCtrlMachineID; df_entity_notify_mutation(entity); @@ -1942,6 +1953,7 @@ df_entity_equip_ctrl_machine_id(DF_Entity *entity, CTRL_MachineID machine_id) internal void df_entity_equip_ctrl_handle(DF_Entity *entity, CTRL_Handle handle) { + df_require_entity_nonnil(entity, return); entity->ctrl_handle = handle; entity->flags |= DF_EntityFlag_HasCtrlHandle; df_entity_notify_mutation(entity); @@ -1950,6 +1962,7 @@ df_entity_equip_ctrl_handle(DF_Entity *entity, CTRL_Handle handle) internal void df_entity_equip_arch(DF_Entity *entity, Architecture arch) { + df_require_entity_nonnil(entity, return); entity->arch = arch; entity->flags |= DF_EntityFlag_HasArch; df_entity_notify_mutation(entity); @@ -1958,6 +1971,7 @@ df_entity_equip_arch(DF_Entity *entity, Architecture arch) internal void df_entity_equip_ctrl_id(DF_Entity *entity, U32 id) { + df_require_entity_nonnil(entity, return); entity->ctrl_id = id; entity->flags |= DF_EntityFlag_HasCtrlID; df_entity_notify_mutation(entity); @@ -1966,6 +1980,7 @@ df_entity_equip_ctrl_id(DF_Entity *entity, U32 id) internal void df_entity_equip_stack_base(DF_Entity *entity, U64 stack_base) { + df_require_entity_nonnil(entity, return); entity->stack_base = stack_base; entity->flags |= DF_EntityFlag_HasStackBase; df_entity_notify_mutation(entity); @@ -1974,6 +1989,7 @@ df_entity_equip_stack_base(DF_Entity *entity, U64 stack_base) internal void df_entity_equip_tls_root(DF_Entity *entity, U64 tls_root) { + df_require_entity_nonnil(entity, return); entity->tls_root = tls_root; entity->flags |= DF_EntityFlag_HasTLSRoot; df_entity_notify_mutation(entity); @@ -1982,6 +1998,7 @@ df_entity_equip_tls_root(DF_Entity *entity, U64 tls_root) internal void df_entity_equip_vaddr_rng(DF_Entity *entity, Rng1U64 range) { + df_require_entity_nonnil(entity, return); entity->vaddr_rng = range; entity->flags |= DF_EntityFlag_HasVAddrRng; df_entity_notify_mutation(entity); @@ -1990,6 +2007,7 @@ df_entity_equip_vaddr_rng(DF_Entity *entity, Rng1U64 range) internal void df_entity_equip_vaddr(DF_Entity *entity, U64 vaddr) { + df_require_entity_nonnil(entity, return); entity->vaddr = vaddr; entity->flags |= DF_EntityFlag_HasVAddr; df_entity_notify_mutation(entity); @@ -2000,7 +2018,19 @@ df_entity_equip_vaddr(DF_Entity *entity, U64 vaddr) internal void df_entity_equip_name(DF_StateDeltaHistory *hist, DF_Entity *entity, String8 name) { - entity->name = df_name_alloc(hist, name); + df_require_entity_nonnil(entity, return); + if(entity->name.size != 0) + { + df_name_release(hist, entity->name); + } + if(name.size != 0) + { + entity->name = df_name_alloc(hist, name); + } + else + { + entity->name = str8_zero(); + } entity->name_generation += 1; df_entity_notify_mutation(entity); } diff --git a/src/df/core/df_core.h b/src/df/core/df_core.h index b9a0bf85..ebaa8101 100644 --- a/src/df/core/df_core.h +++ b/src/df/core/df_core.h @@ -1355,6 +1355,7 @@ internal DF_CoreCmdKind df_core_cmd_kind_from_string(String8 string); //- rjf: nil internal B32 df_entity_is_nil(DF_Entity *entity); +#define df_require_entity_nonnil(entity, if_nil_stmts) do{if(df_entity_is_nil(entity)){if_nil_stmts;}}while(0) //- rjf: handle <-> entity conversions internal U64 df_index_from_entity(DF_Entity *entity); diff --git a/src/raddbg/raddbg.cpp b/src/raddbg/raddbg.cpp index 1eb044fd..7d48a835 100644 --- a/src/raddbg/raddbg.cpp +++ b/src/raddbg/raddbg.cpp @@ -32,12 +32,6 @@ // at all where the font ever was) The font path will now remain "stable" // in the sense that it won't rewrite it anymore. But you cannot use the // debugger because it's the wrong font path, so you get no text. -// [x] When I click in the theme window on something like "Code (Meta)", it -// opens up a tiny window, which only shows a tiny bit of what I imagine -// is the entire color picker? It makes the picker unusable because more -// of the UI for it is clipped and cannot be accessed. -// [x] Clicking anywhere in the color picker that isn't a button closes the -// color picker for some reason? // [ ] In a "hover watch" (where you hover over a variable and it shows a pop- // up watch window), if you expand an item near the bottom of the listing, // it will be clipped to the bottom of the listing instead of showing the @@ -45,8 +39,6 @@ // actually visible) // // [ ] @bug @feature @cleanup general feedback from casey -// [x] ** I don't like how panels get dimmer when they are not active, because -// it makes them harder for me to read. Is there a way to turn this off? // [ ] ** I couldn't figure out how to really view threads in the debugger. // The only place I found a thread list was in "The Scheduler", but it // only lists threads by ID, which is hard to use. I can hover over them @@ -211,6 +203,8 @@ // [ ] @feature search-in-all-files // [ ] @feature memory view mutation controls // [ ] @feature memory view user-made annotations +// +// [ ] @feature auto-scroll output window //////////////////////////////// //~ rjf: 2024/2 tasks @@ -316,140 +310,14 @@ //////////////////////////////// //~ rjf: Completed Tasks // -// [x] adding watches to source locations -// [x] tab overflow buttons -// [x] "working set" of targets, just used 'enabled' slot on target entities -// [x] convert hover-eval ui to a frontend-wide global ui concept, so that we can have watch-hover trees from anywhere -// [x] annotate code slices with relevant watches -// [x] ability to duplicate targets easily -// [x] ability to label targets separately from their EXE -// -// [x] txti layer needs lexing -// [x] dbgi autoconversion layer needs to work reliably -// [x] new dbgi system on frontend -// [x] new bini system on frontend -// [x] new dbgi system needs in-parallel typegraph builders -// [x] eval is busted -// [x] fail or debug non-text -// [x] clean loading -// [x] watch pins -// [x] snapping should still work if a file is loading while the snap first occurs -// [x] keyboard usage (set-next-statement, run-to-line, etc.) -// [x] go-to-name (both file & symbol) -// -// [x] in-memory disassembly view -// [x] visualize conversion tasks as they occur -// [x] raddbg.exe self-sufficiency => built-in converter execution mode -// [x] move path-mapping fallback from pending-entity into code view -// [x] per-target entry point overrides -// [x] logic on when double click on callstack entry goes to source or disassembly is very confusing, -// because it seems it matters whether disassembly tab is in same panel as source, or is placed in -// different panel in VS double clicking on stack stays in disassembly if disassembly tab is currently -// active. Otherwise if source is active, it goes to source code (regardless if disasm is open/visible -// or not) -// [x] global variable (just regular C++ bool in the same file) does not show value when hovering over source -// code, or when put into watch window. -// -// [x] debugger ui thread needs wakeup when a debug event is hit -// [x] ctrl thread needs to re-resolve breakpoints as modules/threads come in during a run -// [x] automatically pull in members from containing struct in member functions -// when looking up locals -// [x] iron out weird state machine bugs with hover-eval -// -// [x] thread-local eval -// [x] tabs in source code render as squares -// -// [x] breakpoint stop-conditions -// [x] first-chance exception hitting (d3d11 as an example offender) -// [x] soft-halt refresh -// -// [x] conditional breakpoints cannot be submitted if they don't compile -// need to (a) visualize and (b) equip the ctrl thread with a 'halt on -// new debug info feature', which can be done repeatedly until something -// compiles (?) -// -// [x] outputdebugstring logs -// -// [x] memory view -// -// [x] when application starts then the focus is on cmd.exe window of application not -// debugger. This means if I press F11 to step into main(), the application cmd.exe will go fullscreen -// hiding debugger (F11 key for ConEmu I use makes it go fullscreen) -// [x] I'd prefer if ctrl+click on word would go to function definition, not double click -// double clicking in source code should select word - which easy way to double click and ctrl+c to copy -// it, pretty much standard thing in any text editorr/viewer and triple click for selecting whole line -// [x] command line DF_CfgSrc -> explicitly visualize as temporary, provide UI -// path to make permanent (in either user or profile?) -// -// [x] memory view annotations: bytes in a range of memory actually have a -// *stack* of possible interpretations, for any particular low-level -// representation (assuming 1 is fine for this case). a U32 member in -// a local struct on the stack has 3 layers: U32 -> local -> stack. -// each of these are useful information about that U32's range of bytes. -// so, the memory view annotation system ought to be expanded to support- -// ing stacks of annotations, and gracefully visualizing multiple nested -// ones. after that is supported, we can use the type info to derive the -// lowest level representation of some bytes. each stack can only have -// one low-level type. that low-level type, then, can be used to directly -// interpret the bytes - the others can be used to show the "conceptual -// stack". -// -// [x] entity tree ui replacement (scheduler, modules, breakpoints, pins) -// [x] theme menu -// [x] complete transition to UI_ScrollPt coordinate space scrolling - eliminate -// old scrolls & scroll regions -// [x] settings menu -// [x] exception filter settings & controls in ctrl layer -// -// [x] @polish @cleanup convert eval/watch to scroll list -// [x] @feature memory view keyboard navigation -// [x] @bug fix general ui line edit editing rules - conflicting with navigation, etc. -// [x] @polish have expander space even if not used in watch window -// [x] @bug references do not expand properly -// [x] @bug panel deletion improper size bug -// [x] @bug panel serialization/deserialization bug? -// [x] unfinished char/string literal lexing -// -// [x] C++ problems in watch window: evaluating "this" shows no members. The type seems correct reference -// to struct (which is local variable) does not show any members, like I have "tm_mem_zone_rt& z = ..." -// in source code, and doing "z" produces no children, nor does "&z" but then at least it shows correct -// address as value. if I ask for member of this when currently inside of member function (like "m_zone_stack") -// it shows "unknown identifier m_zone_stack" but "this->m_zone_stack" works fine! -// [x] if I write x'z in watch window and press enter, it only shows x and loses 'z - it does not show it, but when I edit cell then 'z comes back. It pretends I entered just x and shows x value. -// [x] it seems the glyph advance for default font size is kind of wrong, as characters are a blitted a bit over top of each other -// -// [x] @bug deleting a watch tree while it is expanded causes cursor to go back to first row - cannot simply increment cursor. maybe keep in same spot, but rebuild viz blocks? -// [x] @bug do not squish partially-cut-off view rule block uis -// [x] @feature "solo step", or "solo mode" freeze-all-unselected-threads-on-step-commands -// [x] @feature typing autocomplete lister -// [x] @feature directional navigation of panel focus -// [x] @cleanup @feature cache eviction in texture cache & hash store -// -//- 2023/12/7 -// -// [x] @bug hash store cache eviction can only work if user never blindly tries to go from hash -> data, because -// they must be able to retry... hmm... -// [x] txt cell revamp. keyboard focus in both default & non w/ multiple options, helper lister, etc. -// [x] `bitmap:(w:width, h:height, [fmt:fmt])` - interpret memory as raw bitmap data -// [x] `geo:n[ topology stride]` - interpret memory as geometry -// [x] cursor helper -> upgraded txt cell. classify inputs & show dropdowns (locals, globals, types, view rules, etc.) -// [x] @bug page-up and page-down in src view, when near the end of file -// -//- 2023/12/8 -// -// [x] @bug parse `unsigned int` correctly in eval parser -// [x] @bug ., + operators should work on registers -// [x] @bug straighten out register eval problems -// [x] @cleanup finish ui_em transition -// -//- 2023/12/22 -// -// [x] @bug set-bp-while-running seems to not resume after soft-halt, might be a soft-halt bug -// [x] @bug weird view snapping in watch scrolling down -// -//- 2024/01/10 -// -// [x] @feature allow `,count`, `,x`, `,b` style watch window expression extensions, which add to view rule, for VS-like behavior fastpaths +// [x] ** I don't like how panels get dimmer when they are not active, because +// it makes them harder for me to read. Is there a way to turn this off? +// [x] When I click in the theme window on something like "Code (Meta)", it +// opens up a tiny window, which only shows a tiny bit of what I imagine +// is the entire color picker? It makes the picker unusable because more +// of the UI for it is clipped and cannot be accessed. +// [x] Clicking anywhere in the color picker that isn't a button closes the +// color picker for some reason? //////////////////////////////// //~ rjf: Includes