/* * Unix interface for loader functions * * Copyright (C) 2020 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 */ #if 0 #pragma makedep unix #endif #include "config.h" #include "wine/port.h" #include #include #include #include #include #include #include #ifdef HAVE_PWD_H # include #endif #ifdef HAVE_SYS_MMAN_H # include #endif #ifdef HAVE_SYS_PRCTL_H # include #endif #ifdef HAVE_SYS_RESOURCE_H # include #endif #ifdef HAVE_SYS_UTSNAME_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef __APPLE__ # include # define LoadResource MacLoadResource # define GetCurrentThread MacGetCurrentThread # include # undef LoadResource # undef GetCurrentThread # include # include # include # include # ifndef _POSIX_SPAWN_DISABLE_ASLR # define _POSIX_SPAWN_DISABLE_ASLR 0x0100 # endif #endif #include "ntstatus.h" #define WIN32_NO_STATUS #define NONAMELESSUNION #define NONAMELESSSTRUCT #include "windef.h" #include "winnt.h" #include "winbase.h" #include "winnls.h" #include "winternl.h" #include "unix_private.h" #include "wine/library.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(ntdll); extern IMAGE_NT_HEADERS __wine_spec_nt_header; extern void CDECL __wine_set_unix_funcs( int version, const struct unix_funcs *funcs ); #ifdef __GNUC__ static void fatal_error( const char *err, ... ) __attribute__((noreturn, format(printf,1,2))); #endif extern int __wine_main_argc; extern char **__wine_main_argv; extern char **__wine_main_environ; #if defined(linux) || defined(__APPLE__) static const BOOL use_preloader = TRUE; #else static const BOOL use_preloader = FALSE; #endif static const BOOL is_win64 = (sizeof(void *) > sizeof(int)); static int main_argc; static char **main_argv; static char **main_envp; static char *argv0; static const char *bin_dir; static const char *dll_dir; static const char **dll_paths; static SIZE_T dll_path_maxlen; const char *data_dir = NULL; const char *build_dir = NULL; const char *config_dir = NULL; static CPTABLEINFO unix_table; static inline void *get_rva( const IMAGE_NT_HEADERS *nt, ULONG_PTR addr ) { return (BYTE *)nt + addr; } /* adjust an array of pointers to make them into RVAs */ static inline void fixup_rva_ptrs( void *array, BYTE *base, unsigned int count ) { BYTE **src = array; DWORD *dst = array; for ( ; count; count--, src++, dst++) *dst = *src ? *src - base : 0; } /* fixup an array of RVAs by adding the specified delta */ static inline void fixup_rva_dwords( DWORD *ptr, int delta, unsigned int count ) { for ( ; count; count--, ptr++) if (*ptr) *ptr += delta; } /* fixup an array of name/ordinal RVAs by adding the specified delta */ static inline void fixup_rva_names( UINT_PTR *ptr, int delta ) { for ( ; *ptr; ptr++) if (!(*ptr & IMAGE_ORDINAL_FLAG)) *ptr += delta; } /* fixup RVAs in the resource directory */ static void fixup_so_resources( IMAGE_RESOURCE_DIRECTORY *dir, BYTE *root, int delta ) { IMAGE_RESOURCE_DIRECTORY_ENTRY *entry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)(dir + 1); unsigned int i; for (i = 0; i < dir->NumberOfNamedEntries + dir->NumberOfIdEntries; i++, entry++) { void *ptr = root + entry->u2.s2.OffsetToDirectory; if (entry->u2.s2.DataIsDirectory) fixup_so_resources( ptr, root, delta ); else fixup_rva_dwords( &((IMAGE_RESOURCE_DATA_ENTRY *)ptr)->OffsetToData, delta, 1 ); } } /* die on a fatal error; use only during initialization */ static void fatal_error( const char *err, ... ) { va_list args; va_start( args, err ); fprintf( stderr, "wine: " ); vfprintf( stderr, err, args ); va_end( args ); exit(1); } static void set_max_limit( int limit ) { struct rlimit rlimit; if (!getrlimit( limit, &rlimit )) { rlimit.rlim_cur = rlimit.rlim_max; setrlimit( limit, &rlimit ); } } /* canonicalize path and return its directory name */ static char *realpath_dirname( const char *name ) { char *p, *fullpath = realpath( name, NULL ); if (fullpath) { p = strrchr( fullpath, '/' ); if (p == fullpath) p++; if (p) *p = 0; } return fullpath; } /* if string ends with tail, remove it */ static char *remove_tail( const char *str, const char *tail ) { size_t len = strlen( str ); size_t tail_len = strlen( tail ); char *ret; if (len < tail_len) return NULL; if (strcmp( str + len - tail_len, tail )) return NULL; ret = malloc( len - tail_len + 1 ); memcpy( ret, str, len - tail_len ); ret[len - tail_len] = 0; return ret; } /* build a path from the specified dir and name */ static char *build_path( const char *dir, const char *name ) { size_t len = strlen( dir ); char *ret = malloc( len + strlen( name ) + 2 ); memcpy( ret, dir, len ); if (len && ret[len - 1] != '/') ret[len++] = '/'; strcpy( ret + len, name ); return ret; } static void set_dll_path(void) { char *p, *path = getenv( "WINEDLLPATH" ); int i, count = 0; if (path) for (p = path, count = 1; *p; p++) if (*p == ':') count++; dll_paths = malloc( (count + 2) * sizeof(*dll_paths) ); count = 0; if (!build_dir) dll_paths[count++] = dll_dir; if (path) { path = strdup(path); for (p = strtok( path, ":" ); p; p = strtok( NULL, ":" )) dll_paths[count++] = strdup( p ); free( path ); } for (i = 0; i < count; i++) dll_path_maxlen = max( dll_path_maxlen, strlen(dll_paths[i]) ); dll_paths[count] = NULL; } static void set_config_dir(void) { char *p, *dir; const char *prefix = getenv( "WINEPREFIX" ); if (prefix) { if (prefix[0] != '/') fatal_error( "invalid directory %s in WINEPREFIX: not an absolute path\n", prefix ); config_dir = dir = strdup( prefix ); for (p = dir + strlen(dir) - 1; p > dir && *p == '/'; p--) *p = 0; } else { const char *home = getenv( "HOME" ); if (!home) { struct passwd *pwd = getpwuid( getuid() ); if (pwd) home = pwd->pw_dir; } if (!home) fatal_error( "could not determine your home directory\n" ); if (home[0] != '/') fatal_error( "your home directory %s is not an absolute path\n", home ); config_dir = build_path( home, ".wine" ); } } static void init_paths( int argc, char *argv[], char *envp[] ) { Dl_info info; __wine_main_argc = main_argc = argc; __wine_main_argv = main_argv = argv; __wine_main_environ = main_envp = envp; argv0 = strdup( argv[0] ); if (!dladdr( init_paths, &info ) || !(dll_dir = realpath_dirname( info.dli_fname ))) fatal_error( "cannot get path to ntdll.so\n" ); if (!(build_dir = remove_tail( dll_dir, "/dlls/ntdll" ))) { #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) bin_dir = realpath_dirname( "/proc/self/exe" ); #elif defined (__FreeBSD__) || defined(__DragonFly__) bin_dir = realpath_dirname( "/proc/curproc/file" ); #else bin_dir = realpath_dirname( argv0 ); #endif if (!bin_dir) bin_dir = build_path( dll_dir, DLL_TO_BINDIR ); data_dir = build_path( bin_dir, BIN_TO_DATADIR ); } set_dll_path(); set_config_dir(); } #if !defined(__APPLE__) && !defined(__ANDROID__) /* these platforms always use UTF-8 */ /* charset to codepage map, sorted by name */ static const struct { const char *name; UINT cp; } charset_names[] = { { "ANSIX341968", 20127 }, { "BIG5", 950 }, { "BIG5HKSCS", 950 }, { "CP1250", 1250 }, { "CP1251", 1251 }, { "CP1252", 1252 }, { "CP1253", 1253 }, { "CP1254", 1254 }, { "CP1255", 1255 }, { "CP1256", 1256 }, { "CP1257", 1257 }, { "CP1258", 1258 }, { "CP932", 932 }, { "CP936", 936 }, { "CP949", 949 }, { "CP950", 950 }, { "EUCJP", 20932 }, { "EUCKR", 949 }, { "GB18030", 936 /* 54936 */ }, { "GB2312", 936 }, { "GBK", 936 }, { "IBM037", 37 }, { "IBM1026", 1026 }, { "IBM424", 20424 }, { "IBM437", 437 }, { "IBM500", 500 }, { "IBM850", 850 }, { "IBM852", 852 }, { "IBM855", 855 }, { "IBM857", 857 }, { "IBM860", 860 }, { "IBM861", 861 }, { "IBM862", 862 }, { "IBM863", 863 }, { "IBM864", 864 }, { "IBM865", 865 }, { "IBM866", 866 }, { "IBM869", 869 }, { "IBM874", 874 }, { "IBM875", 875 }, { "ISO88591", 28591 }, { "ISO885913", 28603 }, { "ISO885915", 28605 }, { "ISO88592", 28592 }, { "ISO88593", 28593 }, { "ISO88594", 28594 }, { "ISO88595", 28595 }, { "ISO88596", 28596 }, { "ISO88597", 28597 }, { "ISO88598", 28598 }, { "ISO88599", 28599 }, { "KOI8R", 20866 }, { "KOI8U", 21866 }, { "TIS620", 28601 }, { "UTF8", CP_UTF8 } }; static void load_unix_cptable( unsigned int cp ) { const char *dir = build_dir ? build_dir : data_dir; struct stat st; char *name; void *data; int fd; if (!(name = malloc( strlen(dir) + 22 ))) return; sprintf( name, "%s/nls/c_%03u.nls", dir, cp ); if ((fd = open( name, O_RDONLY )) != -1) { fstat( fd, &st ); if ((data = malloc( st.st_size )) && st.st_size > 0x10000 && read( fd, data, st.st_size ) == st.st_size) { RtlInitCodePageTable( data, &unix_table ); } else { free( data ); } close( fd ); } else ERR( "failed to load %s\n", name ); free( name ); } static void init_unix_codepage(void) { char charset_name[16]; const char *name; size_t i, j; int min = 0, max = ARRAY_SIZE(charset_names) - 1; setlocale( LC_CTYPE, "" ); if (!(name = nl_langinfo( CODESET ))) return; /* remove punctuation characters from charset name */ for (i = j = 0; name[i] && j < sizeof(charset_name)-1; i++) { if (name[i] >= '0' && name[i] <= '9') charset_name[j++] = name[i]; else if (name[i] >= 'A' && name[i] <= 'Z') charset_name[j++] = name[i]; else if (name[i] >= 'a' && name[i] <= 'z') charset_name[j++] = name[i] + ('A' - 'a'); } charset_name[j] = 0; while (min <= max) { int pos = (min + max) / 2; int res = strcmp( charset_names[pos].name, charset_name ); if (!res) { if (charset_names[pos].cp != CP_UTF8) load_unix_cptable( charset_names[pos].cp ); return; } if (res > 0) max = pos - 1; else min = pos + 1; } ERR( "unrecognized charset '%s'\n", name ); } #else /* __APPLE__ || __ANDROID__ */ static void init_unix_codepage(void) { } #endif /* __APPLE__ || __ANDROID__ */ /********************************************************************* * get_version */ const char * CDECL get_version(void) { return PACKAGE_VERSION; } /********************************************************************* * get_build_id */ const char * CDECL get_build_id(void) { extern const char wine_build[]; return wine_build; } /********************************************************************* * get_host_version */ void CDECL get_host_version( const char **sysname, const char **release ) { #ifdef HAVE_SYS_UTSNAME_H static struct utsname buf; static BOOL init_done; if (!init_done) { uname( &buf ); init_done = TRUE; } if (sysname) *sysname = buf.sysname; if (release) *release = buf.release; #else if (sysname) *sysname = ""; if (release) *release = ""; #endif } /************************************************************************* * get_main_args * * Return the initial arguments. */ static void CDECL get_main_args( int *argc, char **argv[], char **envp[] ) { *argc = main_argc; *argv = main_argv; *envp = main_envp; } /************************************************************************* * get_paths * * Return the various configuration paths. */ static void CDECL get_paths( const char **builddir, const char **datadir, const char **configdir ) { *builddir = build_dir; *datadir = data_dir; *configdir = config_dir; } /************************************************************************* * get_dll_path * * Return the various configuration paths. */ static void CDECL get_dll_path( const char ***paths, SIZE_T *maxlen ) { *paths = dll_paths; *maxlen = dll_path_maxlen; } /************************************************************************* * get_unix_codepage * * Return the Unix codepage data. */ static void CDECL get_unix_codepage( CPTABLEINFO *table ) { *table = unix_table; } static void preloader_exec( char **argv ) { if (use_preloader) { static const char *preloader = "wine-preloader"; char *p; if (!(p = strrchr( argv[1], '/' ))) p = argv[1]; else p++; if (strlen(p) > 2 && !strcmp( p + strlen(p) - 2, "64" )) preloader = "wine64-preloader"; argv[0] = malloc( p - argv[1] + strlen(preloader) + 1 ); memcpy( argv[0], argv[1], p - argv[1] ); strcpy( argv[0] + (p - argv[1]), preloader ); #ifdef __APPLE__ { posix_spawnattr_t attr; posix_spawnattr_init( &attr ); posix_spawnattr_setflags( &attr, POSIX_SPAWN_SETEXEC | _POSIX_SPAWN_DISABLE_ASLR ); posix_spawn( NULL, argv[0], NULL, &attr, argv, *_NSGetEnviron() ); posix_spawnattr_destroy( &attr ); } #endif execv( argv[0], argv ); free( argv[0] ); } execv( argv[1], argv + 1 ); } static NTSTATUS loader_exec( const char *loader, char **argv, int is_child_64bit ) { char *p, *path; if (build_dir) { argv[1] = build_path( build_dir, is_child_64bit ? "loader/wine64" : "loader/wine" ); preloader_exec( argv ); return STATUS_INVALID_IMAGE_FORMAT; } if ((p = strrchr( loader, '/' ))) loader = p + 1; argv[1] = build_path( bin_dir, loader ); preloader_exec( argv ); argv[1] = getenv( "WINELOADER" ); if (argv[1]) preloader_exec( argv ); if ((path = getenv( "PATH" ))) { for (p = strtok( strdup( path ), ":" ); p; p = strtok( NULL, ":" )) { argv[1] = build_path( p, loader ); preloader_exec( argv ); } } argv[1] = build_path( BINDIR, loader ); preloader_exec( argv ); return STATUS_INVALID_IMAGE_FORMAT; } /*********************************************************************** * exec_wineloader * * argv[0] and argv[1] must be reserved for the preloader and loader respectively. */ static NTSTATUS CDECL exec_wineloader( char **argv, int socketfd, int is_child_64bit, ULONGLONG res_start, ULONGLONG res_end ) { const char *loader = argv0; const char *loader_env = getenv( "WINELOADER" ); char preloader_reserve[64], socket_env[64]; if (!is_win64 ^ !is_child_64bit) { /* remap WINELOADER to the alternate 32/64-bit version if necessary */ if (loader_env) { int len = strlen( loader_env ); char *env = malloc( sizeof("WINELOADER=") + len + 2 ); if (!env) return STATUS_NO_MEMORY; strcpy( env, "WINELOADER=" ); strcat( env, loader_env ); if (is_child_64bit) { strcat( env, "64" ); } else { len += sizeof("WINELOADER=") - 1; if (!strcmp( env + len - 2, "64" )) env[len - 2] = 0; } loader = env; putenv( env ); } else loader = is_child_64bit ? "wine64" : "wine"; } signal( SIGPIPE, SIG_DFL ); sprintf( socket_env, "WINESERVERSOCKET=%u", socketfd ); sprintf( preloader_reserve, "WINEPRELOADRESERVE=%x%08x-%x%08x", (ULONG)(res_start >> 32), (ULONG)res_start, (ULONG)(res_end >> 32), (ULONG)res_end ); putenv( preloader_reserve ); putenv( socket_env ); return loader_exec( loader, argv, is_child_64bit ); } /*********************************************************************** * exec_wineserver * * Exec a new wine server. */ static void exec_wineserver( char **argv ) { char *path; if (build_dir) { if (!is_win64) /* look for 64-bit server */ { char *loader = realpath_dirname( build_path( build_dir, "loader/wine64" )); if (loader) { argv[0] = build_path( loader, "../server/wineserver" ); execv( argv[0], argv ); } } argv[0] = build_path( build_dir, "server/wineserver" ); execv( argv[0], argv ); return; } argv[0] = build_path( bin_dir, "wineserver" ); execv( argv[0], argv ); argv[0] = getenv( "WINESERVER" ); if (argv[0]) execv( argv[0], argv ); if ((path = getenv( "PATH" ))) { for (path = strtok( strdup( path ), ":" ); path; path = strtok( NULL, ":" )) { argv[0] = build_path( path, "wineserver" ); execvp( argv[0], argv ); } } argv[0] = build_path( BINDIR, "wineserver" ); execv( argv[0], argv ); } /*********************************************************************** * start_server * * Start a new wine server. */ void start_server( BOOL debug ) { static BOOL started; /* we only try once */ char *argv[3]; static char debug_flag[] = "-d"; if (!started) { int status; int pid = fork(); if (pid == -1) fatal_error( "fork: %s", strerror(errno) ); if (!pid) { argv[1] = debug ? debug_flag : NULL; argv[2] = NULL; exec_wineserver( argv ); fatal_error( "could not exec wineserver\n" ); } waitpid( pid, &status, 0 ); status = WIFEXITED(status) ? WEXITSTATUS(status) : 1; if (status == 2) return; /* server lock held by someone else, will retry later */ if (status) exit(status); /* server failed */ started = TRUE; } } /************************************************************************* * map_so_dll * * Map a builtin dll in memory and fixup RVAs. */ static NTSTATUS CDECL map_so_dll( const IMAGE_NT_HEADERS *nt_descr, HMODULE module ) { static const char builtin_signature[32] = "Wine builtin DLL"; IMAGE_DATA_DIRECTORY *dir; IMAGE_DOS_HEADER *dos; IMAGE_NT_HEADERS *nt; IMAGE_SECTION_HEADER *sec; BYTE *addr = (BYTE *)module; DWORD code_start, code_end, data_start, data_end, align_mask; int delta, nb_sections = 2; /* code + data */ unsigned int i; DWORD size = (sizeof(IMAGE_DOS_HEADER) + sizeof(builtin_signature) + sizeof(IMAGE_NT_HEADERS) + nb_sections * sizeof(IMAGE_SECTION_HEADER)); if (wine_anon_mmap( addr, size, PROT_READ | PROT_WRITE, MAP_FIXED ) != addr) return STATUS_NO_MEMORY; dos = (IMAGE_DOS_HEADER *)addr; nt = (IMAGE_NT_HEADERS *)((BYTE *)(dos + 1) + sizeof(builtin_signature)); sec = (IMAGE_SECTION_HEADER *)(nt + 1); /* build the DOS and NT headers */ dos->e_magic = IMAGE_DOS_SIGNATURE; dos->e_cblp = 0x90; dos->e_cp = 3; dos->e_cparhdr = (sizeof(*dos) + 0xf) / 0x10; dos->e_minalloc = 0; dos->e_maxalloc = 0xffff; dos->e_ss = 0x0000; dos->e_sp = 0x00b8; dos->e_lfanew = sizeof(*dos) + sizeof(builtin_signature); *nt = *nt_descr; delta = (const BYTE *)nt_descr - addr; align_mask = nt->OptionalHeader.SectionAlignment - 1; code_start = (size + align_mask) & ~align_mask; data_start = delta & ~align_mask; #ifdef __APPLE__ { Dl_info dli; unsigned long data_size; /* need the mach_header, not the PE header, to give to getsegmentdata(3) */ dladdr(addr, &dli); code_end = getsegmentdata(dli.dli_fbase, "__DATA", &data_size) - addr; data_end = (code_end + data_size + align_mask) & ~align_mask; } #else code_end = data_start; data_end = (nt->OptionalHeader.SizeOfImage + delta + align_mask) & ~align_mask; #endif fixup_rva_ptrs( &nt->OptionalHeader.AddressOfEntryPoint, addr, 1 ); nt->FileHeader.NumberOfSections = nb_sections; nt->OptionalHeader.BaseOfCode = code_start; #ifndef _WIN64 nt->OptionalHeader.BaseOfData = data_start; #endif nt->OptionalHeader.SizeOfCode = code_end - code_start; nt->OptionalHeader.SizeOfInitializedData = data_end - data_start; nt->OptionalHeader.SizeOfUninitializedData = 0; nt->OptionalHeader.SizeOfImage = data_end; nt->OptionalHeader.ImageBase = (ULONG_PTR)addr; /* build the code section */ memcpy( sec->Name, ".text", sizeof(".text") ); sec->SizeOfRawData = code_end - code_start; sec->Misc.VirtualSize = sec->SizeOfRawData; sec->VirtualAddress = code_start; sec->PointerToRawData = code_start; sec->Characteristics = (IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ); sec++; /* build the data section */ memcpy( sec->Name, ".data", sizeof(".data") ); sec->SizeOfRawData = data_end - data_start; sec->Misc.VirtualSize = sec->SizeOfRawData; sec->VirtualAddress = data_start; sec->PointerToRawData = data_start; sec->Characteristics = (IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ); sec++; for (i = 0; i < nt->OptionalHeader.NumberOfRvaAndSizes; i++) fixup_rva_dwords( &nt->OptionalHeader.DataDirectory[i].VirtualAddress, delta, 1 ); /* build the import directory */ dir = &nt->OptionalHeader.DataDirectory[IMAGE_FILE_IMPORT_DIRECTORY]; if (dir->Size) { IMAGE_IMPORT_DESCRIPTOR *imports = (IMAGE_IMPORT_DESCRIPTOR *)(addr + dir->VirtualAddress); while (imports->Name) { fixup_rva_dwords( &imports->u.OriginalFirstThunk, delta, 1 ); fixup_rva_dwords( &imports->Name, delta, 1 ); fixup_rva_dwords( &imports->FirstThunk, delta, 1 ); if (imports->u.OriginalFirstThunk) fixup_rva_names( (UINT_PTR *)(addr + imports->u.OriginalFirstThunk), delta ); if (imports->FirstThunk) fixup_rva_names( (UINT_PTR *)(addr + imports->FirstThunk), delta ); imports++; } } /* build the resource directory */ dir = &nt->OptionalHeader.DataDirectory[IMAGE_FILE_RESOURCE_DIRECTORY]; if (dir->Size) { void *ptr = addr + dir->VirtualAddress; fixup_so_resources( ptr, ptr, delta ); } /* build the export directory */ dir = &nt->OptionalHeader.DataDirectory[IMAGE_FILE_EXPORT_DIRECTORY]; if (dir->Size) { IMAGE_EXPORT_DIRECTORY *exports = (IMAGE_EXPORT_DIRECTORY *)(addr + dir->VirtualAddress); fixup_rva_dwords( &exports->Name, delta, 1 ); fixup_rva_dwords( &exports->AddressOfFunctions, delta, 1 ); fixup_rva_dwords( &exports->AddressOfNames, delta, 1 ); fixup_rva_dwords( &exports->AddressOfNameOrdinals, delta, 1 ); fixup_rva_dwords( (DWORD *)(addr + exports->AddressOfNames), delta, exports->NumberOfNames ); fixup_rva_ptrs( addr + exports->AddressOfFunctions, addr, exports->NumberOfFunctions ); } return STATUS_SUCCESS; } static const IMAGE_EXPORT_DIRECTORY *get_export_dir( HMODULE module ) { const IMAGE_DOS_HEADER *dos = (const IMAGE_DOS_HEADER *)module; const IMAGE_NT_HEADERS *nt; DWORD addr; if (dos->e_magic != IMAGE_DOS_SIGNATURE) return NULL; nt = (IMAGE_NT_HEADERS *)((const BYTE *)dos + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) return NULL; addr = nt->OptionalHeader.DataDirectory[IMAGE_FILE_EXPORT_DIRECTORY].VirtualAddress; if (!addr) return NULL; return (IMAGE_EXPORT_DIRECTORY *)((BYTE *)module + addr); } static ULONG_PTR find_ordinal_export( HMODULE module, const IMAGE_EXPORT_DIRECTORY *exports, DWORD ordinal ) { const DWORD *functions = (const DWORD *)((BYTE *)module + exports->AddressOfFunctions); if (ordinal >= exports->NumberOfFunctions) return 0; if (!functions[ordinal]) return 0; return (ULONG_PTR)module + functions[ordinal]; } static ULONG_PTR find_named_export( HMODULE module, const IMAGE_EXPORT_DIRECTORY *exports, const IMAGE_IMPORT_BY_NAME *name ) { const WORD *ordinals = (const WORD *)((BYTE *)module + exports->AddressOfNameOrdinals); const DWORD *names = (const DWORD *)((BYTE *)module + exports->AddressOfNames); int min = 0, max = exports->NumberOfNames - 1; /* first check the hint */ if (name->Hint <= max) { char *ename = (char *)module + names[name->Hint]; if (!strcmp( ename, (char *)name->Name )) return find_ordinal_export( module, exports, ordinals[name->Hint] ); } /* then do a binary search */ while (min <= max) { int res, pos = (min + max) / 2; char *ename = (char *)module + names[pos]; if (!(res = strcmp( ename, (char *)name->Name ))) return find_ordinal_export( module, exports, ordinals[pos] ); if (res > 0) max = pos - 1; else min = pos + 1; } return 0; } static void fixup_ntdll_imports( const IMAGE_NT_HEADERS *nt, HMODULE ntdll_module ) { const IMAGE_EXPORT_DIRECTORY *ntdll_exports = get_export_dir( ntdll_module ); const IMAGE_IMPORT_DESCRIPTOR *descr; const IMAGE_THUNK_DATA *import_list; IMAGE_THUNK_DATA *thunk_list; assert( ntdll_exports ); descr = get_rva( nt, nt->OptionalHeader.DataDirectory[IMAGE_FILE_IMPORT_DIRECTORY].VirtualAddress ); while (descr->Name) { /* ntdll must be the only import */ assert( !strcmp( get_rva( nt, descr->Name ), "ntdll.dll" )); thunk_list = get_rva( nt, (DWORD)descr->FirstThunk ); if (descr->u.OriginalFirstThunk) import_list = get_rva( nt, (DWORD)descr->u.OriginalFirstThunk ); else import_list = thunk_list; while (import_list->u1.Ordinal) { if (IMAGE_SNAP_BY_ORDINAL( import_list->u1.Ordinal )) { int ordinal = IMAGE_ORDINAL( import_list->u1.Ordinal ) - ntdll_exports->Base; thunk_list->u1.Function = find_ordinal_export( ntdll_module, ntdll_exports, ordinal ); if (!thunk_list->u1.Function) ERR( "ordinal %u not found\n", ordinal ); } else /* import by name */ { IMAGE_IMPORT_BY_NAME *pe_name = get_rva( nt, import_list->u1.AddressOfData ); thunk_list->u1.Function = find_named_export( ntdll_module, ntdll_exports, pe_name ); if (!thunk_list->u1.Function) ERR( "%s not found\n", pe_name->Name ); } import_list++; thunk_list++; } descr++; } } /*********************************************************************** * load_ntdll */ static HMODULE load_ntdll(void) { const IMAGE_NT_HEADERS *nt; HMODULE module; Dl_info info; char *name; void *handle; name = build_path( dll_dir, "ntdll.dll.so" ); if (!dladdr( load_ntdll, &info )) fatal_error( "cannot get path to ntdll.so\n" ); name = malloc( strlen(info.dli_fname) + 5 ); strcpy( name, info.dli_fname ); strcpy( name + strlen(info.dli_fname) - 3, ".dll.so" ); if (!(handle = dlopen( name, RTLD_NOW ))) fatal_error( "failed to load %s: %s\n", name, dlerror() ); if (!(nt = dlsym( handle, "__wine_spec_nt_header" ))) fatal_error( "NT header not found in %s (too old?)\n", name ); dll_dir = realpath_dirname( name ); free( name ); module = (HMODULE)((nt->OptionalHeader.ImageBase + 0xffff) & ~0xffff); map_so_dll( nt, module ); return module; } /*********************************************************************** * unix_funcs */ static struct unix_funcs unix_funcs = { NtAllocateVirtualMemory, NtAreMappedFilesTheSame, NtCancelTimer, NtClearEvent, NtClose, NtCreateEvent, NtCreateKeyedEvent, NtCreateMutant, NtCreateSection, NtCreateSemaphore, NtCreateThreadEx, NtCreateTimer, NtCurrentTeb, NtDelayExecution, NtDuplicateObject, NtFlushVirtualMemory, NtFreeVirtualMemory, NtGetContextThread, NtGetWriteWatch, NtLockVirtualMemory, NtMapViewOfSection, NtOpenEvent, NtOpenKeyedEvent, NtOpenMutant, NtOpenSection, NtOpenSemaphore, NtOpenTimer, NtProtectVirtualMemory, NtPulseEvent, NtQueryEvent, NtQueryMutant, NtQuerySection, NtQuerySemaphore, NtQueryTimer, NtQueryVirtualMemory, NtReadVirtualMemory, NtReleaseKeyedEvent, NtReleaseMutant, NtReleaseSemaphore, NtResetEvent, NtResetWriteWatch, NtSetContextThread, NtSetEvent, NtSetLdtEntries, NtSetTimer, NtSignalAndWaitForSingleObject, NtUnlockVirtualMemory, NtUnmapViewOfSection, NtWaitForKeyedEvent, NtWaitForMultipleObjects, NtWaitForSingleObject, NtWriteVirtualMemory, NtYieldExecution, DbgUiIssueRemoteBreakin, get_main_args, get_paths, get_dll_path, get_unix_codepage, get_version, get_build_id, get_host_version, exec_wineloader, map_so_dll, virtual_map_section, virtual_get_system_info, virtual_create_builtin_view, virtual_alloc_thread_stack, virtual_handle_fault, virtual_locked_server_call, virtual_locked_read, virtual_locked_pread, virtual_locked_recvmsg, virtual_is_valid_code_address, virtual_handle_stack_fault, virtual_check_buffer_for_read, virtual_check_buffer_for_write, virtual_uninterrupted_read_memory, virtual_uninterrupted_write_memory, virtual_set_force_exec, virtual_release_address_space, virtual_set_large_address_space, init_threading, start_process, abort_thread, exit_thread, exit_process, get_thread_ldt_entry, wine_server_call, server_select, server_wait, server_send_fd, server_get_unix_fd, server_fd_to_handle, server_handle_to_fd, server_release_fd, server_init_process_done, __wine_dbg_get_channel_flags, __wine_dbg_strdup, __wine_dbg_output, __wine_dbg_header, }; #ifdef __APPLE__ struct apple_stack_info { void *stack; size_t desired_size; }; static void *apple_wine_thread( void *arg ) { __wine_set_unix_funcs( NTDLL_UNIXLIB_VERSION, &unix_funcs ); return NULL; } /*********************************************************************** * apple_alloc_thread_stack * * Callback for mmap_enum_reserved_areas to allocate space for * the secondary thread's stack. */ #ifndef _WIN64 static int CDECL apple_alloc_thread_stack( void *base, size_t size, void *arg ) { struct apple_stack_info *info = arg; /* For mysterious reasons, putting the thread stack at the very top * of the address space causes subsequent execs to fail, even on the * child side of a fork. Avoid the top 16MB. */ char * const limit = (char*)0xff000000; if ((char *)base >= limit) return 0; if (size > limit - (char*)base) size = limit - (char*)base; if (size < info->desired_size) return 0; info->stack = wine_anon_mmap( (char *)base + size - info->desired_size, info->desired_size, PROT_READ|PROT_WRITE, MAP_FIXED ); return (info->stack != (void *)-1); } #endif /*********************************************************************** * apple_create_wine_thread * * Spin off a secondary thread to complete Wine initialization, leaving * the original thread for the Mac frameworks. * * Invoked as a CFRunLoopSource perform callback. */ static void apple_create_wine_thread( void *arg ) { int success = 0; pthread_t thread; pthread_attr_t attr; if (!pthread_attr_init( &attr )) { #ifndef _WIN64 struct apple_stack_info info; /* Try to put the new thread's stack in the reserved area. If this * fails, just let it go wherever. It'll be a waste of space, but we * can go on. */ if (!pthread_attr_getstacksize( &attr, &info.desired_size ) && mmap_enum_reserved_areas( apple_alloc_thread_stack, &info, 1 )) { mmap_remove_reserved_area( info.stack, info.desired_size ); pthread_attr_setstackaddr( &attr, (char*)info.stack + info.desired_size ); } #endif if (!pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ) && !pthread_create( &thread, &attr, apple_wine_thread, NULL )) success = 1; pthread_attr_destroy( &attr ); } if (!success) exit(1); } /*********************************************************************** * apple_main_thread * * Park the process's original thread in a Core Foundation run loop for * use by the Mac frameworks, especially receiving and handling * distributed notifications. Spin off a new thread for the rest of the * Wine initialization. */ static void apple_main_thread(void) { CFRunLoopSourceContext source_context = { 0 }; CFRunLoopSourceRef source; if (!pthread_main_np()) return; /* Multi-processing Services can get confused about the main thread if the * first time it's used is on a secondary thread. Use it here to make sure * that doesn't happen. */ MPTaskIsPreemptive(MPCurrentTaskID()); /* Give ourselves the best chance of having the distributed notification * center scheduled on this thread's run loop. In theory, it's scheduled * in the first thread to ask for it. */ CFNotificationCenterGetDistributedCenter(); /* We use this run loop source for two purposes. First, a run loop exits * if it has no more sources scheduled. So, we need at least one source * to keep the run loop running. Second, although it's not critical, it's * preferable for the Wine initialization to not proceed until we know * the run loop is running. So, we signal our source immediately after * adding it and have its callback spin off the Wine thread. */ source_context.perform = apple_create_wine_thread; source = CFRunLoopSourceCreate( NULL, 0, &source_context ); CFRunLoopAddSource( CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes ); CFRunLoopSourceSignal( source ); CFRelease( source ); CFRunLoopRun(); /* Should never return, except on error. */ } #endif /* __APPLE__ */ #ifdef __ANDROID__ static int pre_exec(void) { #if defined(__i386__) || defined(__x86_64__) return 1; /* we have a preloader */ #else return 0; /* no exec needed */ #endif } #elif defined(__linux__) && (defined(__i386__) || defined(__arm__)) static void check_vmsplit( void *stack ) { if (stack < (void *)0x80000000) { /* if the stack is below 0x80000000, assume we can safely try a munmap there */ if (munmap( (void *)0x80000000, 1 ) == -1 && errno == EINVAL) ERR( "Warning: memory above 0x80000000 doesn't seem to be accessible.\n" "Wine requires a 3G/1G user/kernel memory split to work properly.\n" ); } } static int pre_exec(void) { int temp; check_vmsplit( &temp ); #ifdef __i386__ return 1; /* we have a preloader on x86 */ #else return 0; #endif } #elif defined(__linux__) && (defined(__x86_64__) || defined(__aarch64__)) static int pre_exec(void) { return 1; /* we have a preloader on x86-64/arm64 */ } #elif defined(__APPLE__) && (defined(__i386__) || defined(__x86_64__)) static int pre_exec(void) { return 1; /* we have a preloader */ } #elif (defined(__FreeBSD__) || defined (__FreeBSD_kernel__) || defined(__DragonFly__)) static int pre_exec(void) { struct rlimit rl; rl.rlim_cur = 0x02000000; rl.rlim_max = 0x02000000; setrlimit( RLIMIT_DATA, &rl ); return 1; } #else static int pre_exec(void) { return 0; /* no exec needed */ } #endif /*********************************************************************** * set_process_name * * Change the process name in the ps output. */ static void set_process_name( int argc, char *argv[] ) { BOOL shift_strings; char *p, *name; int i; #ifdef HAVE_SETPROCTITLE setproctitle("-%s", argv[1]); shift_strings = FALSE; #else p = argv[0]; shift_strings = (argc >= 2); for (i = 1; i < argc; i++) { p += strlen(p) + 1; if (p != argv[i]) { shift_strings = FALSE; break; } } #endif if (shift_strings) { int offset = argv[1] - argv[0]; char *end = argv[argc-1] + strlen(argv[argc-1]) + 1; memmove( argv[0], argv[1], end - argv[1] ); memset( end - offset, 0, offset ); for (i = 1; i < argc; i++) argv[i-1] = argv[i] - offset; argv[i-1] = NULL; } else { /* remove argv[0] */ memmove( argv, argv + 1, argc * sizeof(argv[0]) ); } name = argv[0]; if ((p = strrchr( name, '\\' ))) name = p + 1; if ((p = strrchr( name, '/' ))) name = p + 1; #if defined(HAVE_SETPROGNAME) setprogname( name ); #endif #ifdef HAVE_PRCTL #ifndef PR_SET_NAME # define PR_SET_NAME 15 #endif prctl( PR_SET_NAME, name ); #endif /* HAVE_PRCTL */ } /*********************************************************************** * check_command_line * * Check if command line is one that needs to be handled specially. */ static void check_command_line( int argc, char *argv[] ) { static const char usage[] = "Usage: wine PROGRAM [ARGUMENTS...] Run the specified program\n" " wine --help Display this help and exit\n" " wine --version Output version information and exit"; if (argc <= 1) { fprintf( stderr, "%s\n", usage ); exit(1); } if (!strcmp( argv[1], "--help" )) { printf( "%s\n", usage ); exit(0); } if (!strcmp( argv[1], "--version" )) { printf( "%s\n", get_build_id() ); exit(0); } } /*********************************************************************** * __wine_main * * Main entry point called by the wine loader. */ void __wine_main( int argc, char *argv[], char *envp[] ) { HMODULE module; init_paths( argc, argv, envp ); if (!getenv( "WINELOADERNOEXEC" )) /* first time around */ { static char noexec[] = "WINELOADERNOEXEC=1"; putenv( noexec ); check_command_line( argc, argv ); if (pre_exec()) { char **new_argv = malloc( (argc + 2) * sizeof(*argv) ); memcpy( new_argv + 1, argv, (argc + 1) * sizeof(*argv) ); loader_exec( argv0, new_argv, is_win64 ); fatal_error( "could not exec the wine loader\n" ); } } #ifdef RLIMIT_NOFILE set_max_limit( RLIMIT_NOFILE ); #endif #ifdef RLIMIT_AS set_max_limit( RLIMIT_AS ); #endif virtual_init(); module = load_ntdll(); fixup_ntdll_imports( &__wine_spec_nt_header, module ); set_process_name( argc, argv ); init_unix_codepage(); #ifdef __APPLE__ apple_main_thread(); #endif __wine_set_unix_funcs( NTDLL_UNIXLIB_VERSION, &unix_funcs ); } static int add_area( void *base, size_t size, void *arg ) { mmap_add_reserved_area( base, size ); return 0; } /*********************************************************************** * __wine_init_unix_lib * * Lib entry point called by ntdll.dll.so if not yet initialized. */ NTSTATUS __cdecl __wine_init_unix_lib( HMODULE module, const void *ptr_in, void *ptr_out ) { const IMAGE_NT_HEADERS *nt = ptr_in; #ifdef __APPLE__ extern char **__wine_get_main_environment(void); char **envp = __wine_get_main_environment(); #else char **envp = __wine_main_environ; #endif init_paths( __wine_main_argc, __wine_main_argv, envp ); map_so_dll( nt, module ); fixup_ntdll_imports( &__wine_spec_nt_header, module ); set_process_name( __wine_main_argc, __wine_main_argv ); init_unix_codepage(); *(struct unix_funcs **)ptr_out = &unix_funcs; wine_mmap_enum_reserved_areas( add_area, NULL, 0 ); return STATUS_SUCCESS; } BOOL WINAPI DECLSPEC_HIDDEN DllMain( HINSTANCE inst, DWORD reason, LPVOID reserved ) { if (reason == DLL_PROCESS_ATTACH) LdrDisableThreadCalloutsForDll( inst ); return TRUE; }