current version of "wine_adopt_thread"

Paul Davis paul at linuxaudiosystems.com
Mon Apr 12 13:03:46 CDT 2004


I offer this for Alexandre and other's consideration and
comments. This is already in use in libfst, which is in turn used by
Ardour, a native linux digital audio workstation, and gAlan, a native
linux modular synthesis system.

There is still no proper cleanup on failure, and nothing is done if
the adopted thread calls pthread_exit(). Both are simple to fix.

--p

----------------------------------------------------------------------

/*
 * winelib code - Initializes wine as shared library
 *
 * Copyright 2004 Novell, Inc. (http://www.novell.com/)
 *
 * 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 <stdio.h>
#include <windows.h>
#include <windows/ntstatus.h>
#include <setjmp.h>
#include <signal.h>
#include <fcntl.h>
#include <wine/library.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <limits.h>
#include <errno.h>
#include <string.h>

#include "thread.h"
#include "pthread.h"
#include "server.h"
#include "libwinelib.h"
#include "fst.h"

#include <winternl.h>

typedef NTSTATUS WINAPI (*NtProtectVirtualMemory_ptr)(HANDLE, PVOID*, ULONG*, ULONG, ULONG*);
typedef NTSTATUS WINAPI (*NtFreeVirtualMemory_ptr)(HANDLE, PVOID, ULONG*, ULONG);
typedef NTSTATUS WINAPI (*NtAllocateVirtualMemory_ptr)(HANDLE, PVOID*, PVOID, ULONG*, ULONG, ULONG);
typedef VOID WINAPI (*RtlAcquirePebLock_ptr) (VOID);
typedef VOID WINAPI (*RtlReleasePebLock_ptr) (VOID);

static NtProtectVirtualMemory_ptr ntprotectvm;
static NtAllocateVirtualMemory_ptr ntallocatevm;
static NtFreeVirtualMemory_ptr ntfreevm;
static RtlAcquirePebLock_ptr acquirepeblock;
static RtlReleasePebLock_ptr releasepeblock;
static LIST_ENTRY tls_link_head;
static PEB* peb;
static HTASK16 htask16;

static unsigned int (*server_call)(void*);
static void (*send_fd)(int);
static void (*init_thread)(int,int,void*);

static jmp_buf jump; 
static void (*sharedwine_signal_handler)(int signal, siginfo_t*, void*) = NULL;

void* initial_teb;

static void
setup_signal (int sig)
{
	struct sigaction sigact;
	struct sigaction oldact;

	sigact.sa_handler = (void (*)(int)) sharedwine_signal_handler;
	sigact.sa_flags = SA_RESTART|SA_SIGINFO;
	sigact.sa_sigaction = (void (*)(int signal, siginfo_t* info, void *ptr)) sharedwine_signal_handler;
	sigemptyset (&sigact.sa_mask);
	sigaddset  (&sigact.sa_mask, SIGINT);
	sigaddset  (&sigact.sa_mask, SIGUSR2);

	if (sigaction (sig, &sigact, NULL)) {
		fst_error ("could not install signal handler for signal %d", sig);
	}
}

void
redirect_signals ()
{
	setup_signal (SIGINT);
	setup_signal (SIGFPE);
	setup_signal (SIGILL);
	setup_signal (SIGSEGV);
	setup_signal (SIGABRT);
	setup_signal (SIGTERM);
	setup_signal (SIGBUS);
	setup_signal (SIGTRAP);
}

/* The per-thread signal stack size */
#ifdef __i386__
#define SIGNAL_STACK_SIZE  4096
#else
#define SIGNAL_STACK_SIZE  0  /* we don't need a signal stack on non-i386 */
#endif

/* from winnt.h, but hard to make it appear */

#define MEM_SYSTEM              0x80000000

/***********************************************************************
 *           alloc_teb (no difference from libwine, but its static there)
 */

