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; }