/* * 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 #include #include #include #define NONAMELESSUNION #include "ntstatus.h" #define WIN32_NO_STATUS #include "winternl.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); WINE_DECLARE_DEBUG_CHANNEL(pid); WINE_DECLARE_DEBUG_CHANNEL(timestamp); struct _KUSER_SHARED_DATA *user_shared_data = (void *)0x7ffe0000; struct debug_info { unsigned int str_pos; /* current position in strings buffer */ unsigned int out_pos; /* current position in output buffer */ char strings[1020]; /* buffer for temporary strings */ char output[1020]; /* current output line */ }; C_ASSERT( sizeof(struct debug_info) == 0x800 ); static int nb_debug_options; static struct __wine_debug_channel *debug_options; static inline struct debug_info *get_info(void) { #ifdef _WIN64 return (struct debug_info *)((TEB32 *)((char *)NtCurrentTeb() + 0x2000) + 1); #else return (struct debug_info *)(NtCurrentTeb() + 1); #endif } static void init_options(void) { unsigned int offset = page_size * (sizeof(void *) / 4); debug_options = (struct __wine_debug_channel *)((char *)NtCurrentTeb()->Peb + offset); while (debug_options[nb_debug_options].name[0]) nb_debug_options++; } /* add a string to the output buffer */ static int append_output( struct debug_info *info, const char *str, size_t len ) { if (len >= sizeof(info->output) - info->out_pos) { __wine_dbg_write( info->output, info->out_pos ); info->out_pos = 0; ERR_(thread)( "debug buffer overflow:\n" ); __wine_dbg_write( str, len ); RtlRaiseStatus( STATUS_BUFFER_OVERFLOW ); } memcpy( info->output + info->out_pos, str, len ); info->out_pos += len; return len; } /*********************************************************************** * __wine_dbg_get_channel_flags (NTDLL.@) * * Get the flags to use for a given channel, possibly setting them too in case of lazy init */ unsigned char __cdecl __wine_dbg_get_channel_flags( struct __wine_debug_channel *channel ) { int min, max, pos, res; unsigned char default_flags; if (!debug_options) init_options(); min = 0; max = nb_debug_options - 1; while (min <= max) { pos = (min + max) / 2; res = strcmp( channel->name, debug_options[pos].name ); if (!res) return debug_options[pos].flags; if (res < 0) max = pos - 1; else min = pos + 1; } /* no option for this channel */ default_flags = debug_options[nb_debug_options].flags; if (channel->flags & (1 << __WINE_DBCL_INIT)) channel->flags = default_flags; return default_flags; } /*********************************************************************** * __wine_dbg_strdup (NTDLL.@) */ const char * __cdecl __wine_dbg_strdup( const char *str ) { struct debug_info *info = get_info(); unsigned int pos = info->str_pos; size_t n = strlen( str ) + 1; assert( n <= sizeof(info->strings) ); if (pos + n > sizeof(info->strings)) pos = 0; info->str_pos = pos + n; return memcpy( info->strings + pos, str, n ); } /*********************************************************************** * __wine_dbg_header (NTDLL.@) */ int __cdecl __wine_dbg_header( enum __wine_debug_class cls, struct __wine_debug_channel *channel, const char *function ) { static const char * const classes[] = { "fixme", "err", "warn", "trace" }; struct debug_info *info = get_info(); char *pos = info->output; if (!(__wine_dbg_get_channel_flags( channel ) & (1 << cls))) return -1; /* only print header if we are at the beginning of the line */ if (info->out_pos) return 0; if (TRACE_ON(timestamp)) { ULONG ticks = NtGetTickCount(); pos += sprintf( pos, "%3u.%03u:", ticks / 1000, ticks % 1000 ); } if (TRACE_ON(pid)) pos += sprintf( pos, "%04x:", GetCurrentProcessId() ); pos += sprintf( pos, "%04x:", GetCurrentThreadId() ); if (function && cls < ARRAY_SIZE( classes )) pos += snprintf( pos, sizeof(info->output) - (pos - info->output), "%s:%s:%s ", classes[cls], channel->name, function ); info->out_pos = pos - info->output; return info->out_pos; } /*********************************************************************** * __wine_dbg_output (NTDLL.@) */ int __cdecl __wine_dbg_output( const char *str ) { struct debug_info *info = get_info(); const char *end = strrchr( str, '\n' ); int ret = 0; if (end) { ret += append_output( info, str, end + 1 - str ); __wine_dbg_write( info->output, info->out_pos ); info->out_pos = 0; str = end + 1; } if (*str) ret += append_output( info, str, strlen( str )); return ret; } /*********************************************************************** * RtlExitUserThread (NTDLL.@) */ void WINAPI RtlExitUserThread( ULONG status ) { ULONG last; NtQueryInformationThread( GetCurrentThread(), ThreadAmILastThread, &last, sizeof(last), NULL ); if (last) RtlExitUserProcess( status ); LdrShutdownThread(); for (;;) NtTerminateThread( GetCurrentThread(), status ); } /*********************************************************************** * RtlUserThreadStart (NTDLL.@) */ #ifdef __i386__ __ASM_STDCALL_FUNC( RtlUserThreadStart, 8, "movl %ebx,8(%esp)\n\t" /* arg */ "movl %eax,4(%esp)\n\t" /* entry */ "jmp " __ASM_NAME("call_thread_func") ) /* wrapper to call BaseThreadInitThunk */ extern void DECLSPEC_NORETURN call_thread_func_wrapper( void *thunk, PRTL_THREAD_START_ROUTINE entry, void *arg ); __ASM_GLOBAL_FUNC( call_thread_func_wrapper, "pushl %ebp\n\t" __ASM_CFI(".cfi_adjust_cfa_offset 4\n\t") __ASM_CFI(".cfi_rel_offset %ebp,0\n\t") "movl %esp,%ebp\n\t" __ASM_CFI(".cfi_def_cfa_register %ebp\n\t") "subl $4,%esp\n\t" "andl $~0xf,%esp\n\t" "xorl %ecx,%ecx\n\t" "movl 12(%ebp),%edx\n\t" "movl 16(%ebp),%eax\n\t" "movl %eax,(%esp)\n\t" "call *8(%ebp)" ) void DECLSPEC_HIDDEN call_thread_func( PRTL_THREAD_START_ROUTINE entry, void *arg ) { __TRY { TRACE_(relay)( "\1Starting thread proc %p (arg=%p)\n", entry, arg ); call_thread_func_wrapper( pBaseThreadInitThunk, entry, arg ); } __EXCEPT(call_unhandled_exception_filter) { NtTerminateProcess( GetCurrentProcess(), GetExceptionCode() ); } __ENDTRY } #else /* __i386__ */ void WINAPI RtlUserThreadStart( PRTL_THREAD_START_ROUTINE entry, void *arg ) { __TRY { TRACE_(relay)( "\1Starting thread proc %p (arg=%p)\n", entry, arg ); pBaseThreadInitThunk( 0, (LPTHREAD_START_ROUTINE)entry, arg ); } __EXCEPT(call_unhandled_exception_filter) { NtTerminateProcess( GetCurrentProcess(), GetExceptionCode() ); } __ENDTRY } #endif /* __i386__ */ /*********************************************************************** * RtlCreateUserThread (NTDLL.@) */ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, SECURITY_DESCRIPTOR *descr, BOOLEAN suspended, ULONG zero_bits, SIZE_T stack_reserve, SIZE_T stack_commit, PRTL_THREAD_START_ROUTINE start, void *param, HANDLE *handle_ptr, CLIENT_ID *id ) { ULONG flags = suspended ? THREAD_CREATE_FLAGS_CREATE_SUSPENDED : 0; ULONG_PTR buffer[offsetof( PS_ATTRIBUTE_LIST, Attributes[2] ) / sizeof(ULONG_PTR)]; PS_ATTRIBUTE_LIST *attr_list = (PS_ATTRIBUTE_LIST *)buffer; HANDLE handle, actctx; TEB *teb; ULONG ret; NTSTATUS status; CLIENT_ID client_id; OBJECT_ATTRIBUTES attr; attr_list->TotalLength = sizeof(buffer); attr_list->Attributes[0].Attribute = PS_ATTRIBUTE_CLIENT_ID; attr_list->Attributes[0].Size = sizeof(client_id); attr_list->Attributes[0].ValuePtr = &client_id; attr_list->Attributes[0].ReturnLength = NULL; attr_list->Attributes[1].Attribute = PS_ATTRIBUTE_TEB_ADDRESS; attr_list->Attributes[1].Size = sizeof(teb); attr_list->Attributes[1].ValuePtr = &teb; attr_list->Attributes[1].ReturnLength = NULL; InitializeObjectAttributes( &attr, NULL, 0, NULL, descr ); RtlGetActiveActivationContext( &actctx ); if (actctx) flags |= THREAD_CREATE_FLAGS_CREATE_SUSPENDED; status = NtCreateThreadEx( &handle, THREAD_ALL_ACCESS, &attr, process, start, param, flags, zero_bits, stack_commit, stack_reserve, attr_list ); if (!status) { if (actctx) { ULONG_PTR cookie; RtlActivateActivationContextEx( 0, teb, actctx, &cookie ); if (!suspended) NtResumeThread( handle, &ret ); } if (id) *id = client_id; if (handle_ptr) *handle_ptr = handle; else NtClose( handle ); } if (actctx) RtlReleaseActivationContext( actctx ); return status; } /********************************************************************** * RtlCreateUserStack (NTDLL.@) */ NTSTATUS WINAPI RtlCreateUserStack( SIZE_T commit, SIZE_T reserve, ULONG zero_bits, SIZE_T commit_align, SIZE_T reserve_align, INITIAL_TEB *stack ) { PROCESS_STACK_ALLOCATION_INFORMATION alloc; NTSTATUS status; TRACE("commit %#lx, reserve %#lx, zero_bits %u, commit_align %#lx, reserve_align %#lx, stack %p\n", commit, reserve, zero_bits, commit_align, reserve_align, stack); if (!commit_align || !reserve_align) return STATUS_INVALID_PARAMETER; if (!commit || !reserve) { IMAGE_NT_HEADERS *nt = RtlImageNtHeader( NtCurrentTeb()->Peb->ImageBaseAddress ); if (!reserve) reserve = nt->OptionalHeader.SizeOfStackReserve; if (!commit) commit = nt->OptionalHeader.SizeOfStackCommit; } reserve = (reserve + reserve_align - 1) & ~(reserve_align - 1); commit = (commit + commit_align - 1) & ~(commit_align - 1); if (reserve < commit) reserve = commit; if (reserve < 0x100000) reserve = 0x100000; reserve = (reserve + 0xffff) & ~0xffff; /* round to 64K boundary */ alloc.ReserveSize = reserve; alloc.ZeroBits = zero_bits; status = NtSetInformationProcess( GetCurrentProcess(), ProcessThreadStackAllocation, &alloc, sizeof(alloc) ); if (!status) { void *addr = alloc.StackBase; SIZE_T size = page_size; NtAllocateVirtualMemory( GetCurrentProcess(), &addr, 0, &size, MEM_COMMIT, PAGE_NOACCESS ); addr = (char *)alloc.StackBase + page_size; NtAllocateVirtualMemory( GetCurrentProcess(), &addr, 0, &size, MEM_COMMIT, PAGE_READWRITE | PAGE_GUARD ); addr = (char *)alloc.StackBase + 2 * page_size; size = reserve - 2 * page_size; NtAllocateVirtualMemory( GetCurrentProcess(), &addr, 0, &size, MEM_COMMIT, PAGE_READWRITE ); /* note: limit is lower than base since the stack grows down */ stack->OldStackBase = 0; stack->OldStackLimit = 0; stack->DeallocationStack = alloc.StackBase; stack->StackBase = (char *)alloc.StackBase + reserve; stack->StackLimit = (char *)alloc.StackBase + 2 * page_size; } return status; } /********************************************************************** * RtlFreeUserStack (NTDLL.@) */ void WINAPI RtlFreeUserStack( void *stack ) { SIZE_T size = 0; TRACE("stack %p\n", stack); NtFreeVirtualMemory( NtCurrentProcess(), &stack, &size, MEM_RELEASE ); } /****************************************************************************** * RtlGetNtGlobalFlags (NTDLL.@) */ ULONG WINAPI RtlGetNtGlobalFlags(void) { return NtCurrentTeb()->Peb->NtGlobalFlag; } /****************************************************************************** * RtlPushFrame (NTDLL.@) */ void WINAPI RtlPushFrame( TEB_ACTIVE_FRAME *frame ) { frame->Previous = NtCurrentTeb()->ActiveFrame; NtCurrentTeb()->ActiveFrame = frame; } /****************************************************************************** * RtlPopFrame (NTDLL.@) */ void WINAPI RtlPopFrame( TEB_ACTIVE_FRAME *frame ) { NtCurrentTeb()->ActiveFrame = frame->Previous; } /****************************************************************************** * RtlGetFrame (NTDLL.@) */ TEB_ACTIVE_FRAME * WINAPI RtlGetFrame(void) { return NtCurrentTeb()->ActiveFrame; } /*********************************************************************** * Fibers ***********************************************************************/ static GLOBAL_FLS_DATA fls_data = { { NULL }, { &fls_data.fls_list_head, &fls_data.fls_list_head } }; static RTL_CRITICAL_SECTION fls_section; static RTL_CRITICAL_SECTION_DEBUG fls_critsect_debug = { 0, 0, &fls_section, { &fls_critsect_debug.ProcessLocksList, &fls_critsect_debug.ProcessLocksList }, 0, 0, { (DWORD_PTR)(__FILE__ ": fls_section") } }; static RTL_CRITICAL_SECTION fls_section = { &fls_critsect_debug, -1, 0, 0, 0, 0 }; #define MAX_FLS_DATA_COUNT 0xff0 static void lock_fls_data(void) { RtlEnterCriticalSection( &fls_section ); } static void unlock_fls_data(void) { RtlLeaveCriticalSection( &fls_section ); } static unsigned int fls_chunk_size( unsigned int chunk_index ) { return 0x10 << chunk_index; } static unsigned int fls_index_from_chunk_index( unsigned int chunk_index, unsigned int index ) { return 0x10 * ((1 << chunk_index) - 1) + index; } static unsigned int fls_chunk_index_from_index( unsigned int index, unsigned int *index_in_chunk ) { unsigned int chunk_index = 0; while (index >= fls_chunk_size( chunk_index )) index -= fls_chunk_size( chunk_index++ ); *index_in_chunk = index; return chunk_index; } TEB_FLS_DATA *fls_alloc_data(void) { TEB_FLS_DATA *fls; if (!(fls = RtlAllocateHeap( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*fls) ))) return NULL; lock_fls_data(); InsertTailList( &fls_data.fls_list_head, &fls->fls_list_entry ); unlock_fls_data(); return fls; } /*********************************************************************** * RtlFlsAlloc (NTDLL.@) */ NTSTATUS WINAPI DECLSPEC_HOTPATCH RtlFlsAlloc( PFLS_CALLBACK_FUNCTION callback, ULONG *ret_index ) { unsigned int chunk_index, index, i; FLS_INFO_CHUNK *chunk; TEB_FLS_DATA *fls; if (!(fls = NtCurrentTeb()->FlsSlots) && !(NtCurrentTeb()->FlsSlots = fls = fls_alloc_data())) return STATUS_NO_MEMORY; lock_fls_data(); for (i = 0; i < ARRAY_SIZE(fls_data.fls_callback_chunks); ++i) { if (!fls_data.fls_callback_chunks[i] || fls_data.fls_callback_chunks[i]->count < fls_chunk_size( i )) break; } if ((chunk_index = i) == ARRAY_SIZE(fls_data.fls_callback_chunks)) { unlock_fls_data(); return STATUS_NO_MEMORY; } if ((chunk = fls_data.fls_callback_chunks[chunk_index])) { for (index = 0; index < fls_chunk_size( chunk_index ); ++index) if (!chunk->callbacks[index].callback) break; assert( index < fls_chunk_size( chunk_index )); } else { fls_data.fls_callback_chunks[chunk_index] = chunk = RtlAllocateHeap( GetProcessHeap(), HEAP_ZERO_MEMORY, offsetof(FLS_INFO_CHUNK, callbacks) + sizeof(*chunk->callbacks) * fls_chunk_size( chunk_index )); if (!chunk) { unlock_fls_data(); return STATUS_NO_MEMORY; } if (chunk_index) { index = 0; } else { chunk->count = 1; /* FLS index 0 is prohibited. */ chunk->callbacks[0].callback = (void *)~(ULONG_PTR)0; index = 1; } } ++chunk->count; chunk->callbacks[index].callback = callback ? callback : (PFLS_CALLBACK_FUNCTION)~(ULONG_PTR)0; if ((*ret_index = fls_index_from_chunk_index( chunk_index, index )) > fls_data.fls_high_index) fls_data.fls_high_index = *ret_index; unlock_fls_data(); return STATUS_SUCCESS; } /*********************************************************************** * RtlFlsFree (NTDLL.@) */ NTSTATUS WINAPI DECLSPEC_HOTPATCH RtlFlsFree( ULONG index ) { PFLS_CALLBACK_FUNCTION callback; unsigned int chunk_index, idx; FLS_INFO_CHUNK *chunk; LIST_ENTRY *entry; lock_fls_data(); if (!index || index > fls_data.fls_high_index) { unlock_fls_data(); return STATUS_INVALID_PARAMETER; } chunk_index = fls_chunk_index_from_index( index, &idx ); if (!(chunk = fls_data.fls_callback_chunks[chunk_index]) || !(callback = chunk->callbacks[idx].callback)) { unlock_fls_data(); return STATUS_INVALID_PARAMETER; } for (entry = fls_data.fls_list_head.Flink; entry != &fls_data.fls_list_head; entry = entry->Flink) { TEB_FLS_DATA *fls = CONTAINING_RECORD(entry, TEB_FLS_DATA, fls_list_entry); if (fls->fls_data_chunks[chunk_index] && fls->fls_data_chunks[chunk_index][idx + 1]) { if (callback != (void *)~(ULONG_PTR)0) { TRACE_(relay)("Calling FLS callback %p, arg %p.\n", callback, fls->fls_data_chunks[chunk_index][idx + 1]); callback( fls->fls_data_chunks[chunk_index][idx + 1] ); } fls->fls_data_chunks[chunk_index][idx + 1] = NULL; } } --chunk->count; chunk->callbacks[idx].callback = NULL; unlock_fls_data(); return STATUS_SUCCESS; } /*********************************************************************** * RtlFlsSetValue (NTDLL.@) */ NTSTATUS WINAPI DECLSPEC_HOTPATCH RtlFlsSetValue( ULONG index, void *data ) { unsigned int chunk_index, idx; TEB_FLS_DATA *fls; if (!index || index >= MAX_FLS_DATA_COUNT) return STATUS_INVALID_PARAMETER; if (!(fls = NtCurrentTeb()->FlsSlots) && !(NtCurrentTeb()->FlsSlots = fls = fls_alloc_data())) return STATUS_NO_MEMORY; chunk_index = fls_chunk_index_from_index( index, &idx ); if (!fls->fls_data_chunks[chunk_index] && !(fls->fls_data_chunks[chunk_index] = RtlAllocateHeap( GetProcessHeap(), HEAP_ZERO_MEMORY, (fls_chunk_size( chunk_index ) + 1) * sizeof(*fls->fls_data_chunks[chunk_index]) ))) return STATUS_NO_MEMORY; fls->fls_data_chunks[chunk_index][idx + 1] = data; return STATUS_SUCCESS; } /*********************************************************************** * RtlFlsGetValue (NTDLL.@) */ NTSTATUS WINAPI DECLSPEC_HOTPATCH RtlFlsGetValue( ULONG index, void **data ) { unsigned int chunk_index, idx; TEB_FLS_DATA *fls; if (!index || index >= MAX_FLS_DATA_COUNT || !(fls = NtCurrentTeb()->FlsSlots)) return STATUS_INVALID_PARAMETER; chunk_index = fls_chunk_index_from_index( index, &idx ); *data = fls->fls_data_chunks[chunk_index] ? fls->fls_data_chunks[chunk_index][idx + 1] : NULL; return STATUS_SUCCESS; } /*********************************************************************** * RtlProcessFlsData (NTDLL.@) */ void WINAPI DECLSPEC_HOTPATCH RtlProcessFlsData( void *teb_fls_data, ULONG flags ) { TEB_FLS_DATA *fls = teb_fls_data; unsigned int i, index; TRACE_(thread)( "teb_fls_data %p, flags %#x.\n", teb_fls_data, flags ); if (flags & ~3) FIXME_(thread)( "Unknown flags %#x.\n", flags ); if (!fls) return; if (flags & 1) { lock_fls_data(); for (i = 0; i < ARRAY_SIZE(fls->fls_data_chunks); ++i) { if (!fls->fls_data_chunks[i] || !fls_data.fls_callback_chunks[i] || !fls_data.fls_callback_chunks[i]->count) continue; for (index = 0; index < fls_chunk_size( i ); ++index) { PFLS_CALLBACK_FUNCTION callback = fls_data.fls_callback_chunks[i]->callbacks[index].callback; if (!fls->fls_data_chunks[i][index + 1]) continue; if (callback && callback != (void *)~(ULONG_PTR)0) { TRACE_(relay)("Calling FLS callback %p, arg %p.\n", callback, fls->fls_data_chunks[i][index + 1]); callback( fls->fls_data_chunks[i][index + 1] ); } fls->fls_data_chunks[i][index + 1] = NULL; } } /* Not using RemoveEntryList() as Windows does not zero list entry here. */ fls->fls_list_entry.Flink->Blink = fls->fls_list_entry.Blink; fls->fls_list_entry.Blink->Flink = fls->fls_list_entry.Flink; unlock_fls_data(); } if (flags & 2) { for (i = 0; i < ARRAY_SIZE(fls->fls_data_chunks); ++i) RtlFreeHeap( GetProcessHeap(), 0, fls->fls_data_chunks[i] ); RtlFreeHeap( GetProcessHeap(), 0, fls ); } }