| /* |
| * NT threads support |
| * |
| * Copyright 1996, 2003 Alexandre Julliard |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include "config.h" |
| #include "wine/port.h" |
| |
| #include <assert.h> |
| #include <stdarg.h> |
| #include <sys/types.h> |
| #ifdef HAVE_SYS_MMAN_H |
| #include <sys/mman.h> |
| #endif |
| #ifdef HAVE_SYS_TIMES_H |
| #include <sys/times.h> |
| #endif |
| |
| #define NONAMELESSUNION |
| #include "ntstatus.h" |
| #define WIN32_NO_STATUS |
| #include "winternl.h" |
| #include "wine/library.h" |
| #include "wine/server.h" |
| #include "wine/debug.h" |
| #include "ntdll_misc.h" |
| #include "ddk/wdm.h" |
| #include "wine/exception.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(thread); |
| WINE_DECLARE_DEBUG_CHANNEL(relay); |
| |
| struct _KUSER_SHARED_DATA *user_shared_data = NULL; |
| |
| PUNHANDLED_EXCEPTION_FILTER unhandled_exception_filter = NULL; |
| |
| /* info passed to a starting thread */ |
| struct startup_info |
| { |
| TEB *teb; |
| PRTL_THREAD_START_ROUTINE entry_point; |
| void *entry_arg; |
| }; |
| |
| static PEB *peb; |
| static PEB_LDR_DATA ldr; |
| static RTL_USER_PROCESS_PARAMETERS params; /* default parameters if no parent */ |
| static WCHAR current_dir[MAX_NT_PATH_LENGTH]; |
| static RTL_BITMAP tls_bitmap; |
| static RTL_BITMAP tls_expansion_bitmap; |
| static RTL_BITMAP fls_bitmap; |
| static LIST_ENTRY tls_links; |
| static int nb_threads = 1; |
| |
| /*********************************************************************** |
| * get_unicode_string |
| * |
| * Copy a unicode string from the startup info. |
| */ |
| static inline void get_unicode_string( UNICODE_STRING *str, WCHAR **src, WCHAR **dst, UINT len ) |
| { |
| str->Buffer = *dst; |
| str->Length = len; |
| str->MaximumLength = len + sizeof(WCHAR); |
| memcpy( str->Buffer, *src, len ); |
| str->Buffer[len / sizeof(WCHAR)] = 0; |
| *src += len / sizeof(WCHAR); |
| *dst += len / sizeof(WCHAR) + 1; |
| } |
| |
| /*********************************************************************** |
| * init_user_process_params |
| * |
| * Fill the RTL_USER_PROCESS_PARAMETERS structure from the server. |
| */ |
| static NTSTATUS init_user_process_params( SIZE_T data_size, HANDLE *exe_file ) |
| { |
| void *ptr; |
| WCHAR *src, *dst; |
| SIZE_T info_size, env_size, size, alloc_size; |
| NTSTATUS status; |
| startup_info_t *info; |
| RTL_USER_PROCESS_PARAMETERS *params = NULL; |
| |
| if (!(info = RtlAllocateHeap( GetProcessHeap(), 0, data_size ))) |
| return STATUS_NO_MEMORY; |
| |
| SERVER_START_REQ( get_startup_info ) |
| { |
| wine_server_set_reply( req, info, data_size ); |
| if (!(status = wine_server_call( req ))) |
| { |
| data_size = wine_server_reply_size( reply ); |
| info_size = reply->info_size; |
| env_size = data_size - info_size; |
| *exe_file = wine_server_ptr_handle( reply->exe_file ); |
| } |
| } |
| SERVER_END_REQ; |
| if (status != STATUS_SUCCESS) goto done; |
| |
| size = sizeof(*params); |
| size += MAX_NT_PATH_LENGTH * sizeof(WCHAR); |
| size += info->dllpath_len + sizeof(WCHAR); |
| size += info->imagepath_len + sizeof(WCHAR); |
| size += info->cmdline_len + sizeof(WCHAR); |
| size += info->title_len + sizeof(WCHAR); |
| size += info->desktop_len + sizeof(WCHAR); |
| size += info->shellinfo_len + sizeof(WCHAR); |
| size += info->runtime_len + sizeof(WCHAR); |
| |
| alloc_size = size; |
| status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)¶ms, 0, &alloc_size, |
| MEM_COMMIT, PAGE_READWRITE ); |
| if (status != STATUS_SUCCESS) goto done; |
| |
| NtCurrentTeb()->Peb->ProcessParameters = params; |
| params->AllocationSize = alloc_size; |
| params->Size = size; |
| params->Flags = PROCESS_PARAMS_FLAG_NORMALIZED; |
| params->DebugFlags = info->debug_flags; |
| params->ConsoleHandle = wine_server_ptr_handle( info->console ); |
| params->ConsoleFlags = info->console_flags; |
| params->hStdInput = wine_server_ptr_handle( info->hstdin ); |
| params->hStdOutput = wine_server_ptr_handle( info->hstdout ); |
| params->hStdError = wine_server_ptr_handle( info->hstderr ); |
| params->dwX = info->x; |
| params->dwY = info->y; |
| params->dwXSize = info->xsize; |
| params->dwYSize = info->ysize; |
| params->dwXCountChars = info->xchars; |
| params->dwYCountChars = info->ychars; |
| params->dwFillAttribute = info->attribute; |
| params->dwFlags = info->flags; |
| params->wShowWindow = info->show; |
| |
| src = (WCHAR *)(info + 1); |
| dst = (WCHAR *)(params + 1); |
| |
| /* current directory needs more space */ |
| get_unicode_string( ¶ms->CurrentDirectory.DosPath, &src, &dst, info->curdir_len ); |
| params->CurrentDirectory.DosPath.MaximumLength = MAX_NT_PATH_LENGTH * sizeof(WCHAR); |
| dst = (WCHAR *)(params + 1) + MAX_NT_PATH_LENGTH; |
| |
| get_unicode_string( ¶ms->DllPath, &src, &dst, info->dllpath_len ); |
| get_unicode_string( ¶ms->ImagePathName, &src, &dst, info->imagepath_len ); |
| get_unicode_string( ¶ms->CommandLine, &src, &dst, info->cmdline_len ); |
| get_unicode_string( ¶ms->WindowTitle, &src, &dst, info->title_len ); |
| get_unicode_string( ¶ms->Desktop, &src, &dst, info->desktop_len ); |
| get_unicode_string( ¶ms->ShellInfo, &src, &dst, info->shellinfo_len ); |
| |
| /* runtime info isn't a real string */ |
| params->RuntimeInfo.Buffer = dst; |
| params->RuntimeInfo.Length = params->RuntimeInfo.MaximumLength = info->runtime_len; |
| memcpy( dst, src, info->runtime_len ); |
| |
| /* environment needs to be a separate memory block */ |
| ptr = NULL; |
| alloc_size = max( 1, env_size ); |
| status = NtAllocateVirtualMemory( NtCurrentProcess(), &ptr, 0, &alloc_size, |
| MEM_COMMIT, PAGE_READWRITE ); |
| if (status != STATUS_SUCCESS) goto done; |
| memcpy( ptr, (char *)info + info_size, env_size ); |
| params->Environment = ptr; |
| |
| done: |
| RtlFreeHeap( GetProcessHeap(), 0, info ); |
| return status; |
| } |
| |
| /*********************************************************************** |
| * thread_init |
| * |
| * Setup the initial thread. |
| * |
| * NOTES: The first allocated TEB on NT is at 0x7ffde000. |
| */ |
| HANDLE thread_init(void) |
| { |
| TEB *teb; |
| void *addr; |
| SIZE_T size, info_size; |
| HANDLE exe_file = 0; |
| LARGE_INTEGER now; |
| struct ntdll_thread_data *thread_data; |
| static struct debug_info debug_info; /* debug info for initial thread */ |
| |
| virtual_init(); |
| |
| /* reserve space for shared user data */ |
| |
| addr = (void *)0x7ffe0000; |
| size = 0x10000; |
| NtAllocateVirtualMemory( NtCurrentProcess(), &addr, 0, &size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE ); |
| user_shared_data = addr; |
| |
| /* allocate and initialize the PEB */ |
| |
| addr = NULL; |
| size = sizeof(*peb); |
| NtAllocateVirtualMemory( NtCurrentProcess(), &addr, 1, &size, |
| MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE ); |
| peb = addr; |
| |
| peb->ProcessParameters = ¶ms; |
| peb->TlsBitmap = &tls_bitmap; |
| peb->TlsExpansionBitmap = &tls_expansion_bitmap; |
| peb->FlsBitmap = &fls_bitmap; |
| peb->LdrData = &ldr; |
| params.CurrentDirectory.DosPath.Buffer = current_dir; |
| params.CurrentDirectory.DosPath.MaximumLength = sizeof(current_dir); |
| params.wShowWindow = 1; /* SW_SHOWNORMAL */ |
| RtlInitializeBitMap( &tls_bitmap, peb->TlsBitmapBits, sizeof(peb->TlsBitmapBits) * 8 ); |
| RtlInitializeBitMap( &tls_expansion_bitmap, peb->TlsExpansionBitmapBits, |
| sizeof(peb->TlsExpansionBitmapBits) * 8 ); |
| RtlInitializeBitMap( &fls_bitmap, peb->FlsBitmapBits, sizeof(peb->FlsBitmapBits) * 8 ); |
| InitializeListHead( &peb->FlsListHead ); |
| InitializeListHead( &ldr.InLoadOrderModuleList ); |
| InitializeListHead( &ldr.InMemoryOrderModuleList ); |
| InitializeListHead( &ldr.InInitializationOrderModuleList ); |
| InitializeListHead( &tls_links ); |
| |
| /* allocate and initialize the initial TEB */ |
| |
| signal_alloc_thread( &teb ); |
| teb->Peb = peb; |
| teb->Tib.StackBase = (void *)~0UL; |
| teb->StaticUnicodeString.Buffer = teb->StaticUnicodeBuffer; |
| teb->StaticUnicodeString.MaximumLength = sizeof(teb->StaticUnicodeBuffer); |
| |
| thread_data = (struct ntdll_thread_data *)teb->SpareBytes1; |
| thread_data->request_fd = -1; |
| thread_data->reply_fd = -1; |
| thread_data->wait_fd[0] = -1; |
| thread_data->wait_fd[1] = -1; |
| thread_data->debug_info = &debug_info; |
| InsertHeadList( &tls_links, &teb->TlsLinks ); |
| |
| signal_init_thread( teb ); |
| virtual_init_threading(); |
| |
| debug_info.str_pos = debug_info.strings; |
| debug_info.out_pos = debug_info.output; |
| debug_init(); |
| |
| /* setup the server connection */ |
| server_init_process(); |
| info_size = server_init_thread( peb ); |
| |
| /* create the process heap */ |
| if (!(peb->ProcessHeap = RtlCreateHeap( HEAP_GROWABLE, NULL, 0, 0, NULL, NULL ))) |
| { |
| MESSAGE( "wine: failed to create the process heap\n" ); |
| exit(1); |
| } |
| |
| /* allocate user parameters */ |
| if (info_size) |
| { |
| init_user_process_params( info_size, &exe_file ); |
| } |
| else |
| { |
| /* This is wine specific: we have no parent (we're started from unix) |
| * so, create a simple console with bare handles to unix stdio |
| */ |
| wine_server_fd_to_handle( 0, GENERIC_READ|SYNCHRONIZE, OBJ_INHERIT, ¶ms.hStdInput ); |
| wine_server_fd_to_handle( 1, GENERIC_WRITE|SYNCHRONIZE, OBJ_INHERIT, ¶ms.hStdOutput ); |
| wine_server_fd_to_handle( 2, GENERIC_WRITE|SYNCHRONIZE, OBJ_INHERIT, ¶ms.hStdError ); |
| } |
| |
| /* initialize time values in user_shared_data */ |
| NtQuerySystemTime( &now ); |
| user_shared_data->SystemTime.LowPart = now.u.LowPart; |
| user_shared_data->SystemTime.High1Time = user_shared_data->SystemTime.High2Time = now.u.HighPart; |
| user_shared_data->u.TickCountQuad = (now.QuadPart - server_start_time) / 10000; |
| user_shared_data->u.TickCount.High2Time = user_shared_data->u.TickCount.High1Time; |
| user_shared_data->TickCountLowDeprecated = user_shared_data->u.TickCount.LowPart; |
| user_shared_data->TickCountMultiplier = 1 << 24; |
| |
| fill_cpu_info(); |
| |
| return exe_file; |
| } |
| |
| |
| /*********************************************************************** |
| * terminate_thread |
| */ |
| void terminate_thread( int status ) |
| { |
| pthread_sigmask( SIG_BLOCK, &server_block_set, NULL ); |
| if (interlocked_xchg_add( &nb_threads, -1 ) <= 1) _exit( status ); |
| |
| 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 ); |
| pthread_exit( UIntToPtr(status) ); |
| } |
| |
| |
| /*********************************************************************** |
| * exit_thread |
| */ |
| void exit_thread( int status ) |
| { |
| static void *prev_teb; |
| TEB *teb; |
| |
| if (status) /* send the exit code to the server (0 is already the default) */ |
| { |
| SERVER_START_REQ( terminate_thread ) |
| { |
| req->handle = wine_server_obj_handle( GetCurrentThread() ); |
| req->exit_code = status; |
| wine_server_call( req ); |
| } |
| SERVER_END_REQ; |
| } |
| |
| if (interlocked_xchg_add( &nb_threads, -1 ) <= 1) |
| { |
| LdrShutdownProcess(); |
| exit( status ); |
| } |
| |
| LdrShutdownThread(); |
| RtlAcquirePebLock(); |
| RemoveEntryList( &NtCurrentTeb()->TlsLinks ); |
| RtlReleasePebLock(); |
| RtlFreeHeap( GetProcessHeap(), 0, NtCurrentTeb()->FlsSlots ); |
| RtlFreeHeap( GetProcessHeap(), 0, NtCurrentTeb()->TlsExpansionSlots ); |
| |
| pthread_sigmask( SIG_BLOCK, &server_block_set, NULL ); |
| |
| if ((teb = interlocked_xchg_ptr( &prev_teb, NtCurrentTeb() ))) |
| { |
| struct ntdll_thread_data *thread_data = (struct ntdll_thread_data *)teb->SpareBytes1; |
| |
| pthread_join( thread_data->pthread_id, NULL ); |
| signal_free_thread( teb ); |
| } |
| |
| 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 ); |
| pthread_exit( UIntToPtr(status) ); |
| } |
| |
| |
| /*********************************************************************** |
| * start_thread |
| * |
| * Startup routine for a newly created thread. |
| */ |
| static void start_thread( struct startup_info *info ) |
| { |
| TEB *teb = info->teb; |
| struct ntdll_thread_data *thread_data = (struct ntdll_thread_data *)teb->SpareBytes1; |
| PRTL_THREAD_START_ROUTINE func = info->entry_point; |
| void *arg = info->entry_arg; |
| struct debug_info debug_info; |
| |
| debug_info.str_pos = debug_info.strings; |
| debug_info.out_pos = debug_info.output; |
| thread_data->debug_info = &debug_info; |
| thread_data->pthread_id = pthread_self(); |
| |
| signal_init_thread( teb ); |
| server_init_thread( func ); |
| pthread_sigmask( SIG_UNBLOCK, &server_block_set, NULL ); |
| |
| RtlAcquirePebLock(); |
| InsertHeadList( &tls_links, &teb->TlsLinks ); |
| RtlReleasePebLock(); |
| |
| MODULE_DllThreadAttach( NULL ); |
| |
| 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 ); |
| } |
| |
| |
| /*********************************************************************** |
| * RtlCreateUserThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR *descr, |
| BOOLEAN suspended, PVOID stack_addr, |
| SIZE_T stack_reserve, SIZE_T stack_commit, |
| PRTL_THREAD_START_ROUTINE start, void *param, |
| 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; |
| TEB *teb = NULL; |
| DWORD tid = 0; |
| int request_pipe[2]; |
| NTSTATUS status; |
| |
| if (process != NtCurrentProcess()) |
| { |
| apc_call_t call; |
| apc_result_t result; |
| |
| memset( &call, 0, sizeof(call) ); |
| |
| call.create_thread.type = APC_CREATE_THREAD; |
| call.create_thread.func = wine_server_client_ptr( start ); |
| call.create_thread.arg = wine_server_client_ptr( param ); |
| call.create_thread.reserve = stack_reserve; |
| call.create_thread.commit = stack_commit; |
| call.create_thread.suspend = suspended; |
| status = NTDLL_queue_process_apc( process, &call, &result ); |
| if (status != STATUS_SUCCESS) return status; |
| |
| if (result.create_thread.status == STATUS_SUCCESS) |
| { |
| if (id) id->UniqueThread = ULongToHandle(result.create_thread.tid); |
| if (handle_ptr) *handle_ptr = wine_server_ptr_handle( result.create_thread.handle ); |
| else NtClose( wine_server_ptr_handle( result.create_thread.handle )); |
| } |
| return result.create_thread.status; |
| } |
| |
| if (server_pipe( request_pipe ) == -1) return STATUS_TOO_MANY_OPENED_FILES; |
| wine_server_send_fd( request_pipe[0] ); |
| |
| SERVER_START_REQ( new_thread ) |
| { |
| req->access = THREAD_ALL_ACCESS; |
| req->attributes = 0; /* FIXME */ |
| req->suspend = suspended; |
| req->request_fd = request_pipe[0]; |
| if (!(status = wine_server_call( req ))) |
| { |
| handle = wine_server_ptr_handle( reply->handle ); |
| tid = reply->tid; |
| } |
| close( request_pipe[0] ); |
| } |
| SERVER_END_REQ; |
| |
| if (status) |
| { |
| close( request_pipe[1] ); |
| return status; |
| } |
| |
| pthread_sigmask( SIG_BLOCK, &server_block_set, &sigset ); |
| |
| if ((status = signal_alloc_thread( &teb ))) goto error; |
| |
| teb->Peb = NtCurrentTeb()->Peb; |
| teb->ClientId.UniqueProcess = ULongToHandle(GetCurrentProcessId()); |
| teb->ClientId.UniqueThread = ULongToHandle(tid); |
| teb->StaticUnicodeString.Buffer = teb->StaticUnicodeBuffer; |
| teb->StaticUnicodeString.MaximumLength = sizeof(teb->StaticUnicodeBuffer); |
| |
| info = (struct startup_info *)(teb + 1); |
| info->teb = teb; |
| info->entry_point = start; |
| info->entry_arg = param; |
| |
| thread_data = (struct ntdll_thread_data *)teb->SpareBytes1; |
| thread_data->request_fd = request_pipe[1]; |
| thread_data->reply_fd = -1; |
| thread_data->wait_fd[0] = -1; |
| thread_data->wait_fd[1] = -1; |
| |
| if ((status = virtual_alloc_thread_stack( teb, stack_reserve, stack_commit ))) 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 )) |
| { |
| interlocked_xchg_add( &nb_threads, -1 ); |
| pthread_attr_destroy( &attr ); |
| status = STATUS_NO_MEMORY; |
| goto error; |
| } |
| pthread_attr_destroy( &attr ); |
| pthread_sigmask( SIG_SETMASK, &sigset, NULL ); |
| |
| if (id) id->UniqueThread = ULongToHandle(tid); |
| if (handle_ptr) *handle_ptr = handle; |
| else NtClose( handle ); |
| |
| return STATUS_SUCCESS; |
| |
| error: |
| if (teb) signal_free_thread( teb ); |
| if (handle) NtClose( handle ); |
| pthread_sigmask( SIG_SETMASK, &sigset, NULL ); |
| close( request_pipe[1] ); |
| return status; |
| } |
| |
| |
| /****************************************************************************** |
| * RtlGetNtGlobalFlags (NTDLL.@) |
| */ |
| ULONG WINAPI RtlGetNtGlobalFlags(void) |
| { |
| if (!peb) return 0; /* init not done yet */ |
| return peb->NtGlobalFlag; |
| } |
| |
| |
| /*********************************************************************** |
| * NtOpenThread (NTDLL.@) |
| * ZwOpenThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtOpenThread( HANDLE *handle, ACCESS_MASK access, |
| const OBJECT_ATTRIBUTES *attr, const CLIENT_ID *id ) |
| { |
| NTSTATUS ret; |
| |
| SERVER_START_REQ( open_thread ) |
| { |
| req->tid = HandleToULong(id->UniqueThread); |
| req->access = access; |
| req->attributes = attr ? attr->Attributes : 0; |
| ret = wine_server_call( req ); |
| *handle = wine_server_ptr_handle( reply->handle ); |
| } |
| SERVER_END_REQ; |
| return ret; |
| } |
| |
| |
| /****************************************************************************** |
| * NtSuspendThread (NTDLL.@) |
| * ZwSuspendThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtSuspendThread( HANDLE handle, PULONG count ) |
| { |
| NTSTATUS ret; |
| |
| SERVER_START_REQ( suspend_thread ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| if (!(ret = wine_server_call( req ))) *count = reply->count; |
| } |
| SERVER_END_REQ; |
| return ret; |
| } |
| |
| |
| /****************************************************************************** |
| * NtResumeThread (NTDLL.@) |
| * ZwResumeThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtResumeThread( HANDLE handle, PULONG count ) |
| { |
| NTSTATUS ret; |
| |
| SERVER_START_REQ( resume_thread ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| if (!(ret = wine_server_call( req ))) *count = reply->count; |
| } |
| SERVER_END_REQ; |
| return ret; |
| } |
| |
| |
| /****************************************************************************** |
| * NtAlertResumeThread (NTDLL.@) |
| * ZwAlertResumeThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtAlertResumeThread( HANDLE handle, PULONG count ) |
| { |
| FIXME( "stub: should alert thread %p\n", handle ); |
| return NtResumeThread( handle, count ); |
| } |
| |
| |
| /****************************************************************************** |
| * NtAlertThread (NTDLL.@) |
| * ZwAlertThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtAlertThread( HANDLE handle ) |
| { |
| FIXME( "stub: %p\n", handle ); |
| return STATUS_NOT_IMPLEMENTED; |
| } |
| |
| |
| /****************************************************************************** |
| * NtTerminateThread (NTDLL.@) |
| * ZwTerminateThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtTerminateThread( HANDLE handle, LONG exit_code ) |
| { |
| NTSTATUS ret; |
| BOOL self; |
| |
| SERVER_START_REQ( terminate_thread ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->exit_code = exit_code; |
| ret = wine_server_call( req ); |
| self = !ret && reply->self; |
| } |
| SERVER_END_REQ; |
| |
| if (self) abort_thread( exit_code ); |
| return ret; |
| } |
| |
| |
| /****************************************************************************** |
| * NtQueueApcThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtQueueApcThread( HANDLE handle, PNTAPCFUNC func, ULONG_PTR arg1, |
| ULONG_PTR arg2, ULONG_PTR arg3 ) |
| { |
| NTSTATUS ret; |
| SERVER_START_REQ( queue_apc ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| if (func) |
| { |
| req->call.type = APC_USER; |
| req->call.user.func = wine_server_client_ptr( func ); |
| req->call.user.args[0] = arg1; |
| req->call.user.args[1] = arg2; |
| req->call.user.args[2] = arg3; |
| } |
| else req->call.type = APC_NONE; /* wake up only */ |
| ret = wine_server_call( req ); |
| } |
| SERVER_END_REQ; |
| return ret; |
| } |
| |
| |
| /*********************************************************************** |
| * NtSetContextThread (NTDLL.@) |
| * ZwSetContextThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtSetContextThread( HANDLE handle, const CONTEXT *context ) |
| { |
| NTSTATUS ret; |
| DWORD dummy, i; |
| BOOL self = FALSE; |
| |
| #ifdef __i386__ |
| /* on i386 debug registers always require a server call */ |
| self = (handle == GetCurrentThread()); |
| if (self && (context->ContextFlags & (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386))) |
| { |
| self = (ntdll_get_thread_data()->dr0 == context->Dr0 && |
| ntdll_get_thread_data()->dr1 == context->Dr1 && |
| ntdll_get_thread_data()->dr2 == context->Dr2 && |
| ntdll_get_thread_data()->dr3 == context->Dr3 && |
| ntdll_get_thread_data()->dr6 == context->Dr6 && |
| ntdll_get_thread_data()->dr7 == context->Dr7); |
| } |
| #endif |
| |
| if (!self) |
| { |
| context_t server_context; |
| |
| context_to_server( &server_context, context ); |
| |
| SERVER_START_REQ( set_thread_context ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->suspend = 0; |
| wine_server_add_data( req, &server_context, sizeof(server_context) ); |
| ret = wine_server_call( req ); |
| self = reply->self; |
| } |
| SERVER_END_REQ; |
| |
| if (ret == STATUS_PENDING) |
| { |
| if (NtSuspendThread( handle, &dummy ) == STATUS_SUCCESS) |
| { |
| for (i = 0; i < 100; i++) |
| { |
| SERVER_START_REQ( set_thread_context ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->suspend = 0; |
| wine_server_add_data( req, &server_context, sizeof(server_context) ); |
| ret = wine_server_call( req ); |
| } |
| SERVER_END_REQ; |
| if (ret == STATUS_PENDING) |
| { |
| LARGE_INTEGER timeout; |
| timeout.QuadPart = -10000; |
| NtDelayExecution( FALSE, &timeout ); |
| } |
| else break; |
| } |
| NtResumeThread( handle, &dummy ); |
| } |
| if (ret == STATUS_PENDING) ret = STATUS_ACCESS_DENIED; |
| } |
| |
| if (ret) return ret; |
| } |
| |
| if (self) set_cpu_context( context ); |
| return STATUS_SUCCESS; |
| } |
| |
| |
| /* convert CPU-specific flags to generic server flags */ |
| static inline unsigned int get_server_context_flags( DWORD flags ) |
| { |
| unsigned int ret = 0; |
| |
| flags &= 0x3f; /* mask CPU id flags */ |
| if (flags & CONTEXT_CONTROL) ret |= SERVER_CTX_CONTROL; |
| if (flags & CONTEXT_INTEGER) ret |= SERVER_CTX_INTEGER; |
| #ifdef CONTEXT_SEGMENTS |
| if (flags & CONTEXT_SEGMENTS) ret |= SERVER_CTX_SEGMENTS; |
| #endif |
| #ifdef CONTEXT_FLOATING_POINT |
| if (flags & CONTEXT_FLOATING_POINT) ret |= SERVER_CTX_FLOATING_POINT; |
| #endif |
| #ifdef CONTEXT_DEBUG_REGISTERS |
| if (flags & CONTEXT_DEBUG_REGISTERS) ret |= SERVER_CTX_DEBUG_REGISTERS; |
| #endif |
| #ifdef CONTEXT_EXTENDED_REGISTERS |
| if (flags & CONTEXT_EXTENDED_REGISTERS) ret |= SERVER_CTX_EXTENDED_REGISTERS; |
| #endif |
| return ret; |
| } |
| |
| /*********************************************************************** |
| * NtGetContextThread (NTDLL.@) |
| * ZwGetContextThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) |
| { |
| NTSTATUS ret; |
| DWORD dummy, i; |
| DWORD needed_flags = context->ContextFlags; |
| BOOL self = (handle == GetCurrentThread()); |
| |
| #ifdef __i386__ |
| /* on i386 debug registers always require a server call */ |
| if (context->ContextFlags & (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386)) self = FALSE; |
| #endif |
| |
| if (!self) |
| { |
| unsigned int server_flags = get_server_context_flags( context->ContextFlags ); |
| context_t server_context; |
| |
| SERVER_START_REQ( get_thread_context ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->flags = server_flags; |
| req->suspend = 0; |
| wine_server_set_reply( req, &server_context, sizeof(server_context) ); |
| ret = wine_server_call( req ); |
| self = reply->self; |
| } |
| SERVER_END_REQ; |
| |
| if (ret == STATUS_PENDING) |
| { |
| if (NtSuspendThread( handle, &dummy ) == STATUS_SUCCESS) |
| { |
| for (i = 0; i < 100; i++) |
| { |
| SERVER_START_REQ( get_thread_context ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->flags = server_flags; |
| req->suspend = 0; |
| wine_server_set_reply( req, &server_context, sizeof(server_context) ); |
| ret = wine_server_call( req ); |
| } |
| SERVER_END_REQ; |
| if (ret == STATUS_PENDING) |
| { |
| LARGE_INTEGER timeout; |
| timeout.QuadPart = -10000; |
| NtDelayExecution( FALSE, &timeout ); |
| } |
| else break; |
| } |
| NtResumeThread( handle, &dummy ); |
| } |
| if (ret == STATUS_PENDING) ret = STATUS_ACCESS_DENIED; |
| } |
| if (!ret) ret = context_from_server( context, &server_context ); |
| if (ret) return ret; |
| needed_flags &= ~context->ContextFlags; |
| } |
| |
| if (self) |
| { |
| if (needed_flags) |
| { |
| CONTEXT ctx; |
| RtlCaptureContext( &ctx ); |
| copy_context( context, &ctx, ctx.ContextFlags & needed_flags ); |
| context->ContextFlags |= ctx.ContextFlags & needed_flags; |
| } |
| #ifdef __i386__ |
| /* update the cached version of the debug registers */ |
| if (context->ContextFlags & (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386)) |
| { |
| ntdll_get_thread_data()->dr0 = context->Dr0; |
| ntdll_get_thread_data()->dr1 = context->Dr1; |
| ntdll_get_thread_data()->dr2 = context->Dr2; |
| ntdll_get_thread_data()->dr3 = context->Dr3; |
| ntdll_get_thread_data()->dr6 = context->Dr6; |
| ntdll_get_thread_data()->dr7 = context->Dr7; |
| } |
| #endif |
| } |
| return STATUS_SUCCESS; |
| } |
| |
| |
| /****************************************************************************** |
| * NtQueryInformationThread (NTDLL.@) |
| * ZwQueryInformationThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtQueryInformationThread( HANDLE handle, THREADINFOCLASS class, |
| void *data, ULONG length, ULONG *ret_len ) |
| { |
| NTSTATUS status; |
| |
| switch(class) |
| { |
| case ThreadBasicInformation: |
| { |
| THREAD_BASIC_INFORMATION info; |
| const ULONG_PTR affinity_mask = ((ULONG_PTR)1 << NtCurrentTeb()->Peb->NumberOfProcessors) - 1; |
| |
| SERVER_START_REQ( get_thread_info ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->tid_in = 0; |
| if (!(status = wine_server_call( req ))) |
| { |
| info.ExitStatus = reply->exit_code; |
| info.TebBaseAddress = wine_server_get_ptr( reply->teb ); |
| info.ClientId.UniqueProcess = ULongToHandle(reply->pid); |
| info.ClientId.UniqueThread = ULongToHandle(reply->tid); |
| info.AffinityMask = reply->affinity & affinity_mask; |
| info.Priority = reply->priority; |
| info.BasePriority = reply->priority; /* FIXME */ |
| } |
| } |
| SERVER_END_REQ; |
| if (status == STATUS_SUCCESS) |
| { |
| if (data) memcpy( data, &info, min( length, sizeof(info) )); |
| if (ret_len) *ret_len = min( length, sizeof(info) ); |
| } |
| } |
| return status; |
| case ThreadAffinityMask: |
| { |
| const ULONG_PTR affinity_mask = ((ULONG_PTR)1 << NtCurrentTeb()->Peb->NumberOfProcessors) - 1; |
| ULONG_PTR affinity = 0; |
| |
| SERVER_START_REQ( get_thread_info ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->tid_in = 0; |
| if (!(status = wine_server_call( req ))) |
| affinity = reply->affinity & affinity_mask; |
| } |
| SERVER_END_REQ; |
| if (status == STATUS_SUCCESS) |
| { |
| if (data) memcpy( data, &affinity, min( length, sizeof(affinity) )); |
| if (ret_len) *ret_len = min( length, sizeof(affinity) ); |
| } |
| } |
| return status; |
| case ThreadTimes: |
| { |
| KERNEL_USER_TIMES kusrt; |
| /* We need to do a server call to get the creation time or exit time */ |
| /* This works on any thread */ |
| SERVER_START_REQ( get_thread_info ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->tid_in = 0; |
| status = wine_server_call( req ); |
| if (status == STATUS_SUCCESS) |
| { |
| kusrt.CreateTime.QuadPart = reply->creation_time; |
| kusrt.ExitTime.QuadPart = reply->exit_time; |
| } |
| } |
| SERVER_END_REQ; |
| if (status == STATUS_SUCCESS) |
| { |
| /* We call times(2) for kernel time or user time */ |
| /* We can only (portably) do this for the current thread */ |
| if (handle == GetCurrentThread()) |
| { |
| struct tms time_buf; |
| long clocks_per_sec = sysconf(_SC_CLK_TCK); |
| |
| times(&time_buf); |
| kusrt.KernelTime.QuadPart = (ULONGLONG)time_buf.tms_stime * 10000000 / clocks_per_sec; |
| kusrt.UserTime.QuadPart = (ULONGLONG)time_buf.tms_utime * 10000000 / clocks_per_sec; |
| } |
| else |
| { |
| static BOOL reported = FALSE; |
| |
| kusrt.KernelTime.QuadPart = 0; |
| kusrt.UserTime.QuadPart = 0; |
| if (reported) |
| TRACE("Cannot get kerneltime or usertime of other threads\n"); |
| else |
| { |
| FIXME("Cannot get kerneltime or usertime of other threads\n"); |
| reported = TRUE; |
| } |
| } |
| if (data) memcpy( data, &kusrt, min( length, sizeof(kusrt) )); |
| if (ret_len) *ret_len = min( length, sizeof(kusrt) ); |
| } |
| } |
| return status; |
| case ThreadDescriptorTableEntry: |
| { |
| #ifdef __i386__ |
| THREAD_DESCRIPTOR_INFORMATION* tdi = data; |
| if (length < sizeof(*tdi)) |
| status = STATUS_INFO_LENGTH_MISMATCH; |
| else if (!(tdi->Selector & 4)) /* GDT selector */ |
| { |
| unsigned sel = tdi->Selector & ~3; /* ignore RPL */ |
| status = STATUS_SUCCESS; |
| if (!sel) /* null selector */ |
| memset( &tdi->Entry, 0, sizeof(tdi->Entry) ); |
| else |
| { |
| tdi->Entry.BaseLow = 0; |
| tdi->Entry.HighWord.Bits.BaseMid = 0; |
| tdi->Entry.HighWord.Bits.BaseHi = 0; |
| tdi->Entry.LimitLow = 0xffff; |
| tdi->Entry.HighWord.Bits.LimitHi = 0xf; |
| tdi->Entry.HighWord.Bits.Dpl = 3; |
| tdi->Entry.HighWord.Bits.Sys = 0; |
| tdi->Entry.HighWord.Bits.Pres = 1; |
| tdi->Entry.HighWord.Bits.Granularity = 1; |
| tdi->Entry.HighWord.Bits.Default_Big = 1; |
| tdi->Entry.HighWord.Bits.Type = 0x12; |
| /* it has to be one of the system GDT selectors */ |
| if (sel != (wine_get_ds() & ~3) && sel != (wine_get_ss() & ~3)) |
| { |
| if (sel == (wine_get_cs() & ~3)) |
| tdi->Entry.HighWord.Bits.Type |= 8; /* code segment */ |
| else status = STATUS_ACCESS_DENIED; |
| } |
| } |
| } |
| else |
| { |
| SERVER_START_REQ( get_selector_entry ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->entry = tdi->Selector >> 3; |
| status = wine_server_call( req ); |
| if (!status) |
| { |
| if (!(reply->flags & WINE_LDT_FLAGS_ALLOCATED)) |
| status = STATUS_ACCESS_VIOLATION; |
| else |
| { |
| wine_ldt_set_base ( &tdi->Entry, (void *)reply->base ); |
| wine_ldt_set_limit( &tdi->Entry, reply->limit ); |
| wine_ldt_set_flags( &tdi->Entry, reply->flags ); |
| } |
| } |
| } |
| SERVER_END_REQ; |
| } |
| if (status == STATUS_SUCCESS && ret_len) |
| /* yes, that's a bit strange, but it's the way it is */ |
| *ret_len = sizeof(LDT_ENTRY); |
| #else |
| status = STATUS_NOT_IMPLEMENTED; |
| #endif |
| return status; |
| } |
| case ThreadAmILastThread: |
| { |
| SERVER_START_REQ(get_thread_info) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->tid_in = 0; |
| status = wine_server_call( req ); |
| if (status == STATUS_SUCCESS) |
| { |
| BOOLEAN last = reply->last; |
| if (data) memcpy( data, &last, min( length, sizeof(last) )); |
| if (ret_len) *ret_len = min( length, sizeof(last) ); |
| } |
| } |
| SERVER_END_REQ; |
| return status; |
| } |
| case ThreadPriority: |
| case ThreadBasePriority: |
| case ThreadImpersonationToken: |
| case ThreadEnableAlignmentFaultFixup: |
| case ThreadEventPair_Reusable: |
| case ThreadQuerySetWin32StartAddress: |
| case ThreadZeroTlsCell: |
| case ThreadPerformanceCount: |
| case ThreadIdealProcessor: |
| case ThreadPriorityBoost: |
| case ThreadSetTlsArrayAddress: |
| case ThreadIsIoPending: |
| default: |
| FIXME( "info class %d not supported yet\n", class ); |
| return STATUS_NOT_IMPLEMENTED; |
| } |
| } |
| |
| |
| /****************************************************************************** |
| * NtSetInformationThread (NTDLL.@) |
| * ZwSetInformationThread (NTDLL.@) |
| */ |
| NTSTATUS WINAPI NtSetInformationThread( HANDLE handle, THREADINFOCLASS class, |
| LPCVOID data, ULONG length ) |
| { |
| NTSTATUS status; |
| switch(class) |
| { |
| case ThreadZeroTlsCell: |
| if (handle == GetCurrentThread()) |
| { |
| LIST_ENTRY *entry; |
| DWORD index; |
| |
| if (length != sizeof(DWORD)) return STATUS_INVALID_PARAMETER; |
| index = *(const DWORD *)data; |
| if (index < TLS_MINIMUM_AVAILABLE) |
| { |
| RtlAcquirePebLock(); |
| for (entry = tls_links.Flink; entry != &tls_links; entry = entry->Flink) |
| { |
| TEB *teb = CONTAINING_RECORD(entry, TEB, TlsLinks); |
| teb->TlsSlots[index] = 0; |
| } |
| RtlReleasePebLock(); |
| } |
| else |
| { |
| index -= TLS_MINIMUM_AVAILABLE; |
| if (index >= 8 * sizeof(NtCurrentTeb()->Peb->TlsExpansionBitmapBits)) |
| return STATUS_INVALID_PARAMETER; |
| RtlAcquirePebLock(); |
| for (entry = tls_links.Flink; entry != &tls_links; entry = entry->Flink) |
| { |
| TEB *teb = CONTAINING_RECORD(entry, TEB, TlsLinks); |
| if (teb->TlsExpansionSlots) teb->TlsExpansionSlots[index] = 0; |
| } |
| RtlReleasePebLock(); |
| } |
| return STATUS_SUCCESS; |
| } |
| FIXME( "ZeroTlsCell not supported on other threads\n" ); |
| return STATUS_NOT_IMPLEMENTED; |
| |
| case ThreadImpersonationToken: |
| { |
| const HANDLE *phToken = data; |
| if (length != sizeof(HANDLE)) return STATUS_INVALID_PARAMETER; |
| TRACE("Setting ThreadImpersonationToken handle to %p\n", *phToken ); |
| SERVER_START_REQ( set_thread_info ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->token = wine_server_obj_handle( *phToken ); |
| req->mask = SET_THREAD_INFO_TOKEN; |
| status = wine_server_call( req ); |
| } |
| SERVER_END_REQ; |
| } |
| return status; |
| case ThreadBasePriority: |
| { |
| const DWORD *pprio = data; |
| if (length != sizeof(DWORD)) return STATUS_INVALID_PARAMETER; |
| SERVER_START_REQ( set_thread_info ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->priority = *pprio; |
| req->mask = SET_THREAD_INFO_PRIORITY; |
| status = wine_server_call( req ); |
| } |
| SERVER_END_REQ; |
| } |
| return status; |
| case ThreadAffinityMask: |
| { |
| const ULONG_PTR affinity_mask = ((ULONG_PTR)1 << NtCurrentTeb()->Peb->NumberOfProcessors) - 1; |
| ULONG_PTR req_aff; |
| |
| if (length != sizeof(ULONG_PTR)) return STATUS_INVALID_PARAMETER; |
| req_aff = *(const ULONG_PTR *)data; |
| if ((ULONG)req_aff == ~0u) req_aff = affinity_mask; |
| else if (req_aff & ~affinity_mask) return STATUS_INVALID_PARAMETER; |
| else if (!req_aff) return STATUS_INVALID_PARAMETER; |
| SERVER_START_REQ( set_thread_info ) |
| { |
| req->handle = wine_server_obj_handle( handle ); |
| req->affinity = req_aff; |
| req->mask = SET_THREAD_INFO_AFFINITY; |
| status = wine_server_call( req ); |
| } |
| SERVER_END_REQ; |
| } |
| return status; |
| case ThreadHideFromDebugger: |
| /* pretend the call succeeded to satisfy some code protectors */ |
| return STATUS_SUCCESS; |
| |
| case ThreadBasicInformation: |
| case ThreadTimes: |
| case ThreadPriority: |
| case ThreadDescriptorTableEntry: |
| case ThreadEnableAlignmentFaultFixup: |
| case ThreadEventPair_Reusable: |
| case ThreadQuerySetWin32StartAddress: |
| case ThreadPerformanceCount: |
| case ThreadAmILastThread: |
| case ThreadIdealProcessor: |
| case ThreadPriorityBoost: |
| case ThreadSetTlsArrayAddress: |
| case ThreadIsIoPending: |
| default: |
| FIXME( "info class %d not supported yet\n", class ); |
| return STATUS_NOT_IMPLEMENTED; |
| } |
| } |