Sweden-Number/libs/wine/loader.c

649 lines
18 KiB
C

/*
* Win32 builtin dlls support
*
* 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
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "wine/library.h"
/* argc/argv for the Windows application */
int __wine_main_argc = 0;
char **__wine_main_argv = NULL;
WCHAR **__wine_main_wargv = NULL;
#define MAX_DLLS 100
static struct
{
const IMAGE_NT_HEADERS *nt; /* NT header */
const char *filename; /* DLL file name */
} builtin_dlls[MAX_DLLS];
static int nb_dlls;
static const IMAGE_NT_HEADERS *main_exe;
static load_dll_callback_t load_dll_callback;
static const char **dll_paths;
static int nb_dll_paths;
static int dll_path_maxlen;
static int init_done;
/* build the dll load path from the WINEDLLPATH variable */
static void build_dll_path(void)
{
static const char * const dlldir = DLLDIR;
int len, count = 0;
char *p, *path = getenv( "WINEDLLPATH" );
init_done = 1;
if (path)
{
/* count how many path elements we need */
path = strdup(path);
p = path;
while (*p)
{
while (*p == ':') p++;
if (!*p) break;
count++;
while (*p && *p != ':') p++;
}
}
dll_paths = malloc( (count+1) * sizeof(*dll_paths) );
if (count)
{
p = path;
nb_dll_paths = 0;
while (*p)
{
while (*p == ':') *p++ = 0;
if (!*p) break;
dll_paths[nb_dll_paths] = p;
while (*p && *p != ':') p++;
if (p - dll_paths[nb_dll_paths] > dll_path_maxlen)
dll_path_maxlen = p - dll_paths[nb_dll_paths];
nb_dll_paths++;
}
}
/* append default dll dir (if not empty) to path */
if ((len = strlen(dlldir)))
{
if (len > dll_path_maxlen) dll_path_maxlen = len;
dll_paths[nb_dll_paths++] = dlldir;
}
}
/* check if a given file can be opened */
inline static int file_exists( const char *name )
{
int fd = open( name, O_RDONLY );
if (fd != -1) close( fd );
return (fd != -1);
}
/* open a library for a given dll, searching in the dll path
* 'name' must be the Windows dll name (e.g. "kernel32.dll") */
static void *dlopen_dll( const char *name, char *error, int errorsize,
int test_only, int *exists )
{
int i, namelen = strlen(name);
char *buffer, *p;
void *ret = NULL;
if (!init_done) build_dll_path();
buffer = malloc( dll_path_maxlen + namelen + 5 );
/* store the name at the end of the buffer, followed by .so */
p = buffer + dll_path_maxlen;
*p++ = '/';
memcpy( p, name, namelen );
strcpy( p + namelen, ".so" );
*exists = 0;
for (i = 0; i < nb_dll_paths; i++)
{
int len = strlen(dll_paths[i]);
p = buffer + dll_path_maxlen - len;
memcpy( p, dll_paths[i], len );
if (!test_only && (ret = wine_dlopen( p, RTLD_NOW, error, errorsize ))) break;
if ((*exists = file_exists( p ))) break; /* exists but cannot be loaded, return the error */
}
free( buffer );
return ret;
}
/* adjust an array of pointers to make them into RVAs */
static inline void fixup_rva_ptrs( void *array, void *base, int count )
{
void **ptr = (void **)array;
while (count--)
{
if (*ptr) *ptr = (void *)((char *)*ptr - (char *)base);
ptr++;
}
}
/* fixup RVAs in the import directory */
static void fixup_imports( IMAGE_IMPORT_DESCRIPTOR *dir, DWORD size, void *base )
{
int count = size / sizeof(void *);
void **ptr = (void **)dir;
/* everything is either a pointer or a ordinal value below 0x10000 */
while (count--)
{
if (*ptr >= (void *)0x10000) *ptr = (void *)((char *)*ptr - (char *)base);
else if (*ptr) *ptr = (void *)(0x80000000 | (unsigned int)*ptr);
ptr++;
}
}
/* fixup RVAs in the resource directory */
static void fixup_resources( IMAGE_RESOURCE_DIRECTORY *dir, char *root, void *base )
{
IMAGE_RESOURCE_DIRECTORY_ENTRY *entry;
int i;
entry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)(dir + 1);
for (i = 0; i < dir->NumberOfNamedEntries + dir->NumberOfIdEntries; i++, entry++)
{
void *ptr = root + entry->u2.s3.OffsetToDirectory;
if (entry->u2.s3.DataIsDirectory) fixup_resources( ptr, root, base );
else
{
IMAGE_RESOURCE_DATA_ENTRY *data = ptr;
fixup_rva_ptrs( &data->OffsetToData, base, 1 );
}
}
}
/* map a builtin dll in memory and fixup RVAs */
static void *map_dll( const IMAGE_NT_HEADERS *nt_descr )
{
#ifdef HAVE_MMAP
IMAGE_DATA_DIRECTORY *dir;
IMAGE_DOS_HEADER *dos;
IMAGE_NT_HEADERS *nt;
IMAGE_SECTION_HEADER *sec;
BYTE *addr, *code_start, *data_start;
size_t page_size = getpagesize();
int nb_sections = 2; /* code + data */
size_t size = (sizeof(IMAGE_DOS_HEADER)
+ sizeof(IMAGE_NT_HEADERS)
+ nb_sections * sizeof(IMAGE_SECTION_HEADER));
assert( size <= page_size );
/* module address must be aligned on 64K boundary */
addr = (BYTE *)((nt_descr->OptionalHeader.ImageBase + 0xffff) & ~0xffff);
if (wine_anon_mmap( addr, page_size, PROT_READ|PROT_WRITE, MAP_FIXED ) != addr) return NULL;
dos = (IMAGE_DOS_HEADER *)addr;
nt = (IMAGE_NT_HEADERS *)(dos + 1);
sec = (IMAGE_SECTION_HEADER *)(nt + 1);
code_start = addr + page_size;
/* HACK! */
data_start = code_start + page_size;
/* Build the DOS and NT headers */
dos->e_magic = IMAGE_DOS_SIGNATURE;
dos->e_lfanew = sizeof(*dos);
*nt = *nt_descr;
nt->FileHeader.NumberOfSections = nb_sections;
nt->OptionalHeader.SizeOfCode = data_start - code_start;
nt->OptionalHeader.SizeOfInitializedData = 0;
nt->OptionalHeader.SizeOfUninitializedData = 0;
nt->OptionalHeader.ImageBase = (DWORD)addr;
fixup_rva_ptrs( &nt->OptionalHeader.AddressOfEntryPoint, addr, 1 );
/* Build the code section */
strcpy( sec->Name, ".text" );
sec->SizeOfRawData = data_start - code_start;
sec->Misc.VirtualSize = sec->SizeOfRawData;
sec->VirtualAddress = code_start - addr;
sec->PointerToRawData = code_start - addr;
sec->Characteristics = (IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ);
sec++;
/* Build the data section */
strcpy( sec->Name, ".data" );
sec->SizeOfRawData = 0;
sec->Misc.VirtualSize = sec->SizeOfRawData;
sec->VirtualAddress = data_start - addr;
sec->PointerToRawData = data_start - addr;
sec->Characteristics = (IMAGE_SCN_CNT_INITIALIZED_DATA |
IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ);
sec++;
/* Build the import directory */
dir = &nt->OptionalHeader.DataDirectory[IMAGE_FILE_IMPORT_DIRECTORY];
if (dir->Size)
{
IMAGE_IMPORT_DESCRIPTOR *imports = (void *)dir->VirtualAddress;
fixup_rva_ptrs( &dir->VirtualAddress, addr, 1 );
fixup_imports( imports, dir->Size, addr );
}
/* Build the resource directory */
dir = &nt->OptionalHeader.DataDirectory[IMAGE_FILE_RESOURCE_DIRECTORY];
if (dir->Size)
{
void *ptr = (void *)dir->VirtualAddress;
fixup_rva_ptrs( &dir->VirtualAddress, addr, 1 );
fixup_resources( ptr, ptr, addr );
}
/* Build the export directory */
dir = &nt->OptionalHeader.DataDirectory[IMAGE_FILE_EXPORT_DIRECTORY];
if (dir->Size)
{
IMAGE_EXPORT_DIRECTORY *exports = (void *)dir->VirtualAddress;
fixup_rva_ptrs( &dir->VirtualAddress, addr, 1 );
fixup_rva_ptrs( (void *)exports->AddressOfFunctions, addr, exports->NumberOfFunctions );
fixup_rva_ptrs( (void *)exports->AddressOfNames, addr, exports->NumberOfNames );
fixup_rva_ptrs( &exports->Name, addr, 1 );
fixup_rva_ptrs( &exports->AddressOfFunctions, addr, 1 );
fixup_rva_ptrs( &exports->AddressOfNames, addr, 1 );
fixup_rva_ptrs( &exports->AddressOfNameOrdinals, addr, 1 );
}
return addr;
#else /* HAVE_MMAP */
return NULL;
#endif /* HAVE_MMAP */
}
/***********************************************************************
* __wine_dll_register
*
* Register a built-in DLL descriptor.
*/
void __wine_dll_register( const IMAGE_NT_HEADERS *header, const char *filename )
{
if (load_dll_callback) load_dll_callback( map_dll(header), filename );
else
{
if (!(header->FileHeader.Characteristics & IMAGE_FILE_DLL))
main_exe = header;
else
{
assert( nb_dlls < MAX_DLLS );
builtin_dlls[nb_dlls].nt = header;
builtin_dlls[nb_dlls].filename = filename;
nb_dlls++;
}
}
}
/***********************************************************************
* wine_dll_set_callback
*
* Set the callback function for dll loading, and call it
* for all dlls that were implicitly loaded already.
*/
void wine_dll_set_callback( load_dll_callback_t load )
{
int i;
load_dll_callback = load;
for (i = 0; i < nb_dlls; i++)
{
const IMAGE_NT_HEADERS *nt = builtin_dlls[i].nt;
if (!nt) continue;
builtin_dlls[i].nt = NULL;
load_dll_callback( map_dll(nt), builtin_dlls[i].filename );
}
nb_dlls = 0;
if (main_exe) load_dll_callback( map_dll(main_exe), "" );
}
/***********************************************************************
* wine_dll_load
*
* Load a builtin dll.
*/
void *wine_dll_load( const char *filename, char *error, int errorsize, int *file_exists )
{
int i;
/* callback must have been set already */
assert( load_dll_callback );
/* check if we have it in the list */
/* this can happen when initializing pre-loaded dlls in wine_dll_set_callback */
for (i = 0; i < nb_dlls; i++)
{
if (!builtin_dlls[i].nt) continue;
if (!strcmp( builtin_dlls[i].filename, filename ))
{
const IMAGE_NT_HEADERS *nt = builtin_dlls[i].nt;
builtin_dlls[i].nt = NULL;
load_dll_callback( map_dll(nt), builtin_dlls[i].filename );
*file_exists = 1;
return (void *)1;
}
}
return dlopen_dll( filename, error, errorsize, 0, file_exists );
}
/***********************************************************************
* wine_dll_unload
*
* Unload a builtin dll.
*/
void wine_dll_unload( void *handle )
{
if (handle != (void *)1)
wine_dlclose( handle, NULL, 0 );
}
/***********************************************************************
* wine_dll_load_main_exe
*
* Try to load the .so for the main exe.
*/
void *wine_dll_load_main_exe( const char *name, char *error, int errorsize,
int test_only, int *file_exists )
{
return dlopen_dll( name, error, errorsize, test_only, file_exists );
}
/***********************************************************************
* wine_init
*
* Main Wine initialisation.
*/
void wine_init( int argc, char *argv[], char *error, int error_size )
{
int file_exists;
void *ntdll;
void (*init_func)(int, char **);
if (!(ntdll = dlopen_dll( "ntdll.dll", error, error_size, 0, &file_exists ))) return;
if (!(init_func = wine_dlsym( ntdll, "__wine_process_init", error, error_size ))) return;
init_func( argc, argv );
}
#if defined(__svr4__) || defined(__NetBSD__)
/***********************************************************************
* 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__ */
/***********************************************************************
* 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
#if 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
* a dl*() function after a previous dl*() function has failed
* without a dlerror() call between the two will cause a crash.
* They all take a pointer to a buffer that
* will receive the error description (from dlerror()). This
* parameter may be NULL if the error description is not required.
*/
/***********************************************************************
* wine_dlopen
*/
void *wine_dlopen( const char *filename, int flag, char *error, int errorsize )
{
#ifdef HAVE_DLOPEN
void *ret;
const char *s;
dlerror(); dlerror();
ret = dlopen( filename, flag );
s = dlerror();
if (error)
{
strncpy( error, s ? s : "", errorsize );
error[errorsize - 1] = '\0';
}
dlerror();
return ret;
#else
if (error)
{
strncpy( error, "dlopen interface not detected by configure", errorsize );
error[errorsize - 1] = '\0';
}
return NULL;
#endif
}
/***********************************************************************
* wine_dlsym
*/
void *wine_dlsym( void *handle, const char *symbol, char *error, int errorsize )
{
#ifdef HAVE_DLOPEN
void *ret;
const char *s;
dlerror(); dlerror();
ret = dlsym( handle, symbol );
s = dlerror();
if (error)
{
strncpy( error, s ? s : "", errorsize );
error[errorsize - 1] = '\0';
}
dlerror();
return ret;
#else
if (error)
{
strncpy( error, "dlopen interface not detected by configure", errorsize );
error[errorsize - 1] = '\0';
}
return NULL;
#endif
}
/***********************************************************************
* wine_dlclose
*/
int wine_dlclose( void *handle, char *error, int errorsize )
{
#ifdef HAVE_DLOPEN
int ret;
const char *s;
dlerror(); dlerror();
ret = dlclose( handle );
s = dlerror();
if (error)
{
strncpy( error, s ? s : "", errorsize );
error[errorsize - 1] = '\0';
}
dlerror();
return ret;
#else
if (error)
{
strncpy( error, "dlopen interface not detected by configure", errorsize );
error[errorsize - 1] = '\0';
}
return 1;
#endif
}