From 0bf470c7180061ec43eb88c126af409fd2accc8b Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Mon, 16 Mar 2015 14:33:41 +0100 Subject: [PATCH] TESTING -- override pthreads to fix gstreamer v5 I believe the code is ready and will work properly now in all cases. but please test before cherry picking this patch, and report success or failure to me please. Changes since v1: - Call pthread_yield to make sure that we link against libpthread. This fixes the build on saucy. Changes since v2: - Set thread_data->detached before creating the thread to prevent a race condition. Changes since v3: - Set thread_data->detached CORRECTLY. Fix a small race between thread creation and pthread_detach. Changes since v4: - Set native thread stack defaults in a similar way to eglibc, and respect the defaults for win32 again. This fixes the regression where you can only create 194 threads in java. --- dlls/ntdll/ntdll_misc.h | 3 + dlls/ntdll/thread.c | 331 ++++++++++++++++++++++++++++++++++++++-- dlls/winegstreamer/glibthread.c | 13 ++ libs/wine/loader.c | 7 + libs/wine/wine.map | 6 + loader/Makefile.in | 4 +- loader/main.c | 41 +++++ 7 files changed, 387 insertions(+), 18 deletions(-) diff --git a/dlls/ntdll/ntdll_misc.h b/dlls/ntdll/ntdll_misc.h index cbd19db30f9..52b8e8defd0 100644 --- a/dlls/ntdll/ntdll_misc.h +++ b/dlls/ntdll/ntdll_misc.h @@ -28,6 +28,7 @@ #include "winnt.h" #include "winternl.h" #include "wine/server.h" +#include "wine/list.h" #define MAX_NT_PATH_LENGTH 277 @@ -240,6 +241,8 @@ struct ntdll_thread_data WINE_VM86_TEB_INFO vm86; /* 1fc vm86 private data */ void *exit_frame; /* 204 exit frame pointer */ #endif + struct list entry; + BOOL detached; }; static inline struct ntdll_thread_data *ntdll_get_thread_data(void) diff --git a/dlls/ntdll/thread.c b/dlls/ntdll/thread.c index aaf7a713593..62277a12000 100644 --- a/dlls/ntdll/thread.c +++ b/dlls/ntdll/thread.c @@ -33,6 +33,7 @@ #ifdef HAVE_SYS_SYSCALL_H #include #endif +#include #define NONAMELESSUNION #include "ntstatus.h" @@ -58,6 +59,7 @@ struct startup_info TEB *teb; PRTL_THREAD_START_ROUTINE entry_point; void *entry_arg; + BOOL native_thread; }; static PEB *peb; @@ -202,6 +204,80 @@ static ULONG64 get_dyld_image_info_addr(void) } #endif /* __APPLE__ */ +#ifdef __linux__ +#include + +extern typeof(pthread_create) *__glob_pthread_create, *call_pthread_create; +extern typeof(pthread_join) *__glob_pthread_join, *call_pthread_join; +extern typeof(pthread_detach) *__glob_pthread_detach, *call_pthread_detach; + +static typeof(pthread_create) __hook_pthread_create; +static typeof(pthread_join) __hook_pthread_join; +static typeof(pthread_detach) __hook_pthread_detach; + +static pthread_mutex_t thread_lock; + +static void thread_wrap_init(void) +{ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); + pthread_mutex_init(&thread_lock, &attr); + pthread_mutexattr_destroy(&attr); + + call_pthread_create = __hook_pthread_create; + call_pthread_join = __hook_pthread_join; + call_pthread_detach = __hook_pthread_detach; +} + +static TEB *dead_teb; +static struct list active_list = LIST_INIT(active_list); + +static void take_thread_lock(void) +{ + int ret = pthread_mutex_lock(&thread_lock); + if (ret == EOWNERDEAD) + pthread_mutex_consistent(&thread_lock); +} + +static void detach_thread_unlock(TEB *own_teb) +{ + struct ntdll_thread_data *thread_data; + TEB *teb = dead_teb; + + dead_teb = own_teb; + + pthread_mutex_unlock(&thread_lock); + if (!teb) + return; + + thread_data = (struct ntdll_thread_data *)teb->SpareBytes1; + __glob_pthread_join(thread_data->pthread_id, NULL); + signal_free_thread(teb); +} + +static void reap_thread(TEB *teb) +{ + struct ntdll_thread_data *thread_data = (struct ntdll_thread_data *)teb->SpareBytes1; + take_thread_lock(); + if (thread_data->detached) + detach_thread_unlock(teb); + else { + /* + * Do not unlock, wait until the thread is thoroughly dead. + * This prevents a race condition where detach is called + * after the thread has not finished dying yet. + */ + } +} + +#else +#define __glob_pthread_create pthread_create +#define __glob_pthread_join pthread_join +#define __glob_pthread_detach pthread_detach +#define thread_wrap_init() +#endif + /*********************************************************************** * thread_init * @@ -223,6 +299,7 @@ HANDLE thread_init(void) ULONG64 dyld_image_info; #endif + thread_wrap_init(); virtual_init(); /* reserve space for shared user data */ @@ -369,14 +446,12 @@ void terminate_thread( int status ) pthread_exit( UIntToPtr(status) ); } - -/*********************************************************************** - * exit_thread - */ -void exit_thread( int status ) +static void exit_thread_common( int status ) { +#ifndef __linux__ static void *prev_teb; TEB *teb; +#endif if (status) /* send the exit code to the server (0 is already the default) */ { @@ -400,24 +475,199 @@ void exit_thread( int status ) pthread_sigmask( SIG_BLOCK, &server_block_set, NULL ); +#ifndef __linux__ if ((teb = interlocked_xchg_ptr( &prev_teb, NtCurrentTeb() ))) { struct ntdll_thread_data *thread_data = (struct ntdll_thread_data *)teb->SpareBytes1; if (thread_data->pthread_id) { - pthread_join( thread_data->pthread_id, NULL ); + __glob_pthread_join( thread_data->pthread_id, NULL ); signal_free_thread( teb ); } } +#else + reap_thread(NtCurrentTeb()); +#endif close( ntdll_get_thread_data()->wait_fd[0] ); close( ntdll_get_thread_data()->wait_fd[1] ); close( ntdll_get_thread_data()->reply_fd ); close( ntdll_get_thread_data()->request_fd ); +} + +void exit_thread( int status ) +{ + exit_thread_common(status); pthread_exit( UIntToPtr(status) ); } +#ifdef __linux__ + +struct unix_arg { + void *(*start)(void *); + void *arg; +}; + +/* dummy used for comparison */ +static DWORD native_unix_start; + +static void call_native_cleanup(void *arg) +{ + exit_thread_common(0); +} + +static int +__hook_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *parm) +{ + NTSTATUS ret; + pthread_t tid; + size_t stack = 0, stack_commit; + static size_t default_stack; + struct unix_arg arg; + IMAGE_NT_HEADERS *nt; + + arg.start = start_routine; + arg.arg = parm; + + if (!default_stack) { + struct rlimit limit; + + if (getrlimit(RLIMIT_STACK, &limit) == 0 && + limit.rlim_cur != RLIM_INFINITY) + default_stack = limit.rlim_cur; + else + default_stack = 2 * 1024 * 1024; + } + + if (!thread) + thread = &tid; + + TRACE("Overriding thread creation!\n"); + if (attr) { + static int once; + if (!once++) + FIXME("most thread attributes ignored!\n"); + else + WARN("most thread attributes ignored!\n"); + + pthread_attr_getstacksize(attr, &stack); + } + + if (!stack) + stack = default_stack; + + nt = RtlImageNtHeader( NtCurrentTeb()->Peb->ImageBaseAddress ); + stack_commit = nt->OptionalHeader.SizeOfStackCommit; + + if (!stack_commit || stack_commit > stack) + stack_commit = stack; + + ret = RtlCreateUserThread( NtCurrentProcess(), NULL, FALSE, NULL, stack, stack_commit, (void*)&native_unix_start, &arg, NULL, (void*)thread ); + if (ret != STATUS_SUCCESS) + FIXME("ret: %08x\n", ret); + switch (ret) { + case STATUS_SUCCESS: + TRACE("created thread %lx for %p/%p\n", *thread, start_routine, parm); + return 0; + case STATUS_NO_MEMORY: + return ENOMEM; + case STATUS_TOO_MANY_OPENED_FILES: + return EMFILE; + default: + ERR("Unhandled ntstatus %08x\n", ret); + return ENOMEM; + } +} + +static int __hook_pthread_detach(pthread_t thread) +{ + struct ntdll_thread_data *thread_data; + TEB *teb = NULL; + + if (pthread_equal(thread, pthread_self())) { + TRACE("Detached self: %lx\n", pthread_self()); + ntdll_get_thread_data()->detached = 1; + return 0; + } + + take_thread_lock(); + LIST_FOR_EACH_ENTRY(thread_data, &active_list, typeof(*thread_data), entry) { + if (pthread_equal(thread_data->pthread_id, thread)) { + teb = CONTAINING_RECORD(thread_data, typeof(*teb), SpareBytes1); + + list_remove(&thread_data->entry); + if (!pthread_tryjoin_np(thread, NULL)) { + detach_thread_unlock(NULL); + TRACE("Thread %lx was dead, cleaning up\n", thread); + signal_free_thread(teb); + return 0; + } + thread_data->detached = 1; + break; + } + } + detach_thread_unlock(NULL); + if (!teb) + TRACE("Could not find thread %lx to detach\n", thread); + else + TRACE("Changed thread %lx to detached\n", thread); + return teb ? 0 : ESRCH; +} + +static int __hook_pthread_join(pthread_t thread, void **retval) +{ + struct ntdll_thread_data *thread_data, *t2; + int ret = ESRCH; + + if (pthread_equal(thread, pthread_self())) + return EDEADLK; + + take_thread_lock(); + LIST_FOR_EACH_ENTRY(thread_data, &active_list, typeof(*thread_data), entry) { + TEB *teb = CONTAINING_RECORD(thread_data, typeof(*teb), SpareBytes1); + + if (pthread_equal(thread, thread_data->pthread_id)) { + + ret = pthread_tryjoin_np(thread, retval); + if (!ret) { + TRACE("Thread %lx was dead fastpath, cleaning up\n", thread); + goto free; + } + detach_thread_unlock(NULL); + + ret = __glob_pthread_join(thread, retval); + if (ret) { + TRACE("Thread %lx join failed with %i, ignoring\n", thread, ret); + return ret; + } + + take_thread_lock(); + /* Check if someone else freed the thread yet */ + LIST_FOR_EACH_ENTRY(t2, &active_list, typeof(*thread_data), entry) + if (t2 == thread_data) { + TRACE("Cleaning up after successful join\n"); + goto free; + } + TRACE("No clean up after successful join, multiple pthread_join's?\n"); + break; + +free: + list_remove(&thread_data->entry); + detach_thread_unlock(NULL); + signal_free_thread(teb); + return 0; + } + } + + detach_thread_unlock(NULL); + if (ret) + TRACE("failed with %i\n", ret); + return ret; +} + +#endif /*********************************************************************** * start_thread @@ -446,9 +696,19 @@ static void start_thread( struct startup_info *info ) if (TRACE_ON(relay)) DPRINTF( "%04x:Starting thread proc %p (arg=%p)\n", GetCurrentThreadId(), func, arg ); - call_thread_entry_point( (LPTHREAD_START_ROUTINE)func, arg ); -} +#ifdef __linux__ + if (info->native_thread) { + void *(*start)(void*) = (void*)func; + FIXME("Started native thread %08x\n", GetCurrentThreadId()); + pthread_cleanup_push(call_native_cleanup, NULL); + pthread_exit(start(arg)); + pthread_cleanup_pop(1); + return; + } +#endif + call_thread_entry_point( (LPTHREAD_START_ROUTINE)func, arg ); +} /*********************************************************************** * RtlCreateUserThread (NTDLL.@) @@ -460,14 +720,13 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR * HANDLE *handle_ptr, CLIENT_ID *id ) { sigset_t sigset; - pthread_t pthread_id; pthread_attr_t attr; struct ntdll_thread_data *thread_data; struct startup_info *info = NULL; HANDLE handle = 0, actctx = 0; TEB *teb = NULL; DWORD tid = 0; - int request_pipe[2]; + int request_pipe[2], ret; NTSTATUS status; if (process != NtCurrentProcess()) @@ -492,10 +751,14 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR * if (handle_ptr) *handle_ptr = wine_server_ptr_handle( result.create_thread.handle ); else NtClose( wine_server_ptr_handle( result.create_thread.handle )); } + TRACE("CreateThread for other process returns %08x\n", result.create_thread.status); return result.create_thread.status; } - if (server_pipe( request_pipe ) == -1) return STATUS_TOO_MANY_OPENED_FILES; + if (server_pipe( request_pipe ) == -1) { + TRACE("CreateThread cannot create request pipe: %m\n"); + return STATUS_TOO_MANY_OPENED_FILES; + } wine_server_send_fd( request_pipe[0] ); SERVER_START_REQ( new_thread ) @@ -516,12 +779,16 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR * if (status) { close( request_pipe[1] ); + TRACE("CreateThread server request failed with %08x\n", status); return status; } pthread_sigmask( SIG_BLOCK, &server_block_set, &sigset ); - if ((status = signal_alloc_thread( &teb ))) goto error; + if ((status = signal_alloc_thread( &teb ))) { + TRACE("CreateThread signal thread allocation failed with %08x\n", status); + goto error; + } teb->Peb = NtCurrentTeb()->Peb; teb->ClientId.UniqueProcess = ULongToHandle(GetCurrentProcessId()); @@ -544,32 +811,64 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR * info = (struct startup_info *)(teb + 1); info->teb = teb; - info->entry_point = start; - info->entry_arg = param; +#ifdef __linux__ + info->native_thread = (void*)start == (void*)&native_unix_start; + if (info->native_thread) { + struct unix_arg *arg = param; + info->entry_point = (void*)arg->start; + info->entry_arg = arg->arg; + } else +#endif + { + info->entry_point = start; + info->entry_arg = param; + } thread_data = (struct ntdll_thread_data *)teb->SpareBytes1; +#ifdef __linux__ + thread_data->detached = !info->native_thread; +#endif thread_data->request_fd = request_pipe[1]; thread_data->reply_fd = -1; thread_data->wait_fd[0] = -1; thread_data->wait_fd[1] = -1; + thread_data->entry.next = NULL; - if ((status = virtual_alloc_thread_stack( teb, stack_reserve, stack_commit ))) goto error; + if ((status = virtual_alloc_thread_stack( teb, stack_reserve, stack_commit ))) { + TRACE("Allocating virtual stack for %p (%li/%li) failed with %08x\n", start, stack_reserve, stack_commit, status); + goto error; + } pthread_attr_init( &attr ); pthread_attr_setstack( &attr, teb->DeallocationStack, (char *)teb->Tib.StackBase - (char *)teb->DeallocationStack ); pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ); /* force creating a kernel thread */ interlocked_xchg_add( &nb_threads, 1 ); - if (pthread_create( &pthread_id, &attr, (void * (*)(void *))start_thread, info )) + + take_thread_lock(); + ret = __glob_pthread_create( &thread_data->pthread_id, &attr, (void * (*)(void *))start_thread, info ); + if (ret) { + TRACE("pthread create failed with %i/%m\n", ret); interlocked_xchg_add( &nb_threads, -1 ); pthread_attr_destroy( &attr ); status = STATUS_NO_MEMORY; goto error; } + if (!thread_data->detached) + list_add_tail(&active_list, &thread_data->entry); + detach_thread_unlock(NULL); + pthread_attr_destroy( &attr ); pthread_sigmask( SIG_SETMASK, &sigset, NULL ); + TRACE("Created thread succesfully, win handle: %04x, pthread: %lx\n", tid, thread_data->pthread_id); + +#ifdef __linux__ + if ((void*)start == (void*)&native_unix_start && id) + *(pthread_t*)id = thread_data->pthread_id; + else +#endif if (id) id->UniqueThread = ULongToHandle(tid); if (handle_ptr) *handle_ptr = handle; else NtClose( handle ); diff --git a/dlls/winegstreamer/glibthread.c b/dlls/winegstreamer/glibthread.c index 0d829a01f01..46e22f41d99 100644 --- a/dlls/winegstreamer/glibthread.c +++ b/dlls/winegstreamer/glibthread.c @@ -43,6 +43,7 @@ #include #include +#if 0 #include "windef.h" #include "winbase.h" #include "winnls.h" @@ -388,3 +389,15 @@ void g_thread_impl_init (void) g_thread_self_tls = TlsAlloc (); g_thread_init(&g_thread_functions_for_glib_use_default); } + +#else + +void g_thread_impl_init (void) +{ + static gboolean beenhere = FALSE; + + if (!beenhere++) + g_thread_init(NULL); +} + +#endif diff --git a/libs/wine/loader.c b/libs/wine/loader.c index 1644955f92e..82fbd740dba 100644 --- a/libs/wine/loader.c +++ b/libs/wine/loader.c @@ -73,6 +73,13 @@ char **__wine_main_argv = NULL; WCHAR **__wine_main_wargv = NULL; char **__wine_main_environ = NULL; +#ifdef __linux__ +#include +typeof(pthread_create) *call_pthread_create, *__glob_pthread_create; +typeof(pthread_join) *call_pthread_join, *__glob_pthread_join; +typeof(pthread_detach) *call_pthread_detach, *__glob_pthread_detach; +#endif + struct dll_path_context { unsigned int index; /* current index in the dll path list */ diff --git a/libs/wine/wine.map b/libs/wine/wine.map index 2159fac852b..fb3b9519732 100644 --- a/libs/wine/wine.map +++ b/libs/wine/wine.map @@ -117,6 +117,12 @@ WINE_1.0 wine_utf8_mbstowcs; wine_utf8_wcstombs; wine_wctype_table; + __glob_pthread_create; + call_pthread_create; + __glob_pthread_join; + call_pthread_join; + __glob_pthread_detach; + call_pthread_detach; local: *; }; diff --git a/loader/Makefile.in b/loader/Makefile.in index 819003773b0..f470c3287c3 100644 --- a/loader/Makefile.in +++ b/loader/Makefile.in @@ -19,11 +19,11 @@ preloader_EXTRADEFS = $(MSVCRTFLAGS) wine_OBJS = main.o wine_DEPS = $(WINELOADER_DEPENDS) -wine_LDFLAGS = $(LDEXECFLAGS) -lwine $(PTHREAD_LIBS) +wine_LDFLAGS = $(LDEXECFLAGS) -lwine $(PTHREAD_LIBS) $(DL_LIBS) wine64_OBJS = main.o wine64_DEPS = $(WINELOADER_DEPENDS) -wine64_LDFLAGS = $(LDEXECFLAGS) -lwine $(PTHREAD_LIBS) +wine64_LDFLAGS = $(LDEXECFLAGS) -lwine $(PTHREAD_LIBS) $(DL_LIBS) wine_preloader_OBJS = preloader.o wine_preloader_LDFLAGS = -static -nostartfiles -nodefaultlibs -Wl,-Ttext=0x7c400000 diff --git a/loader/main.c b/loader/main.c index bb752b5b097..bb5000684d3 100644 --- a/loader/main.c +++ b/loader/main.c @@ -212,6 +212,45 @@ static int pre_exec(void) #endif +#ifdef __linux__ + +extern typeof(pthread_create) *call_pthread_create, *__glob_pthread_create; +extern typeof(pthread_detach) *call_pthread_detach, *__glob_pthread_detach; +extern typeof(pthread_join) *call_pthread_join, *__glob_pthread_join; + +int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg) +{ + return call_pthread_create(thread, attr, start_routine, arg); +} + +int pthread_detach(pthread_t thread) +{ + return call_pthread_detach(thread); +} + +int pthread_join(pthread_t thread, void **retval) +{ + return call_pthread_join(thread, retval); +} + +static void init_thread_hook(void) { + call_pthread_create = __glob_pthread_create = dlvsym(RTLD_NEXT, "pthread_create", "GLIBC_2.2.5"); + if (!__glob_pthread_create) + call_pthread_create = __glob_pthread_create = dlvsym(RTLD_NEXT, "pthread_create", "GLIBC_2.1"); + + call_pthread_detach = __glob_pthread_detach = dlsym(RTLD_NEXT, "pthread_detach"); + call_pthread_join = __glob_pthread_join = dlsym(RTLD_NEXT, "pthread_join"); + + /* Call a function from libpthread to ensure being linked against it */ + pthread_yield(); +} + +#else + +#define init_thread_hook() + +#endif /********************************************************************** * main @@ -221,6 +260,8 @@ int main( int argc, char *argv[] ) char error[1024]; int i; + init_thread_hook(); + if (!getenv( "WINELOADERNOEXEC" )) /* first time around */ { static char noexec[] = "WINELOADERNOEXEC=1"; -- 2.11.4.GIT