Commit 8574412e authored by Alexandre Julliard's avatar Alexandre Julliard

Added wine_pthread_create_thread and wine_pthread_exit_thread to the

pthread support, and removed the corresponding SYSDEPS functions. Moved stack allocation for new threads to wine_pthread_create_thread to allow more flexibility.
parent 8defcd38
......@@ -24,6 +24,7 @@
#include <assert.h>
#include <fcntl.h>
#include <stdarg.h>
#include <signal.h>
#include <sys/types.h>
#ifdef HAVE_SYS_TIMES_H
#include <sys/times.h>
......@@ -41,6 +42,7 @@
#include "thread.h"
#include "wine/winbase16.h"
#include "wine/library.h"
#include "wine/pthread.h"
#include "wine/server.h"
#include "wine/debug.h"
......@@ -197,11 +199,45 @@ void WINAPI ExitThread( DWORD code ) /* [in] Exit code for this thread */
}
else
{
struct wine_pthread_thread_info info;
sigset_t block_set;
ULONG size;
LdrShutdownThread();
RtlAcquirePebLock();
RemoveEntryList( &NtCurrentTeb()->TlsLinks );
RtlReleasePebLock();
SYSDEPS_ExitThread( code );
info.stack_base = NtCurrentTeb()->DeallocationStack;
info.teb_base = NtCurrentTeb();
info.teb_sel = wine_get_fs();
info.exit_status = code;
size = 0;
NtFreeVirtualMemory( GetCurrentProcess(), &info.stack_base, &size, MEM_RELEASE | MEM_SYSTEM );
info.stack_size = size;
size = 0;
NtFreeVirtualMemory( GetCurrentProcess(), &info.teb_base, &size, MEM_RELEASE | MEM_SYSTEM );
info.teb_size = size;
/* block the async signals */
sigemptyset( &block_set );
sigaddset( &block_set, SIGALRM );
sigaddset( &block_set, SIGIO );
sigaddset( &block_set, SIGINT );
sigaddset( &block_set, SIGHUP );
sigaddset( &block_set, SIGUSR1 );
sigaddset( &block_set, SIGUSR2 );
sigaddset( &block_set, SIGTERM );
sigprocmask( SIG_BLOCK, &block_set, NULL );
close( NtCurrentTeb()->wait_fd[0] );
close( NtCurrentTeb()->wait_fd[1] );
close( NtCurrentTeb()->reply_fd );
close( NtCurrentTeb()->request_fd );
wine_pthread_exit_thread( &info );
}
}
......
......@@ -4,7 +4,7 @@ TOPOBJDIR = ../..
SRCDIR = @srcdir@
VPATH = @srcdir@
MODULE = ntdll.dll
EXTRALIBS = $(LIBUNICODE) @LIBPTHREAD@
EXTRALIBS = $(LIBUNICODE)
C_SRCS = \
cdrom.c \
......
......@@ -1082,6 +1082,5 @@
@ cdecl MODULE_DllThreadAttach(ptr)
@ cdecl MODULE_GetLoadOrderA(ptr str str long)
@ cdecl MODULE_GetLoadOrderW(ptr wstr wstr long)
@ cdecl SYSDEPS_ExitThread(long)
@ cdecl VERSION_Init(wstr)
@ cdecl VIRTUAL_SetFaultHandler(ptr ptr ptr)
......@@ -55,21 +55,6 @@
WINE_DEFAULT_DEBUG_CHANNEL(thread);
struct thread_cleanup_info
{
void *stack_base;
ULONG stack_size;
void *teb_base;
ULONG teb_size;
int status;
};
/* temporary stacks used on thread exit */
#define TEMP_STACK_SIZE 1024
#define NB_TEMP_STACKS 8
static char temp_stacks[NB_TEMP_STACKS][TEMP_STACK_SIZE];
static LONG next_temp_stack; /* next temp stack to use */
/***********************************************************************
* SYSDEPS_SetCurThread
......@@ -97,148 +82,7 @@ void SYSDEPS_SetCurThread( TEB *teb )
/* On non-i386 Solaris, we use the LWP private pointer */
_lwp_setprivate( teb );
#endif
#ifdef HAVE_NPTL
teb->pthread_data = (void *)pthread_self();
#else
wine_pthread_init_thread();
#endif
}
/***********************************************************************
* get_temp_stack
*
* Get a temporary stack address to run the thread exit code on.
*/
inline static char *get_temp_stack(void)
{
unsigned int next = interlocked_xchg_add( &next_temp_stack, 1 );
return temp_stacks[next % NB_TEMP_STACKS] + TEMP_STACK_SIZE;
}
/***********************************************************************
* cleanup_thread
*
* Cleanup the remains of a thread. Runs on a temporary stack.
*/
static void cleanup_thread( void *ptr )
{
/* copy the info structure since it is on the stack we will free */
struct thread_cleanup_info info = *(struct thread_cleanup_info *)ptr;
munmap( info.stack_base, info.stack_size );
munmap( info.teb_base, info.teb_size );
wine_ldt_free_fs( wine_get_fs() );
#ifdef HAVE__LWP_CREATE
_lwp_exit();
#endif
_exit( info.status );
}
/***********************************************************************
* SYSDEPS_SpawnThread
*
* Start running a new thread.
* Return -1 on error, 0 if OK.
*/
int SYSDEPS_SpawnThread( void (*func)(TEB *), TEB *teb )
{
#ifdef HAVE_NPTL
pthread_t id;
pthread_attr_t attr;
pthread_attr_init( &attr );
pthread_attr_setstack( &attr, teb->DeallocationStack,
(char *)teb->Tib.StackBase - (char *)teb->DeallocationStack );
if (pthread_create( &id, &attr, (void * (*)(void *))func, teb )) return -1;
return 0;
#elif defined(HAVE_CLONE)
if (clone( (int (*)(void *))func, teb->Tib.StackBase,
CLONE_VM | CLONE_FS | CLONE_FILES | SIGCHLD, teb ) < 0)
return -1;
return 0;
#elif defined(HAVE_RFORK)
void **sp = (void **)teb->Tib.StackBase;
*--sp = teb;
*--sp = 0;
*--sp = func;
__asm__ __volatile__(
"pushl %2;\n\t" /* flags */
"pushl $0;\n\t" /* 0 ? */
"movl %1,%%eax;\n\t" /* SYS_rfork */
".byte 0x9a; .long 0; .word 7;\n\t" /* lcall 7:0... FreeBSD syscall */
"cmpl $0, %%edx;\n\t"
"je 1f;\n\t"
"movl %0,%%esp;\n\t" /* child -> new thread */
"ret;\n"
"1:\n\t" /* parent -> caller thread */
"addl $8,%%esp" :
: "r" (sp), "g" (SYS_rfork), "g" (RFPROC | RFMEM)
: "eax", "edx");
return 0;
#elif defined(HAVE__LWP_CREATE)
ucontext_t context;
_lwp_makecontext( &context, (void(*)(void *))func, teb,
NULL, teb->DeallocationStack, (char *)teb->Tib.StackBase - (char *)teb->DeallocationStack );
if ( _lwp_create( &context, 0, NULL ) )
return -1;
return 0;
#endif
FIXME("CreateThread: stub\n" );
return -1;
}
/***********************************************************************
* SYSDEPS_ExitThread
*
* Exit a running thread; must not return.
*/
void SYSDEPS_ExitThread( int status )
{
#ifdef HAVE_NPTL
static TEB *teb_to_free;
TEB *free_teb;
if ((free_teb = interlocked_xchg_ptr( (void **)&teb_to_free, NtCurrentTeb() )) != NULL)
{
DWORD size = 0;
void *ptr;
TRACE("freeing prev teb %p stack %p fs %04x\n",
free_teb, free_teb->DeallocationStack, free_teb->teb_sel );
pthread_join( (pthread_t)free_teb->pthread_data, &ptr );
wine_ldt_free_fs( free_teb->teb_sel );
ptr = free_teb->DeallocationStack;
NtFreeVirtualMemory( GetCurrentProcess(), &ptr, &size, MEM_RELEASE );
ptr = free_teb;
NtFreeVirtualMemory( GetCurrentProcess(), &ptr, &size, MEM_RELEASE | MEM_SYSTEM );
munmap( ptr, size );
}
SYSDEPS_AbortThread( status );
#else
struct thread_cleanup_info info;
SIGNAL_Block();
info.status = status;
info.stack_base = NtCurrentTeb()->DeallocationStack;
info.stack_size = 0;
NtFreeVirtualMemory( GetCurrentProcess(), &info.stack_base,
&info.stack_size, MEM_RELEASE | MEM_SYSTEM );
info.teb_base = NtCurrentTeb();
info.teb_size = 0;
NtFreeVirtualMemory( GetCurrentProcess(), &info.teb_base,
&info.teb_size, MEM_RELEASE | MEM_SYSTEM );
close( NtCurrentTeb()->wait_fd[0] );
close( NtCurrentTeb()->wait_fd[1] );
close( NtCurrentTeb()->reply_fd );
close( NtCurrentTeb()->request_fd );
wine_switch_to_stack( cleanup_thread, &info, get_temp_stack() );
#endif
}
......@@ -254,15 +98,7 @@ void SYSDEPS_AbortThread( int status )
close( NtCurrentTeb()->wait_fd[1] );
close( NtCurrentTeb()->reply_fd );
close( NtCurrentTeb()->request_fd );
#ifdef HAVE_NPTL
pthread_exit( (void *)status );
#endif
SIGNAL_Reset();
#ifdef HAVE__LWP_CREATE
_lwp_exit();
#endif
for (;;) /* avoid warning */
_exit( status );
wine_pthread_abort_thread( status );
}
/***********************************************************************
......
......@@ -31,6 +31,7 @@
#include "winternl.h"
#include "wine/library.h"
#include "wine/server.h"
#include "wine/pthread.h"
#include "wine/debug.h"
#include "ntdll_misc.h"
......@@ -60,6 +61,7 @@ static TEB *alloc_teb( ULONG *size )
return NULL;
}
teb->Tib.ExceptionList = (void *)~0UL;
teb->Tib.StackBase = (void *)~0UL;
teb->Tib.Self = &teb->Tib;
teb->Peb = &peb;
teb->StaticUnicodeString.Buffer = teb->StaticUnicodeBuffer;
......@@ -76,8 +78,6 @@ static inline void free_teb( TEB *teb )
ULONG size = 0;
void *addr = teb;
if (teb->DeallocationStack)
NtFreeVirtualMemory( GetCurrentProcess(), &teb->DeallocationStack, &size, MEM_RELEASE );
NtFreeVirtualMemory( GetCurrentProcess(), &addr, &size, MEM_RELEASE );
wine_ldt_free_fs( teb->teb_sel );
munmap( teb, SIGNAL_STACK_SIZE + sizeof(TEB) );
......@@ -110,7 +110,6 @@ void thread_init(void)
InitializeListHead( &tls_links );
teb = alloc_teb( &size );
teb->Tib.StackBase = (void *)~0UL;
teb->tibflags = TEBF_WIN32;
teb->request_fd = -1;
teb->reply_fd = -1;
......@@ -143,19 +142,35 @@ void thread_init(void)
*
* Startup routine for a newly created thread.
*/
static void start_thread( TEB *teb )
static void start_thread( struct wine_pthread_thread_info *info )
{
TEB *teb = info->teb_base;
LPTHREAD_START_ROUTINE func = (LPTHREAD_START_ROUTINE)teb->entry_point;
struct debug_info info;
struct debug_info debug_info;
ULONG size;
info.str_pos = info.strings;
info.out_pos = info.output;
teb->debug_info = &info;
debug_info.str_pos = debug_info.strings;
debug_info.out_pos = debug_info.output;
teb->debug_info = &debug_info;
SYSDEPS_SetCurThread( teb );
SIGNAL_Init();
wine_server_init_thread();
/* allocate a memory view for the stack */
size = info->stack_size;
NtAllocateVirtualMemory( GetCurrentProcess(), &teb->DeallocationStack, info->stack_base,
&size, MEM_SYSTEM, PAGE_EXECUTE_READWRITE );
/* limit is lower than base since the stack grows down */
teb->Tib.StackBase = (char *)info->stack_base + info->stack_size;
teb->Tib.StackLimit = info->stack_base;
/* setup the guard page */
size = 1;
NtProtectVirtualMemory( GetCurrentProcess(), &teb->DeallocationStack, &size,
PAGE_EXECUTE_READWRITE | PAGE_GUARD, NULL );
RtlFreeHeap( GetProcessHeap(), 0, info );
RtlAcquirePebLock();
InsertHeadList( &tls_links, &teb->TlsLinks );
RtlReleasePebLock();
......@@ -173,11 +188,11 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR *
PRTL_THREAD_START_ROUTINE start, void *param,
HANDLE *handle_ptr, CLIENT_ID *id )
{
struct wine_pthread_thread_info *info = NULL;
HANDLE handle = 0;
TEB *teb = NULL;
DWORD tid = 0;
ULONG size;
void *base;
int request_pipe[2];
NTSTATUS status;
......@@ -201,6 +216,12 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR *
if (status) goto error;
if (!(info = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*info) )))
{
status = STATUS_NO_MEMORY;
goto error;
}
if (!(teb = alloc_teb( &size )))
{
status = STATUS_NO_MEMORY;
......@@ -219,8 +240,10 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR *
teb->entry_arg = param;
teb->htask16 = NtCurrentTeb()->htask16;
NtAllocateVirtualMemory( GetCurrentProcess(), &base, teb, &size,
NtAllocateVirtualMemory( GetCurrentProcess(), &info->teb_base, teb, &size,
MEM_SYSTEM, PAGE_EXECUTE_READWRITE );
info->teb_size = size;
info->teb_sel = teb->teb_sel;
if (!stack_reserve || !stack_commit)
{
......@@ -231,22 +254,13 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR *
if (stack_reserve < stack_commit) stack_reserve = stack_commit;
stack_reserve = (stack_reserve + 0xffff) & ~0xffff; /* round to 64K boundary */
status = NtAllocateVirtualMemory( GetCurrentProcess(), &teb->DeallocationStack, NULL,
&stack_reserve, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
if (status != STATUS_SUCCESS) goto error;
info->stack_base = NULL;
info->stack_size = stack_reserve;
info->entry = start_thread;
/* limit is lower than base since the stack grows down */
teb->Tib.StackBase = (char *)teb->DeallocationStack + stack_reserve;
teb->Tib.StackLimit = teb->DeallocationStack;
/* setup the guard page */
size = 1;
NtProtectVirtualMemory( GetCurrentProcess(), &teb->DeallocationStack, &size,
PAGE_EXECUTE_READWRITE | PAGE_GUARD, NULL );
if (SYSDEPS_SpawnThread( start_thread, teb ) == -1)
if (wine_pthread_create_thread( info ) == -1)
{
status = STATUS_TOO_MANY_THREADS;
status = STATUS_NO_MEMORY;
goto error;
}
......@@ -258,6 +272,7 @@ NTSTATUS WINAPI RtlCreateUserThread( HANDLE process, const SECURITY_DESCRIPTOR *
error:
if (teb) free_teb( teb );
if (info) RtlFreeHeap( GetProcessHeap(), 0, info );
if (handle) NtClose( handle );
close( request_pipe[1] );
return status;
......
......@@ -21,6 +21,10 @@
#ifndef __WINE_WINE_PTHREAD_H
#define __WINE_WINE_PTHREAD_H
struct wine_pthread_functions;
#ifdef HAVE_PTHREAD_H
#define _GNU_SOURCE
#include <pthread.h>
......@@ -65,7 +69,35 @@ struct wine_pthread_functions
const struct timespec *abstime);
};
#endif /* HAVE_PTHREAD_H */
/* we don't want to include winnt.h here */
#ifndef DECLSPEC_NORETURN
# if defined(_MSC_VER) && (_MSC_VER >= 1200)
# define DECLSPEC_NORETURN __declspec(noreturn)
# elif defined(__GNUC__)
# define DECLSPEC_NORETURN __attribute__((noreturn))
# else
# define DECLSPEC_NORETURN
# endif
#endif
/* thread information used to creating and exiting threads */
struct wine_pthread_thread_info
{
void *stack_base;
size_t stack_size;
void *teb_base;
size_t teb_size;
unsigned short teb_sel;
void (*entry)( struct wine_pthread_thread_info *info );
int exit_status;
};
extern void wine_pthread_init_process( const struct wine_pthread_functions *functions );
extern void wine_pthread_init_thread(void);
extern int wine_pthread_create_thread( struct wine_pthread_thread_info *info );
extern void DECLSPEC_NORETURN wine_pthread_exit_thread( struct wine_pthread_thread_info *info );
extern void DECLSPEC_NORETURN wine_pthread_abort_thread( int status );
#endif /* __WINE_WINE_PTHREAD_H */
......@@ -38,33 +38,49 @@
#endif
#include "wine/library.h"
#ifdef HAVE_PTHREAD_H
#include "wine/pthread.h"
/* Note: the wine_pthread functions are just placeholders,
* they will be overridden by the pthread support code.
*/
/***********************************************************************
* wine_pthread_init_process
*
* This function is just a placeholder, it will be overridden by the pthread support code.
*/
void wine_pthread_init_process( const struct wine_pthread_functions *functions )
{
assert(0); /* we must never get here */
}
/***********************************************************************
* wine_pthread_init_thread
*
* This function is just a placeholder, it will be overridden by the pthread support code.
*/
void wine_pthread_init_thread(void)
{
assert(0); /* we must never get here */
}
#endif /* HAVE_PTHREAD_H */
/***********************************************************************
* wine_pthread_create_thread
*/
int wine_pthread_create_thread( struct wine_pthread_thread_info *info )
{
return -1;
}
/***********************************************************************
* wine_pthread_exit_thread
*/
void wine_pthread_exit_thread( struct wine_pthread_thread_info *info )
{
exit( info->exit_status );
}
/***********************************************************************
* wine_pthread_abort_thread
*/
void wine_pthread_abort_thread( int status )
{
exit( status );
}
/***********************************************************************
......
......@@ -25,8 +25,6 @@
#include "config.h"
#include "wine/port.h"
#ifndef HAVE_NPTL
struct _pthread_cleanup_buffer;
#include <assert.h>
......@@ -58,8 +56,11 @@ struct _pthread_cleanup_buffer;
#include <valgrind/memcheck.h>
#endif
#include "wine/library.h"
#include "wine/pthread.h"
#ifndef HAVE_NPTL
#define P_OUTPUT(stuff) write(2,stuff,strlen(stuff))
#define PSTR(str) __ASM_NAME(#str)
......@@ -151,6 +152,43 @@ static inline void writejump( const char *symbol, void *dest )
#endif /* __GLIBC__ && __i386__ */
}
/* temporary stacks used on thread exit */
#define TEMP_STACK_SIZE 1024
#define NB_TEMP_STACKS 8
static char temp_stacks[NB_TEMP_STACKS][TEMP_STACK_SIZE];
static LONG next_temp_stack; /* next temp stack to use */
/***********************************************************************
* get_temp_stack
*
* Get a temporary stack address to run the thread exit code on.
*/
inline static char *get_temp_stack(void)
{
unsigned int next = interlocked_xchg_add( &next_temp_stack, 1 );
return temp_stacks[next % NB_TEMP_STACKS] + TEMP_STACK_SIZE;
}
/***********************************************************************
* cleanup_thread
*
* Cleanup the remains of a thread. Runs on a temporary stack.
*/
static void cleanup_thread( void *ptr )
{
/* copy the info structure since it is on the stack we will free */
struct wine_pthread_thread_info info = *(struct wine_pthread_thread_info *)ptr;
wine_ldt_free_fs( info.teb_sel );
munmap( info.stack_base, info.stack_size );
munmap( info.teb_base, info.teb_size );
#ifdef HAVE__LWP_CREATE
_lwp_exit();
#endif
_exit( info.exit_status );
}
/***********************************************************************
* wine_pthread_init_process
*
......@@ -191,6 +229,78 @@ void wine_pthread_init_thread(void)
}
/***********************************************************************
* wine_pthread_create_thread
*/
int wine_pthread_create_thread( struct wine_pthread_thread_info *info )
{
if (!info->stack_base)
{
info->stack_base = wine_anon_mmap( NULL, info->stack_size,
PROT_READ | PROT_WRITE | PROT_EXEC, 0 );
if (info->stack_base == (void *)-1) return -1;
}
#ifdef HAVE_CLONE
if (clone( (int (*)(void *))info->entry, (char *)info->stack_base + info->stack_size,
CLONE_VM | CLONE_FS | CLONE_FILES | SIGCHLD, info ) < 0)
return -1;
return 0;
#elif defined(HAVE_RFORK)
{
void **sp = (void **)((char *)info->stack_base + info->stack_size);
*--sp = info;
*--sp = 0;
*--sp = info->entry;
__asm__ __volatile__(
"pushl %2;\n\t" /* flags */
"pushl $0;\n\t" /* 0 ? */
"movl %1,%%eax;\n\t" /* SYS_rfork */
".byte 0x9a; .long 0; .word 7;\n\t" /* lcall 7:0... FreeBSD syscall */
"cmpl $0, %%edx;\n\t"
"je 1f;\n\t"
"movl %0,%%esp;\n\t" /* child -> new thread */
"ret;\n"
"1:\n\t" /* parent -> caller thread */
"addl $8,%%esp" :
: "r" (sp), "g" (SYS_rfork), "g" (RFPROC | RFMEM)
: "eax", "edx");
return 0;
}
#elif defined(HAVE__LWP_CREATE)
{
ucontext_t context;
_lwp_makecontext( &context, (void(*)(void *))info->entry, info,
NULL, info->stack_base, info->stack_size );
if ( _lwp_create( &context, 0, NULL ) )
return -1;
return 0;
}
#endif
return -1;
}
/***********************************************************************
* wine_pthread_exit_thread
*/
void wine_pthread_exit_thread( struct wine_pthread_thread_info *info )
{
wine_switch_to_stack( cleanup_thread, info, get_temp_stack() );
}
/***********************************************************************
* wine_pthread_abort_thread
*/
void wine_pthread_abort_thread( int status )
{
#ifdef HAVE__LWP_CREATE
_lwp_exit();
#endif
_exit( status );
}
/* Currently this probably works only for glibc2,
* which checks for the presence of double-underscore-prepended
* pthread primitives, and use them if available.
......@@ -825,12 +935,84 @@ static struct pthread_functions libc_pthread_functions =
#else /* HAVE_NPTL */
/***********************************************************************
* wine_pthread_init_process
*
* Initialization for a newly created process.
*/
void wine_pthread_init_process( const struct wine_pthread_functions *functions )
{
}
/***********************************************************************
* wine_pthread_init_thread
*
* Initialization for a newly created thread.
*/
void wine_pthread_init_thread(void)
{
}
/***********************************************************************
* wine_pthread_create_thread
*/
int wine_pthread_create_thread( struct wine_pthread_thread_info *info )
{
pthread_t id;
pthread_attr_t attr;
if (!info->stack_base)
{
info->stack_base = wine_anon_mmap( NULL, info->stack_size,
PROT_READ | PROT_WRITE | PROT_EXEC, 0 );
if (info->stack_base == (void *)-1) return -1;
}
pthread_attr_init( &attr );
pthread_attr_setstack( &attr, info->stack_base, info->stack_size );
if (pthread_create( &id, &attr, (void * (*)(void *))info->entry, info )) return -1;
return 0;
}
/***********************************************************************
* wine_pthread_exit_thread
*/
void wine_pthread_exit_thread( struct wine_pthread_thread_info *info )
{
struct cleanup_info
{
pthread_t self;
struct wine_pthread_thread_info thread_info;
};
static struct cleanup_info *previous_info;
struct cleanup_info *cleanup_info, *free_info;
void *ptr;
/* store it at the end of the TEB structure */
cleanup_info = (struct cleanup_info *)((char *)info->teb_base + info->teb_size) - 1;
cleanup_info->self = pthread_self();
cleanup_info->thread_info = *info;
if ((free_info = interlocked_xchg_ptr( (void **)&previous_info, cleanup_info )) != NULL)
{
pthread_join( free_info->self, &ptr );
wine_ldt_free_fs( free_info->thread_info.teb_sel );
munmap( free_info->thread_info.stack_base, free_info->thread_info.stack_size );
munmap( free_info->thread_info.teb_base, free_info->thread_info.teb_size );
}
pthread_exit( (void *)info->exit_status );
}
/***********************************************************************
* wine_pthread_abort_thread
*/
void wine_pthread_abort_thread( int status )
{
pthread_exit( (void *)status );
}
#endif /* HAVE_NPTL */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment