From 0e19b5d757d88eedd23709a4ea40aa1512a1ff21 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Fri, 20 Dec 2019 20:59:07 +0200 Subject: [PATCH] Support setting OS names of threads on MS-Windows * src/w32fns.c (setup_w32_kbdhook): Don't initialize is_debugger_present here... (globals_of_w32fns): ...initialize it here. Also initialize the new global variable set_thread_description. * src/systhread.c: [WINDOWSNT] Include mbctype.h (w32_set_thread_name): New function. (MS_VC_EXCEPTION): New macro. (THREADNAME_INFO, IsDebuggerPresent_Proc) (SetThreadDescription_Proc): New typedefs. (w32_beginthread_wrapper): Call w32_set_thread_name to set the name of the new thread. * src/thread.h (struct thread_state): New member thread_name. * src/thread.c (Fmake_thread): Set the thread_name field of the new thread object. (run_thread): Free the thread_name member after the thread exits. --- src/systhread.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++- src/thread.c | 6 +++++ src/thread.h | 4 +++ src/w32fns.c | 16 ++++++++--- 4 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/systhread.c b/src/systhread.c index 6f4de536fba..baa30751c79 100644 --- a/src/systhread.c +++ b/src/systhread.c @@ -248,7 +248,8 @@ sys_thread_yield (void) #elif defined (WINDOWSNT) -#include +#include +#include "w32term.h" /* Cannot include because of the local header by the same name, sigh. */ @@ -390,6 +391,65 @@ sys_thread_equal (sys_thread_t t, sys_thread_t u) return t == u; } +/* Special exception used to communicate with a debugger. The name is + taken from example code shown on MSDN. */ +#define MS_VC_EXCEPTION 0x406d1388UL + +/* Structure used to communicate thread name to a debugger. */ +typedef struct _THREADNAME_INFO +{ + DWORD_PTR type; + LPCSTR name; + DWORD_PTR thread_id; + DWORD_PTR reserved; +} THREADNAME_INFO; + +typedef BOOL (WINAPI *IsDebuggerPresent_Proc) (void); +extern IsDebuggerPresent_Proc is_debugger_present; +extern int (WINAPI *pMultiByteToWideChar)(UINT,DWORD,LPCSTR,int,LPWSTR,int); +typedef HRESULT (WINAPI *SetThreadDescription_Proc) + (HANDLE hThread, PCWSTR lpThreadDescription); +extern SetThreadDescription_Proc set_thread_description; + +/* Set the name of the thread identified by its thread ID. */ +static void +w32_set_thread_name (DWORD thread_id, const char *name) +{ + if (!name || !*name) + return; + + /* Use the new API provided since Windows 10, if available. */ + if (set_thread_description) + { + /* GDB pulls only the first 1024 characters of thread's name. */ + wchar_t name_w[1025]; + /* The thread name is encoded in locale's encoding, but + SetThreadDescription wants a wchar_t string. */ + int codepage = _getmbcp (); + if (!codepage) + codepage = GetACP (); + int cnv_result = pMultiByteToWideChar (codepage, MB_ERR_INVALID_CHARS, + name, -1, + name_w, 1025); + if (cnv_result + && set_thread_description (GetCurrentThread (), name_w) == S_OK) + return; + } + /* We can only support this fallback method when Emacs is being + debugged. */ + if (!(is_debugger_present && is_debugger_present ())) + return; + + THREADNAME_INFO tninfo; + + tninfo.type = 0x1000; /* magic constant */ + tninfo.name = name; + tninfo.thread_id = thread_id; + tninfo.reserved = 0; + RaiseException (MS_VC_EXCEPTION, 0, sizeof (tninfo) / sizeof (ULONG_PTR), + (ULONG_PTR *) &tninfo); +} + static thread_creation_function *thread_start_address; /* _beginthread wants a void function, while we are passed a function @@ -398,6 +458,14 @@ static thread_creation_function *thread_start_address; static void ALIGN_STACK w32_beginthread_wrapper (void *arg) { + /* FIXME: This isn't very clean: systhread.c is not supposed to know + that ARG is a pointer to a thread_state object, or be familiar + with thread_state object's structure in general. */ + struct thread_state *this_thread = arg; + + if (this_thread->thread_name) + w32_set_thread_name (GetCurrentThreadId (), this_thread->thread_name); + (void)thread_start_address (arg); } diff --git a/src/thread.c b/src/thread.c index e2deadd7a83..42c2bf52d28 100644 --- a/src/thread.c +++ b/src/thread.c @@ -756,6 +756,8 @@ run_thread (void *state) } } + xfree (self->thread_name); + current_thread = NULL; sys_cond_broadcast (&self->thread_condvar); @@ -825,6 +827,10 @@ If NAME is given, it must be a string; it names the new thread. */) all_threads = new_thread; char const *c_name = !NILP (name) ? SSDATA (ENCODE_UTF_8 (name)) : NULL; + if (c_name) + new_thread->thread_name = xstrdup (c_name); + else + new_thread->thread_name = NULL; sys_thread_t thr; if (! sys_thread_create (&thr, c_name, run_thread, new_thread)) { diff --git a/src/thread.h b/src/thread.h index 498b9909c9f..2b85f0893e7 100644 --- a/src/thread.h +++ b/src/thread.h @@ -169,6 +169,10 @@ struct thread_state interrupter should broadcast to this condition. */ sys_cond_t *wait_condvar; + /* Thread's name in the locale encoding. Actually used only on + WINDOWSNT. */ + char *thread_name; + /* This thread might have released the global lock. If so, this is non-zero. When a thread runs outside thread_select with this flag non-zero, it means it has been interrupted by SIGINT while diff --git a/src/w32fns.c b/src/w32fns.c index 4ef075f715b..bf2a7a3e54e 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -178,6 +178,10 @@ typedef BOOL (WINAPI * EnumDisplayMonitors_Proc) typedef BOOL (WINAPI * GetTitleBarInfo_Proc) (IN HWND hwnd, OUT TITLEBAR_INFO* info); +typedef BOOL (WINAPI *IsDebuggerPresent_Proc) (void); +typedef HRESULT (WINAPI *SetThreadDescription_Proc) + (HANDLE hThread, PCWSTR lpThreadDescription); + TrackMouseEvent_Proc track_mouse_event_fn = NULL; ImmGetCompositionString_Proc get_composition_string_fn = NULL; ImmGetContext_Proc get_ime_context_fn = NULL; @@ -188,6 +192,8 @@ GetMonitorInfo_Proc get_monitor_info_fn = NULL; MonitorFromWindow_Proc monitor_from_window_fn = NULL; EnumDisplayMonitors_Proc enum_display_monitors_fn = NULL; GetTitleBarInfo_Proc get_title_bar_info_fn = NULL; +IsDebuggerPresent_Proc is_debugger_present = NULL; +SetThreadDescription_Proc set_thread_description = NULL; extern AppendMenuW_Proc unicode_append_menu; @@ -284,8 +290,6 @@ static struct } kbdhook; typedef HWND (WINAPI *GetConsoleWindow_Proc) (void); -typedef BOOL (WINAPI *IsDebuggerPresent_Proc) (void); - /* stdin, from w32console.c */ extern HANDLE keyboard_handle; @@ -2737,8 +2741,6 @@ setup_w32_kbdhook (void) hook if the process is being debugged. */ if (w32_kbdhook_active) { - IsDebuggerPresent_Proc is_debugger_present = (IsDebuggerPresent_Proc) - get_proc_addr (GetModuleHandle ("kernel32.dll"), "IsDebuggerPresent"); if (is_debugger_present && is_debugger_present ()) return; } @@ -11031,6 +11033,12 @@ globals_of_w32fns (void) get_proc_addr (imm32_lib, "ImmSetCompositionWindow"); } + HMODULE hm_kernel32 = GetModuleHandle ("kernel32.dll"); + is_debugger_present = (IsDebuggerPresent_Proc) + get_proc_addr (hm_kernel32, "IsDebuggerPresent"); + set_thread_description = (SetThreadDescription_Proc) + get_proc_addr (hm_kernel32, "SetThreadDescription"); + except_code = 0; except_addr = 0; #ifndef CYGWIN -- 2.39.2