Export SYSDEPS_SwitchToThreadStack() functionality from libwine as
wine_switch_to_stack().
This commit is contained in:
parent
d4c1ebabdb
commit
ca3bfd8318
|
@ -115,7 +115,7 @@ void SYSDEPS_SetCurThread( TEB *teb )
|
|||
inline static char *get_temp_stack(void)
|
||||
{
|
||||
unsigned int next = interlocked_xchg_add( &next_temp_stack, 1 );
|
||||
return temp_stacks[next % NB_TEMP_STACKS];
|
||||
return temp_stacks[next % NB_TEMP_STACKS] + TEMP_STACK_SIZE;
|
||||
}
|
||||
|
||||
|
||||
|
@ -192,73 +192,6 @@ int SYSDEPS_SpawnThread( void (*func)(TEB *), TEB *teb )
|
|||
}
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* SYSDEPS_SwitchToThreadStack
|
||||
*
|
||||
* Switch to the stack specified in the current thread TEB
|
||||
* and call the specified function.
|
||||
*/
|
||||
void DECLSPEC_NORETURN SYSDEPS_SwitchToThreadStack( void (*func)(void *), void *arg );
|
||||
#ifdef __i386__
|
||||
# ifdef __GNUC__
|
||||
__ASM_GLOBAL_FUNC( SYSDEPS_SwitchToThreadStack,
|
||||
"movl 4(%esp),%ecx\n\t" /* func */
|
||||
"movl 8(%esp),%edx\n\t" /* arg */
|
||||
".byte 0x64\n\tmovl 0x04,%esp\n\t" /* teb->Tib.StackBase */
|
||||
"pushl %edx\n\t"
|
||||
"xorl %ebp,%ebp\n\t"
|
||||
"call *%ecx\n\t"
|
||||
"int $3" /* we never return here */ );
|
||||
# elif defined(_MSC_VER)
|
||||
__declspec(naked) void SYSDEPS_SwitchToThreadStack( void (*func)(void *), void *arg )
|
||||
{
|
||||
__asm mov ecx, 4[esp];
|
||||
__asm mov edx, 8[esp];
|
||||
__asm mov fs:[0x04], esp;
|
||||
__asm push edx;
|
||||
__asm xor ebp, ebp;
|
||||
__asm call [ecx];
|
||||
__asm int 3;
|
||||
}
|
||||
# endif /* defined(__GNUC__) || defined(_MSC_VER) */
|
||||
#elif defined(__sparc__) && defined(__GNUC__)
|
||||
__ASM_GLOBAL_FUNC( SYSDEPS_SwitchToThreadStack,
|
||||
"mov %o0, %l0\n\t" /* store first argument */
|
||||
"call " __ASM_NAME("NtCurrentTeb") ", 0\n\t"
|
||||
"mov %o1, %l1\n\t" /* delay slot: store second argument */
|
||||
"ld [%o0+4], %sp\n\t" /* teb->Tib.StackBase */
|
||||
"call %l0, 0\n\t" /* call func */
|
||||
"mov %l1, %o0\n\t" /* delay slot: arg for func */
|
||||
"ta 0x01\n\t"); /* breakpoint - we never get here */
|
||||
#elif defined(__powerpc__) && defined(__APPLE__)
|
||||
/* Darwin SYSDEPS_SwitchToThreadStack
|
||||
Function Pointer to call is on r3, Args to pass on r4 and stack on r1 */
|
||||
__ASM_GLOBAL_FUNC( SYSDEPS_SwitchToThreadStack,
|
||||
"stw r1, 0x4(r13)\n\t" /* teb->Tib.StackBase */
|
||||
"mr r12,r3\n\t"
|
||||
"mtctr r12\n\t" /* func->ctr */
|
||||
"mr r3,r4\n\t" /* args->function param 1 (r3) */
|
||||
"bctr\n\t" /* call ctr */
|
||||
"b _SYSDEPS_SwitchToThreadStack+24\n\t"); /* loop */
|
||||
#elif defined(__powerpc__) && defined(__GNUC__)
|
||||
/* Linux SYSDEPS_SwitchToThreadStack
|
||||
Function Pointer to call is on r3, Args to pass on r4 and stack on r1 */
|
||||
__ASM_GLOBAL_FUNC( SYSDEPS_SwitchToThreadStack,
|
||||
"stw 1, 0x4(13)\n\t" /* teb->Tib.StackBase */
|
||||
"mr 12,3\n\t"
|
||||
"mtctr 12\n\t" /* func->ctr */
|
||||
"mr 3,4\n\t" /* args->function param 1 (r3) */
|
||||
"bctr\n\t" /* call ctr */
|
||||
"b _SYSDEPS_SwitchToThreadStack+24\n\t"); /* loop */
|
||||
#else /* !powerpc, !sparc, !i386 */
|
||||
void SYSDEPS_SwitchToThreadStack( void (*func)(void *), void *arg )
|
||||
{
|
||||
func( arg );
|
||||
while(1); /* avoid warning */
|
||||
}
|
||||
#endif /* !defined(__i386__) && !defined(__sparc__) */
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* SYSDEPS_ExitThread
|
||||
*
|
||||
|
@ -285,7 +218,6 @@ void SYSDEPS_ExitThread( int status )
|
|||
ptr = free_teb->DeallocationStack;
|
||||
NtFreeVirtualMemory( GetCurrentProcess(), &ptr, &size, MEM_RELEASE );
|
||||
}
|
||||
SIGNAL_Block();
|
||||
SYSDEPS_AbortThread( status );
|
||||
#else
|
||||
struct thread_cleanup_info info;
|
||||
|
@ -305,9 +237,7 @@ void SYSDEPS_ExitThread( int status )
|
|||
close( teb->reply_fd );
|
||||
close( teb->request_fd );
|
||||
SIGNAL_Reset();
|
||||
teb->Tib.StackLimit = get_temp_stack();
|
||||
teb->Tib.StackBase = (char *)teb->Tib.StackLimit + TEMP_STACK_SIZE;
|
||||
SYSDEPS_SwitchToThreadStack( cleanup_thread, &info );
|
||||
wine_switch_to_stack( cleanup_thread, &info, get_temp_stack() );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -149,7 +149,6 @@ extern void SYSDEPS_SetCurThread( TEB *teb );
|
|||
extern int SYSDEPS_GetUnixTid(void);
|
||||
extern void DECLSPEC_NORETURN SYSDEPS_ExitThread( int status );
|
||||
extern void DECLSPEC_NORETURN SYSDEPS_AbortThread( int status );
|
||||
extern void DECLSPEC_NORETURN SYSDEPS_SwitchToThreadStack( void (*func)(void *), void *arg );
|
||||
|
||||
/* signal handling */
|
||||
extern BOOL SIGNAL_Init(void);
|
||||
|
|
|
@ -61,12 +61,9 @@ extern int wine_dbg_parse_options( const char *str );
|
|||
|
||||
/* portability */
|
||||
|
||||
extern void DECLSPEC_NORETURN wine_switch_to_stack( void (*func)(void *), void *arg, void *stack );
|
||||
extern void *wine_anon_mmap( void *start, size_t size, int prot, int flags );
|
||||
|
||||
/* errno support */
|
||||
extern int* (*wine_errno_location)(void);
|
||||
extern int* (*wine_h_errno_location)(void);
|
||||
|
||||
/* LDT management */
|
||||
|
||||
extern void wine_ldt_init_locking( void (*lock_func)(void), void (*unlock_func)(void) );
|
||||
|
|
|
@ -11,7 +11,8 @@ C_SRCS = \
|
|||
config.c \
|
||||
debug.c \
|
||||
ldt.c \
|
||||
loader.c
|
||||
loader.c \
|
||||
port.c
|
||||
|
||||
@MAKE_LIB_RULES@
|
||||
|
||||
|
|
|
@ -33,9 +33,6 @@
|
|||
#ifdef HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDINT_H
|
||||
# include <stdint.h>
|
||||
#endif
|
||||
|
||||
#define NONAMELESSUNION
|
||||
#define NONAMELESSSTRUCT
|
||||
|
@ -428,134 +425,6 @@ void wine_init( int argc, char *argv[], char *error, int error_size )
|
|||
}
|
||||
|
||||
|
||||
#if (defined(__svr4__) || defined(__NetBSD__)) && !defined(MAP_TRYFIXED)
|
||||
/***********************************************************************
|
||||
* try_mmap_fixed
|
||||
*
|
||||
* The purpose of this routine is to emulate the behaviour of
|
||||
* the Linux mmap() routine if a non-NULL address is passed,
|
||||
* but the MAP_FIXED flag is not set. Linux in this case tries
|
||||
* to place the mapping at the specified address, *unless* the
|
||||
* range is already in use. Solaris, however, completely ignores
|
||||
* the address argument in this case.
|
||||
*
|
||||
* As Wine code occasionally relies on the Linux behaviour, e.g. to
|
||||
* be able to map non-relocateable PE executables to their proper
|
||||
* start addresses, or to map the DOS memory to 0, this routine
|
||||
* emulates the Linux behaviour by checking whether the desired
|
||||
* address range is still available, and placing the mapping there
|
||||
* using MAP_FIXED if so.
|
||||
*/
|
||||
static int try_mmap_fixed (void *addr, size_t len, int prot, int flags,
|
||||
int fildes, off_t off)
|
||||
{
|
||||
char * volatile result = NULL;
|
||||
int pagesize = getpagesize();
|
||||
pid_t pid;
|
||||
|
||||
/* We only try to map to a fixed address if
|
||||
addr is non-NULL and properly aligned,
|
||||
and MAP_FIXED isn't already specified. */
|
||||
|
||||
if ( !addr )
|
||||
return 0;
|
||||
if ( (uintptr_t)addr & (pagesize-1) )
|
||||
return 0;
|
||||
if ( flags & MAP_FIXED )
|
||||
return 0;
|
||||
|
||||
/* We use vfork() to freeze all threads of the
|
||||
current process. This allows us to check without
|
||||
race condition whether the desired memory range is
|
||||
already in use. Note that because vfork() shares
|
||||
the address spaces between parent and child, we
|
||||
can actually perform the mapping in the child. */
|
||||
|
||||
if ( (pid = vfork()) == -1 )
|
||||
{
|
||||
perror("try_mmap_fixed: vfork");
|
||||
exit(1);
|
||||
}
|
||||
if ( pid == 0 )
|
||||
{
|
||||
int i;
|
||||
char vec;
|
||||
|
||||
/* We call mincore() for every page in the desired range.
|
||||
If any of these calls succeeds, the page is already
|
||||
mapped and we must fail. */
|
||||
for ( i = 0; i < len; i += pagesize )
|
||||
if ( mincore( (caddr_t)addr + i, pagesize, &vec ) != -1 )
|
||||
_exit(1);
|
||||
|
||||
/* Perform the mapping with MAP_FIXED set. This is safe
|
||||
now, as none of the pages is currently in use. */
|
||||
result = mmap( addr, len, prot, flags | MAP_FIXED, fildes, off );
|
||||
if ( result == addr )
|
||||
_exit(0);
|
||||
|
||||
if ( result != (void *) -1 ) /* This should never happen ... */
|
||||
munmap( result, len );
|
||||
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
/* vfork() lets the parent continue only after the child
|
||||
has exited. Furthermore, Wine sets SIGCHLD to SIG_IGN,
|
||||
so we don't need to wait for the child. */
|
||||
|
||||
return result == addr;
|
||||
}
|
||||
#endif /* (__svr4__ || __NetBSD__) && !MAP_TRYFIXED */
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* wine_anon_mmap
|
||||
*
|
||||
* Portable wrapper for anonymous mmaps
|
||||
*/
|
||||
void *wine_anon_mmap( void *start, size_t size, int prot, int flags )
|
||||
{
|
||||
#ifdef HAVE_MMAP
|
||||
static int fdzero = -1;
|
||||
|
||||
#ifdef MAP_ANON
|
||||
flags |= MAP_ANON;
|
||||
#else
|
||||
if (fdzero == -1)
|
||||
{
|
||||
if ((fdzero = open( "/dev/zero", O_RDONLY )) == -1)
|
||||
{
|
||||
perror( "/dev/zero: open" );
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
#endif /* MAP_ANON */
|
||||
|
||||
#ifdef MAP_SHARED
|
||||
flags &= ~MAP_SHARED;
|
||||
#endif
|
||||
|
||||
/* Linux EINVAL's on us if we don't pass MAP_PRIVATE to an anon mmap */
|
||||
#ifdef MAP_PRIVATE
|
||||
flags |= MAP_PRIVATE;
|
||||
#endif
|
||||
|
||||
#ifdef MAP_TRYFIXED
|
||||
/* If available, this will attempt a fixed mapping in-kernel */
|
||||
flags |= MAP_TRYFIXED;
|
||||
#elif defined(__svr4__) || defined(__NetBSD__)
|
||||
if ( try_mmap_fixed( start, size, prot, flags, fdzero, 0 ) )
|
||||
return start;
|
||||
#endif
|
||||
|
||||
return mmap( start, size, prot, flags, fdzero, 0 );
|
||||
#else
|
||||
return (void *)-1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* These functions provide wrappers around dlopen() and associated
|
||||
* functions. They work around a bug in glibc 2.1.x where calling
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Wine portability routines
|
||||
*
|
||||
* Copyright 2000 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "wine/port.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef HAVE_SYS_MMAN_H
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDINT_H
|
||||
# include <stdint.h>
|
||||
#endif
|
||||
|
||||
#include "wine/library.h"
|
||||
|
||||
/***********************************************************************
|
||||
* wine_switch_to_stack
|
||||
*
|
||||
* Switch to the specified stack and call the function.
|
||||
*/
|
||||
void DECLSPEC_NORETURN wine_switch_to_stack( void (*func)(void *), void *arg, void *stack );
|
||||
#if defined(__i386__) && defined(__GNUC__)
|
||||
__ASM_GLOBAL_FUNC( wine_switch_to_stack,
|
||||
"movl 4(%esp),%ecx\n\t" /* func */
|
||||
"movl 8(%esp),%edx\n\t" /* arg */
|
||||
"movl 12(%esp),%esp\n\t" /* stack */
|
||||
"pushl %edx\n\t"
|
||||
"xorl %ebp,%ebp\n\t"
|
||||
"call *%ecx\n\t"
|
||||
"int $3" /* we never return here */ );
|
||||
#elif defined(__i386__) && defined(_MSC_VER)
|
||||
__declspec(naked) void wine_switch_to_stack( void (*func)(void *), void *arg, void *stack )
|
||||
{
|
||||
__asm mov ecx, 4[esp];
|
||||
__asm mov edx, 8[esp];
|
||||
__asm mov esp, 12[esp];
|
||||
__asm push edx;
|
||||
__asm xor ebp, ebp;
|
||||
__asm call [ecx];
|
||||
__asm int 3;
|
||||
}
|
||||
#elif defined(__sparc__) && defined(__GNUC__)
|
||||
__ASM_GLOBAL_FUNC( wine_switch_to_stack,
|
||||
"mov %o0, %l0\n\t" /* store first argument */
|
||||
"mov %o1, %l1\n\t" /* store second argument */
|
||||
"mov %o2, %sp\n\t" /* store stack */
|
||||
"call %l0, 0\n\t" /* call func */
|
||||
"mov %l1, %o0\n\t" /* delay slot: arg for func */
|
||||
"ta 0x01"); /* breakpoint - we never get here */
|
||||
#elif defined(__powerpc__) && defined(__APPLE__)
|
||||
__ASM_GLOBAL_FUNC( wine_switch_to_stack,
|
||||
"mtctr r3\n\t" /* func -> ctr */
|
||||
"mr r3,r4\n\t" /* args -> function param 1 (r3) */
|
||||
"mr r1,r5\n\t" /* stack */
|
||||
"bctr\n" /* call ctr */
|
||||
"1:\tb 1b"); /* loop */
|
||||
#elif defined(__powerpc__) && defined(__GNUC__)
|
||||
__ASM_GLOBAL_FUNC( wine_switch_to_stack,
|
||||
"mtctr 3\n\t" /* func -> ctr */
|
||||
"mr 3,4\n\t" /* args -> function param 1 (r3) */
|
||||
"mr 1,5\n\t" /* stack */
|
||||
"bctr\n\t" /* call ctr */
|
||||
"1:\tb 1b"); /* loop */
|
||||
#else
|
||||
#error You must implement wine_switch_to_stack for your platform
|
||||
#endif
|
||||
|
||||
|
||||
#if (defined(__svr4__) || defined(__NetBSD__)) && !defined(MAP_TRYFIXED)
|
||||
/***********************************************************************
|
||||
* try_mmap_fixed
|
||||
*
|
||||
* The purpose of this routine is to emulate the behaviour of
|
||||
* the Linux mmap() routine if a non-NULL address is passed,
|
||||
* but the MAP_FIXED flag is not set. Linux in this case tries
|
||||
* to place the mapping at the specified address, *unless* the
|
||||
* range is already in use. Solaris, however, completely ignores
|
||||
* the address argument in this case.
|
||||
*
|
||||
* As Wine code occasionally relies on the Linux behaviour, e.g. to
|
||||
* be able to map non-relocateable PE executables to their proper
|
||||
* start addresses, or to map the DOS memory to 0, this routine
|
||||
* emulates the Linux behaviour by checking whether the desired
|
||||
* address range is still available, and placing the mapping there
|
||||
* using MAP_FIXED if so.
|
||||
*/
|
||||
static int try_mmap_fixed (void *addr, size_t len, int prot, int flags,
|
||||
int fildes, off_t off)
|
||||
{
|
||||
char * volatile result = NULL;
|
||||
int pagesize = getpagesize();
|
||||
pid_t pid;
|
||||
|
||||
/* We only try to map to a fixed address if
|
||||
addr is non-NULL and properly aligned,
|
||||
and MAP_FIXED isn't already specified. */
|
||||
|
||||
if ( !addr )
|
||||
return 0;
|
||||
if ( (uintptr_t)addr & (pagesize-1) )
|
||||
return 0;
|
||||
if ( flags & MAP_FIXED )
|
||||
return 0;
|
||||
|
||||
/* We use vfork() to freeze all threads of the
|
||||
current process. This allows us to check without
|
||||
race condition whether the desired memory range is
|
||||
already in use. Note that because vfork() shares
|
||||
the address spaces between parent and child, we
|
||||
can actually perform the mapping in the child. */
|
||||
|
||||
if ( (pid = vfork()) == -1 )
|
||||
{
|
||||
perror("try_mmap_fixed: vfork");
|
||||
exit(1);
|
||||
}
|
||||
if ( pid == 0 )
|
||||
{
|
||||
int i;
|
||||
char vec;
|
||||
|
||||
/* We call mincore() for every page in the desired range.
|
||||
If any of these calls succeeds, the page is already
|
||||
mapped and we must fail. */
|
||||
for ( i = 0; i < len; i += pagesize )
|
||||
if ( mincore( (caddr_t)addr + i, pagesize, &vec ) != -1 )
|
||||
_exit(1);
|
||||
|
||||
/* Perform the mapping with MAP_FIXED set. This is safe
|
||||
now, as none of the pages is currently in use. */
|
||||
result = mmap( addr, len, prot, flags | MAP_FIXED, fildes, off );
|
||||
if ( result == addr )
|
||||
_exit(0);
|
||||
|
||||
if ( result != (void *) -1 ) /* This should never happen ... */
|
||||
munmap( result, len );
|
||||
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
/* vfork() lets the parent continue only after the child
|
||||
has exited. Furthermore, Wine sets SIGCHLD to SIG_IGN,
|
||||
so we don't need to wait for the child. */
|
||||
|
||||
return result == addr;
|
||||
}
|
||||
#endif /* (__svr4__ || __NetBSD__) && !MAP_TRYFIXED */
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* wine_anon_mmap
|
||||
*
|
||||
* Portable wrapper for anonymous mmaps
|
||||
*/
|
||||
void *wine_anon_mmap( void *start, size_t size, int prot, int flags )
|
||||
{
|
||||
#ifdef HAVE_MMAP
|
||||
static int fdzero = -1;
|
||||
|
||||
#ifdef MAP_ANON
|
||||
flags |= MAP_ANON;
|
||||
#else
|
||||
if (fdzero == -1)
|
||||
{
|
||||
if ((fdzero = open( "/dev/zero", O_RDONLY )) == -1)
|
||||
{
|
||||
perror( "/dev/zero: open" );
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
#endif /* MAP_ANON */
|
||||
|
||||
#ifdef MAP_SHARED
|
||||
flags &= ~MAP_SHARED;
|
||||
#endif
|
||||
|
||||
/* Linux EINVAL's on us if we don't pass MAP_PRIVATE to an anon mmap */
|
||||
#ifdef MAP_PRIVATE
|
||||
flags |= MAP_PRIVATE;
|
||||
#endif
|
||||
|
||||
#ifdef MAP_TRYFIXED
|
||||
/* If available, this will attempt a fixed mapping in-kernel */
|
||||
flags |= MAP_TRYFIXED;
|
||||
#elif defined(__svr4__) || defined(__NetBSD__)
|
||||
if ( try_mmap_fixed( start, size, prot, flags, fdzero, 0 ) )
|
||||
return start;
|
||||
#endif
|
||||
|
||||
return mmap( start, size, prot, flags, fdzero, 0 );
|
||||
#else
|
||||
return (void *)-1;
|
||||
#endif
|
||||
}
|
|
@ -27,6 +27,7 @@
|
|||
#include "winbase.h"
|
||||
#include "winerror.h"
|
||||
#include "wine/exception.h"
|
||||
#include "wine/library.h"
|
||||
#include "thread.h"
|
||||
|
||||
struct fiber_data
|
||||
|
@ -190,7 +191,7 @@ void WINAPI SwitchToFiber( LPVOID fiber )
|
|||
NtCurrentTeb()->Tib.StackLimit = new_fiber->stack_limit;
|
||||
NtCurrentTeb()->DeallocationStack = new_fiber->stack_allocation;
|
||||
if (new_fiber->start) /* first time */
|
||||
SYSDEPS_SwitchToThreadStack( start_fiber, new_fiber );
|
||||
wine_switch_to_stack( start_fiber, new_fiber, new_fiber->stack_base );
|
||||
else
|
||||
longjmp( new_fiber->jmpbuf, 1 );
|
||||
}
|
||||
|
|
|
@ -586,7 +586,7 @@ void __wine_process_init( int argc, char *argv[] )
|
|||
if (!THREAD_InitStack( NtCurrentTeb(), stack_size )) goto error;
|
||||
|
||||
/* switch to the new stack */
|
||||
SYSDEPS_SwitchToThreadStack( start_process, NULL );
|
||||
wine_switch_to_stack( start_process, NULL, NtCurrentTeb()->Tib.StackBase );
|
||||
|
||||
error:
|
||||
ExitProcess( GetLastError() );
|
||||
|
|
Loading…
Reference in New Issue