static TEB *shared_alloc_teb( ULONG *size )
{
    TEB *teb;

    *size = SIGNAL_STACK_SIZE + sizeof(TEB);
    teb = wine_anon_mmap( NULL, *size, PROT_READ | PROT_WRITE | PROT_EXEC, 0 );
    if (teb == (TEB *)-1) return NULL;
    if (!(teb->teb_sel = wine_ldt_alloc_fs()))
    {
        munmap( teb, *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;
    teb->StaticUnicodeString.MaximumLength = sizeof(teb->StaticUnicodeBuffer);

    return teb;
}

/***********************************************************************
 *           free_teb (no change from libwine, but its static there)
 */
static inline void shared_free_teb( TEB *teb )
{
    ULONG size = 0;
    void *addr = teb;

    ntfreevm ( GetCurrentProcess(), &addr, &size, MEM_RELEASE );
    wine_ldt_free_fs( teb->teb_sel );
    munmap( teb, SIGNAL_STACK_SIZE + sizeof(TEB) );
}

static int proxy_pripe_request[2];
static int proxy_pripe_status[2];
static pthread_mutex_t proxy_thread_lock;

static DWORD WINAPI new_thread_proxy (LPVOID param)
{
	int request_pipe[2];
	NTSTATUS status;
	struct __server_request_info sri;
	struct new_thread_request *req = &sri.u.req.new_thread_request;
	int fd = -1;
	TEB *teb;
	struct wine_pthread_thread_info thread_info;
	ULONG size;

	if (pipe (proxy_pripe_request)) {
		fst_error ("cannot set up proxy request pipe (%s)", strerror (errno));
		return -1;
	}

	if (pipe (proxy_pripe_status)) {
		fst_error ("cannot set up proxy reply pipe (%s)", strerror (errno));
		return -1;
	}

	while (1) {
		
		/* wait for a request size to be written */
		
		if (read (proxy_pripe_request[0], &sri, sizeof (sri)) != sizeof (sri)) {
			fst_error ("cannot read new thread request from proxy request pipe (%s)", strerror (errno));
			return -1;
		}

		if (pipe (request_pipe) >= 0) {

			/* set up a new request pipe */
			
			fcntl (request_pipe[1], F_SETFD, 1); /* set close on exec flag */
			/* server_*/ send_fd (request_pipe[0]);
			
			req->request_fd = request_pipe[0];
			
			if ((status = server_call (req)) == 0) {
				fd = request_pipe[1];
			} else {
				close (request_pipe[0]);
				close (request_pipe[1]);
			}
		}

		/* create a new TEB. the "thread_info" structure seems to be
		   the most convenient way of passing back the result.
		 */

		teb = shared_alloc_teb (&size);

		thread_info.stack_base = NULL;
		thread_info.stack_size = 0;
		thread_info.teb_base   = teb;
		thread_info.teb_size   = size;
		thread_info.teb_sel    = teb->teb_sel;
		
		/* send back the result, request fd and the newly allocated TEB info */

		if (write (proxy_pripe_status[1], &sri, sizeof (sri)) != sizeof (sri)) {
			fst_error ("cannot write request/reply back to proxy reply pipe (%s)", strerror (errno));
			/* XXX free teb */
			return -1;
		}
		
		if (write (proxy_pripe_status[1], &fd, sizeof (fd)) != sizeof (status)) {
			fst_error ("cannot write status back to proxy reply pipe (%s)", strerror (errno));
			/* XXX free teb */
			return -1;
		}

		if (write (proxy_pripe_status[1], &thread_info, sizeof (thread_info)) != sizeof (thread_info)) {
			fst_error ("cannot write thread info to proxy reply pipe (%s)", strerror (errno));
			/* XXX free teb */
			return -1;
		}

		close (request_pipe[0]);
	}

	return;
}

extern int wine_shared_premain ();

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
	int jmpstat = 1;

	if (CreateThread (NULL, 0, new_thread_proxy, NULL, 0, NULL) == NULL) {
		fst_error ("could not create new thread proxy");
	}

	if (wine_shared_premain ()) {
		jmpstat = 2;
	} 
	
	// return to caller.

	longjmp (jump, jmpstat);
}

/*
	WineLoadLibrary is used by System.Windows.Forms to import the Wine dlls
*/
void *
WineLoadLibrary(unsigned char *dll)
{
	return(LoadLibraryA(dll));
}

void *
WineGetProcAddress(void *handle, unsigned char *function)
{
	return(GetProcAddress(handle, function));
}

int
SharedWineInit (void (*sighandler)(int,siginfo_t*,void*))
{
	unsigned char	Error[1024]="";
	char *WineArguments[] = {"sharedapp", LIBPATH "/libfst.so", NULL};
	void* stackbase;
	size_t stacksize;
	void *ntdll;
	void *ntso;
	char ntdllpath[PATH_MAX+1];
	char* dlerr;
	
	sharedwine_signal_handler = sighandler;

	if (setjmp (jump) == 0) {

		wine_init (2, WineArguments, Error, sizeof (Error));
		
		if (Error[0]!='\0') {
			fst_error ("Wine initialization error:%s\n", Error);
			return -1;
		}
	}

	redirect_signals ();
	
	initial_teb = NtCurrentTeb();

	/* set the stack limits so that any exception handling based on
	   tracking stack boundaries has a chance of working.
	*/

	NtCurrentTeb()->Tib.StackBase  = (void*) ~0UL;
	NtCurrentTeb()->Tib.StackLimit = 0;

	putenv ("_WINE_SHAREDLIB_PATH=" DLLPATH);

	/* Loading this will pull in many other DLLs that would
	   otherwise be loaded+unloaded over and over again
	   during plugin discovery.
	*/

	if (WineLoadLibrary ("user32.dll") == NULL) {
		fst_error ("cannot load Windows DLL user32");
		return 1;
	}

	if (WineLoadLibrary ("comdlg32.dll") == NULL) {
		fst_error ("cannot load Windows DLL comdlg32");
		return 1;
	}

	if (WineLoadLibrary ("oleaut32.dll") == NULL) {
		fst_error ("cannot load Windows DLL oleaut32");
		return 1;
	}

	/* set up the functions and data we need to be able to adopt
	   threads.
	*/

#define GET_SYMBOL(res,handle,name) if ((res = WineGetProcAddress (handle,name)) == NULL) { fst_error ("cannot find Windows function %s", name); return -1; }
	
	if ((ntdll = WineLoadLibrary( "ntdll.dll")) == NULL) {
		fst_error ("cannot load Windows DLL ntdll");
		return 1;
	}

	GET_SYMBOL (ntallocatevm, ntdll, "NtAllocateVirtualMemory");
	GET_SYMBOL (ntfreevm, ntdll, "NtFreeVirtualMemory");
	GET_SYMBOL (ntprotectvm, ntdll, "NtProtectVirtualMemory");
	GET_SYMBOL (acquirepeblock, ntdll, "RtlAcquirePebLock");
	GET_SYMBOL (releasepeblock, ntdll, "RtlReleasePebLock");

	tls_link_head = NtCurrentTeb()->TlsLinks;
	peb = NtCurrentTeb()->Peb;
	htask16 = NtCurrentTeb()->htask16;

	sprintf (ntdllpath, "%s/ntdll.dll.so", DLLPATH);

	if ((ntso = dlopen (ntdllpath, RTLD_NOW|RTLD_GLOBAL)) == NULL) {
		fst_error ("cannot open NT DLL (%s)", ntdllpath);
		return -1;
	}

	server_call = dlsym (ntso, "wine_server_call");
	if ((dlerr = dlerror ()) != NULL) {
		fst_error ("cannot find wine_server_call (%s)", dlerr);
		return -1;
	}
	send_fd = dlsym (ntso, "wine_server_send_fd");
	if ((dlerr = dlerror ()) != NULL) {
		fst_error ("cannot find wine_server_send_fd (%s)", dlerr);
		return -1;
	}
	init_thread = dlsym (ntso, "server_init_thread");
	if ((dlerr = dlerror ()) != NULL) {
		fst_error ("cannot find server_init_thread (%s)", dlerr);
		return -1;
	}

        return 0;
}

/***********************************************************************
 *  adopt_thread - take an existing linux thread (of some kind),
 *  make the wineserver aware of it, and set up the TEB so that
 *  we can execute win32 functions in it.
 */
int
wine_adopt_thread (void)
{
    struct wine_pthread_thread_info thread_info;
    struct debug_info* debug_info;
    TEB* teb;
    int request_fd;
    int tid;
    stack_t altstack;
    void *addr;
    ULONG size;

    SERVER_START_REQ( new_thread )
    {
        req->suspend    = FALSE;
        req->inherit    = 0;  /* FIXME */

	pthread_mutex_lock (&proxy_thread_lock);

	if (write (proxy_pripe_request[1], &__req, sizeof (__req)) != sizeof (__req) ||
	    read (proxy_pripe_status[0], &__req, sizeof (__req)) != sizeof (__req) ||
	    read (proxy_pripe_status[0], &request_fd, sizeof (request_fd)) != sizeof (request_fd) ||
	    read (proxy_pripe_status[0], &thread_info, sizeof (thread_info)) != sizeof (thread_info))

	{
		fst_error ("cannot read/write proxy thread request (%s)", strerror (errno));
	}

	pthread_mutex_unlock (&proxy_thread_lock);
	
        if (request_fd >= 0)
        {
            tid = reply->tid;
        }
	
    }
    SERVER_END_REQ;

    if (request_fd < 0) goto error;
    
    debug_info = (struct debug_info *) malloc (sizeof (struct debug_info));
    
    debug_info->str_pos = debug_info->strings;
    debug_info->out_pos = debug_info->output;
    
    teb = (TEB*) thread_info.teb_base;
    size = thread_info.teb_size;

    teb->tibflags      = TEBF_WIN32;
    teb->exit_code     = STILL_ACTIVE;
    teb->request_fd    = -1;
    teb->request_fd    = -1;
    teb->reply_fd      = -1;
    teb->wait_fd[0]    = -1;
    teb->wait_fd[1]    = -1;
    teb->debug_info    = debug_info;
    teb->htask16       = htask16;

    wine_pthread_init_current_teb (&thread_info);

    teb->ClientId.UniqueProcess = (HANDLE)GetCurrentProcessId();
    teb->ClientId.UniqueThread  = (HANDLE)tid;
    teb->request_fd = request_fd;

    /* don't bother with signal initialization since we redirect all signals
       back to linux anyway.
    */
    /* SIGNAL_Init(); */

    /* server_*/ init_thread( thread_info.pid, thread_info.tid, NULL );
    wine_pthread_init_thread( &thread_info );

    /* create a memory view for the TEB */
    ntallocatevm ( GetCurrentProcess(), &addr, teb, &size,
                             MEM_SYSTEM, PAGE_EXECUTE_READWRITE );

    /* allocate a memory view for the stack */
    size = thread_info.stack_size;
    ntallocatevm ( GetCurrentProcess(), &teb->DeallocationStack, thread_info.stack_base,
                             &size, MEM_SYSTEM, PAGE_EXECUTE_READWRITE );
    /* limit is lower than base since the stack grows down */
    teb->Tib.StackBase  = (char *)thread_info.stack_base + thread_info.stack_size;
    teb->Tib.StackLimit = thread_info.stack_base;

    /* setup the guard page */
    size = 1;
    ntprotectvm ( GetCurrentProcess(), &teb->DeallocationStack, &size,
                            PAGE_EXECUTE_READWRITE | PAGE_GUARD, NULL );

    acquirepeblock ();
    InsertHeadList( &tls_link_head, &teb->TlsLinks );
    releasepeblock ();
    
    return 0;

  error:
    shared_free_teb (teb);
    /* XXX deallocate VM */
    /* close thread handle */
    close (request_fd);
    return -1;
}



More information about the wine-devel mailing list