/* * Unix interface for virtual memory 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 #ifdef HAVE_SYS_MMAN_H # include #endif #if defined(__APPLE__) # include # include #endif #include #include "ntstatus.h" #define WIN32_NO_STATUS #define NONAMELESSUNION #define NONAMELESSSTRUCT #include "windef.h" #include "winnt.h" #include "winternl.h" #include "unix_private.h" #include "wine/list.h" struct preload_info { void *addr; size_t size; }; 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 */ extern IMAGE_NT_HEADERS __wine_spec_nt_header; #ifndef MAP_NORESERVE #define MAP_NORESERVE 0 #endif #ifndef MAP_TRYFIXED #define MAP_TRYFIXED 0 #endif static void reserve_area( void *addr, void *end ) { #ifdef __APPLE__ #ifdef __i386__ static const mach_vm_address_t max_address = VM_MAX_ADDRESS; #else static const mach_vm_address_t max_address = MACH_VM_MAX_ADDRESS; #endif mach_vm_address_t address = (mach_vm_address_t)addr; mach_vm_address_t end_address = (mach_vm_address_t)end; if (!end_address || max_address < end_address) end_address = max_address; while (address < end_address) { mach_vm_address_t hole_address = address; kern_return_t ret; mach_vm_size_t size; vm_region_basic_info_data_64_t info; mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; mach_port_t dummy_object_name = MACH_PORT_NULL; /* find the mapped region at or above the current address. */ ret = mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &count, &dummy_object_name); if (ret != KERN_SUCCESS) { address = max_address; size = 0; } if (end_address < address) address = end_address; if (hole_address < address) { /* found a hole, attempt to reserve it. */ size_t hole_size = address - hole_address; mach_vm_address_t alloc_address = hole_address; ret = mach_vm_map( mach_task_self(), &alloc_address, hole_size, 0, VM_FLAGS_FIXED, MEMORY_OBJECT_NULL, 0, 0, PROT_NONE, VM_PROT_ALL, VM_INHERIT_COPY ); if (!ret) mmap_add_reserved_area( (void*)hole_address, hole_size ); else if (ret == KERN_NO_SPACE) { /* something filled (part of) the hole before we could. go back and look again. */ address = hole_address; continue; } } address += size; } #else void *ptr; int flags = MAP_PRIVATE | MAP_ANON | MAP_NORESERVE | MAP_TRYFIXED; size_t size = (char *)end - (char *)addr; if (!size) return; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) ptr = mmap( addr, size, PROT_NONE, flags | MAP_FIXED | MAP_EXCL, -1, 0 ); #else ptr = mmap( addr, size, PROT_NONE, flags, -1, 0 ); #endif if (ptr == addr) { mmap_add_reserved_area( addr, size ); return; } if (ptr != (void *)-1) munmap( ptr, size ); size = (size / 2) & ~granularity_mask; if (size) { reserve_area( addr, (char *)addr + size ); reserve_area( (char *)addr + size, end ); } #endif /* __APPLE__ */ } static void mmap_init( const struct preload_info *preload_info ) { #ifdef __i386__ #ifndef __APPLE__ char stack; char * const stack_ptr = &stack; #endif char *user_space_limit = (char *)0x7ffe0000; int i; if (preload_info) { /* check for a reserved area starting at the user space limit */ /* to avoid wasting time trying to allocate it again */ for (i = 0; preload_info[i].size; i++) { if ((char *)preload_info[i].addr > user_space_limit) break; if ((char *)preload_info[i].addr + preload_info[i].size > user_space_limit) { user_space_limit = (char *)preload_info[i].addr + preload_info[i].size; break; } } } else reserve_area( (void *)0x00010000, (void *)0x40000000 ); #ifndef __APPLE__ 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); #if defined(linux) || defined(__FreeBSD__) || defined (__FreeBSD_kernel__) || defined(__DragonFly__) /* 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 #endif reserve_area( user_space_limit, 0 ); #elif defined(__x86_64__) || defined(__aarch64__) if (preload_info) return; /* if we don't have a preloader, try to reserve the space now */ reserve_area( (void *)0x000000010000, (void *)0x000068000000 ); reserve_area( (void *)0x00007ff00000, (void *)0x00007fff0000 ); reserve_area( (void *)0x7ffffe000000, (void *)0x7fffffff0000 ); #endif } void CDECL 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 ); } } void CDECL mmap_remove_reserved_area( void *addr, SIZE_T size ) { 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 */ 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 ); 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; break; } else { /* range overlaps end of area only -> shrink area */ area->size = (char *)addr - (char *)area->base; } } } ptr = list_next( &reserved_areas, ptr ); } } int CDECL 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; } int CDECL mmap_enum_reserved_areas( int (CDECL *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; } void virtual_init(void) { const struct preload_info **preload_info = dlsym( RTLD_DEFAULT, "wine_main_preload_info" ); int i; if (preload_info && *preload_info) for (i = 0; (*preload_info)[i].size; i++) mmap_add_reserved_area( (*preload_info)[i].addr, (*preload_info)[i].size ); mmap_init( preload_info ? *preload_info : NULL ); }