This reworks `set_native_thread_name()` for macOS to not rely on mach thread ports returned server side (which were potentially colliding cross-process anyways) and re-implements what `pthread_setname_np()` would do on wines side, with additionally allowing setting threadnames cross-process.
On the kernel side `bsd_setthreadname()` is apparently "known to race with regards to the contents of the thread name", so that is only exposed for the thread calling it on itself.
Since the pthread struct is private, the location of the thread name is determined at runtime (although in practice, it is constant (80 bytes) from at least 10.15 up to 14.0).
This has an effect on both `pthread_getname_np()` and the `NSThread` implementation on top a native library would use.
Cross-thread naming is only not visible to external debuggers, which have acquired a mach port right and calling `thread_info()` with `THREAD_EXTENDED_INFO`
From: Marc-Aurel Zent mzent@codeweavers.com
--- dlls/ntdll/unix/thread.c | 91 ++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 21 deletions(-)
diff --git a/dlls/ntdll/unix/thread.c b/dlls/ntdll/unix/thread.c index 9e84ec3cc96..37341416a9e 100644 --- a/dlls/ntdll/unix/thread.c +++ b/dlls/ntdll/unix/thread.c @@ -1911,39 +1911,88 @@ static void set_native_thread_name( HANDLE handle, const UNICODE_STRING *name ) close( fd ); } #elif defined(__APPLE__) - /* pthread_setname_np() silently fails if the name is longer than 63 characters + null terminator */ - char nameA[64]; - NTSTATUS status; - int unix_pid, unix_tid, len, current_tid; + extern int __proc_info( int callnum, int pid, int flavor, uint64_t arg, void *buffer, int buffersize ); + static int pthread_name_offset = -1; + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + char nameA[MAXTHREADNAMESIZE]; + char *pthread_name; + int len; + THREAD_BASIC_INFORMATION info; + struct ntdll_thread_data *thread_data; + pthread_t pthread_id;
- SERVER_START_REQ( get_thread_times ) + if (NtQueryInformationThread( handle, ThreadBasicInformation, &info, sizeof(info), NULL )) + return; + + if (HandleToULong( info.ClientId.UniqueProcess ) != GetCurrentProcessId()) { - req->handle = wine_server_obj_handle( handle ); - status = wine_server_call( req ); - if (status == STATUS_SUCCESS) + static int once; + if (!once++) FIXME("cross-process native thread naming not supported\n"); + return; + } + + pthread_mutex_lock( &lock ); + + if (pthread_name_offset == -1) + { + char current_thread_name[MAXTHREADNAMESIZE]; + const char *test_name = "__WINE_THREAD_NAME_OFFSET_TEST__"; + const int max_search_offset = 256; + int test_name_len, search_offset = 0; + + pthread_getname_np( pthread_self(), current_thread_name, sizeof(current_thread_name) ); + test_name_len = strlen( test_name ); + pthread_setname_np( test_name ); + + for (; search_offset < max_search_offset; search_offset++) { - unix_pid = reply->unix_pid; - unix_tid = reply->unix_tid; + char *candidate = (char *)pthread_self() + search_offset; + if (!strncmp( candidate, test_name, test_name_len )) + { + pthread_name_offset = search_offset; + break; + } + } + + pthread_setname_np( current_thread_name ); + + if (pthread_name_offset == -1) + { + static int once; + if (!once++) ERR("could not determine pthread_name struct offset\n"); + goto out; } } - SERVER_END_REQ;
- if (status != STATUS_SUCCESS || unix_pid == -1 || unix_tid == -1) - return; + thread_data = (struct ntdll_thread_data *)&((PTEB)(info.TebBaseAddress))->GdiTebBatch; + pthread_id = thread_data->pthread_id; + pthread_name = (char *)pthread_id + pthread_name_offset;
- current_tid = mach_thread_self(); - mach_port_deallocate(mach_task_self(), current_tid); + len = ntdll_wcstoumbs( name->Buffer, name->Length / sizeof(WCHAR), nameA, sizeof(nameA) - 1, FALSE );
- if (unix_tid != current_tid) + if (len >= 0) + { + nameA[len] = '\0'; + strcpy( pthread_name, nameA ); + } + else + { + bzero( pthread_name, MAXTHREADNAMESIZE ); + goto out; + } + + if (!pthread_equal( pthread_self(), pthread_id )) { static int once; - if (!once++) FIXME("setting other thread name not supported\n"); - return; + if (!once++) FIXME("cross-thread native thread naming not visible cross-process\n"); + goto out; }
- len = ntdll_wcstoumbs( name->Buffer, name->Length / sizeof(WCHAR), nameA, sizeof(nameA) - 1, FALSE ); - nameA[len] = '\0'; - pthread_setname_np(nameA); + if (__proc_info( 5, getpid(), 2, (uint64_t)0, (void *)nameA, len )) + ERR("could not set native thread name kernel-side\n"); + +out: + pthread_mutex_unlock( &lock ); #else static int once; if (!once++) FIXME("not implemented on this platform\n");
I don't know if we want to get into this amount of private APIs and modifying OS-private structures, especially for something that's just a debugging aid.
Is it possible to move away from the Mach thread port usage but also stick with public APIs (even if that doesn't allow cross-thread naming)?