605 lines
19 KiB
C
605 lines
19 KiB
C
/*
|
|
* Wine memory mappings support
|
|
*
|
|
* Copyright 2000, 2004 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
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "wine/port.h"
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.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"
|
|
#include "wine/list.h"
|
|
|
|
struct reserved_area
|
|
{
|
|
struct list entry;
|
|
void *base;
|
|
size_t size;
|
|
};
|
|
|
|
static struct list reserved_areas = LIST_INIT(reserved_areas);
|
|
static const unsigned int granularity_mask = 0xffff; /* reserved areas have 64k granularity */
|
|
|
|
#ifdef HAVE_MMAP
|
|
|
|
#ifndef MAP_NORESERVE
|
|
#define MAP_NORESERVE 0
|
|
#endif
|
|
#ifndef MAP_PRIVATE
|
|
#define MAP_PRIVATE 0
|
|
#endif
|
|
#ifndef MAP_ANON
|
|
#define MAP_ANON 0
|
|
#endif
|
|
|
|
static inline int get_fdzero(void)
|
|
{
|
|
static int fd = -1;
|
|
|
|
if (MAP_ANON == 0 && fd == -1)
|
|
{
|
|
if ((fd = open( "/dev/zero", O_RDONLY )) == -1)
|
|
{
|
|
perror( "/dev/zero: open" );
|
|
exit(1);
|
|
}
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
#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-relocatable 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;
|
|
}
|
|
|
|
#elif defined(__APPLE__)
|
|
|
|
#include <mach/mach_init.h>
|
|
#include <mach/vm_map.h>
|
|
|
|
/*
|
|
* On Darwin, we can use the Mach call vm_allocate to allocate
|
|
* anonymous memory at the specified address, and then use mmap with
|
|
* MAP_FIXED to replace the mapping.
|
|
*/
|
|
static int try_mmap_fixed (void *addr, size_t len, int prot, int flags,
|
|
int fildes, off_t off)
|
|
{
|
|
vm_address_t result = (vm_address_t)addr;
|
|
|
|
if (!vm_allocate(mach_task_self(),&result,len,0))
|
|
{
|
|
if (mmap( (void *)result, len, prot, flags | MAP_FIXED, fildes, off ) != MAP_FAILED)
|
|
return 1;
|
|
vm_deallocate(mach_task_self(),result,len);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#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 MAP_SHARED
|
|
flags &= ~MAP_SHARED;
|
|
#endif
|
|
|
|
/* Linux EINVAL's on us if we don't pass MAP_PRIVATE to an anon mmap */
|
|
flags |= MAP_PRIVATE | MAP_ANON;
|
|
|
|
if (!(flags & MAP_FIXED))
|
|
{
|
|
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
|
/* Even FreeBSD 5.3 does not properly support NULL here. */
|
|
if( start == NULL ) start = (void *)0x110000;
|
|
#endif
|
|
|
|
#ifdef MAP_TRYFIXED
|
|
/* If available, this will attempt a fixed mapping in-kernel */
|
|
flags |= MAP_TRYFIXED;
|
|
#elif defined(__svr4__) || defined(__NetBSD__) || defined(__APPLE__)
|
|
if ( try_mmap_fixed( start, size, prot, flags, get_fdzero(), 0 ) )
|
|
return start;
|
|
#endif
|
|
}
|
|
return mmap( start, size, prot, flags, get_fdzero(), 0 );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* mmap_reserve
|
|
*
|
|
* mmap wrapper used for reservations, only maps the specified address
|
|
*/
|
|
static inline int mmap_reserve( void *addr, size_t size )
|
|
{
|
|
void *ptr;
|
|
int flags = MAP_PRIVATE | MAP_ANON | MAP_NORESERVE;
|
|
|
|
#ifdef MAP_TRYFIXED
|
|
flags |= MAP_TRYFIXED;
|
|
#elif defined(__APPLE__)
|
|
return try_mmap_fixed( addr, size, PROT_NONE, flags, get_fdzero(), 0 );
|
|
#endif
|
|
ptr = mmap( addr, size, PROT_NONE, flags, get_fdzero(), 0 );
|
|
if (ptr != addr && ptr != (void *)-1) munmap( ptr, size );
|
|
return (ptr == addr);
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* reserve_area
|
|
*
|
|
* Reserve as much memory as possible in the given area.
|
|
*/
|
|
#if defined(__i386__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) /* commented out until FreeBSD gets fixed */
|
|
static void reserve_area( void *addr, void *end )
|
|
{
|
|
size_t size = (char *)end - (char *)addr;
|
|
|
|
#if (defined(__svr4__) || defined(__NetBSD__)) && !defined(MAP_TRYFIXED)
|
|
/* try_mmap_fixed is inefficient when using vfork, so we need a different algorithm here */
|
|
/* we assume no other thread is running at this point */
|
|
size_t i, pagesize = getpagesize();
|
|
char vec;
|
|
|
|
while (size)
|
|
{
|
|
for (i = 0; i < size; i += pagesize)
|
|
if (mincore( (caddr_t)addr + i, pagesize, &vec ) != -1) break;
|
|
|
|
i &= ~granularity_mask;
|
|
if (i && mmap( addr, i, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE,
|
|
get_fdzero(), 0 ) != (void *)-1)
|
|
wine_mmap_add_reserved_area( addr, i );
|
|
|
|
i += granularity_mask + 1;
|
|
if ((char *)addr + i < (char *)addr) break; /* overflow */
|
|
addr = (char *)addr + i;
|
|
if (addr >= end) break;
|
|
size = (char *)end - (char *)addr;
|
|
}
|
|
#else
|
|
if (!size) return;
|
|
|
|
if (mmap_reserve( addr, size ))
|
|
{
|
|
wine_mmap_add_reserved_area( addr, size );
|
|
return;
|
|
}
|
|
if (size > granularity_mask + 1)
|
|
{
|
|
size_t new_size = (size / 2) & ~granularity_mask;
|
|
reserve_area( addr, (char *)addr + new_size );
|
|
reserve_area( (char *)addr + new_size, end );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* reserve_malloc_space
|
|
*
|
|
* Solaris malloc is not smart enough to obtain space through mmap(), so try to make
|
|
* sure that there is some available sbrk() space before we reserve other things.
|
|
*/
|
|
static void reserve_malloc_space( size_t size )
|
|
{
|
|
#ifdef __sun
|
|
size_t i, count = size / 1024;
|
|
void **ptrs = malloc( count * sizeof(ptrs[0]) );
|
|
|
|
if (!ptrs) return;
|
|
|
|
for (i = 0; i < count; i++) if (!(ptrs[i] = malloc( 1024 ))) break;
|
|
if (i--) /* free everything except the last one */
|
|
while (i) free( ptrs[--i] );
|
|
free( ptrs );
|
|
#endif
|
|
}
|
|
|
|
#endif /* __i386__ */
|
|
|
|
|
|
/***********************************************************************
|
|
* reserve_dos_area
|
|
*
|
|
* Reserve the DOS area (0x00000000-0x00110000).
|
|
*/
|
|
static void reserve_dos_area(void)
|
|
{
|
|
const size_t page_size = getpagesize();
|
|
const size_t dos_area_size = 0x110000;
|
|
void *ptr;
|
|
|
|
/* first page has to be handled specially */
|
|
ptr = wine_anon_mmap( (void *)page_size, dos_area_size - page_size, PROT_NONE, MAP_NORESERVE );
|
|
if (ptr != (void *)page_size)
|
|
{
|
|
if (ptr != (void *)-1) munmap( ptr, dos_area_size - page_size );
|
|
return;
|
|
}
|
|
/* now add first page with MAP_FIXED */
|
|
wine_anon_mmap( NULL, page_size, PROT_NONE, MAP_NORESERVE|MAP_FIXED );
|
|
wine_mmap_add_reserved_area( NULL, dos_area_size );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* mmap_init
|
|
*/
|
|
void mmap_init(void)
|
|
{
|
|
struct reserved_area *area;
|
|
struct list *ptr;
|
|
#if defined(__i386__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) /* commented out until FreeBSD gets fixed */
|
|
char stack;
|
|
char * const stack_ptr = &stack;
|
|
char *user_space_limit = (char *)0x7ffe0000;
|
|
|
|
reserve_malloc_space( 8 * 1024 * 1024 );
|
|
|
|
/* check for a reserved area starting at the user space limit */
|
|
/* to avoid wasting time trying to allocate it again */
|
|
LIST_FOR_EACH( ptr, &reserved_areas )
|
|
{
|
|
area = LIST_ENTRY( ptr, struct reserved_area, entry );
|
|
if ((char *)area->base > user_space_limit) break;
|
|
if ((char *)area->base + area->size > user_space_limit)
|
|
{
|
|
user_space_limit = (char *)area->base + area->size;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (stack_ptr >= user_space_limit)
|
|
{
|
|
char *end = 0;
|
|
char *base = stack_ptr - ((unsigned int)stack_ptr & granularity_mask) - (granularity_mask + 1);
|
|
if (base > user_space_limit) reserve_area( user_space_limit, base );
|
|
base = stack_ptr - ((unsigned int)stack_ptr & granularity_mask) + (granularity_mask + 1);
|
|
#ifdef linux
|
|
/* Linux heuristic: assume the stack is near the end of the address */
|
|
/* space, this avoids a lot of futile allocation attempts */
|
|
end = (char *)(((unsigned long)base + 0x0fffffff) & 0xf0000000);
|
|
#endif
|
|
reserve_area( base, end );
|
|
}
|
|
else reserve_area( user_space_limit, 0 );
|
|
#endif /* __i386__ */
|
|
|
|
/* reserve the DOS area if not already done */
|
|
|
|
ptr = list_head( &reserved_areas );
|
|
if (ptr)
|
|
{
|
|
area = LIST_ENTRY( ptr, struct reserved_area, entry );
|
|
if (!area->base) return; /* already reserved */
|
|
}
|
|
reserve_dos_area();
|
|
}
|
|
|
|
#else /* HAVE_MMAP */
|
|
|
|
void *wine_anon_mmap( void *start, size_t size, int prot, int flags )
|
|
{
|
|
return (void *)-1;
|
|
}
|
|
|
|
static inline int munmap( void *ptr, size_t size )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void mmap_init(void)
|
|
{
|
|
}
|
|
|
|
#endif
|
|
|
|
/***********************************************************************
|
|
* wine_mmap_add_reserved_area
|
|
*
|
|
* Add an address range to the list of reserved areas.
|
|
* Caller must have made sure the range is not used by anything else.
|
|
*
|
|
* Note: the reserved areas functions are not reentrant, caller is
|
|
* responsible for proper locking.
|
|
*/
|
|
void wine_mmap_add_reserved_area( void *addr, size_t size )
|
|
{
|
|
struct reserved_area *area;
|
|
struct list *ptr;
|
|
|
|
if (!((char *)addr + size)) size--; /* avoid wrap-around */
|
|
|
|
LIST_FOR_EACH( ptr, &reserved_areas )
|
|
{
|
|
area = LIST_ENTRY( ptr, struct reserved_area, entry );
|
|
if (area->base > addr)
|
|
{
|
|
/* try to merge with the next one */
|
|
if ((char *)addr + size == (char *)area->base)
|
|
{
|
|
area->base = addr;
|
|
area->size += size;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
else if ((char *)area->base + area->size == (char *)addr)
|
|
{
|
|
/* merge with the previous one */
|
|
area->size += size;
|
|
|
|
/* try to merge with the next one too */
|
|
if ((ptr = list_next( &reserved_areas, ptr )))
|
|
{
|
|
struct reserved_area *next = LIST_ENTRY( ptr, struct reserved_area, entry );
|
|
if ((char *)addr + size == (char *)next->base)
|
|
{
|
|
area->size += next->size;
|
|
list_remove( &next->entry );
|
|
free( next );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((area = malloc( sizeof(*area) )))
|
|
{
|
|
area->base = addr;
|
|
area->size = size;
|
|
list_add_before( ptr, &area->entry );
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_mmap_remove_reserved_area
|
|
*
|
|
* Remove an address range from the list of reserved areas.
|
|
* If 'unmap' is non-zero the range is unmapped too.
|
|
*
|
|
* Note: the reserved areas functions are not reentrant, caller is
|
|
* responsible for proper locking.
|
|
*/
|
|
void wine_mmap_remove_reserved_area( void *addr, size_t size, int unmap )
|
|
{
|
|
struct reserved_area *area;
|
|
struct list *ptr;
|
|
|
|
if (!((char *)addr + size)) size--; /* avoid wrap-around */
|
|
|
|
ptr = list_head( &reserved_areas );
|
|
/* find the first area covering address */
|
|
while (ptr)
|
|
{
|
|
area = LIST_ENTRY( ptr, struct reserved_area, entry );
|
|
if ((char *)area->base >= (char *)addr + size) break; /* outside the range */
|
|
if ((char *)area->base + area->size > (char *)addr) /* overlaps range */
|
|
{
|
|
if (area->base >= addr)
|
|
{
|
|
if ((char *)area->base + area->size > (char *)addr + size)
|
|
{
|
|
/* range overlaps beginning of area only -> shrink area */
|
|
if (unmap) munmap( area->base, (char *)addr + size - (char *)area->base );
|
|
area->size -= (char *)addr + size - (char *)area->base;
|
|
area->base = (char *)addr + size;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* range contains the whole area -> remove area completely */
|
|
ptr = list_next( &reserved_areas, ptr );
|
|
if (unmap) munmap( area->base, area->size );
|
|
list_remove( &area->entry );
|
|
free( area );
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((char *)area->base + area->size > (char *)addr + size)
|
|
{
|
|
/* range is in the middle of area -> split area in two */
|
|
struct reserved_area *new_area = malloc( sizeof(*new_area) );
|
|
if (new_area)
|
|
{
|
|
new_area->base = (char *)addr + size;
|
|
new_area->size = (char *)area->base + area->size - (char *)new_area->base;
|
|
list_add_after( ptr, &new_area->entry );
|
|
}
|
|
else size = (char *)area->base + area->size - (char *)addr;
|
|
area->size = (char *)addr - (char *)area->base;
|
|
if (unmap) munmap( addr, size );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* range overlaps end of area only -> shrink area */
|
|
if (unmap) munmap( addr, (char *)area->base + area->size - (char *)addr );
|
|
area->size = (char *)addr - (char *)area->base;
|
|
}
|
|
}
|
|
}
|
|
ptr = list_next( &reserved_areas, ptr );
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_mmap_is_in_reserved_area
|
|
*
|
|
* Check if the specified range is included in a reserved area.
|
|
* Returns 1 if range is fully included, 0 if range is not included
|
|
* at all, and -1 if it is only partially included.
|
|
*
|
|
* Note: the reserved areas functions are not reentrant, caller is
|
|
* responsible for proper locking.
|
|
*/
|
|
int wine_mmap_is_in_reserved_area( void *addr, size_t size )
|
|
{
|
|
struct reserved_area *area;
|
|
struct list *ptr;
|
|
|
|
LIST_FOR_EACH( ptr, &reserved_areas )
|
|
{
|
|
area = LIST_ENTRY( ptr, struct reserved_area, entry );
|
|
if (area->base > addr) break;
|
|
if ((char *)area->base + area->size <= (char *)addr) continue;
|
|
/* area must contain block completely */
|
|
if ((char *)area->base + area->size < (char *)addr + size) return -1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_mmap_enum_reserved_areas
|
|
*
|
|
* Enumerate the list of reserved areas, sorted by addresses.
|
|
* If enum_func returns a non-zero value, enumeration is stopped and the value is returned.
|
|
*
|
|
* Note: the reserved areas functions are not reentrant, caller is
|
|
* responsible for proper locking.
|
|
*/
|
|
int wine_mmap_enum_reserved_areas( int (*enum_func)(void *base, size_t size, void *arg), void *arg,
|
|
int top_down )
|
|
{
|
|
int ret = 0;
|
|
struct list *ptr;
|
|
|
|
if (top_down)
|
|
{
|
|
for (ptr = reserved_areas.prev; ptr != &reserved_areas; ptr = ptr->prev)
|
|
{
|
|
struct reserved_area *area = LIST_ENTRY( ptr, struct reserved_area, entry );
|
|
if ((ret = enum_func( area->base, area->size, arg ))) break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (ptr = reserved_areas.next; ptr != &reserved_areas; ptr = ptr->next)
|
|
{
|
|
struct reserved_area *area = LIST_ENTRY( ptr, struct reserved_area, entry );
|
|
if ((ret = enum_func( area->base, area->size, arg ))) break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|